diff --git a/vendor/endroid/qr-code/.gitattributes b/vendor/endroid/qr-code/.gitattributes
new file mode 100644
index 0000000..dbc5d83
--- /dev/null
+++ b/vendor/endroid/qr-code/.gitattributes
@@ -0,0 +1,2 @@
+/.github/workflows/ export-ignore
+/tests/ export-ignore
diff --git a/vendor/endroid/qr-code/README.md b/vendor/endroid/qr-code/README.md
index cf45d31..cfea126 100644
--- a/vendor/endroid/qr-code/README.md
+++ b/vendor/endroid/qr-code/README.md
@@ -11,79 +11,128 @@
This library helps you generate QR codes in a jiffy. Makes use of [bacon/bacon-qr-code](https://github.com/Bacon/BaconQrCode)
to generate the matrix and [khanamiryan/qrcode-detector-decoder](https://github.com/khanamiryan/php-qrcode-detector-decoder)
for validating generated QR codes. Further extended with Twig extensions, generation routes, a factory and a
-Symfony bundle for easy installation and configuration.
-
-Different writers are provided to generate the QR code as PNG, SVG, EPS or in binary format.
+Symfony bundle for easy installation and configuration. Different writers are provided to generate the QR code
+as PNG, SVG, EPS or in binary format.
## Installation
-Use [Composer](https://getcomposer.org/) to install the library.
+Use [Composer](https://getcomposer.org/) to install the library. Also make sure you have enabled and configured the
+[GD extension](https://www.php.net/manual/en/book.image.php) if you want to generate images.
``` bash
$ composer require endroid/qr-code
```
-## Basic usage
+## Usage: using the builder
```php
-use Endroid\QrCode\QrCode;
+use Endroid\QrCode\Builder\Builder;
+use Endroid\QrCode\Encoding\Encoding;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
+use Endroid\QrCode\Label\Alignment\LabelAlignmentCenter;
+use Endroid\QrCode\Label\Font\NotoSans;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
+use Endroid\QrCode\Writer\PngWriter;
-$qrCode = new QrCode('Life is too short to be generating QR codes');
-
-header('Content-Type: '.$qrCode->getContentType());
-echo $qrCode->writeString();
+$result = Builder::create()
+ ->writer(new PngWriter())
+ ->writerOptions([])
+ ->data('Custom QR code contents')
+ ->encoding(new Encoding('UTF-8'))
+ ->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
+ ->size(300)
+ ->margin(10)
+ ->roundBlockSizeMode(new RoundBlockSizeModeMargin())
+ ->logoPath(__DIR__.'/assets/symfony.png')
+ ->labelText('This is the label')
+ ->labelFont(new NotoSans(20))
+ ->labelAlignment(new LabelAlignmentCenter())
+ ->build();
```
-## Advanced usage
+## Usage: without using the builder
```php
-use Endroid\QrCode\ErrorCorrectionLevel;
-use Endroid\QrCode\LabelAlignment;
+use Endroid\QrCode\Color\Color;
+use Endroid\QrCode\Encoding\Encoding;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
use Endroid\QrCode\QrCode;
-use Endroid\QrCode\Response\QrCodeResponse;
+use Endroid\QrCode\Label\Label;
+use Endroid\QrCode\Logo\Logo;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
+use Endroid\QrCode\Writer\PngWriter;
-// Create a basic QR code
-$qrCode = new QrCode('Life is too short to be generating QR codes');
-$qrCode->setSize(300);
+$writer = new PngWriter();
-// Set advanced options
-$qrCode->setWriterByName('png');
-$qrCode->setEncoding('UTF-8');
-$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH());
-$qrCode->setForegroundColor(['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]);
-$qrCode->setBackgroundColor(['r' => 255, 'g' => 255, 'b' => 255, 'a' => 0]);
-$qrCode->setLabel('Scan the code', 16, __DIR__.'/../assets/fonts/noto_sans.otf', LabelAlignment::CENTER());
-$qrCode->setLogoPath(__DIR__.'/../assets/images/symfony.png');
-$qrCode->setLogoSize(150, 200);
-$qrCode->setValidateResult(false);
+// Create QR code
+$qrCode = QrCode::create('Data')
+ ->setEncoding(new Encoding('UTF-8'))
+ ->setErrorCorrectionLevel(new ErrorCorrectionLevelLow())
+ ->setSize(300)
+ ->setMargin(10)
+ ->setRoundBlockSizeMode(new RoundBlockSizeModeMargin())
+ ->setForegroundColor(new Color(0, 0, 0))
+ ->setBackgroundColor(new Color(255, 255, 255));
-// Apply a margin and round block sizes to improve readability
-// Please note that rounding block sizes can result in additional margin
-$qrCode->setRoundBlockSize(true);
-$qrCode->setMargin(10);
+// Create generic logo
+$logo = Logo::create(__DIR__.'/assets/symfony.png')
+ ->setResizeToWidth(50);
-// Set additional writer options (SvgWriter example)
-$qrCode->setWriterOptions(['exclude_xml_declaration' => true]);
+// Create generic label
+$label = Label::create('Label')
+ ->setTextColor(new Color(255, 0, 0));
+
+$result = $writer->write($qrCode, $logo, $label);
+```
+
+## Usage: working with results
+
+```php
// Directly output the QR code
-header('Content-Type: '.$qrCode->getContentType());
-echo $qrCode->writeString();
+header('Content-Type: '.$result->getMimeType());
+echo $result->getString();
// Save it to a file
-$qrCode->writeFile(__DIR__.'/qrcode.png');
+$result->saveToFile(__DIR__.'/qrcode.png');
// Generate a data URI to include image data inline (i.e. inside an tag)
-$dataUri = $qrCode->writeDataUri();
+$dataUri = $result->getDataUri();
```
-
+
+
+### Writer options
+
+```php
+use Endroid\QrCode\Writer\SvgWriter;
+
+$builder->setWriterOptions([SvgWriter::WRITER_OPTION_EXCLUDE_XML_DECLARATION => true]);
+```
### Encoding
-You can pick one of these values for encoding:
-`ISO-8859-1`, `ISO-8859-2`, `ISO-8859-3`, `ISO-8859-4`, `ISO-8859-5`, `ISO-8859-6`, `ISO-8859-7`, `ISO-8859-8`, `ISO-8859-9`, `ISO-8859-10`, `ISO-8859-11`, `ISO-8859-12`, `ISO-8859-13`, `ISO-8859-14`, `ISO-8859-15`, `ISO-8859-16`, `Shift_JIS`, `windows-1250`, `windows-1251`, `windows-1252`, `windows-1256`, `UTF-16BE`, `UTF-8`, `US-ASCII`, `GBK` `EUC-KR`
+If you use a barcode scanner you can have some troubles while reading the
+generated QR codes. Depending on the encoding you chose you will have an extra
+amount of data corresponding to the ECI block. Some barcode scanner are not
+programmed to interpret this block of information. To ensure a maximum
+compatibility you can use the `ISO-8859-1` encoding that is the default
+encoding used by barcode scanners (if your character set supports it,
+i.e. no Chinese characters are present).
-If you use a barcode scanner you can have some troubles while reading the generated QR codes. Depending on the encoding you chose you will have an extra amount of data corresponding to the ECI block. Some barcode scanner are not programmed to interpret this block of information. For exemple the ECI block for `UTF-8` is `000026` so the above exemple will produce : `\000026Life is too short to be generating QR codes`. To ensure a maximum compatibility you can use the `ISO-8859-1` encoding that is the default encoding used by barcode scanners.
+### Round block size mode
+
+By default block sizes are rounded to guarantee sharp images and improve
+readability. However some other rounding variants are available.
+
+* `margin (default)`: the size of the QR code is shrunk if necessary but the size
+ of the final image remains unchanged due to additional margin being added.
+* `enlarge`: the size of the QR code and the final image are enlarged when
+ rounding differences occur.
+* `shrink`: the size of the QR code and the final image are
+ shrunk when rounding differences occur.
+* `none`: No rounding. This mode can be used when blocks don't need to be rounded
+ to pixels (for instance SVG).
## Readability
@@ -93,15 +142,16 @@ can tweak these parameters if you are looking for optimal results. You can also
check $qrCode->getRoundBlockSize() value to see if block dimensions are rounded
so that the image is more sharp and readable. Please note that rounding block
size can result in additional padding to compensate for the rounding difference.
+And finally the encoding (default UTF-8 to support large character sets) can be
+set to `ISO-8859-1` if possible to improve readability.
## Built-in validation reader
You can enable the built-in validation reader (disabled by default) by calling
setValidateResult(true). This validation reader does not guarantee that the QR
code will be readable by all readers but it helps you provide a minimum level
-of quality.
-
-Take note that the validator can consume quite amount of additional resources.
+of quality. Take note that the validator can consume quite amount of additional
+resources and it should be installed separately only if you use it.
## Symfony integration
@@ -109,8 +159,8 @@ The [endroid/qr-code-bundle](https://github.com/endroid/qr-code-bundle)
integrates the QR code library in Symfony for an even better experience.
* Configure your defaults (like image size, default writer etc.)
-* Generate QR codes quickly from anywhere via the factory service
-* Generate QR codes directly by typing an URL like /qr-code/\.png?size=300
+* Support for multiple configurations and injection via aliases
+* Generate QR codes for defined configurations via URL like /qr-code//Hello
* Generate QR codes or URLs directly from Twig using dedicated functions
Read the [bundle documentation](https://github.com/endroid/qr-code-bundle)
diff --git a/vendor/endroid/qr-code/assets/noto_sans.otf b/vendor/endroid/qr-code/assets/noto_sans.otf
new file mode 100644
index 0000000..296fbeb
Binary files /dev/null and b/vendor/endroid/qr-code/assets/noto_sans.otf differ
diff --git a/vendor/endroid/qr-code/assets/open_sans.ttf b/vendor/endroid/qr-code/assets/open_sans.ttf
new file mode 100644
index 0000000..db43334
Binary files /dev/null and b/vendor/endroid/qr-code/assets/open_sans.ttf differ
diff --git a/vendor/endroid/qr-code/composer.json b/vendor/endroid/qr-code/composer.json
index 0befa27..9fd62c0 100644
--- a/vendor/endroid/qr-code/composer.json
+++ b/vendor/endroid/qr-code/composer.json
@@ -1,7 +1,7 @@
{
"name": "endroid/qr-code",
"description": "Endroid QR Code",
- "keywords": ["endroid", "qrcode", "qr", "code", "bundle", "php"],
+ "keywords": ["endroid", "qrcode", "qr", "code", "php"],
"homepage": "https://github.com/endroid/qr-code",
"type": "library",
"license": "MIT",
@@ -12,20 +12,20 @@
}
],
"require": {
- "php": ">=7.2",
- "ext-gd": "*",
- "bacon/bacon-qr-code": "^2.0",
- "khanamiryan/qrcode-detector-decoder": "^1.0.2",
- "symfony/options-resolver": "^3.4||^4.4||^5.0",
- "symfony/property-access": "^3.4||^4.4||^5.0",
- "myclabs/php-enum": "^1.5"
+ "php": "^7.4||^8.0",
+ "bacon/bacon-qr-code": "^2.0.5"
},
"require-dev": {
- "endroid/quality": "dev-master"
+ "ext-gd": "*",
+ "endroid/quality": "dev-master",
+ "khanamiryan/qrcode-detector-decoder": "^1.0.4",
+ "setasign/fpdf": "^1.8.2"
},
"suggest": {
- "roave/security-advisories": "Avoids installation of package versions with vulnerabilities",
- "symfony/security-checker": "Checks your composer.lock for vulnerabilities"
+ "ext-gd": "Enables you to write PNG images",
+ "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator",
+ "roave/security-advisories": "Makes sure package versions with known security issues are not installed",
+ "setasign/fpdf": "Enables you to use the PDF writer"
},
"autoload": {
"psr-4": {
@@ -38,11 +38,14 @@
}
},
"config": {
- "sort-packages": true
+ "sort-packages": true,
+ "preferred-install": {
+ "endroid/*": "source"
+ }
},
"extra": {
"branch-alias": {
- "dev-master": "3.x-dev"
+ "dev-master": "4.x-dev"
}
}
}
diff --git a/vendor/endroid/qr-code/src/Bacon/ErrorCorrectionLevelConverter.php b/vendor/endroid/qr-code/src/Bacon/ErrorCorrectionLevelConverter.php
new file mode 100644
index 0000000..90f23e0
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Bacon/ErrorCorrectionLevelConverter.php
@@ -0,0 +1,30 @@
+getErrorCorrectionLevel());
+ $baconMatrix = Encoder::encode($qrCode->getData(), $baconErrorCorrectionLevel, strval($qrCode->getEncoding()))->getMatrix();
+
+ $blockValues = [];
+ $columnCount = $baconMatrix->getWidth();
+ $rowCount = $baconMatrix->getHeight();
+ for ($rowIndex = 0; $rowIndex < $rowCount; ++$rowIndex) {
+ $blockValues[$rowIndex] = [];
+ for ($columnIndex = 0; $columnIndex < $columnCount; ++$columnIndex) {
+ $blockValues[$rowIndex][$columnIndex] = $baconMatrix->get($columnIndex, $rowIndex);
+ }
+ }
+
+ return new Matrix($blockValues, $qrCode->getSize(), $qrCode->getMargin(), $qrCode->getRoundBlockSizeMode());
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Builder/Builder.php b/vendor/endroid/qr-code/src/Builder/Builder.php
new file mode 100644
index 0000000..c00be13
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Builder/Builder.php
@@ -0,0 +1,278 @@
+{
+ * data: string,
+ * writer: WriterInterface,
+ * writerOptions: array,
+ * qrCodeClass: class-string,
+ * logoClass: class-string,
+ * labelClass: class-string,
+ * validateResult: bool,
+ * size?: int,
+ * encoding?: EncodingInterface,
+ * errorCorrectionLevel?: ErrorCorrectionLevelInterface,
+ * roundBlockSizeMode?: RoundBlockSizeModeInterface,
+ * margin?: int,
+ * backgroundColor?: ColorInterface,
+ * foregroundColor?: ColorInterface,
+ * labelText?: string,
+ * labelFont?: FontInterface,
+ * labelAlignment?: LabelAlignmentInterface,
+ * labelMargin?: MarginInterface,
+ * labelTextColor?: ColorInterface,
+ * logoPath?: string,
+ * logoResizeToWidth?: int,
+ * logoResizeToHeight?: int,
+ * logoPunchoutBackground?: bool
+ * }
+ */
+ private array $options;
+
+ public function __construct()
+ {
+ $this->options = [
+ 'data' => '',
+ 'writer' => new PngWriter(),
+ 'writerOptions' => [],
+ 'qrCodeClass' => QrCode::class,
+ 'logoClass' => Logo::class,
+ 'labelClass' => Label::class,
+ 'validateResult' => false,
+ ];
+ }
+
+ public static function create(): BuilderInterface
+ {
+ return new self();
+ }
+
+ public function writer(WriterInterface $writer): BuilderInterface
+ {
+ $this->options['writer'] = $writer;
+
+ return $this;
+ }
+
+ /** @param array $writerOptions */
+ public function writerOptions(array $writerOptions): BuilderInterface
+ {
+ $this->options['writerOptions'] = $writerOptions;
+
+ return $this;
+ }
+
+ public function data(string $data): BuilderInterface
+ {
+ $this->options['data'] = $data;
+
+ return $this;
+ }
+
+ public function encoding(EncodingInterface $encoding): BuilderInterface
+ {
+ $this->options['encoding'] = $encoding;
+
+ return $this;
+ }
+
+ public function errorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): BuilderInterface
+ {
+ $this->options['errorCorrectionLevel'] = $errorCorrectionLevel;
+
+ return $this;
+ }
+
+ public function size(int $size): BuilderInterface
+ {
+ $this->options['size'] = $size;
+
+ return $this;
+ }
+
+ public function margin(int $margin): BuilderInterface
+ {
+ $this->options['margin'] = $margin;
+
+ return $this;
+ }
+
+ public function roundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): BuilderInterface
+ {
+ $this->options['roundBlockSizeMode'] = $roundBlockSizeMode;
+
+ return $this;
+ }
+
+ public function foregroundColor(ColorInterface $foregroundColor): BuilderInterface
+ {
+ $this->options['foregroundColor'] = $foregroundColor;
+
+ return $this;
+ }
+
+ public function backgroundColor(ColorInterface $backgroundColor): BuilderInterface
+ {
+ $this->options['backgroundColor'] = $backgroundColor;
+
+ return $this;
+ }
+
+ public function logoPath(string $logoPath): BuilderInterface
+ {
+ $this->options['logoPath'] = $logoPath;
+
+ return $this;
+ }
+
+ public function logoResizeToWidth(int $logoResizeToWidth): BuilderInterface
+ {
+ $this->options['logoResizeToWidth'] = $logoResizeToWidth;
+
+ return $this;
+ }
+
+ public function logoResizeToHeight(int $logoResizeToHeight): BuilderInterface
+ {
+ $this->options['logoResizeToHeight'] = $logoResizeToHeight;
+
+ return $this;
+ }
+
+ public function logoPunchoutBackground(bool $logoPunchoutBackground): BuilderInterface
+ {
+ $this->options['logoPunchoutBackground'] = $logoPunchoutBackground;
+
+ return $this;
+ }
+
+ public function labelText(string $labelText): BuilderInterface
+ {
+ $this->options['labelText'] = $labelText;
+
+ return $this;
+ }
+
+ public function labelFont(FontInterface $labelFont): BuilderInterface
+ {
+ $this->options['labelFont'] = $labelFont;
+
+ return $this;
+ }
+
+ public function labelAlignment(LabelAlignmentInterface $labelAlignment): BuilderInterface
+ {
+ $this->options['labelAlignment'] = $labelAlignment;
+
+ return $this;
+ }
+
+ public function labelMargin(MarginInterface $labelMargin): BuilderInterface
+ {
+ $this->options['labelMargin'] = $labelMargin;
+
+ return $this;
+ }
+
+ public function labelTextColor(ColorInterface $labelTextColor): BuilderInterface
+ {
+ $this->options['labelTextColor'] = $labelTextColor;
+
+ return $this;
+ }
+
+ public function validateResult(bool $validateResult): BuilderInterface
+ {
+ $this->options['validateResult'] = $validateResult;
+
+ return $this;
+ }
+
+ public function build(): ResultInterface
+ {
+ $writer = $this->options['writer'];
+
+ if ($this->options['validateResult'] && !$writer instanceof ValidatingWriterInterface) {
+ throw new \Exception('Unable to validate result with '.get_class($writer));
+ }
+
+ /** @var QrCode $qrCode */
+ $qrCode = $this->buildObject($this->options['qrCodeClass']);
+
+ /** @var LogoInterface|null $logo */
+ $logo = $this->buildObject($this->options['logoClass'], 'logo');
+
+ /** @var LabelInterface|null $label */
+ $label = $this->buildObject($this->options['labelClass'], 'label');
+
+ $result = $writer->write($qrCode, $logo, $label, $this->options['writerOptions']);
+
+ if ($this->options['validateResult'] && $writer instanceof ValidatingWriterInterface) {
+ $writer->validateResult($result, $qrCode->getData());
+ }
+
+ return $result;
+ }
+
+ /**
+ * @param class-string $class
+ *
+ * @return mixed
+ */
+ private function buildObject(string $class, string $optionsPrefix = null)
+ {
+ /** @var \ReflectionClass $reflectionClass */
+ $reflectionClass = new \ReflectionClass($class);
+
+ $arguments = [];
+ $hasBuilderOptions = false;
+ $missingRequiredArguments = [];
+ /** @var \ReflectionMethod $constructor */
+ $constructor = $reflectionClass->getConstructor();
+ $constructorParameters = $constructor->getParameters();
+ foreach ($constructorParameters as $parameter) {
+ $optionName = null === $optionsPrefix ? $parameter->getName() : $optionsPrefix.ucfirst($parameter->getName());
+ if (isset($this->options[$optionName])) {
+ $hasBuilderOptions = true;
+ $arguments[] = $this->options[$optionName];
+ } elseif ($parameter->isDefaultValueAvailable()) {
+ $arguments[] = $parameter->getDefaultValue();
+ } else {
+ $missingRequiredArguments[] = $optionName;
+ }
+ }
+
+ if (!$hasBuilderOptions) {
+ return null;
+ }
+
+ if (count($missingRequiredArguments) > 0) {
+ throw new \Exception(sprintf('Missing required arguments: %s', implode(', ', $missingRequiredArguments)));
+ }
+
+ return $reflectionClass->newInstanceArgs($arguments);
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Builder/BuilderInterface.php b/vendor/endroid/qr-code/src/Builder/BuilderInterface.php
new file mode 100644
index 0000000..5e69bc2
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Builder/BuilderInterface.php
@@ -0,0 +1,63 @@
+ $writerOptions */
+ public function writerOptions(array $writerOptions): BuilderInterface;
+
+ public function data(string $data): BuilderInterface;
+
+ public function encoding(EncodingInterface $encoding): BuilderInterface;
+
+ public function errorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): BuilderInterface;
+
+ public function size(int $size): BuilderInterface;
+
+ public function margin(int $margin): BuilderInterface;
+
+ public function roundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): BuilderInterface;
+
+ public function foregroundColor(ColorInterface $foregroundColor): BuilderInterface;
+
+ public function backgroundColor(ColorInterface $backgroundColor): BuilderInterface;
+
+ public function logoPath(string $logoPath): BuilderInterface;
+
+ public function logoResizeToWidth(int $logoResizeToWidth): BuilderInterface;
+
+ public function logoResizeToHeight(int $logoResizeToHeight): BuilderInterface;
+
+ public function logoPunchoutBackground(bool $logoPunchoutBackground): BuilderInterface;
+
+ public function labelText(string $labelText): BuilderInterface;
+
+ public function labelFont(FontInterface $labelFont): BuilderInterface;
+
+ public function labelAlignment(LabelAlignmentInterface $labelAlignment): BuilderInterface;
+
+ public function labelMargin(MarginInterface $labelMargin): BuilderInterface;
+
+ public function labelTextColor(ColorInterface $labelTextColor): BuilderInterface;
+
+ public function validateResult(bool $validateResult): BuilderInterface;
+
+ public function build(): ResultInterface;
+}
diff --git a/vendor/endroid/qr-code/src/Builder/BuilderRegistry.php b/vendor/endroid/qr-code/src/Builder/BuilderRegistry.php
new file mode 100644
index 0000000..cf1449c
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Builder/BuilderRegistry.php
@@ -0,0 +1,25 @@
+ */
+ private array $builders = [];
+
+ public function getBuilder(string $name): BuilderInterface
+ {
+ if (!isset($this->builders[$name])) {
+ throw new \Exception(sprintf('Builder with name "%s" not available from registry', $name));
+ }
+
+ return $this->builders[$name];
+ }
+
+ public function addBuilder(string $name, BuilderInterface $builder): void
+ {
+ $this->builders[$name] = $builder;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Builder/BuilderRegistryInterface.php b/vendor/endroid/qr-code/src/Builder/BuilderRegistryInterface.php
new file mode 100644
index 0000000..048d649
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Builder/BuilderRegistryInterface.php
@@ -0,0 +1,12 @@
+red = $red;
+ $this->green = $green;
+ $this->blue = $blue;
+ $this->alpha = $alpha;
+ }
+
+ public function getRed(): int
+ {
+ return $this->red;
+ }
+
+ public function getGreen(): int
+ {
+ return $this->green;
+ }
+
+ public function getBlue(): int
+ {
+ return $this->blue;
+ }
+
+ public function getAlpha(): int
+ {
+ return $this->alpha;
+ }
+
+ public function getOpacity(): float
+ {
+ return 1 - $this->alpha / 127;
+ }
+
+ public function toArray(): array
+ {
+ return [
+ 'red' => $this->red,
+ 'green' => $this->green,
+ 'blue' => $this->blue,
+ 'alpha' => $this->alpha,
+ ];
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Color/ColorInterface.php b/vendor/endroid/qr-code/src/Color/ColorInterface.php
new file mode 100644
index 0000000..91d3818
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Color/ColorInterface.php
@@ -0,0 +1,21 @@
+ */
+ public function toArray(): array;
+}
diff --git a/vendor/endroid/qr-code/src/Encoding/Encoding.php b/vendor/endroid/qr-code/src/Encoding/Encoding.php
new file mode 100644
index 0000000..2126d1e
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Encoding/Encoding.php
@@ -0,0 +1,24 @@
+value = $value;
+ }
+
+ public function __toString(): string
+ {
+ return $this->value;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Encoding/EncodingInterface.php b/vendor/endroid/qr-code/src/Encoding/EncodingInterface.php
new file mode 100644
index 0000000..f57001b
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Encoding/EncodingInterface.php
@@ -0,0 +1,10 @@
+width = $width;
+ $this->height = $height;
+ }
+
+ public static function createForLabel(LabelInterface $label): self
+ {
+ if (false !== strpos($label->getText(), "\n")) {
+ throw new \Exception('Label does not support line breaks');
+ }
+
+ if (!function_exists('imagettfbbox')) {
+ throw new \Exception('Function "imagettfbbox" does not exist: check your FreeType installation');
+ }
+
+ $labelBox = imagettfbbox($label->getFont()->getSize(), 0, $label->getFont()->getPath(), $label->getText());
+
+ if (!is_array($labelBox)) {
+ throw new \Exception('Unable to generate label image box: check your FreeType installation');
+ }
+
+ return new self(
+ intval($labelBox[2] - $labelBox[0]),
+ intval($labelBox[0] - $labelBox[7])
+ );
+ }
+
+ public function getWidth(): int
+ {
+ return $this->width;
+ }
+
+ public function getHeight(): int
+ {
+ return $this->height;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/ImageData/LogoImageData.php b/vendor/endroid/qr-code/src/ImageData/LogoImageData.php
new file mode 100644
index 0000000..75b4078
--- /dev/null
+++ b/vendor/endroid/qr-code/src/ImageData/LogoImageData.php
@@ -0,0 +1,164 @@
+data = $data;
+ $this->image = $image;
+ $this->mimeType = $mimeType;
+ $this->width = $width;
+ $this->height = $height;
+ $this->punchoutBackground = $punchoutBackground;
+ }
+
+ public static function createForLogo(LogoInterface $logo): self
+ {
+ $data = @file_get_contents($logo->getPath());
+
+ if (!is_string($data)) {
+ throw new \Exception(sprintf('Invalid data at path "%s"', $logo->getPath()));
+ }
+
+ if (false !== filter_var($logo->getPath(), FILTER_VALIDATE_URL)) {
+ $mimeType = self::detectMimeTypeFromUrl($logo->getPath());
+ } else {
+ $mimeType = self::detectMimeTypeFromPath($logo->getPath());
+ }
+
+ $width = $logo->getResizeToWidth();
+ $height = $logo->getResizeToHeight();
+
+ if ('image/svg+xml' === $mimeType) {
+ if (null === $width || null === $height) {
+ throw new \Exception('SVG Logos require an explicitly set resize width and height');
+ }
+
+ return new self($data, null, $mimeType, $width, $height, $logo->getPunchoutBackground());
+ }
+
+ $image = @imagecreatefromstring($data);
+
+ if (!$image) {
+ throw new \Exception(sprintf('Unable to parse image data at path "%s"', $logo->getPath()));
+ }
+
+ // No target width and height specified: use from original image
+ if (null !== $width && null !== $height) {
+ return new self($data, $image, $mimeType, $width, $height, $logo->getPunchoutBackground());
+ }
+
+ // Only target width specified: calculate height
+ if (null !== $width && null === $height) {
+ return new self($data, $image, $mimeType, $width, intval(imagesy($image) * $width / imagesx($image)), $logo->getPunchoutBackground());
+ }
+
+ // Only target height specified: calculate width
+ if (null === $width && null !== $height) {
+ return new self($data, $image, $mimeType, intval(imagesx($image) * $height / imagesy($image)), $height, $logo->getPunchoutBackground());
+ }
+
+ return new self($data, $image, $mimeType, imagesx($image), imagesy($image), $logo->getPunchoutBackground());
+ }
+
+ public function getData(): string
+ {
+ return $this->data;
+ }
+
+ /** @return mixed */
+ public function getImage()
+ {
+ if (null === $this->image) {
+ throw new \Exception('SVG Images have no image resource');
+ }
+
+ return $this->image;
+ }
+
+ public function getMimeType(): string
+ {
+ return $this->mimeType;
+ }
+
+ public function getWidth(): int
+ {
+ return $this->width;
+ }
+
+ public function getHeight(): int
+ {
+ return $this->height;
+ }
+
+ public function getPunchoutBackground(): bool
+ {
+ return $this->punchoutBackground;
+ }
+
+ public function createDataUri(): string
+ {
+ return 'data:'.$this->mimeType.';base64,'.base64_encode($this->data);
+ }
+
+ private static function detectMimeTypeFromUrl(string $url): string
+ {
+ /** @var mixed $format */
+ $format = PHP_VERSION_ID >= 80000 ? true : 1;
+
+ $headers = get_headers($url, $format);
+
+ if (!is_array($headers) || !isset($headers['Content-Type'])) {
+ throw new \Exception(sprintf('Content type could not be determined for logo URL "%s"', $url));
+ }
+
+ return is_array($headers['Content-Type']) ? $headers['Content-Type'][1] : $headers['Content-Type'];
+ }
+
+ private static function detectMimeTypeFromPath(string $path): string
+ {
+ if (!function_exists('mime_content_type')) {
+ throw new \Exception('You need the ext-fileinfo extension to determine logo mime type');
+ }
+
+ $mimeType = @mime_content_type($path);
+
+ if (!is_string($mimeType)) {
+ throw new \Exception('Could not determine mime type');
+ }
+
+ if (!preg_match('#^image/#', $mimeType)) {
+ throw new \Exception('Logo path is not an image');
+ }
+
+ // Passing mime type image/svg results in invisible images
+ if ('image/svg' === $mimeType) {
+ return 'image/svg+xml';
+ }
+
+ return $mimeType;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Label/Alignment/LabelAlignmentCenter.php b/vendor/endroid/qr-code/src/Label/Alignment/LabelAlignmentCenter.php
new file mode 100644
index 0000000..c13e287
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Label/Alignment/LabelAlignmentCenter.php
@@ -0,0 +1,9 @@
+validatePath($path);
+
+ $this->path = $path;
+ $this->size = $size;
+ }
+
+ private function validatePath(string $path): void
+ {
+ if (!file_exists($path)) {
+ throw new \Exception(sprintf('Invalid font path "%s"', $path));
+ }
+ }
+
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ public function getSize(): int
+ {
+ return $this->size;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Label/Font/FontInterface.php b/vendor/endroid/qr-code/src/Label/Font/FontInterface.php
new file mode 100644
index 0000000..1c0de9a
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Label/Font/FontInterface.php
@@ -0,0 +1,12 @@
+size = $size;
+ }
+
+ public function getPath(): string
+ {
+ return __DIR__.'/../../../assets/noto_sans.otf';
+ }
+
+ public function getSize(): int
+ {
+ return $this->size;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Label/Font/OpenSans.php b/vendor/endroid/qr-code/src/Label/Font/OpenSans.php
new file mode 100644
index 0000000..4daab73
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Label/Font/OpenSans.php
@@ -0,0 +1,25 @@
+size = $size;
+ }
+
+ public function getPath(): string
+ {
+ return __DIR__.'/../../../assets/open_sans.ttf';
+ }
+
+ public function getSize(): int
+ {
+ return $this->size;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Label/Label.php b/vendor/endroid/qr-code/src/Label/Label.php
new file mode 100644
index 0000000..5d5d013
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Label/Label.php
@@ -0,0 +1,102 @@
+text = $text;
+ $this->font = isset($font) ? $font : new Font(__DIR__.'/../../assets/noto_sans.otf', 16);
+ $this->alignment = isset($alignment) ? $alignment : new LabelAlignmentCenter();
+ $this->margin = isset($margin) ? $margin : new Margin(0, 10, 10, 10);
+ $this->textColor = isset($textColor) ? $textColor : new Color(0, 0, 0);
+ }
+
+ public static function create(string $text): self
+ {
+ return new self($text);
+ }
+
+ public function getText(): string
+ {
+ return $this->text;
+ }
+
+ public function setText(string $text): self
+ {
+ $this->text = $text;
+
+ return $this;
+ }
+
+ public function getFont(): FontInterface
+ {
+ return $this->font;
+ }
+
+ public function setFont(FontInterface $font): self
+ {
+ $this->font = $font;
+
+ return $this;
+ }
+
+ public function getAlignment(): LabelAlignmentInterface
+ {
+ return $this->alignment;
+ }
+
+ public function setAlignment(LabelAlignmentInterface $alignment): self
+ {
+ $this->alignment = $alignment;
+
+ return $this;
+ }
+
+ public function getMargin(): MarginInterface
+ {
+ return $this->margin;
+ }
+
+ public function setMargin(MarginInterface $margin): self
+ {
+ $this->margin = $margin;
+
+ return $this;
+ }
+
+ public function getTextColor(): ColorInterface
+ {
+ return $this->textColor;
+ }
+
+ public function setTextColor(ColorInterface $textColor): self
+ {
+ $this->textColor = $textColor;
+
+ return $this;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Label/LabelInterface.php b/vendor/endroid/qr-code/src/Label/LabelInterface.php
new file mode 100644
index 0000000..a7d2ed6
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Label/LabelInterface.php
@@ -0,0 +1,23 @@
+top = $top;
+ $this->right = $right;
+ $this->bottom = $bottom;
+ $this->left = $left;
+ }
+
+ public function getTop(): int
+ {
+ return $this->top;
+ }
+
+ public function getRight(): int
+ {
+ return $this->right;
+ }
+
+ public function getBottom(): int
+ {
+ return $this->bottom;
+ }
+
+ public function getLeft(): int
+ {
+ return $this->left;
+ }
+
+ /** @return array */
+ public function toArray(): array
+ {
+ return [
+ 'top' => $this->top,
+ 'right' => $this->right,
+ 'bottom' => $this->bottom,
+ 'left' => $this->left,
+ ];
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Label/Margin/MarginInterface.php b/vendor/endroid/qr-code/src/Label/Margin/MarginInterface.php
new file mode 100644
index 0000000..bc428bd
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Label/Margin/MarginInterface.php
@@ -0,0 +1,19 @@
+ */
+ public function toArray(): array;
+}
diff --git a/vendor/endroid/qr-code/src/Logo/Logo.php b/vendor/endroid/qr-code/src/Logo/Logo.php
new file mode 100644
index 0000000..b50d743
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Logo/Logo.php
@@ -0,0 +1,74 @@
+path = $path;
+ $this->resizeToWidth = $resizeToWidth;
+ $this->resizeToHeight = $resizeToHeight;
+ $this->punchoutBackground = $punchoutBackground;
+ }
+
+ public static function create(string $path): self
+ {
+ return new self($path);
+ }
+
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ public function setPath(string $path): self
+ {
+ $this->path = $path;
+
+ return $this;
+ }
+
+ public function getResizeToWidth(): ?int
+ {
+ return $this->resizeToWidth;
+ }
+
+ public function setResizeToWidth(?int $resizeToWidth): self
+ {
+ $this->resizeToWidth = $resizeToWidth;
+
+ return $this;
+ }
+
+ public function getResizeToHeight(): ?int
+ {
+ return $this->resizeToHeight;
+ }
+
+ public function setResizeToHeight(?int $resizeToHeight): self
+ {
+ $this->resizeToHeight = $resizeToHeight;
+
+ return $this;
+ }
+
+ public function getPunchoutBackground(): bool
+ {
+ return $this->punchoutBackground;
+ }
+
+ public function setPunchoutBackground(bool $punchoutBackground): self
+ {
+ $this->punchoutBackground = $punchoutBackground;
+
+ return $this;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Logo/LogoInterface.php b/vendor/endroid/qr-code/src/Logo/LogoInterface.php
new file mode 100644
index 0000000..13d940c
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Logo/LogoInterface.php
@@ -0,0 +1,16 @@
+> */
+ private array $blockValues = [];
+
+ private float $blockSize;
+ private int $innerSize;
+ private int $outerSize;
+ private int $marginLeft;
+ private int $marginRight;
+
+ /** @param array> $blockValues */
+ public function __construct(array $blockValues, int $size, int $margin, RoundBlockSizeModeInterface $roundBlockSizeMode)
+ {
+ $this->blockValues = $blockValues;
+
+ $this->blockSize = $size / $this->getBlockCount();
+ $this->innerSize = $size;
+ $this->outerSize = $size + 2 * $margin;
+
+ if ($roundBlockSizeMode instanceof RoundBlockSizeModeEnlarge) {
+ $this->blockSize = intval(ceil($this->blockSize));
+ $this->innerSize = intval($this->blockSize * $this->getBlockCount());
+ $this->outerSize = $this->innerSize + 2 * $margin;
+ } elseif ($roundBlockSizeMode instanceof RoundBlockSizeModeShrink) {
+ $this->blockSize = intval(floor($this->blockSize));
+ $this->innerSize = intval($this->blockSize * $this->getBlockCount());
+ $this->outerSize = $this->innerSize + 2 * $margin;
+ } elseif ($roundBlockSizeMode instanceof RoundBlockSizeModeMargin) {
+ $this->blockSize = intval(floor($this->blockSize));
+ $this->innerSize = intval($this->blockSize * $this->getBlockCount());
+ }
+
+ if ($this->blockSize < 1) {
+ throw new \Exception('Too much data: increase image dimensions or lower error correction level');
+ }
+
+ $this->marginLeft = intval(($this->outerSize - $this->innerSize) / 2);
+ $this->marginRight = $this->outerSize - $this->innerSize - $this->marginLeft;
+ }
+
+ public function getBlockValue(int $rowIndex, int $columnIndex): int
+ {
+ return $this->blockValues[$rowIndex][$columnIndex];
+ }
+
+ public function getBlockCount(): int
+ {
+ return count($this->blockValues[0]);
+ }
+
+ public function getBlockSize(): float
+ {
+ return $this->blockSize;
+ }
+
+ public function getInnerSize(): int
+ {
+ return $this->innerSize;
+ }
+
+ public function getOuterSize(): int
+ {
+ return $this->outerSize;
+ }
+
+ public function getMarginLeft(): int
+ {
+ return $this->marginLeft;
+ }
+
+ public function getMarginRight(): int
+ {
+ return $this->marginRight;
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Matrix/MatrixFactoryInterface.php b/vendor/endroid/qr-code/src/Matrix/MatrixFactoryInterface.php
new file mode 100644
index 0000000..be501f1
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Matrix/MatrixFactoryInterface.php
@@ -0,0 +1,12 @@
+
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode;
-use BaconQrCode\Encoder\Encoder;
-use Endroid\QrCode\Exception\InvalidFontException;
-use Endroid\QrCode\Exception\UnsupportedExtensionException;
-use Endroid\QrCode\Writer\WriterInterface;
+use Endroid\QrCode\Color\Color;
+use Endroid\QrCode\Color\ColorInterface;
+use Endroid\QrCode\Encoding\Encoding;
+use Endroid\QrCode\Encoding\EncodingInterface;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
-class QrCode implements QrCodeInterface
+final class QrCode implements QrCodeInterface
{
- const LABEL_FONT_PATH_DEFAULT = __DIR__.'/../assets/fonts/noto_sans.otf';
+ private string $data;
+ private EncodingInterface $encoding;
+ private ErrorCorrectionLevelInterface $errorCorrectionLevel;
+ private int $size;
+ private int $margin;
+ private RoundBlockSizeModeInterface $roundBlockSizeMode;
+ private ColorInterface $foregroundColor;
+ private ColorInterface $backgroundColor;
- private $text;
-
- /** @var int */
- private $size = 300;
-
- /** @var int */
- private $margin = 10;
-
- /** @var array */
- private $foregroundColor = [
- 'r' => 0,
- 'g' => 0,
- 'b' => 0,
- 'a' => 0,
- ];
-
- /** @var array */
- private $backgroundColor = [
- 'r' => 255,
- 'g' => 255,
- 'b' => 255,
- 'a' => 0,
- ];
-
- /** @var string */
- private $encoding = 'UTF-8';
-
- /** @var bool */
- private $roundBlockSize = true;
-
- private $errorCorrectionLevel;
-
- /** @var string */
- private $logoPath;
-
- /** @var int|null */
- private $logoWidth;
-
- /** @var int|null */
- private $logoHeight;
-
- /** @var string */
- private $label;
-
- /** @var int */
- private $labelFontSize = 16;
-
- /** @var string */
- private $labelFontPath = self::LABEL_FONT_PATH_DEFAULT;
-
- private $labelAlignment;
-
- /** @var array */
- private $labelMargin = [
- 't' => 0,
- 'r' => 10,
- 'b' => 10,
- 'l' => 10,
- ];
-
- /** @var WriterRegistryInterface */
- private $writerRegistry;
-
- /** @var WriterInterface|null */
- private $writer;
-
- /** @var array */
- private $writerOptions = [];
-
- /** @var bool */
- private $validateResult = false;
-
- public function __construct(string $text = '')
- {
- $this->text = $text;
-
- $this->errorCorrectionLevel = ErrorCorrectionLevel::LOW();
- $this->labelAlignment = LabelAlignment::CENTER();
-
- $this->createWriterRegistry();
- }
-
- public function setText(string $text): void
- {
- $this->text = $text;
- }
-
- public function getText(): string
- {
- return $this->text;
- }
-
- public function setSize(int $size): void
- {
+ public function __construct(
+ string $data,
+ EncodingInterface $encoding = null,
+ ErrorCorrectionLevelInterface $errorCorrectionLevel = null,
+ int $size = 300,
+ int $margin = 10,
+ RoundBlockSizeModeInterface $roundBlockSizeMode = null,
+ ColorInterface $foregroundColor = null,
+ ColorInterface $backgroundColor = null
+ ) {
+ $this->data = $data;
+ $this->encoding = $encoding ?? new Encoding('UTF-8');
+ $this->errorCorrectionLevel = $errorCorrectionLevel ?? new ErrorCorrectionLevelLow();
$this->size = $size;
+ $this->margin = $margin;
+ $this->roundBlockSizeMode = $roundBlockSizeMode ?? new RoundBlockSizeModeMargin();
+ $this->foregroundColor = $foregroundColor ?? new Color(0, 0, 0);
+ $this->backgroundColor = $backgroundColor ?? new Color(255, 255, 255);
+ }
+
+ public static function create(string $data): self
+ {
+ return new self($data);
+ }
+
+ public function getData(): string
+ {
+ return $this->data;
+ }
+
+ public function setData(string $data): self
+ {
+ $this->data = $data;
+
+ return $this;
+ }
+
+ public function getEncoding(): EncodingInterface
+ {
+ return $this->encoding;
+ }
+
+ public function setEncoding(Encoding $encoding): self
+ {
+ $this->encoding = $encoding;
+
+ return $this;
+ }
+
+ public function getErrorCorrectionLevel(): ErrorCorrectionLevelInterface
+ {
+ return $this->errorCorrectionLevel;
+ }
+
+ public function setErrorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): self
+ {
+ $this->errorCorrectionLevel = $errorCorrectionLevel;
+
+ return $this;
}
public function getSize(): int
@@ -122,9 +90,11 @@ class QrCode implements QrCodeInterface
return $this->size;
}
- public function setMargin(int $margin): void
+ public function setSize(int $size): self
{
- $this->margin = $margin;
+ $this->size = $size;
+
+ return $this;
}
public function getMargin(): int
@@ -132,308 +102,46 @@ class QrCode implements QrCodeInterface
return $this->margin;
}
- public function setForegroundColor(array $foregroundColor): void
+ public function setMargin(int $margin): self
{
- if (!isset($foregroundColor['a'])) {
- $foregroundColor['a'] = 0;
- }
+ $this->margin = $margin;
- foreach ($foregroundColor as &$color) {
- $color = intval($color);
- }
-
- $this->foregroundColor = $foregroundColor;
+ return $this;
}
- public function getForegroundColor(): array
+ public function getRoundBlockSizeMode(): RoundBlockSizeModeInterface
+ {
+ return $this->roundBlockSizeMode;
+ }
+
+ public function setRoundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): self
+ {
+ $this->roundBlockSizeMode = $roundBlockSizeMode;
+
+ return $this;
+ }
+
+ public function getForegroundColor(): ColorInterface
{
return $this->foregroundColor;
}
- public function setBackgroundColor(array $backgroundColor): void
+ public function setForegroundColor(ColorInterface $foregroundColor): self
{
- if (!isset($backgroundColor['a'])) {
- $backgroundColor['a'] = 0;
- }
+ $this->foregroundColor = $foregroundColor;
- foreach ($backgroundColor as &$color) {
- $color = intval($color);
- }
-
- $this->backgroundColor = $backgroundColor;
+ return $this;
}
- public function getBackgroundColor(): array
+ public function getBackgroundColor(): ColorInterface
{
return $this->backgroundColor;
}
- public function setEncoding(string $encoding): void
+ public function setBackgroundColor(ColorInterface $backgroundColor): self
{
- $this->encoding = $encoding;
- }
+ $this->backgroundColor = $backgroundColor;
- public function getEncoding(): string
- {
- return $this->encoding;
- }
-
- public function setRoundBlockSize(bool $roundBlockSize): void
- {
- $this->roundBlockSize = $roundBlockSize;
- }
-
- public function getRoundBlockSize(): bool
- {
- return $this->roundBlockSize;
- }
-
- public function setErrorCorrectionLevel(ErrorCorrectionLevel $errorCorrectionLevel): void
- {
- $this->errorCorrectionLevel = $errorCorrectionLevel;
- }
-
- public function getErrorCorrectionLevel(): ErrorCorrectionLevel
- {
- return $this->errorCorrectionLevel;
- }
-
- public function setLogoPath(string $logoPath): void
- {
- $this->logoPath = $logoPath;
- }
-
- public function getLogoPath(): ?string
- {
- return $this->logoPath;
- }
-
- public function setLogoSize(int $logoWidth, int $logoHeight = null): void
- {
- $this->logoWidth = $logoWidth;
- $this->logoHeight = $logoHeight;
- }
-
- public function setLogoWidth(int $logoWidth): void
- {
- $this->logoWidth = $logoWidth;
- }
-
- public function getLogoWidth(): ?int
- {
- return $this->logoWidth;
- }
-
- public function setLogoHeight(int $logoHeight): void
- {
- $this->logoHeight = $logoHeight;
- }
-
- public function getLogoHeight(): ?int
- {
- return $this->logoHeight;
- }
-
- public function setLabel(string $label, int $labelFontSize = null, string $labelFontPath = null, string $labelAlignment = null, array $labelMargin = null): void
- {
- $this->label = $label;
-
- if (null !== $labelFontSize) {
- $this->setLabelFontSize($labelFontSize);
- }
-
- if (null !== $labelFontPath) {
- $this->setLabelFontPath($labelFontPath);
- }
-
- if (null !== $labelAlignment) {
- $this->setLabelAlignment($labelAlignment);
- }
-
- if (null !== $labelMargin) {
- $this->setLabelMargin($labelMargin);
- }
- }
-
- public function getLabel(): ?string
- {
- return $this->label;
- }
-
- public function setLabelFontSize(int $labelFontSize): void
- {
- $this->labelFontSize = $labelFontSize;
- }
-
- public function getLabelFontSize(): int
- {
- return $this->labelFontSize;
- }
-
- public function setLabelFontPath(string $labelFontPath): void
- {
- $resolvedLabelFontPath = (string) realpath($labelFontPath);
-
- if (!is_file($resolvedLabelFontPath)) {
- throw new InvalidFontException('Invalid label font path: '.$labelFontPath);
- }
-
- $this->labelFontPath = $resolvedLabelFontPath;
- }
-
- public function getLabelFontPath(): string
- {
- return $this->labelFontPath;
- }
-
- public function setLabelAlignment(string $labelAlignment): void
- {
- $this->labelAlignment = new LabelAlignment($labelAlignment);
- }
-
- public function getLabelAlignment(): string
- {
- return $this->labelAlignment->getValue();
- }
-
- public function setLabelMargin(array $labelMargin): void
- {
- $this->labelMargin = array_merge($this->labelMargin, $labelMargin);
- }
-
- public function getLabelMargin(): array
- {
- return $this->labelMargin;
- }
-
- public function setWriterRegistry(WriterRegistryInterface $writerRegistry): void
- {
- $this->writerRegistry = $writerRegistry;
- }
-
- public function setWriter(WriterInterface $writer): void
- {
- $this->writer = $writer;
- }
-
- public function getWriter(string $name = null): WriterInterface
- {
- if (!is_null($name)) {
- return $this->writerRegistry->getWriter($name);
- }
-
- if ($this->writer instanceof WriterInterface) {
- return $this->writer;
- }
-
- return $this->writerRegistry->getDefaultWriter();
- }
-
- public function setWriterOptions(array $writerOptions): void
- {
- $this->writerOptions = $writerOptions;
- }
-
- public function getWriterOptions(): array
- {
- return $this->writerOptions;
- }
-
- private function createWriterRegistry(): void
- {
- $this->writerRegistry = new WriterRegistry();
- $this->writerRegistry->loadDefaultWriters();
- }
-
- public function setWriterByName(string $name): void
- {
- $this->writer = $this->getWriter($name);
- }
-
- public function setWriterByPath(string $path): void
- {
- $extension = pathinfo($path, PATHINFO_EXTENSION);
-
- $this->setWriterByExtension($extension);
- }
-
- public function setWriterByExtension(string $extension): void
- {
- foreach ($this->writerRegistry->getWriters() as $writer) {
- if ($writer->supportsExtension($extension)) {
- $this->writer = $writer;
-
- return;
- }
- }
-
- throw new UnsupportedExtensionException('Missing writer for extension "'.$extension.'"');
- }
-
- public function writeString(): string
- {
- return $this->getWriter()->writeString($this);
- }
-
- public function writeDataUri(): string
- {
- return $this->getWriter()->writeDataUri($this);
- }
-
- public function writeFile(string $path): void
- {
- $this->getWriter()->writeFile($this, $path);
- }
-
- public function getContentType(): string
- {
- return $this->getWriter()->getContentType();
- }
-
- public function setValidateResult(bool $validateResult): void
- {
- $this->validateResult = $validateResult;
- }
-
- public function getValidateResult(): bool
- {
- return $this->validateResult;
- }
-
- public function getData(): array
- {
- $baconErrorCorrectionLevel = $this->errorCorrectionLevel->toBaconErrorCorrectionLevel();
-
- $baconQrCode = Encoder::encode($this->text, $baconErrorCorrectionLevel, $this->encoding);
-
- $baconMatrix = $baconQrCode->getMatrix();
-
- $matrix = [];
- $columnCount = $baconMatrix->getWidth();
- $rowCount = $baconMatrix->getHeight();
- for ($rowIndex = 0; $rowIndex < $rowCount; ++$rowIndex) {
- $matrix[$rowIndex] = [];
- for ($columnIndex = 0; $columnIndex < $columnCount; ++$columnIndex) {
- $matrix[$rowIndex][$columnIndex] = $baconMatrix->get($columnIndex, $rowIndex);
- }
- }
-
- $data = ['matrix' => $matrix];
- $data['block_count'] = count($matrix[0]);
- $data['block_size'] = $this->size / $data['block_count'];
- if ($this->roundBlockSize) {
- $data['block_size'] = intval(floor($data['block_size']));
- }
- $data['inner_width'] = $data['block_size'] * $data['block_count'];
- $data['inner_height'] = $data['block_size'] * $data['block_count'];
- $data['outer_width'] = $this->size + 2 * $this->margin;
- $data['outer_height'] = $this->size + 2 * $this->margin;
- $data['margin_left'] = ($data['outer_width'] - $data['inner_width']) / 2;
- if ($this->roundBlockSize) {
- $data['margin_left'] = intval(floor($data['margin_left']));
- }
- $data['margin_right'] = $data['outer_width'] - $data['inner_width'] - $data['margin_left'];
-
- return $data;
+ return $this;
}
}
diff --git a/vendor/endroid/qr-code/src/QrCodeInterface.php b/vendor/endroid/qr-code/src/QrCodeInterface.php
index e6382a7..2c96984 100644
--- a/vendor/endroid/qr-code/src/QrCodeInterface.php
+++ b/vendor/endroid/qr-code/src/QrCodeInterface.php
@@ -2,62 +2,28 @@
declare(strict_types=1);
-/*
- * (c) Jeroen van den Enden
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode;
+use Endroid\QrCode\Color\ColorInterface;
+use Endroid\QrCode\Encoding\EncodingInterface;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
+
interface QrCodeInterface
{
- public function getText(): string;
+ public function getData(): string;
+
+ public function getEncoding(): EncodingInterface;
+
+ public function getErrorCorrectionLevel(): ErrorCorrectionLevelInterface;
public function getSize(): int;
public function getMargin(): int;
- public function getForegroundColor(): array;
+ public function getRoundBlockSizeMode(): RoundBlockSizeModeInterface;
- public function getBackgroundColor(): array;
+ public function getForegroundColor(): ColorInterface;
- public function getEncoding(): string;
-
- public function getRoundBlockSize(): bool;
-
- public function getErrorCorrectionLevel(): ErrorCorrectionLevel;
-
- public function getLogoPath(): ?string;
-
- public function getLogoWidth(): ?int;
-
- public function getLogoHeight(): ?int;
-
- public function getLabel(): ?string;
-
- public function getLabelFontPath(): string;
-
- public function getLabelFontSize(): int;
-
- public function getLabelAlignment(): string;
-
- public function getLabelMargin(): array;
-
- public function getValidateResult(): bool;
-
- public function getWriterOptions(): array;
-
- public function getContentType(): string;
-
- public function setWriterRegistry(WriterRegistryInterface $writerRegistry): void;
-
- public function writeString(): string;
-
- public function writeDataUri(): string;
-
- public function writeFile(string $path): void;
-
- public function getData(): array;
+ public function getBackgroundColor(): ColorInterface;
}
diff --git a/vendor/endroid/qr-code/src/RoundBlockSizeMode/RoundBlockSizeModeEnlarge.php b/vendor/endroid/qr-code/src/RoundBlockSizeMode/RoundBlockSizeModeEnlarge.php
new file mode 100644
index 0000000..24f51d6
--- /dev/null
+++ b/vendor/endroid/qr-code/src/RoundBlockSizeMode/RoundBlockSizeModeEnlarge.php
@@ -0,0 +1,9 @@
+
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode\Writer;
+use Endroid\QrCode\Bacon\MatrixFactory;
+use Endroid\QrCode\Label\LabelInterface;
+use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
+use Endroid\QrCode\Writer\Result\BinaryResult;
+use Endroid\QrCode\Writer\Result\ResultInterface;
-class BinaryWriter extends AbstractWriter
+final class BinaryWriter implements WriterInterface
{
- public function writeString(QrCodeInterface $qrCode): string
+ public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface
{
- $rows = [];
- $data = $qrCode->getData();
- foreach ($data['matrix'] as $row) {
- $values = '';
- foreach ($row as $value) {
- $values .= $value;
- }
- $rows[] = $values;
- }
+ $matrixFactory = new MatrixFactory();
+ $matrix = $matrixFactory->create($qrCode);
- return implode("\n", $rows);
- }
-
- public static function getContentType(): string
- {
- return 'text/plain';
- }
-
- public static function getSupportedExtensions(): array
- {
- return ['bin', 'txt'];
- }
-
- public function getName(): string
- {
- return 'binary';
+ return new BinaryResult($matrix);
}
}
diff --git a/vendor/endroid/qr-code/src/Writer/DebugWriter.php b/vendor/endroid/qr-code/src/Writer/DebugWriter.php
index 81be0ea..3e89515 100644
--- a/vendor/endroid/qr-code/src/Writer/DebugWriter.php
+++ b/vendor/endroid/qr-code/src/Writer/DebugWriter.php
@@ -2,59 +2,27 @@
declare(strict_types=1);
-/*
- * (c) Jeroen van den Enden
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode\Writer;
+use Endroid\QrCode\Label\LabelInterface;
+use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
-use Exception;
-use ReflectionClass;
+use Endroid\QrCode\Writer\Result\DebugResult;
+use Endroid\QrCode\Writer\Result\ResultInterface;
-class DebugWriter extends AbstractWriter
+final class DebugWriter implements WriterInterface, ValidatingWriterInterface
{
- public function writeString(QrCodeInterface $qrCode): string
+ public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface
{
- $data = [];
- $skip = ['getData'];
+ return new DebugResult($qrCode, $logo, $label, $options);
+ }
- $reflectionClass = new ReflectionClass($qrCode);
- foreach ($reflectionClass->getMethods() as $method) {
- $methodName = $method->getShortName();
- if (0 === strpos($methodName, 'get') && 0 == $method->getNumberOfParameters() && !in_array($methodName, $skip)) {
- $value = $qrCode->{$methodName}();
- if (is_array($value) && !is_object(current($value))) {
- $value = '['.implode(', ', $value).']';
- } elseif (is_bool($value)) {
- $value = $value ? 'true' : 'false';
- } elseif (is_string($value)) {
- $value = '"'.$value.'"';
- } elseif (is_null($value)) {
- $value = 'null';
- }
- try {
- $data[] = $methodName.': '.$value;
- } catch (Exception $exception) {
- }
- }
+ public function validateResult(ResultInterface $result, string $expectedData): void
+ {
+ if (!$result instanceof DebugResult) {
+ throw new \Exception('Unable to write logo: instance of DebugResult expected');
}
- $string = implode(" \n", $data);
-
- return $string;
- }
-
- public static function getContentType(): string
- {
- return 'text/plain';
- }
-
- public function getName(): string
- {
- return 'debug';
+ $result->setValidateResult(true);
}
}
diff --git a/vendor/endroid/qr-code/src/Writer/EpsWriter.php b/vendor/endroid/qr-code/src/Writer/EpsWriter.php
index 9979680..2ffd219 100644
--- a/vendor/endroid/qr-code/src/Writer/EpsWriter.php
+++ b/vendor/endroid/qr-code/src/Writer/EpsWriter.php
@@ -2,58 +2,43 @@
declare(strict_types=1);
-/*
- * (c) Jeroen van den Enden
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode\Writer;
+use Endroid\QrCode\Bacon\MatrixFactory;
+use Endroid\QrCode\Label\LabelInterface;
+use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
+use Endroid\QrCode\Writer\Result\EpsResult;
+use Endroid\QrCode\Writer\Result\ResultInterface;
-class EpsWriter extends AbstractWriter
+final class EpsWriter implements WriterInterface
{
- public function writeString(QrCodeInterface $qrCode): string
+ public const DECIMAL_PRECISION = 10;
+
+ public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface
{
- $data = $qrCode->getData();
+ $matrixFactory = new MatrixFactory();
+ $matrix = $matrixFactory->create($qrCode);
- $epsData = [];
- $epsData[] = '%!PS-Adobe-3.0 EPSF-3.0';
- $epsData[] = '%%BoundingBox: 0 0 '.$data['outer_width'].' '.$data['outer_height'];
- $epsData[] = '/F { rectfill } def';
- $epsData[] = number_format($qrCode->getBackgroundColor()['r'] / 100, 2, '.', ',').' '.number_format($qrCode->getBackgroundColor()['g'] / 100, 2, '.', ',').' '.number_format($qrCode->getBackgroundColor()['b'] / 100, 2, '.', ',').' setrgbcolor';
- $epsData[] = '0 0 '.$data['outer_width'].' '.$data['outer_height'].' F';
- $epsData[] = number_format($qrCode->getForegroundColor()['r'] / 100, 2, '.', ',').' '.number_format($qrCode->getForegroundColor()['g'] / 100, 2, '.', ',').' '.number_format($qrCode->getForegroundColor()['b'] / 100, 2, '.', ',').' setrgbcolor';
+ $lines = [
+ '%!PS-Adobe-3.0 EPSF-3.0',
+ '%%BoundingBox: 0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize(),
+ '/F { rectfill } def',
+ number_format($qrCode->getBackgroundColor()->getRed() / 100, 2, '.', ',').' '.number_format($qrCode->getBackgroundColor()->getGreen() / 100, 2, '.', ',').' '.number_format($qrCode->getBackgroundColor()->getBlue() / 100, 2, '.', ',').' setrgbcolor',
+ '0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize().' F',
+ number_format($qrCode->getForegroundColor()->getRed() / 100, 2, '.', ',').' '.number_format($qrCode->getForegroundColor()->getGreen() / 100, 2, '.', ',').' '.number_format($qrCode->getForegroundColor()->getBlue() / 100, 2, '.', ',').' setrgbcolor',
+ ];
- // Please note an EPS has a reversed Y axis compared to PNG and SVG
- $data['matrix'] = array_reverse($data['matrix']);
- foreach ($data['matrix'] as $row => $values) {
- foreach ($values as $column => $value) {
- if (1 === $value) {
- $x = $data['margin_left'] + $data['block_size'] * $column;
- $y = $data['margin_left'] + $data['block_size'] * $row;
- $epsData[] = $x.' '.$y.' '.$data['block_size'].' '.$data['block_size'].' F';
+ for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
+ for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
+ if (1 === $matrix->getBlockValue($matrix->getBlockCount() - 1 - $rowIndex, $columnIndex)) {
+ $x = $matrix->getMarginLeft() + $matrix->getBlockSize() * $columnIndex;
+ $y = $matrix->getMarginLeft() + $matrix->getBlockSize() * $rowIndex;
+ $lines[] = number_format($x, self::DECIMAL_PRECISION, '.', '').' '.number_format($y, self::DECIMAL_PRECISION, '.', '').' '.number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', '').' '.number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', '').' F';
}
}
}
- return implode("\n", $epsData);
- }
-
- public static function getContentType(): string
- {
- return 'image/eps';
- }
-
- public static function getSupportedExtensions(): array
- {
- return ['eps'];
- }
-
- public function getName(): string
- {
- return 'eps';
+ return new EpsResult($lines);
}
}
diff --git a/vendor/endroid/qr-code/src/Writer/PdfWriter.php b/vendor/endroid/qr-code/src/Writer/PdfWriter.php
new file mode 100644
index 0000000..78f0603
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/PdfWriter.php
@@ -0,0 +1,133 @@
+create($qrCode);
+
+ $unit = 'mm';
+ if (isset($options[self::WRITER_OPTION_UNIT])) {
+ $unit = $options[self::WRITER_OPTION_UNIT];
+ }
+
+ $allowedUnits = ['mm', 'pt', 'cm', 'in'];
+ if (!in_array($unit, $allowedUnits)) {
+ throw new \Exception(sprintf('PDF Measure unit should be one of [%s]', implode(', ', $allowedUnits)));
+ }
+
+ $labelSpace = 0;
+ if ($label instanceof LabelInterface) {
+ $labelSpace = 30;
+ }
+
+ if (!class_exists(\FPDF::class)) {
+ throw new \Exception('Unable to find FPDF: check your installation');
+ }
+
+ $foregroundColor = $qrCode->getForegroundColor();
+ if ($foregroundColor->getAlpha() > 0) {
+ throw new \Exception('PDF Writer does not support alpha channels');
+ }
+ $backgroundColor = $qrCode->getBackgroundColor();
+ if ($backgroundColor->getAlpha() > 0) {
+ throw new \Exception('PDF Writer does not support alpha channels');
+ }
+
+ if (isset($options[self::WRITER_OPTION_PDF])) {
+ $fpdf = $options[self::WRITER_OPTION_PDF];
+ if (!$fpdf instanceof \FPDF) {
+ throw new \Exception('pdf option must be an instance of FPDF');
+ }
+ } else {
+ // @todo Check how to add label height later
+ $fpdf = new \FPDF('P', $unit, [$matrix->getOuterSize(), $matrix->getOuterSize() + $labelSpace]);
+ $fpdf->AddPage();
+ }
+
+ $x = 0;
+ if (isset($options[self::WRITER_OPTION_X])) {
+ $x = $options[self::WRITER_OPTION_X];
+ }
+ $y = 0;
+ if (isset($options[self::WRITER_OPTION_Y])) {
+ $y = $options[self::WRITER_OPTION_Y];
+ }
+
+ $fpdf->SetFillColor($backgroundColor->getRed(), $backgroundColor->getGreen(), $backgroundColor->getBlue());
+ $fpdf->Rect($x, $y, $matrix->getOuterSize(), $matrix->getOuterSize(), 'F');
+ $fpdf->SetFillColor($foregroundColor->getRed(), $foregroundColor->getGreen(), $foregroundColor->getBlue());
+
+ for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
+ for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
+ if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
+ $fpdf->Rect(
+ $x + $matrix->getMarginLeft() + ($columnIndex * $matrix->getBlockSize()),
+ $y + $matrix->getMarginLeft() + ($rowIndex * $matrix->getBlockSize()),
+ $matrix->getBlockSize(),
+ $matrix->getBlockSize(),
+ 'F'
+ );
+ }
+ }
+ }
+
+ if ($logo instanceof LogoInterface) {
+ $this->addLogo($logo, $fpdf, $x, $y, $matrix->getOuterSize());
+ }
+
+ if ($label instanceof LabelInterface) {
+ $fpdf->SetXY($x, $y + $matrix->getOuterSize() + $labelSpace - 25);
+ $fpdf->SetFont('Helvetica', '', $label->getFont()->getSize());
+ $fpdf->Cell($matrix->getOuterSize(), 0, $label->getText(), 0, 0, 'C');
+ }
+
+ return new PdfResult($fpdf);
+ }
+
+ private function addLogo(LogoInterface $logo, \FPDF $fpdf, float $x, float $y, float $size): void
+ {
+ $logoPath = $logo->getPath();
+ $logoHeight = $logo->getResizeToHeight();
+ $logoWidth = $logo->getResizeToWidth();
+
+ if (null === $logoHeight || null === $logoWidth) {
+ $imageSize = \getimagesize($logoPath);
+ if (!$imageSize) {
+ throw new \Exception(sprintf('Unable to read image size for logo "%s"', $logoPath));
+ }
+ [$logoSourceWidth, $logoSourceHeight] = $imageSize;
+
+ if (null === $logoWidth) {
+ $logoWidth = (int) $logoSourceWidth;
+ }
+
+ if (null === $logoHeight) {
+ $aspectRatio = $logoWidth / $logoSourceWidth;
+ $logoHeight = (int) ($logoSourceHeight * $aspectRatio);
+ }
+ }
+
+ $logoX = $x + $size / 2 - $logoWidth / 2;
+ $logoY = $y + $size / 2 - $logoHeight / 2;
+
+ $fpdf->Image($logoPath, $logoX, $logoY, $logoWidth, $logoHeight);
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/PngWriter.php b/vendor/endroid/qr-code/src/Writer/PngWriter.php
index 2b60b8b..e944d84 100644
--- a/vendor/endroid/qr-code/src/Writer/PngWriter.php
+++ b/vendor/endroid/qr-code/src/Writer/PngWriter.php
@@ -2,209 +2,226 @@
declare(strict_types=1);
-/*
- * (c) Jeroen van den Enden
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode\Writer;
-use Endroid\QrCode\Exception\GenerateImageException;
-use Endroid\QrCode\Exception\MissingFunctionException;
-use Endroid\QrCode\Exception\MissingLogoHeightException;
-use Endroid\QrCode\Exception\ValidationException;
-use Endroid\QrCode\LabelAlignment;
+use Endroid\QrCode\Bacon\MatrixFactory;
+use Endroid\QrCode\ImageData\LabelImageData;
+use Endroid\QrCode\ImageData\LogoImageData;
+use Endroid\QrCode\Label\Alignment\LabelAlignmentLeft;
+use Endroid\QrCode\Label\Alignment\LabelAlignmentRight;
+use Endroid\QrCode\Label\LabelInterface;
+use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeNone;
+use Endroid\QrCode\Writer\Result\PngResult;
+use Endroid\QrCode\Writer\Result\ResultInterface;
use Zxing\QrReader;
-class PngWriter extends AbstractWriter
+final class PngWriter implements WriterInterface, ValidatingWriterInterface
{
- public function writeString(QrCodeInterface $qrCode): string
+ public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface
{
- $image = $this->createImage($qrCode->getData(), $qrCode);
-
- $logoPath = $qrCode->getLogoPath();
- if (null !== $logoPath) {
- $image = $this->addLogo($image, $logoPath, $qrCode->getLogoWidth(), $qrCode->getLogoHeight());
+ if (!extension_loaded('gd')) {
+ throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
}
- $label = $qrCode->getLabel();
- if (null !== $label) {
- $image = $this->addLabel($image, $label, $qrCode->getLabelFontPath(), $qrCode->getLabelFontSize(), $qrCode->getLabelAlignment(), $qrCode->getLabelMargin(), $qrCode->getForegroundColor(), $qrCode->getBackgroundColor());
+ $matrixFactory = new MatrixFactory();
+ $matrix = $matrixFactory->create($qrCode);
+
+ $baseBlockSize = $qrCode->getRoundBlockSizeMode() instanceof RoundBlockSizeModeNone ? 10 : intval($matrix->getBlockSize());
+ $baseImage = imagecreatetruecolor($matrix->getBlockCount() * $baseBlockSize, $matrix->getBlockCount() * $baseBlockSize);
+
+ if (!$baseImage) {
+ throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
}
- $string = $this->imageToString($image);
+ /** @var int $foregroundColor */
+ $foregroundColor = imagecolorallocatealpha(
+ $baseImage,
+ $qrCode->getForegroundColor()->getRed(),
+ $qrCode->getForegroundColor()->getGreen(),
+ $qrCode->getForegroundColor()->getBlue(),
+ $qrCode->getForegroundColor()->getAlpha()
+ );
- imagedestroy($image);
+ /** @var int $transparentColor */
+ $transparentColor = imagecolorallocatealpha($baseImage, 255, 255, 255, 127);
- if ($qrCode->getValidateResult()) {
- $reader = new QrReader($string, QrReader::SOURCE_TYPE_BLOB);
- if ($reader->text() !== $qrCode->getText()) {
- throw new ValidationException('Built-in validation reader read "'.$reader->text().'" instead of "'.$qrCode->getText().'".
- Adjust your parameters to increase readability or disable built-in validation.');
- }
- }
+ imagefill($baseImage, 0, 0, $transparentColor);
- return $string;
- }
-
- private function createImage(array $data, QrCodeInterface $qrCode)
- {
- $baseSize = $qrCode->getRoundBlockSize() ? $data['block_size'] : 25;
-
- $baseImage = $this->createBaseImage($baseSize, $data, $qrCode);
- $interpolatedImage = $this->createInterpolatedImage($baseImage, $data, $qrCode);
-
- imagedestroy($baseImage);
-
- return $interpolatedImage;
- }
-
- private function createBaseImage(int $baseSize, array $data, QrCodeInterface $qrCode)
- {
- $image = imagecreatetruecolor($data['block_count'] * $baseSize, $data['block_count'] * $baseSize);
-
- if (!is_resource($image)) {
- throw new GenerateImageException('Unable to generate image: check your GD installation');
- }
-
- $foregroundColor = imagecolorallocatealpha($image, $qrCode->getForegroundColor()['r'], $qrCode->getForegroundColor()['g'], $qrCode->getForegroundColor()['b'], $qrCode->getForegroundColor()['a']);
- $backgroundColor = imagecolorallocatealpha($image, $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b'], $qrCode->getBackgroundColor()['a']);
- imagefill($image, 0, 0, $backgroundColor);
-
- foreach ($data['matrix'] as $row => $values) {
- foreach ($values as $column => $value) {
- if (1 === $value) {
- imagefilledrectangle($image, $column * $baseSize, $row * $baseSize, intval(($column + 1) * $baseSize), intval(($row + 1) * $baseSize), $foregroundColor);
+ for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
+ for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
+ if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
+ imagefilledrectangle(
+ $baseImage,
+ $columnIndex * $baseBlockSize,
+ $rowIndex * $baseBlockSize,
+ ($columnIndex + 1) * $baseBlockSize - 1,
+ ($rowIndex + 1) * $baseBlockSize - 1,
+ $foregroundColor
+ );
}
}
}
- return $image;
- }
+ $targetWidth = $matrix->getOuterSize();
+ $targetHeight = $matrix->getOuterSize();
- private function createInterpolatedImage($baseImage, array $data, QrCodeInterface $qrCode)
- {
- $image = imagecreatetruecolor($data['outer_width'], $data['outer_height']);
-
- if (!is_resource($image)) {
- throw new GenerateImageException('Unable to generate image: check your GD installation');
+ if ($label instanceof LabelInterface) {
+ $labelImageData = LabelImageData::createForLabel($label);
+ $targetHeight += $labelImageData->getHeight() + $label->getMargin()->getTop() + $label->getMargin()->getBottom();
}
- $backgroundColor = imagecolorallocatealpha($image, $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b'], $qrCode->getBackgroundColor()['a']);
- imagefill($image, 0, 0, $backgroundColor);
- imagecopyresampled($image, $baseImage, (int) $data['margin_left'], (int) $data['margin_left'], 0, 0, (int) $data['inner_width'], (int) $data['inner_height'], imagesx($baseImage), imagesy($baseImage));
- imagesavealpha($image, true);
-
- return $image;
- }
-
- private function addLogo($sourceImage, string $logoPath, int $logoWidth = null, int $logoHeight = null)
- {
- $mimeType = $this->getMimeType($logoPath);
- $logoImage = imagecreatefromstring(strval(file_get_contents($logoPath)));
-
- if ('image/svg+xml' === $mimeType && (null === $logoHeight || null === $logoWidth)) {
- throw new MissingLogoHeightException('SVG Logos require an explicit height set via setLogoSize($width, $height)');
- }
-
- if (!is_resource($logoImage)) {
- throw new GenerateImageException('Unable to generate image: check your GD installation or logo path');
- }
-
- $logoSourceWidth = imagesx($logoImage);
- $logoSourceHeight = imagesy($logoImage);
-
- if (null === $logoWidth) {
- $logoWidth = $logoSourceWidth;
- }
-
- if (null === $logoHeight) {
- $aspectRatio = $logoWidth / $logoSourceWidth;
- $logoHeight = intval($logoSourceHeight * $aspectRatio);
- }
-
- $logoX = imagesx($sourceImage) / 2 - $logoWidth / 2;
- $logoY = imagesy($sourceImage) / 2 - $logoHeight / 2;
-
- imagecopyresampled($sourceImage, $logoImage, intval($logoX), intval($logoY), 0, 0, $logoWidth, $logoHeight, $logoSourceWidth, $logoSourceHeight);
-
- imagedestroy($logoImage);
-
- return $sourceImage;
- }
-
- private function addLabel($sourceImage, string $label, string $labelFontPath, int $labelFontSize, string $labelAlignment, array $labelMargin, array $foregroundColor, array $backgroundColor)
- {
- if (!function_exists('imagettfbbox')) {
- throw new MissingFunctionException('Missing function "imagettfbbox", please make sure you installed the FreeType library');
- }
-
- $labelBox = imagettfbbox($labelFontSize, 0, $labelFontPath, $label);
- $labelBoxWidth = intval($labelBox[2] - $labelBox[0]);
- $labelBoxHeight = intval($labelBox[0] - $labelBox[7]);
-
- $sourceWidth = imagesx($sourceImage);
- $sourceHeight = imagesy($sourceImage);
- $targetWidth = $sourceWidth;
- $targetHeight = $sourceHeight + $labelBoxHeight + $labelMargin['t'] + $labelMargin['b'];
-
- // Create empty target image
$targetImage = imagecreatetruecolor($targetWidth, $targetHeight);
- if (!is_resource($targetImage)) {
- throw new GenerateImageException('Unable to generate image: check your GD installation');
+ if (!$targetImage) {
+ throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
}
- $foregroundColor = imagecolorallocate($targetImage, $foregroundColor['r'], $foregroundColor['g'], $foregroundColor['b']);
- $backgroundColor = imagecolorallocate($targetImage, $backgroundColor['r'], $backgroundColor['g'], $backgroundColor['b']);
+ /** @var int $backgroundColor */
+ $backgroundColor = imagecolorallocatealpha(
+ $targetImage,
+ $qrCode->getBackgroundColor()->getRed(),
+ $qrCode->getBackgroundColor()->getGreen(),
+ $qrCode->getBackgroundColor()->getBlue(),
+ $qrCode->getBackgroundColor()->getAlpha()
+ );
+
imagefill($targetImage, 0, 0, $backgroundColor);
- // Copy source image to target image
- imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $sourceWidth, $sourceHeight, $sourceWidth, $sourceHeight);
+ imagecopyresampled(
+ $targetImage,
+ $baseImage,
+ $matrix->getMarginLeft(),
+ $matrix->getMarginLeft(),
+ 0,
+ 0,
+ $matrix->getInnerSize(),
+ $matrix->getInnerSize(),
+ imagesx($baseImage),
+ imagesy($baseImage)
+ );
- imagedestroy($sourceImage);
-
- switch ($labelAlignment) {
- case LabelAlignment::LEFT:
- $labelX = $labelMargin['l'];
- break;
- case LabelAlignment::RIGHT:
- $labelX = $targetWidth - $labelBoxWidth - $labelMargin['r'];
- break;
- default:
- $labelX = intval($targetWidth / 2 - $labelBoxWidth / 2);
- break;
+ if (PHP_VERSION_ID < 80000) {
+ imagedestroy($baseImage);
}
- $labelY = $targetHeight - $labelMargin['b'];
- imagettftext($targetImage, $labelFontSize, 0, $labelX, $labelY, $foregroundColor, $labelFontPath, $label);
+ if ($qrCode->getBackgroundColor()->getAlpha() > 0) {
+ imagesavealpha($targetImage, true);
+ }
- return $targetImage;
+ $result = new PngResult($targetImage);
+
+ if ($logo instanceof LogoInterface) {
+ $result = $this->addLogo($logo, $result);
+ }
+
+ if ($label instanceof LabelInterface) {
+ $result = $this->addLabel($label, $result);
+ }
+
+ return $result;
}
- private function imageToString($image): string
+ private function addLogo(LogoInterface $logo, PngResult $result): PngResult
{
- ob_start();
- imagepng($image);
+ $logoImageData = LogoImageData::createForLogo($logo);
- return (string) ob_get_clean();
+ if ('image/svg+xml' === $logoImageData->getMimeType()) {
+ throw new \Exception('PNG Writer does not support SVG logo');
+ }
+
+ $targetImage = $result->getImage();
+
+ if ($logoImageData->getPunchoutBackground()) {
+ /** @var int $transparent */
+ $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
+ imagealphablending($targetImage, false);
+ for (
+ $x_offset = intval(imagesx($targetImage) / 2 - $logoImageData->getWidth() / 2);
+ $x_offset < intval(imagesx($targetImage) / 2 - $logoImageData->getWidth() / 2) + $logoImageData->getWidth();
+ ++$x_offset
+ ) {
+ for (
+ $y_offset = intval(imagesy($targetImage) / 2 - $logoImageData->getHeight() / 2);
+ $y_offset < intval(imagesy($targetImage) / 2 - $logoImageData->getHeight() / 2) + $logoImageData->getHeight();
+ ++$y_offset
+ ) {
+ imagesetpixel(
+ $targetImage,
+ $x_offset,
+ $y_offset,
+ $transparent
+ );
+ }
+ }
+ }
+
+ imagecopyresampled(
+ $targetImage,
+ $logoImageData->getImage(),
+ intval(imagesx($targetImage) / 2 - $logoImageData->getWidth() / 2),
+ intval(imagesx($targetImage) / 2 - $logoImageData->getHeight() / 2),
+ 0,
+ 0,
+ $logoImageData->getWidth(),
+ $logoImageData->getHeight(),
+ imagesx($logoImageData->getImage()),
+ imagesy($logoImageData->getImage())
+ );
+
+ if (PHP_VERSION_ID < 80000) {
+ imagedestroy($logoImageData->getImage());
+ }
+
+ return new PngResult($targetImage);
}
- public static function getContentType(): string
+ private function addLabel(LabelInterface $label, PngResult $result): PngResult
{
- return 'image/png';
+ $targetImage = $result->getImage();
+
+ $labelImageData = LabelImageData::createForLabel($label);
+
+ /** @var int $textColor */
+ $textColor = imagecolorallocatealpha(
+ $targetImage,
+ $label->getTextColor()->getRed(),
+ $label->getTextColor()->getGreen(),
+ $label->getTextColor()->getBlue(),
+ $label->getTextColor()->getAlpha()
+ );
+
+ $x = intval(imagesx($targetImage) / 2 - $labelImageData->getWidth() / 2);
+ $y = imagesy($targetImage) - $label->getMargin()->getBottom();
+
+ if ($label->getAlignment() instanceof LabelAlignmentLeft) {
+ $x = $label->getMargin()->getLeft();
+ } elseif ($label->getAlignment() instanceof LabelAlignmentRight) {
+ $x = imagesx($targetImage) - $labelImageData->getWidth() - $label->getMargin()->getRight();
+ }
+
+ imagettftext($targetImage, $label->getFont()->getSize(), 0, $x, $y, $textColor, $label->getFont()->getPath(), $label->getText());
+
+ return new PngResult($targetImage);
}
- public static function getSupportedExtensions(): array
+ public function validateResult(ResultInterface $result, string $expectedData): void
{
- return ['png'];
- }
+ $string = $result->getString();
- public function getName(): string
- {
- return 'png';
+ if (!class_exists(QrReader::class)) {
+ throw new \Exception('Please install khanamiryan/qrcode-detector-decoder or disable image validation');
+ }
+
+ if (PHP_VERSION_ID >= 80000) {
+ throw new \Exception('The validator is not compatible with PHP 8 yet, see https://github.com/khanamiryan/php-qrcode-detector-decoder/pull/103');
+ }
+
+ $reader = new QrReader($string, QrReader::SOURCE_TYPE_BLOB);
+ if ($reader->text() !== $expectedData) {
+ throw new \Exception('Built-in validation reader read "'.$reader->text().'" instead of "'.$expectedData.'".
+ Adjust your parameters to increase readability or disable built-in validation.');
+ }
}
}
diff --git a/vendor/endroid/qr-code/src/Writer/Result/AbstractResult.php b/vendor/endroid/qr-code/src/Writer/Result/AbstractResult.php
new file mode 100644
index 0000000..43b680b
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/Result/AbstractResult.php
@@ -0,0 +1,19 @@
+getMimeType().';base64,'.base64_encode($this->getString());
+ }
+
+ public function saveToFile(string $path): void
+ {
+ $string = $this->getString();
+ file_put_contents($path, $string);
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/Result/BinaryResult.php b/vendor/endroid/qr-code/src/Writer/Result/BinaryResult.php
new file mode 100644
index 0000000..0618940
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/Result/BinaryResult.php
@@ -0,0 +1,35 @@
+matrix = $matrix;
+ }
+
+ public function getString(): string
+ {
+ $binaryString = '';
+ for ($rowIndex = 0; $rowIndex < $this->matrix->getBlockCount(); ++$rowIndex) {
+ for ($columnIndex = 0; $columnIndex < $this->matrix->getBlockCount(); ++$columnIndex) {
+ $binaryString .= $this->matrix->getBlockValue($rowIndex, $columnIndex);
+ }
+ $binaryString .= "\n";
+ }
+
+ return $binaryString;
+ }
+
+ public function getMimeType(): string
+ {
+ return 'text/plain';
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/Result/DebugResult.php b/vendor/endroid/qr-code/src/Writer/Result/DebugResult.php
new file mode 100644
index 0000000..c3cbff0
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/Result/DebugResult.php
@@ -0,0 +1,77 @@
+ */
+ private array $options;
+
+ private bool $validateResult = false;
+
+ /** @param array $options */
+ public function __construct(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = [])
+ {
+ $this->qrCode = $qrCode;
+ $this->logo = $logo;
+ $this->label = $label;
+ $this->options = $options;
+ }
+
+ public function setValidateResult(bool $validateResult): void
+ {
+ $this->validateResult = $validateResult;
+ }
+
+ public function getString(): string
+ {
+ $debugLines = [];
+
+ $debugLines[] = 'Data: '.$this->qrCode->getData();
+ $debugLines[] = 'Encoding: '.$this->qrCode->getEncoding();
+ $debugLines[] = 'Error Correction Level: '.get_class($this->qrCode->getErrorCorrectionLevel());
+ $debugLines[] = 'Size: '.$this->qrCode->getSize();
+ $debugLines[] = 'Margin: '.$this->qrCode->getMargin();
+ $debugLines[] = 'Round block size mode: '.get_class($this->qrCode->getRoundBlockSizeMode());
+ $debugLines[] = 'Foreground color: ['.implode(', ', $this->qrCode->getForegroundColor()->toArray()).']';
+ $debugLines[] = 'Background color: ['.implode(', ', $this->qrCode->getBackgroundColor()->toArray()).']';
+
+ foreach ($this->options as $key => $value) {
+ $debugLines[] = 'Writer option: '.$key.': '.$value;
+ }
+
+ if (isset($this->logo)) {
+ $debugLines[] = 'Logo path: '.$this->logo->getPath();
+ $debugLines[] = 'Logo resize to width: '.$this->logo->getResizeToWidth();
+ $debugLines[] = 'Logo resize to height: '.$this->logo->getResizeToHeight();
+ }
+
+ if (isset($this->label)) {
+ $debugLines[] = 'Label text: '.$this->label->getText();
+ $debugLines[] = 'Label font path: '.$this->label->getFont()->getPath();
+ $debugLines[] = 'Label font size: '.$this->label->getFont()->getSize();
+ $debugLines[] = 'Label alignment: '.get_class($this->label->getAlignment());
+ $debugLines[] = 'Label margin: ['.implode(', ', $this->label->getMargin()->toArray()).']';
+ $debugLines[] = 'Label text color: ['.implode(', ', $this->label->getTextColor()->toArray()).']';
+ }
+
+ $debugLines[] = 'Validate result: '.($this->validateResult ? 'true' : 'false');
+
+ return implode("\n", $debugLines);
+ }
+
+ public function getMimeType(): string
+ {
+ return 'text/plain';
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/Result/EpsResult.php b/vendor/endroid/qr-code/src/Writer/Result/EpsResult.php
new file mode 100644
index 0000000..0e55991
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/Result/EpsResult.php
@@ -0,0 +1,27 @@
+ */
+ private array $lines;
+
+ /** @param array $lines */
+ public function __construct(array $lines)
+ {
+ $this->lines = $lines;
+ }
+
+ public function getString(): string
+ {
+ return implode("\n", $this->lines);
+ }
+
+ public function getMimeType(): string
+ {
+ return 'image/eps';
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/Result/PdfResult.php b/vendor/endroid/qr-code/src/Writer/Result/PdfResult.php
new file mode 100644
index 0000000..b4c644f
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/Result/PdfResult.php
@@ -0,0 +1,30 @@
+fpdf = $fpdf;
+ }
+
+ public function getPdf(): \FPDF
+ {
+ return $this->fpdf;
+ }
+
+ public function getString(): string
+ {
+ return $this->fpdf->Output('S');
+ }
+
+ public function getMimeType(): string
+ {
+ return 'application/pdf';
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/Result/PngResult.php b/vendor/endroid/qr-code/src/Writer/Result/PngResult.php
new file mode 100644
index 0000000..efa26c5
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/Result/PngResult.php
@@ -0,0 +1,36 @@
+image = $image;
+ }
+
+ /** @return mixed */
+ public function getImage()
+ {
+ return $this->image;
+ }
+
+ public function getString(): string
+ {
+ ob_start();
+ imagepng($this->image);
+
+ return strval(ob_get_clean());
+ }
+
+ public function getMimeType(): string
+ {
+ return 'image/png';
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/Result/ResultInterface.php b/vendor/endroid/qr-code/src/Writer/Result/ResultInterface.php
new file mode 100644
index 0000000..a255605
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/Result/ResultInterface.php
@@ -0,0 +1,16 @@
+xml = $xml;
+ $this->excludeXmlDeclaration = $excludeXmlDeclaration;
+ }
+
+ public function getXml(): \SimpleXMLElement
+ {
+ return $this->xml;
+ }
+
+ public function getString(): string
+ {
+ $string = $this->xml->asXML();
+
+ if (!is_string($string)) {
+ throw new \Exception('Could not save SVG XML to string');
+ }
+
+ if ($this->excludeXmlDeclaration) {
+ $string = str_replace("\n", '', $string);
+ }
+
+ return $string;
+ }
+
+ public function getMimeType(): string
+ {
+ return 'image/svg+xml';
+ }
+}
diff --git a/vendor/endroid/qr-code/src/Writer/SvgWriter.php b/vendor/endroid/qr-code/src/Writer/SvgWriter.php
index 072dfc1..233c903 100644
--- a/vendor/endroid/qr-code/src/Writer/SvgWriter.php
+++ b/vendor/endroid/qr-code/src/Writer/SvgWriter.php
@@ -2,151 +2,106 @@
declare(strict_types=1);
-/*
- * (c) Jeroen van den Enden
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode\Writer;
-use Endroid\QrCode\Exception\GenerateImageException;
-use Endroid\QrCode\Exception\MissingLogoHeightException;
-use Endroid\QrCode\Exception\ValidationException;
+use Endroid\QrCode\Bacon\MatrixFactory;
+use Endroid\QrCode\ImageData\LogoImageData;
+use Endroid\QrCode\Label\LabelInterface;
+use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
-use SimpleXMLElement;
+use Endroid\QrCode\Writer\Result\ResultInterface;
+use Endroid\QrCode\Writer\Result\SvgResult;
-class SvgWriter extends AbstractWriter
+final class SvgWriter implements WriterInterface
{
- public function writeString(QrCodeInterface $qrCode): string
+ public const DECIMAL_PRECISION = 10;
+ public const WRITER_OPTION_BLOCK_ID = 'block_id';
+ public const WRITER_OPTION_EXCLUDE_XML_DECLARATION = 'exclude_xml_declaration';
+ public const WRITER_OPTION_FORCE_XLINK_HREF = 'force_xlink_href';
+
+ public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface
{
- if ($qrCode->getValidateResult()) {
- throw new ValidationException('Built-in validation reader can not check SVG images: please disable via setValidateResult(false)');
+ if (!isset($options[self::WRITER_OPTION_BLOCK_ID])) {
+ $options[self::WRITER_OPTION_BLOCK_ID] = 'block';
}
- $data = $qrCode->getData();
+ if (!isset($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION])) {
+ $options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION] = false;
+ }
- $svg = new SimpleXMLElement(' ');
- $svg->addAttribute('version', '1.1');
- $svg->addAttribute('width', $data['outer_width'].'px');
- $svg->addAttribute('height', $data['outer_height'].'px');
- $svg->addAttribute('viewBox', '0 0 '.$data['outer_width'].' '.$data['outer_height']);
- $svg->addChild('defs');
+ $matrixFactory = new MatrixFactory();
+ $matrix = $matrixFactory->create($qrCode);
- // Block definition
- $blockDefinition = $svg->defs->addChild('rect');
- $blockDefinition->addAttribute('id', 'block');
- $blockDefinition->addAttribute('width', strval($data['block_size']));
- $blockDefinition->addAttribute('height', strval($data['block_size']));
- $blockDefinition->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getForegroundColor()['r'], $qrCode->getForegroundColor()['g'], $qrCode->getForegroundColor()['b']));
- $blockDefinition->addAttribute('fill-opacity', strval($this->getOpacity($qrCode->getForegroundColor()['a'])));
+ $xml = new \SimpleXMLElement(' ');
+ $xml->addAttribute('version', '1.1');
+ $xml->addAttribute('width', $matrix->getOuterSize().'px');
+ $xml->addAttribute('height', $matrix->getOuterSize().'px');
+ $xml->addAttribute('viewBox', '0 0 '.$matrix->getOuterSize().' '.$matrix->getOuterSize());
+ $xml->addChild('defs');
- // Background
- $background = $svg->addChild('rect');
+ $blockDefinition = $xml->defs->addChild('rect');
+ $blockDefinition->addAttribute('id', strval($options[self::WRITER_OPTION_BLOCK_ID]));
+ $blockDefinition->addAttribute('width', number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', ''));
+ $blockDefinition->addAttribute('height', number_format($matrix->getBlockSize(), self::DECIMAL_PRECISION, '.', ''));
+ $blockDefinition->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getForegroundColor()->getRed(), $qrCode->getForegroundColor()->getGreen(), $qrCode->getForegroundColor()->getBlue()));
+ $blockDefinition->addAttribute('fill-opacity', strval($qrCode->getForegroundColor()->getOpacity()));
+
+ $background = $xml->addChild('rect');
$background->addAttribute('x', '0');
$background->addAttribute('y', '0');
- $background->addAttribute('width', strval($data['outer_width']));
- $background->addAttribute('height', strval($data['outer_height']));
- $background->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b']));
- $background->addAttribute('fill-opacity', strval($this->getOpacity($qrCode->getBackgroundColor()['a'])));
+ $background->addAttribute('width', strval($matrix->getOuterSize()));
+ $background->addAttribute('height', strval($matrix->getOuterSize()));
+ $background->addAttribute('fill', '#'.sprintf('%02x%02x%02x', $qrCode->getBackgroundColor()->getRed(), $qrCode->getBackgroundColor()->getGreen(), $qrCode->getBackgroundColor()->getBlue()));
+ $background->addAttribute('fill-opacity', strval($qrCode->getBackgroundColor()->getOpacity()));
- foreach ($data['matrix'] as $row => $values) {
- foreach ($values as $column => $value) {
- if (1 === $value) {
- $block = $svg->addChild('use');
- $block->addAttribute('x', strval($data['margin_left'] + $data['block_size'] * $column));
- $block->addAttribute('y', strval($data['margin_left'] + $data['block_size'] * $row));
- $block->addAttribute('xlink:href', '#block', 'http://www.w3.org/1999/xlink');
+ for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
+ for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
+ if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
+ $block = $xml->addChild('use');
+ $block->addAttribute('x', number_format($matrix->getMarginLeft() + $matrix->getBlockSize() * $columnIndex, self::DECIMAL_PRECISION, '.', ''));
+ $block->addAttribute('y', number_format($matrix->getMarginLeft() + $matrix->getBlockSize() * $rowIndex, self::DECIMAL_PRECISION, '.', ''));
+ $block->addAttribute('xlink:href', '#'.$options[self::WRITER_OPTION_BLOCK_ID], 'http://www.w3.org/1999/xlink');
}
}
}
- $logoPath = $qrCode->getLogoPath();
- if (is_string($logoPath)) {
- $this->addLogo($svg, $data['outer_width'], $data['outer_height'], $logoPath, $qrCode->getLogoWidth(), $qrCode->getLogoHeight());
+ $result = new SvgResult($xml, boolval($options[self::WRITER_OPTION_EXCLUDE_XML_DECLARATION]));
+
+ if ($logo instanceof LogoInterface) {
+ $this->addLogo($logo, $result, $options);
}
- $xml = $svg->asXML();
-
- if (!is_string($xml)) {
- throw new GenerateImageException('Unable to save SVG XML');
- }
-
- $options = $qrCode->getWriterOptions();
- if (isset($options['exclude_xml_declaration']) && $options['exclude_xml_declaration']) {
- $xml = str_replace("\n", '', $xml);
- }
-
- return $xml;
+ return $result;
}
- private function addLogo(SimpleXMLElement $svg, int $imageWidth, int $imageHeight, string $logoPath, int $logoWidth = null, int $logoHeight = null): void
+ /** @param array $options */
+ private function addLogo(LogoInterface $logo, SvgResult $result, array $options): void
{
- $mimeType = $this->getMimeType($logoPath);
- $imageData = file_get_contents($logoPath);
+ $logoImageData = LogoImageData::createForLogo($logo);
- if (!is_string($imageData)) {
- throw new GenerateImageException('Unable to read image data: check your logo path');
+ if (!isset($options[self::WRITER_OPTION_FORCE_XLINK_HREF])) {
+ $options[self::WRITER_OPTION_FORCE_XLINK_HREF] = false;
}
- if ('image/svg+xml' === $mimeType && (null === $logoHeight || null === $logoWidth)) {
- throw new MissingLogoHeightException('SVG Logos require an explicit height set via setLogoSize($width, $height)');
- }
+ $xml = $result->getXml();
- if (null === $logoHeight || null === $logoWidth) {
- $logoImage = imagecreatefromstring(strval($imageData));
+ /** @var \SimpleXMLElement $xmlAttributes */
+ $xmlAttributes = $xml->attributes();
- if (!is_resource($logoImage)) {
- throw new GenerateImageException('Unable to generate image: check your GD installation or logo path');
- }
+ $x = intval($xmlAttributes->width) / 2 - $logoImageData->getWidth() / 2;
+ $y = intval($xmlAttributes->height) / 2 - $logoImageData->getHeight() / 2;
- $logoSourceWidth = imagesx($logoImage);
- $logoSourceHeight = imagesy($logoImage);
-
- imagedestroy($logoImage);
-
- if (null === $logoWidth) {
- $logoWidth = $logoSourceWidth;
- }
-
- if (null === $logoHeight) {
- $aspectRatio = $logoWidth / $logoSourceWidth;
- $logoHeight = intval($logoSourceHeight * $aspectRatio);
- }
- }
-
- $logoX = $imageWidth / 2 - $logoWidth / 2;
- $logoY = $imageHeight / 2 - $logoHeight / 2;
-
- $imageDefinition = $svg->addChild('image');
- $imageDefinition->addAttribute('x', strval($logoX));
- $imageDefinition->addAttribute('y', strval($logoY));
- $imageDefinition->addAttribute('width', strval($logoWidth));
- $imageDefinition->addAttribute('height', strval($logoHeight));
+ $imageDefinition = $xml->addChild('image');
+ $imageDefinition->addAttribute('x', strval($x));
+ $imageDefinition->addAttribute('y', strval($y));
+ $imageDefinition->addAttribute('width', strval($logoImageData->getWidth()));
+ $imageDefinition->addAttribute('height', strval($logoImageData->getHeight()));
$imageDefinition->addAttribute('preserveAspectRatio', 'none');
- $imageDefinition->addAttribute('xlink:href', 'data:'.$mimeType.';base64,'.base64_encode($imageData));
- }
- private function getOpacity(int $alpha): float
- {
- $opacity = 1 - $alpha / 127;
-
- return $opacity;
- }
-
- public static function getContentType(): string
- {
- return 'image/svg+xml';
- }
-
- public static function getSupportedExtensions(): array
- {
- return ['svg'];
- }
-
- public function getName(): string
- {
- return 'svg';
+ if ($options[self::WRITER_OPTION_FORCE_XLINK_HREF]) {
+ $imageDefinition->addAttribute('xlink:href', $logoImageData->createDataUri(), 'http://www.w3.org/1999/xlink');
+ } else {
+ $imageDefinition->addAttribute('href', $logoImageData->createDataUri());
+ }
}
}
diff --git a/vendor/endroid/qr-code/src/Writer/ValidatingWriterInterface.php b/vendor/endroid/qr-code/src/Writer/ValidatingWriterInterface.php
new file mode 100644
index 0000000..4f40048
--- /dev/null
+++ b/vendor/endroid/qr-code/src/Writer/ValidatingWriterInterface.php
@@ -0,0 +1,12 @@
+
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Endroid\QrCode\Writer;
+use Endroid\QrCode\Label\LabelInterface;
+use Endroid\QrCode\Logo\LogoInterface;
use Endroid\QrCode\QrCodeInterface;
+use Endroid\QrCode\Writer\Result\ResultInterface;
interface WriterInterface
{
- public function writeString(QrCodeInterface $qrCode): string;
-
- public function writeDataUri(QrCodeInterface $qrCode): string;
-
- public function writeFile(QrCodeInterface $qrCode, string $path): void;
-
- public static function getContentType(): string;
-
- public static function supportsExtension(string $extension): bool;
-
- public static function getSupportedExtensions(): array;
-
- public function getName(): string;
+ /** @param array $options */
+ public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface;
}
diff --git a/vendor/guzzlehttp/guzzle/.php_cs b/vendor/guzzlehttp/guzzle/.php_cs
new file mode 100644
index 0000000..2dd5036
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/.php_cs
@@ -0,0 +1,23 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'declare_strict_types' => false,
+ 'concat_space' => ['spacing'=>'one'],
+ 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+ 'ordered_imports' => true,
+ // 'phpdoc_align' => ['align'=>'vertical'],
+ // 'native_function_invocation' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__.'/src')
+ ->in(__DIR__.'/tests')
+ ->name('*.php')
+ )
+;
+
+return $config;
diff --git a/vendor/guzzlehttp/guzzle/Dockerfile b/vendor/guzzlehttp/guzzle/Dockerfile
new file mode 100644
index 0000000..f6a0952
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/Dockerfile
@@ -0,0 +1,18 @@
+FROM composer:latest as setup
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+RUN set -xe \
+ && composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár " --no-interaction \
+ && composer require guzzlehttp/guzzle
+
+
+FROM php:7.3
+
+RUN mkdir /guzzle
+
+WORKDIR /guzzle
+
+COPY --from=setup /guzzle /guzzle
diff --git a/vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php b/vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..bfd20e2
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+handle);
+ $curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
$stats = new TransferStats(
$easy->request,
$easy->response,
@@ -137,7 +140,9 @@ class CurlFactory implements CurlFactoryInterface
$ctx = [
'errno' => $easy->errno,
'error' => curl_error($easy->handle),
+ 'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
] + curl_getinfo($easy->handle);
+ $ctx[self::CURL_VERSION_STR] = curl_version()['version'];
$factory->release($easy);
// Retry when nothing is present or when curl failed to rewind.
@@ -173,13 +178,22 @@ class CurlFactory implements CurlFactoryInterface
)
);
}
-
- $message = sprintf(
- 'cURL error %s: %s (%s)',
- $ctx['errno'],
- $ctx['error'],
- 'see http://curl.haxx.se/libcurl/c/libcurl-errors.html'
- );
+ if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
+ $message = sprintf(
+ 'cURL error %s: %s (%s)',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
+ );
+ } else {
+ $message = sprintf(
+ 'cURL error %s: %s (%s) for %s',
+ $ctx['errno'],
+ $ctx['error'],
+ 'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
+ $easy->request->getUri()
+ );
+ }
// Create a connection exception if it was a specific error code.
$error = isset($connectionErrors[$easy->errno])
@@ -288,7 +302,14 @@ class CurlFactory implements CurlFactoryInterface
{
foreach ($conf['_headers'] as $name => $values) {
foreach ($values as $value) {
- $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
+ $value = (string) $value;
+ if ($value === '') {
+ // cURL requires a special format for empty headers.
+ // See https://github.com/guzzle/guzzle/issues/1882 for more details.
+ $conf[CURLOPT_HTTPHEADER][] = "$name;";
+ } else {
+ $conf[CURLOPT_HTTPHEADER][] = "$name: $value";
+ }
}
}
@@ -388,7 +409,7 @@ class CurlFactory implements CurlFactoryInterface
if (isset($options['force_ip_resolve'])) {
if ('v4' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
- } else if ('v6' === $options['force_ip_resolve']) {
+ } elseif ('v6' === $options['force_ip_resolve']) {
$conf[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V6;
}
}
@@ -433,11 +454,16 @@ class CurlFactory implements CurlFactoryInterface
}
if (isset($options['ssl_key'])) {
- $sslKey = $options['ssl_key'];
- if (is_array($sslKey)) {
- $conf[CURLOPT_SSLKEYPASSWD] = $sslKey[1];
- $sslKey = $sslKey[0];
+ if (is_array($options['ssl_key'])) {
+ if (count($options['ssl_key']) === 2) {
+ list($sslKey, $conf[CURLOPT_SSLKEYPASSWD]) = $options['ssl_key'];
+ } else {
+ list($sslKey) = $options['ssl_key'];
+ }
}
+
+ $sslKey = isset($sslKey) ? $sslKey: $options['ssl_key'];
+
if (!file_exists($sslKey)) {
throw new \InvalidArgumentException(
"SSL private key not found: {$sslKey}"
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
index 945d06e..564c95f 100644
--- a/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
+++ b/vendor/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php
@@ -3,7 +3,7 @@ namespace GuzzleHttp\Handler;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
-use GuzzleHttp\Psr7;
+use GuzzleHttp\Utils;
use Psr\Http\Message\RequestInterface;
/**
@@ -23,6 +23,7 @@ class CurlMultiHandler
private $active;
private $handles = [];
private $delays = [];
+ private $options = [];
/**
* This handler accepts the following options:
@@ -30,6 +31,8 @@ class CurlMultiHandler
* - handle_factory: An optional factory used to create curl handles
* - select_timeout: Optional timeout (in seconds) to block before timing
* out while selecting curl handles. Defaults to 1 second.
+ * - options: An associative array of CURLMOPT_* options and
+ * corresponding values for curl_multi_setopt()
*
* @param array $options
*/
@@ -37,14 +40,31 @@ class CurlMultiHandler
{
$this->factory = isset($options['handle_factory'])
? $options['handle_factory'] : new CurlFactory(50);
- $this->selectTimeout = isset($options['select_timeout'])
- ? $options['select_timeout'] : 1;
+
+ if (isset($options['select_timeout'])) {
+ $this->selectTimeout = $options['select_timeout'];
+ } elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
+ $this->selectTimeout = $selectTimeout;
+ } else {
+ $this->selectTimeout = 1;
+ }
+
+ $this->options = isset($options['options']) ? $options['options'] : [];
}
public function __get($name)
{
if ($name === '_mh') {
- return $this->_mh = curl_multi_init();
+ $this->_mh = curl_multi_init();
+
+ foreach ($this->options as $option => $value) {
+ // A warning is raised in case of a wrong option.
+ curl_multi_setopt($this->_mh, $option, $value);
+ }
+
+ // Further calls to _mh will return the value directly, without entering the
+ // __get() method at all.
+ return $this->_mh;
}
throw new \BadMethodCallException();
@@ -65,7 +85,9 @@ class CurlMultiHandler
$promise = new Promise(
[$this, 'execute'],
- function () use ($id) { return $this->cancel($id); }
+ function () use ($id) {
+ return $this->cancel($id);
+ }
);
$this->addRequest(['easy' => $easy, 'deferred' => $promise]);
@@ -80,7 +102,7 @@ class CurlMultiHandler
{
// Add any delayed handles if needed.
if ($this->delays) {
- $currentTime = microtime(true);
+ $currentTime = Utils::currentTime();
foreach ($this->delays as $id => $delay) {
if ($currentTime >= $delay) {
unset($this->delays[$id]);
@@ -132,7 +154,7 @@ class CurlMultiHandler
if (empty($easy->options['delay'])) {
curl_multi_add_handle($this->_mh, $easy->handle);
} else {
- $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
+ $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000);
}
}
@@ -184,7 +206,7 @@ class CurlMultiHandler
private function timeToNext()
{
- $currentTime = microtime(true);
+ $currentTime = Utils::currentTime();
$nextTime = PHP_INT_MAX;
foreach ($this->delays as $time) {
if ($time < $nextTime) {
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
index d892061..5b312bc 100644
--- a/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
+++ b/vendor/guzzlehttp/guzzle/src/Handler/MockHandler.php
@@ -66,7 +66,7 @@ class MockHandler implements \Countable
throw new \OutOfBoundsException('Mock queue is empty');
}
- if (isset($options['delay'])) {
+ if (isset($options['delay']) && is_numeric($options['delay'])) {
usleep($options['delay'] * 1000);
}
@@ -175,6 +175,11 @@ class MockHandler implements \Countable
return count($this->queue);
}
+ public function reset()
+ {
+ $this->queue = [];
+ }
+
private function invokeStats(
RequestInterface $request,
array $options,
@@ -182,7 +187,8 @@ class MockHandler implements \Countable
$reason = null
) {
if (isset($options['on_stats'])) {
- $stats = new TransferStats($request, $response, 0, $reason);
+ $transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0;
+ $stats = new TransferStats($request, $response, $transferTime, $reason);
call_user_func($options['on_stats'], $stats);
}
}
diff --git a/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php
index b12bfd9..a15734a 100644
--- a/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php
+++ b/vendor/guzzlehttp/guzzle/src/Handler/StreamHandler.php
@@ -1,13 +1,13 @@
getBody()->getSize()) {
- $request = $request->withHeader('Content-Length', 0);
+ $request = $request->withHeader('Content-Length', '0');
}
return $this->createResponse(
@@ -61,6 +61,7 @@ class StreamHandler
if (strpos($message, 'getaddrinfo') // DNS lookup failed
|| strpos($message, 'Connection refused')
|| strpos($message, "couldn't connect to host") // error on HHVM
+ || strpos($message, "connection attempt failed")
) {
$e = new ConnectException($e->getMessage(), $request, $e);
}
@@ -82,7 +83,7 @@ class StreamHandler
$stats = new TransferStats(
$request,
$response,
- microtime(true) - $startTime,
+ Utils::currentTime() - $startTime,
$error,
[]
);
@@ -103,7 +104,7 @@ class StreamHandler
$status = $parts[1];
$reason = isset($parts[2]) ? $parts[2] : null;
$headers = \GuzzleHttp\headers_from_lines($hdrs);
- list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
+ list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
$stream = Psr7\stream_for($stream);
$sink = $stream;
@@ -276,7 +277,7 @@ class StreamHandler
}
$params = [];
- $context = $this->getDefaultContext($request, $options);
+ $context = $this->getDefaultContext($request);
if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
@@ -307,7 +308,6 @@ class StreamHandler
&& isset($options['auth'][2])
&& 'ntlm' == $options['auth'][2]
) {
-
throw new \InvalidArgumentException('Microsoft NTLM authentication only supported with curl handler');
}
@@ -344,13 +344,25 @@ class StreamHandler
if ('v4' === $options['force_ip_resolve']) {
$records = dns_get_record($uri->getHost(), DNS_A);
if (!isset($records[0]['ip'])) {
- throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv4 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
}
$uri = $uri->withHost($records[0]['ip']);
} elseif ('v6' === $options['force_ip_resolve']) {
$records = dns_get_record($uri->getHost(), DNS_AAAA);
if (!isset($records[0]['ipv6'])) {
- throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
+ throw new ConnectException(
+ sprintf(
+ "Could not resolve IPv6 address for host '%s'",
+ $uri->getHost()
+ ),
+ $request
+ );
}
$uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
}
diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php
new file mode 100644
index 0000000..c698acb
--- /dev/null
+++ b/vendor/guzzlehttp/guzzle/src/Utils.php
@@ -0,0 +1,92 @@
+getHost()) {
+ $asciiHost = self::idnToAsci($uri->getHost(), $options, $info);
+ if ($asciiHost === false) {
+ $errorBitSet = isset($info['errors']) ? $info['errors'] : 0;
+
+ $errorConstants = array_filter(array_keys(get_defined_constants()), function ($name) {
+ return substr($name, 0, 11) === 'IDNA_ERROR_';
+ });
+
+ $errors = [];
+ foreach ($errorConstants as $errorConstant) {
+ if ($errorBitSet & constant($errorConstant)) {
+ $errors[] = $errorConstant;
+ }
+ }
+
+ $errorMessage = 'IDN conversion failed';
+ if ($errors) {
+ $errorMessage .= ' (errors: ' . implode(', ', $errors) . ')';
+ }
+
+ throw new InvalidArgumentException($errorMessage);
+ } else {
+ if ($uri->getHost() !== $asciiHost) {
+ // Replace URI only if the ASCII version is different
+ $uri = $uri->withHost($asciiHost);
+ }
+ }
+ }
+
+ return $uri;
+ }
+
+ /**
+ * @param string $domain
+ * @param int $options
+ * @param array $info
+ *
+ * @return string|false
+ */
+ private static function idnToAsci($domain, $options, &$info = [])
+ {
+ if (\preg_match('%^[ -~]+$%', $domain) === 1) {
+ return $domain;
+ }
+
+ if (\extension_loaded('intl') && defined('INTL_IDNA_VARIANT_UTS46')) {
+ return \idn_to_ascii($domain, $options, INTL_IDNA_VARIANT_UTS46, $info);
+ }
+
+ /*
+ * The Idn class is marked as @internal. Verify that class and method exists.
+ */
+ if (method_exists(Idn::class, 'idn_to_ascii')) {
+ return Idn::idn_to_ascii($domain, $options, Idn::INTL_IDNA_VARIANT_UTS46, $info);
+ }
+
+ throw new \RuntimeException('ext-intl or symfony/polyfill-intl-idn not loaded or too old');
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/Create.php b/vendor/guzzlehttp/promises/src/Create.php
new file mode 100644
index 0000000..8d038e9
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/Create.php
@@ -0,0 +1,84 @@
+then([$promise, 'resolve'], [$promise, 'reject']);
+ return $promise;
+ }
+
+ return new FulfilledPromise($value);
+ }
+
+ /**
+ * Creates a rejected promise for a reason if the reason is not a promise.
+ * If the provided reason is a promise, then it is returned as-is.
+ *
+ * @param mixed $reason Promise or reason.
+ *
+ * @return PromiseInterface
+ */
+ public static function rejectionFor($reason)
+ {
+ if ($reason instanceof PromiseInterface) {
+ return $reason;
+ }
+
+ return new RejectedPromise($reason);
+ }
+
+ /**
+ * Create an exception for a rejected promise value.
+ *
+ * @param mixed $reason
+ *
+ * @return \Exception|\Throwable
+ */
+ public static function exceptionFor($reason)
+ {
+ if ($reason instanceof \Exception || $reason instanceof \Throwable) {
+ return $reason;
+ }
+
+ return new RejectionException($reason);
+ }
+
+ /**
+ * Returns an iterator for the given value.
+ *
+ * @param mixed $value
+ *
+ * @return \Iterator
+ */
+ public static function iterFor($value)
+ {
+ if ($value instanceof \Iterator) {
+ return $value;
+ }
+
+ if (is_array($value)) {
+ return new \ArrayIterator($value);
+ }
+
+ return new \ArrayIterator([$value]);
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/Each.php b/vendor/guzzlehttp/promises/src/Each.php
new file mode 100644
index 0000000..1dda354
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/Each.php
@@ -0,0 +1,90 @@
+ $onFulfilled,
+ 'rejected' => $onRejected
+ ]))->promise();
+ }
+
+ /**
+ * Like of, but only allows a certain number of outstanding promises at any
+ * given time.
+ *
+ * $concurrency may be an integer or a function that accepts the number of
+ * pending promises and returns a numeric concurrency limit value to allow
+ * for dynamic a concurrency size.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ * @param callable $onRejected
+ *
+ * @return PromiseInterface
+ */
+ public static function ofLimit(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null,
+ callable $onRejected = null
+ ) {
+ return (new EachPromise($iterable, [
+ 'fulfilled' => $onFulfilled,
+ 'rejected' => $onRejected,
+ 'concurrency' => $concurrency
+ ]))->promise();
+ }
+
+ /**
+ * Like limit, but ensures that no promise in the given $iterable argument
+ * is rejected. If any promise is rejected, then the aggregate promise is
+ * rejected with the encountered rejection.
+ *
+ * @param mixed $iterable
+ * @param int|callable $concurrency
+ * @param callable $onFulfilled
+ *
+ * @return PromiseInterface
+ */
+ public static function ofLimitAll(
+ $iterable,
+ $concurrency,
+ callable $onFulfilled = null
+ ) {
+ return each_limit(
+ $iterable,
+ $concurrency,
+ $onFulfilled,
+ function ($reason, $idx, PromiseInterface $aggregate) {
+ $aggregate->reject($reason);
+ }
+ );
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/Is.php b/vendor/guzzlehttp/promises/src/Is.php
new file mode 100644
index 0000000..c3ed8d0
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/Is.php
@@ -0,0 +1,46 @@
+getState() === PromiseInterface::PENDING;
+ }
+
+ /**
+ * Returns true if a promise is fulfilled or rejected.
+ *
+ * @return bool
+ */
+ public static function settled(PromiseInterface $promise)
+ {
+ return $promise->getState() !== PromiseInterface::PENDING;
+ }
+
+ /**
+ * Returns true if a promise is fulfilled.
+ *
+ * @return bool
+ */
+ public static function fulfilled(PromiseInterface $promise)
+ {
+ return $promise->getState() === PromiseInterface::FULFILLED;
+ }
+
+ /**
+ * Returns true if a promise is rejected.
+ *
+ * @return bool
+ */
+ public static function rejected(PromiseInterface $promise)
+ {
+ return $promise->getState() === PromiseInterface::REJECTED;
+ }
+}
diff --git a/vendor/guzzlehttp/promises/src/Utils.php b/vendor/guzzlehttp/promises/src/Utils.php
new file mode 100644
index 0000000..8647126
--- /dev/null
+++ b/vendor/guzzlehttp/promises/src/Utils.php
@@ -0,0 +1,276 @@
+
+ * while ($eventLoop->isRunning()) {
+ * GuzzleHttp\Promise\Utils::queue()->run();
+ * }
+ *
+ *
+ * @param TaskQueueInterface $assign Optionally specify a new queue instance.
+ *
+ * @return TaskQueueInterface
+ */
+ public static function queue(TaskQueueInterface $assign = null)
+ {
+ static $queue;
+
+ if ($assign) {
+ $queue = $assign;
+ } elseif (!$queue) {
+ $queue = new TaskQueue();
+ }
+
+ return $queue;
+ }
+
+ /**
+ * Adds a function to run in the task queue when it is next `run()` and
+ * returns a promise that is fulfilled or rejected with the result.
+ *
+ * @param callable $task Task function to run.
+ *
+ * @return PromiseInterface
+ */
+ public static function task(callable $task)
+ {
+ $queue = self::queue();
+ $promise = new Promise([$queue, 'run']);
+ $queue->add(function () use ($task, $promise) {
+ try {
+ if (Is::pending($promise)) {
+ $promise->resolve($task());
+ }
+ } catch (\Throwable $e) {
+ $promise->reject($e);
+ } catch (\Exception $e) {
+ $promise->reject($e);
+ }
+ });
+
+ return $promise;
+ }
+
+ /**
+ * Synchronously waits on a promise to resolve and returns an inspection
+ * state array.
+ *
+ * Returns a state associative array containing a "state" key mapping to a
+ * valid promise state. If the state of the promise is "fulfilled", the
+ * array will contain a "value" key mapping to the fulfilled value of the
+ * promise. If the promise is rejected, the array will contain a "reason"
+ * key mapping to the rejection reason of the promise.
+ *
+ * @param PromiseInterface $promise Promise or value.
+ *
+ * @return array
+ */
+ public static function inspect(PromiseInterface $promise)
+ {
+ try {
+ return [
+ 'state' => PromiseInterface::FULFILLED,
+ 'value' => $promise->wait()
+ ];
+ } catch (RejectionException $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
+ } catch (\Throwable $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ } catch (\Exception $e) {
+ return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
+ }
+ }
+
+ /**
+ * Waits on all of the provided promises, but does not unwrap rejected
+ * promises as thrown exception.
+ *
+ * Returns an array of inspection state arrays.
+ *
+ * @see inspect for the inspection state array format.
+ *
+ * @param PromiseInterface[] $promises Traversable of promises to wait upon.
+ *
+ * @return array
+ */
+ public static function inspectAll($promises)
+ {
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = inspect($promise);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Waits on all of the provided promises and returns the fulfilled values.
+ *
+ * Returns an array that contains the value of each promise (in the same
+ * order the promises were provided). An exception is thrown if any of the
+ * promises are rejected.
+ *
+ * @param iterable $promises Iterable of PromiseInterface objects to wait on.
+ *
+ * @return array
+ *
+ * @throws \Exception on error
+ * @throws \Throwable on error in PHP >=7
+ */
+ public static function unwrap($promises)
+ {
+ $results = [];
+ foreach ($promises as $key => $promise) {
+ $results[$key] = $promise->wait();
+ }
+
+ return $results;
+ }
+
+ /**
+ * Given an array of promises, return a promise that is fulfilled when all
+ * the items in the array are fulfilled.
+ *
+ * The promise's fulfillment value is an array with fulfillment values at
+ * respective positions to the original array. If any promise in the array
+ * rejects, the returned promise is rejected with the rejection reason.
+ *
+ * @param mixed $promises Promises or values.
+ * @param bool $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
+ *
+ * @return PromiseInterface
+ */
+ public static function all($promises, $recursive = false)
+ {
+ $results = [];
+ $promise = Each::of(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = $value;
+ },
+ function ($reason, $idx, Promise $aggregate) {
+ $aggregate->reject($reason);
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+
+ if (true === $recursive) {
+ $promise = $promise->then(function ($results) use ($recursive, &$promises) {
+ foreach ($promises as $promise) {
+ if (Is::pending($promise)) {
+ return self::all($promises, $recursive);
+ }
+ }
+ return $results;
+ });
+ }
+
+ return $promise;
+ }
+
+ /**
+ * Initiate a competitive race between multiple promises or values (values
+ * will become immediately fulfilled promises).
+ *
+ * When count amount of promises have been fulfilled, the returned promise
+ * is fulfilled with an array that contains the fulfillment values of the
+ * winners in order of resolution.
+ *
+ * This promise is rejected with a {@see AggregateException} if the number
+ * of fulfilled promises is less than the desired $count.
+ *
+ * @param int $count Total number of promises.
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+ public static function some($count, $promises)
+ {
+ $results = [];
+ $rejections = [];
+
+ return Each::of(
+ $promises,
+ function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
+ if (Is::settled($p)) {
+ return;
+ }
+ $results[$idx] = $value;
+ if (count($results) >= $count) {
+ $p->resolve(null);
+ }
+ },
+ function ($reason) use (&$rejections) {
+ $rejections[] = $reason;
+ }
+ )->then(
+ function () use (&$results, &$rejections, $count) {
+ if (count($results) !== $count) {
+ throw new AggregateException(
+ 'Not enough promises to fulfill count',
+ $rejections
+ );
+ }
+ ksort($results);
+ return array_values($results);
+ }
+ );
+ }
+
+ /**
+ * Like some(), with 1 as count. However, if the promise fulfills, the
+ * fulfillment value is not an array of 1 but the value directly.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+ public static function any($promises)
+ {
+ return self::some(1, $promises)->then(function ($values) {
+ return $values[0];
+ });
+ }
+
+ /**
+ * Returns a promise that is fulfilled when all of the provided promises have
+ * been fulfilled or rejected.
+ *
+ * The returned promise is fulfilled with an array of inspection state arrays.
+ *
+ * @see inspect for the inspection state array format.
+ *
+ * @param mixed $promises Promises or values.
+ *
+ * @return PromiseInterface
+ */
+ public static function settle($promises)
+ {
+ $results = [];
+
+ return Each::of(
+ $promises,
+ function ($value, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
+ },
+ function ($reason, $idx) use (&$results) {
+ $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
+ }
+ )->then(function () use (&$results) {
+ ksort($results);
+ return $results;
+ });
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/.github/FUNDING.yml b/vendor/guzzlehttp/psr7/.github/FUNDING.yml
new file mode 100644
index 0000000..7d222c5
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: [Nyholm, GrahamCampbell]
+tidelift: "packagist/guzzlehttp/psr7"
diff --git a/vendor/guzzlehttp/psr7/.github/stale.yml b/vendor/guzzlehttp/psr7/.github/stale.yml
new file mode 100644
index 0000000..53faa71
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/.github/stale.yml
@@ -0,0 +1,14 @@
+daysUntilStale: 120
+daysUntilClose: 14
+exemptLabels:
+ - lifecycle/keep-open
+ - lifecycle/ready-for-merge
+# Label to use when marking an issue as stale
+staleLabel: lifecycle/stale
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed after 2 weeks if no further activity occurs. Thank you
+ for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
diff --git a/vendor/guzzlehttp/psr7/.github/workflows/ci.yml b/vendor/guzzlehttp/psr7/.github/workflows/ci.yml
new file mode 100644
index 0000000..eda7dce
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/.github/workflows/ci.yml
@@ -0,0 +1,34 @@
+name: CI
+
+on:
+ pull_request:
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 10
+ matrix:
+ php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1']
+
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: 'none'
+ extensions: mbstring
+
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Mimic PHP 8.0
+ run: composer config platform.php 8.0.999
+ if: matrix.php > 8
+
+ - name: Install dependencies
+ run: composer update --no-interaction --no-progress
+
+ - name: Run tests
+ run: make test
diff --git a/vendor/guzzlehttp/psr7/.github/workflows/integration.yml b/vendor/guzzlehttp/psr7/.github/workflows/integration.yml
new file mode 100644
index 0000000..3c31f9e
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/.github/workflows/integration.yml
@@ -0,0 +1,37 @@
+name: Integration
+
+on:
+ pull_request:
+
+jobs:
+
+ build:
+ name: Test
+ runs-on: ubuntu-latest
+ strategy:
+ max-parallel: 10
+ matrix:
+ php: ['7.2', '7.3', '7.4', '8.0']
+
+ steps:
+ - name: Set up PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Download dependencies
+ uses: ramsey/composer-install@v1
+ with:
+ composer-options: --no-interaction --optimize-autoloader
+
+ - name: Start server
+ run: php -S 127.0.0.1:10002 tests/Integration/server.php &
+
+ - name: Run tests
+ env:
+ TEST_SERVER: 127.0.0.1:10002
+ run: ./vendor/bin/phpunit --testsuite Integration
diff --git a/vendor/guzzlehttp/psr7/.github/workflows/static.yml b/vendor/guzzlehttp/psr7/.github/workflows/static.yml
new file mode 100644
index 0000000..ab4d68b
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/.github/workflows/static.yml
@@ -0,0 +1,29 @@
+name: Static analysis
+
+on:
+ pull_request:
+
+jobs:
+ php-cs-fixer:
+ name: PHP-CS-Fixer
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '7.4'
+ coverage: none
+ extensions: mbstring
+
+ - name: Download dependencies
+ run: composer update --no-interaction --no-progress
+
+ - name: Download PHP CS Fixer
+ run: composer require "friendsofphp/php-cs-fixer:2.18.4"
+
+ - name: Execute PHP CS Fixer
+ run: vendor/bin/php-cs-fixer fix --diff-format udiff --dry-run
diff --git a/vendor/guzzlehttp/psr7/.php_cs.dist b/vendor/guzzlehttp/psr7/.php_cs.dist
new file mode 100644
index 0000000..e4f0bd5
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/.php_cs.dist
@@ -0,0 +1,56 @@
+setRiskyAllowed(true)
+ ->setRules([
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'concat_space' => ['spacing' => 'one'],
+ 'declare_strict_types' => false,
+ 'final_static_access' => true,
+ 'fully_qualified_strict_types' => true,
+ 'header_comment' => false,
+ 'is_null' => ['use_yoda_style' => true],
+ 'list_syntax' => ['syntax' => 'long'],
+ 'lowercase_cast' => true,
+ 'magic_method_casing' => true,
+ 'modernize_types_casting' => true,
+ 'multiline_comment_opening_closing' => true,
+ 'no_alias_functions' => true,
+ 'no_alternative_syntax' => true,
+ 'no_blank_lines_after_phpdoc' => true,
+ 'no_empty_comment' => true,
+ 'no_empty_phpdoc' => true,
+ 'no_empty_statement' => true,
+ 'no_extra_blank_lines' => true,
+ 'no_leading_import_slash' => true,
+ 'no_trailing_comma_in_singleline_array' => true,
+ 'no_unset_cast' => true,
+ 'no_unused_imports' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'ordered_imports' => true,
+ 'php_unit_ordered_covers' => true,
+ 'php_unit_test_annotation' => ['style' => 'prefix'],
+ 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+ 'phpdoc_align' => ['align' => 'vertical'],
+ 'phpdoc_no_useless_inheritdoc' => true,
+ 'phpdoc_scalar' => true,
+ 'phpdoc_separation' => true,
+ 'phpdoc_single_line_var_spacing' => true,
+ 'phpdoc_trim' => true,
+ 'phpdoc_trim_consecutive_blank_line_separation' => true,
+ 'phpdoc_types' => true,
+ 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
+ 'phpdoc_var_without_name' => true,
+ 'single_trait_insert_per_statement' => true,
+ 'standardize_not_equals' => true,
+ ])
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__.'/src')
+ ->in(__DIR__.'/tests')
+ ->name('*.php')
+ )
+;
+
+return $config;
diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php
new file mode 100644
index 0000000..865d742
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Header.php
@@ -0,0 +1,71 @@
+]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @return array Returns the normalized header field values.
+ */
+ public static function normalize($header)
+ {
+ if (!is_array($header)) {
+ return array_map('trim', explode(',', $header));
+ }
+
+ $result = [];
+ foreach ($header as $value) {
+ foreach ((array) $value as $v) {
+ if (strpos($v, ',') === false) {
+ $result[] = $v;
+ continue;
+ }
+ foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) {
+ $result[] = trim($vv);
+ }
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php
new file mode 100644
index 0000000..516d1cb
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Message.php
@@ -0,0 +1,252 @@
+getMethod() . ' '
+ . $message->getRequestTarget())
+ . ' HTTP/' . $message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: " . $message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/' . $message->getProtocolVersion() . ' '
+ . $message->getStatusCode() . ' '
+ . $message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ if (strtolower($name) === 'set-cookie') {
+ foreach ($values as $value) {
+ $msg .= "\r\n{$name}: " . $value;
+ }
+ } else {
+ $msg .= "\r\n{$name}: " . implode(', ', $values);
+ }
+ }
+
+ return "{$msg}\r\n\r\n" . $message->getBody();
+ }
+
+ /**
+ * Get a short summary of the message body.
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param MessageInterface $message The message to get the body summary
+ * @param int $truncateAt The maximum allowed size of the summary
+ *
+ * @return string|null
+ */
+ public static function bodySummary(MessageInterface $message, $truncateAt = 120)
+ {
+ $body = $message->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $summary = $body->read($truncateAt);
+ $body->rewind();
+
+ if ($size > $truncateAt) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary)) {
+ return null;
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()`
+ * returns a value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+ public static function rewindBody(MessageInterface $message)
+ {
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+ }
+
+ /**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ *
+ * @return array
+ */
+ public static function parseMessage($message)
+ {
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ $message = ltrim($message, "\r\n");
+
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+ if ($messageParts === false || count($messageParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+ }
+
+ list($rawHeaders, $body) = $messageParts;
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+ if ($headerParts === false || count($headerParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
+ }
+
+ list($startLine, $rawHeaders) = $headerParts;
+
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+ }
+
+ /** @var array[] $headerLines */
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+ // If these aren't the same, then one line didn't match and there's an invalid header.
+ if ($count !== substr_count($rawHeaders, "\n")) {
+ // Folding is deprecated, see https://tools.ietf.org/html/rfc7230#section-3.2.4
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+ }
+
+ throw new \InvalidArgumentException('Invalid header syntax');
+ }
+
+ $headers = [];
+
+ foreach ($headerLines as $headerLine) {
+ $headers[$headerLine[1]][] = $headerLine[2];
+ }
+
+ return [
+ 'start-line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body,
+ ];
+ }
+
+ /**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ *
+ * @return string
+ */
+ public static function parseRequestUri($path, array $headers)
+ {
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme . '://' . $host . '/' . ltrim($path, '/');
+ }
+
+ /**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ *
+ * @return Request
+ */
+ public static function parseRequest($message)
+ {
+ $data = self::parseMessage($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+ }
+
+ /**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ *
+ * @return Response
+ */
+ public static function parseResponse($message)
+ {
+ $data = self::parseMessage($message);
+ // According to https://tools.ietf.org/html/rfc7230#section-3.1.2 the space
+ // between status-code and reason-phrase is required. But browsers accept
+ // responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string: ' . $data['start-line']);
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ (int) $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ isset($parts[2]) ? $parts[2] : null
+ );
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php
new file mode 100644
index 0000000..205c7b1
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MimeType.php
@@ -0,0 +1,140 @@
+ 'video/3gpp',
+ '7z' => 'application/x-7z-compressed',
+ 'aac' => 'audio/x-aac',
+ 'ai' => 'application/postscript',
+ 'aif' => 'audio/x-aiff',
+ 'asc' => 'text/plain',
+ 'asf' => 'video/x-ms-asf',
+ 'atom' => 'application/atom+xml',
+ 'avi' => 'video/x-msvideo',
+ 'bmp' => 'image/bmp',
+ 'bz2' => 'application/x-bzip2',
+ 'cer' => 'application/pkix-cert',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'css' => 'text/css',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'deb' => 'application/x-debian-package',
+ 'doc' => 'application/msword',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dvi' => 'application/x-dvi',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'etx' => 'text/x-setext',
+ 'flac' => 'audio/flac',
+ 'flv' => 'video/x-flv',
+ 'gif' => 'image/gif',
+ 'gz' => 'application/gzip',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ini' => 'text/plain',
+ 'iso' => 'application/x-iso9660-image',
+ 'jar' => 'application/java-archive',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'js' => 'text/javascript',
+ 'json' => 'application/json',
+ 'latex' => 'application/x-latex',
+ 'log' => 'text/plain',
+ 'm4a' => 'audio/mp4',
+ 'm4v' => 'video/mp4',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mov' => 'video/quicktime',
+ 'mkv' => 'video/x-matroska',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'oga' => 'audio/ogg',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-portable-graymap',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'ppt' => 'application/vnd.ms-powerpoint',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'ps' => 'application/postscript',
+ 'qt' => 'video/quicktime',
+ 'rar' => 'application/x-rar-compressed',
+ 'ras' => 'image/x-cmu-raster',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'application/rtf',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'svg' => 'image/svg+xml',
+ 'swf' => 'application/x-shockwave-flash',
+ 'tar' => 'application/x-tar',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'torrent' => 'application/x-bittorrent',
+ 'ttf' => 'application/x-font-ttf',
+ 'txt' => 'text/plain',
+ 'wav' => 'audio/x-wav',
+ 'webm' => 'video/webm',
+ 'webp' => 'image/webp',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmv' => 'video/x-ms-wmv',
+ 'woff' => 'application/x-font-woff',
+ 'wsdl' => 'application/wsdl+xml',
+ 'xbm' => 'image/x-xbitmap',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xml' => 'application/xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xwd' => 'image/x-xwindowdump',
+ 'yaml' => 'text/yaml',
+ 'yml' => 'text/yaml',
+ 'zip' => 'application/zip',
+ ];
+
+ $extension = strtolower($extension);
+
+ return isset($mimetypes[$extension])
+ ? $mimetypes[$extension]
+ : null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php
new file mode 100644
index 0000000..5a7cc03
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Query.php
@@ -0,0 +1,113 @@
+ '1', 'foo[b]' => '2'])`.
+ *
+ * @param string $str Query string to parse
+ * @param int|bool $urlEncoding How the query string is encoded
+ *
+ * @return array
+ */
+ public static function parse($str, $urlEncoding = true)
+ {
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', $value));
+ };
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) {
+ return $str;
+ };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!isset($result[$key])) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of `parse()` to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like `http_build_query()` would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode, PHP_QUERY_RFC3986
+ * to encode using RFC3986, or PHP_QUERY_RFC1738
+ * to encode using RFC1738.
+ *
+ * @return string
+ */
+ public static function build(array $params, $encoding = PHP_QUERY_RFC3986)
+ {
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function ($str) {
+ return $str;
+ };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder($k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ if ($v !== null) {
+ $qs .= '=' . $encoder($v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ if ($vv !== null) {
+ $qs .= '=' . $encoder($vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php
new file mode 100644
index 0000000..51b571f
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php
@@ -0,0 +1,19 @@
+@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriComparator.php b/vendor/guzzlehttp/psr7/src/UriComparator.php
new file mode 100644
index 0000000..ccf51ff
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriComparator.php
@@ -0,0 +1,55 @@
+getHost(), $modified->getHost()) !== 0) {
+ return true;
+ }
+
+ if ($original->getScheme() !== $modified->getScheme()) {
+ return true;
+ }
+
+ if (self::computePort($original) !== self::computePort($modified)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return int
+ */
+ private static function computePort(UriInterface $uri)
+ {
+ $port = $uri->getPort();
+
+ if (null !== $port) {
+ return $port;
+ }
+
+ return 'https' === $uri->getScheme() ? 443 : 80;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php
new file mode 100644
index 0000000..6b6c8cc
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Utils.php
@@ -0,0 +1,428 @@
+ $keys
+ *
+ * @return array
+ */
+ public static function caselessRemove($keys, array $data)
+ {
+ $result = [];
+
+ foreach ($keys as &$key) {
+ $key = strtolower($key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower($k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)
+ {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @return string
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function copyToString(StreamInterface $stream, $maxLen = -1)
+ {
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ // Using a loose equality here to match on '' and false.
+ if ($buf == null) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Calculate a hash of a stream.
+ *
+ * This method reads the entire stream to calculate a rolling hash, based
+ * on PHP's `hash_init` functions.
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function hash(StreamInterface $stream, $algo, $rawOutput = false)
+ {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+ }
+
+ /**
+ * Clone and modify a request with the given changes.
+ *
+ * This method is useful for reducing the number of clones needed to mutate
+ * a message.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ *
+ * @return RequestInterface
+ */
+ public static function modifyRequest(RequestInterface $request, array $changes)
+ {
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':' . $port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = self::caselessRemove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ $new = (new ServerRequest(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion(),
+ $request->getServerParams()
+ ))
+ ->withParsedBody($request->getParsedBody())
+ ->withQueryParams($request->getQueryParams())
+ ->withCookieParams($request->getCookieParams())
+ ->withUploadedFiles($request->getUploadedFiles());
+
+ foreach ($request->getAttributes() as $key => $value) {
+ $new = $new->withAttribute($key, $value);
+ }
+
+ return $new;
+ }
+
+ return new Request(
+ isset($changes['method']) ? $changes['method'] : $request->getMethod(),
+ $uri,
+ $headers,
+ isset($changes['body']) ? $changes['body'] : $request->getBody(),
+ isset($changes['version'])
+ ? $changes['version']
+ : $request->getProtocolVersion()
+ );
+ }
+
+ /**
+ * Read a line from the stream up to the maximum allowed buffer length.
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int|null $maxLength Maximum buffer length
+ *
+ * @return string
+ */
+ public static function readLine(StreamInterface $stream, $maxLength = null)
+ {
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ // Using a loose equality here to match on '' and false.
+ if (null == ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * This method accepts the following `$resource` types:
+ * - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+ * - `string`: Creates a stream object that uses the given string as the contents.
+ * - `resource`: Creates a stream object that wraps the given PHP stream resource.
+ * - `Iterator`: If the provided value implements `Iterator`, then a read-only
+ * stream object will be created that wraps the given iterable. Each time the
+ * stream is read from, data from the iterator will fill a buffer and will be
+ * continuously called until the buffer is equal to the requested read size.
+ * Subsequent read calls will first read from the buffer and then call `next`
+ * on the underlying iterator until it is exhausted.
+ * - `object` with `__toString()`: If the object has the `__toString()` method,
+ * the object will be cast to a string and then a stream will be returned that
+ * uses the string value.
+ * - `NULL`: When `null` is passed, an empty stream object is returned.
+ * - `callable` When a callable is passed, a read-only stream object will be
+ * created that invokes the given callable. The callable is invoked with the
+ * number of suggested bytes to read. The callable can return any number of
+ * bytes, but MUST return `false` when there is no more data to return. The
+ * stream object that wraps the callable will invoke the callable until the
+ * number of requested bytes are available. Any additional bytes will be
+ * buffered and used in subsequent reads.
+ *
+ * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return StreamInterface
+ *
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+ public static function streamFor($resource = '', array $options = [])
+ {
+ if (is_scalar($resource)) {
+ $stream = self::tryFopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ /*
+ * The 'php://input' is a special stream with quirks and inconsistencies.
+ * We avoid using that stream by reading it into php://temp
+ */
+ $metaData = \stream_get_meta_data($resource);
+ if (isset($metaData['uri']) && $metaData['uri'] === 'php://input') {
+ $stream = self::tryFopen('php://temp', 'w+');
+ fwrite($stream, stream_get_contents($resource));
+ fseek($stream, 0);
+ $resource = $stream;
+ }
+ return new Stream($resource, $options);
+ case 'object':
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return Utils::streamFor((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(self::tryFopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
+ }
+
+ /**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ *
+ * @throws \RuntimeException if the file cannot be opened
+ */
+ public static function tryFopen($filename, $mode)
+ {
+ $ex = null;
+ set_error_handler(function () use ($filename, $mode, &$ex) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open "%s" using mode "%s": %s',
+ $filename,
+ $mode,
+ func_get_args()[1]
+ ));
+
+ return true;
+ });
+
+ try {
+ $handle = fopen($filename, $mode);
+ } catch (\Throwable $e) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open "%s" using mode "%s": %s',
+ $filename,
+ $mode,
+ $e->getMessage()
+ ), 0, $e);
+ }
+
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+ }
+
+ /**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or UriInterface and returns a
+ * UriInterface for the given value. If the value is already a
+ * UriInterface, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @return UriInterface
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function uriFor($uri)
+ {
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ }
+
+ if (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/.editorconfig b/vendor/guzzlehttp/ringphp/.editorconfig
new file mode 100644
index 0000000..70dabca
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[{Makefile,*.mk}]
+indent_style = tab
diff --git a/vendor/guzzlehttp/ringphp/.gitignore b/vendor/guzzlehttp/ringphp/.gitignore
new file mode 100644
index 0000000..290a945
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/.gitignore
@@ -0,0 +1,4 @@
+vendor
+build/artifacts/
+composer.lock
+docs/_build/
diff --git a/vendor/guzzlehttp/ringphp/.travis.yml b/vendor/guzzlehttp/ringphp/.travis.yml
new file mode 100644
index 0000000..18562e4
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/.travis.yml
@@ -0,0 +1,43 @@
+language: php
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - 7.0
+ - 7.1
+ - 7.2
+ - hhvm
+ - nightly
+
+env:
+ global:
+ - TEST_COMMAND="composer test"
+
+matrix:
+ allow_failures:
+ - php: hhvm
+ - php: nightly
+ fast_finish: true
+ include:
+ - php: 5.4
+ env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest"
+
+before_install:
+ - if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
+
+install:
+ # To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355
+ - if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
+ - travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
+
+before_script:
+ - ~/.nvm/nvm.sh install v0.6.14
+ - ~/.nvm/nvm.sh run v0.6.14
+
+script:
+ - $TEST_COMMAND
diff --git a/vendor/guzzlehttp/ringphp/CHANGELOG.md b/vendor/guzzlehttp/ringphp/CHANGELOG.md
new file mode 100644
index 0000000..8e12bf3
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/CHANGELOG.md
@@ -0,0 +1,118 @@
+# Changelog
+
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+
+
+## [Unreleased]
+
+
+## [1.1.1] - 2018-07-31
+
+### Fixed
+
+- `continue` keyword usage on PHP 7.3
+
+
+## [1.1.0] - 2015-05-19
+
+### Added
+
+- Added `CURL_HTTP_VERSION_2_0`
+
+### Changed
+
+- The PHP stream wrapper handler now sets `allow_self_signed` to `false` to
+ match the cURL handler when `verify` is set to `true` or a certificate file.
+- Ensuring that a directory exists before using the `save_to` option.
+- Response protocol version is now correctly extracted from a response.
+
+### Fixed
+
+- Fixed a bug in which the result of `CurlFactory::retryFailedRewind` did not
+ return an array.
+
+
+## [1.0.7] - 2015-03-29
+
+### Fixed
+
+- PHP 7 fixes.
+
+
+## [1.0.6] - 2015-02-26
+
+### Changed
+
+- The multi handle of the CurlMultiHandler is now created lazily.
+
+### Fixed
+
+- Bug fix: futures now extend from React's PromiseInterface to ensure that they
+ are properly forwarded down the promise chain.
+
+
+## [1.0.5] - 2014-12-10
+
+### Added
+
+- Adding more error information to PHP stream wrapper exceptions.
+- Added digest auth integration test support to test server.
+
+
+## [1.0.4] - 2014-12-01
+
+### Added
+
+- Added support for older versions of cURL that do not have CURLOPT_TIMEOUT_MS.
+- Added a fix to the StreamHandler to return a `FutureArrayInterface` when an
+
+### Changed
+
+- Setting debug to `false` does not enable debug output. error occurs.
+
+
+## [1.0.3] - 2014-11-03
+
+### Fixed
+
+- Setting the `header` stream option as a string to be compatible with GAE.
+- Header parsing now ensures that header order is maintained in the parsed
+ message.
+
+
+## [1.0.2] - 2014-10-28
+
+### Fixed
+
+- Now correctly honoring a `version` option is supplied in a request.
+ See https://github.com/guzzle/RingPHP/pull/8
+
+
+## [1.0.1] - 2014-10-26
+
+### Fixed
+
+- Fixed a header parsing issue with the `CurlHandler` and `CurlMultiHandler`
+ that caused cURL requests with multiple responses to merge repsonses together
+ (e.g., requests with digest authentication).
+
+
+## 1.0.0 - 2014-10-12
+
+- Initial release
+
+
+[Unreleased]: https://github.com/guzzle/RingPHP/compare/1.1.1...HEAD
+[1.1.1]: https://github.com/guzzle/RingPHP/compare/1.1.0...1.1.1
+[1.1.0]: https://github.com/guzzle/RingPHP/compare/1.0.7...1.1.0
+[1.0.7]: https://github.com/guzzle/RingPHP/compare/1.0.6...1.0.7
+[1.0.6]: https://github.com/guzzle/RingPHP/compare/1.0.5...1.0.6
+[1.0.5]: https://github.com/guzzle/RingPHP/compare/1.0.4...1.0.5
+[1.0.4]: https://github.com/guzzle/RingPHP/compare/1.0.3...1.0.4
+[1.0.3]: https://github.com/guzzle/RingPHP/compare/1.0.2...1.0.3
+[1.0.2]: https://github.com/guzzle/RingPHP/compare/1.0.1...1.0.2
+[1.0.1]: https://github.com/guzzle/RingPHP/compare/1.0.0...1.0.1
diff --git a/vendor/guzzlehttp/ringphp/LICENSE b/vendor/guzzlehttp/ringphp/LICENSE
new file mode 100644
index 0000000..71d3b78
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/guzzlehttp/ringphp/Makefile b/vendor/guzzlehttp/ringphp/Makefile
new file mode 100644
index 0000000..21c812e
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/Makefile
@@ -0,0 +1,46 @@
+all: clean coverage docs
+
+docs:
+ cd docs && make html
+
+view-docs:
+ open docs/_build/html/index.html
+
+start-server: stop-server
+ node tests/Client/server.js &> /dev/null &
+
+stop-server:
+ @PID=$(shell ps axo pid,command \
+ | grep 'tests/Client/server.js' \
+ | grep -v grep \
+ | cut -f 1 -d " "\
+ ) && [ -n "$$PID" ] && kill $$PID || true
+
+test: start-server
+ vendor/bin/phpunit $(TEST)
+ $(MAKE) stop-server
+
+coverage: start-server
+ vendor/bin/phpunit --coverage-html=build/artifacts/coverage $(TEST)
+ $(MAKE) stop-server
+
+view-coverage:
+ open build/artifacts/coverage/index.html
+
+clean:
+ rm -rf build/artifacts/*
+ cd docs && make clean
+
+tag:
+ $(if $(TAG),,$(error TAG is not defined. Pass via "make tag TAG=4.2.1"))
+ @echo Tagging $(TAG)
+ chag update -m '$(TAG) ()'
+ git add -A
+ git commit -m '$(TAG) release'
+ chag tag
+
+perf: start-server
+ php tests/perf.php
+ $(MAKE) stop-server
+
+.PHONY: docs
diff --git a/vendor/guzzlehttp/ringphp/README.rst b/vendor/guzzlehttp/ringphp/README.rst
new file mode 100644
index 0000000..10374e8
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/README.rst
@@ -0,0 +1,46 @@
+=======
+RingPHP
+=======
+
+Provides a simple API and specification that abstracts away the details of HTTP
+into a single PHP function. RingPHP be used to power HTTP clients and servers
+through a PHP function that accepts a request hash and returns a response hash
+that is fulfilled using a `promise `_,
+allowing RingPHP to support both synchronous and asynchronous workflows.
+
+By abstracting the implementation details of different HTTP clients and
+servers, RingPHP allows you to utilize pluggable HTTP clients and servers
+without tying your application to a specific implementation.
+
+.. code-block:: php
+
+ 'GET',
+ 'uri' => '/',
+ 'headers' => [
+ 'host' => ['www.google.com'],
+ 'x-foo' => ['baz']
+ ]
+ ]);
+
+ $response->then(function (array $response) {
+ echo $response['status'];
+ });
+
+ $response->wait();
+
+RingPHP is inspired by Clojure's `Ring `_,
+which, in turn, was inspired by Python's WSGI and Ruby's Rack. RingPHP is
+utilized as the handler layer in `Guzzle `_ 5.0+ to send
+HTTP requests.
+
+Documentation
+-------------
+
+See http://ringphp.readthedocs.org/ for the full online documentation.
diff --git a/vendor/guzzlehttp/ringphp/composer.json b/vendor/guzzlehttp/ringphp/composer.json
new file mode 100644
index 0000000..8df60ec
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "guzzlehttp/ringphp",
+ "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "guzzlehttp/streams": "~3.0",
+ "react/promise": "~2.0"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "ext-curl": "Guzzle will use specific adapters if cURL is present"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Ring\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\Ring\\": "tests/"
+ }
+ },
+ "scripts": {
+ "test": "make test",
+ "test-ci": "make coverage"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/docs/Makefile b/vendor/guzzlehttp/ringphp/docs/Makefile
new file mode 100644
index 0000000..51270aa
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+ @echo "Please use \`make ' where is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " texinfo to make Texinfo files"
+ @echo " info to make Texinfo files and run them through makeinfo"
+ @echo " gettext to make PO message catalogs"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GuzzleRing.qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GuzzleRing.qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/GuzzleRing"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GuzzleRing"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ $(MAKE) -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo
+ @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+ @echo "Run \`make' in that directory to run these through makeinfo" \
+ "(use \`make info' here to do that automatically)."
+
+info:
+ $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+ @echo "Running Texinfo files through makeinfo..."
+ make -C $(BUILDDIR)/texinfo info
+ @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+ $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+ @echo
+ @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/vendor/guzzlehttp/ringphp/docs/client_handlers.rst b/vendor/guzzlehttp/ringphp/docs/client_handlers.rst
new file mode 100644
index 0000000..3151f00
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/client_handlers.rst
@@ -0,0 +1,173 @@
+===============
+Client Handlers
+===============
+
+Client handlers accept a request array and return a future response array that
+can be used synchronously as an array or asynchronously using a promise.
+
+Built-In Handlers
+-----------------
+
+RingPHP comes with three built-in client handlers.
+
+Stream Handler
+~~~~~~~~~~~~~~
+
+The ``GuzzleHttp\Ring\Client\StreamHandler`` uses PHP's
+`http stream wrapper `_ to send
+requests.
+
+.. note::
+
+ This handler cannot send requests concurrently.
+
+You can provide an associative array of custom stream context options to the
+StreamHandler using the ``stream_context`` key of the ``client`` request
+option.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\StreamHandler;
+
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => ['httpbin.org']],
+ 'client' => [
+ 'stream_context' => [
+ 'http' => [
+ 'request_fulluri' => true,
+ 'method' => 'HEAD'
+ ],
+ 'socket' => [
+ 'bindto' => '127.0.0.1:0'
+ ],
+ 'ssl' => [
+ 'verify_peer' => false
+ ]
+ ]
+ ]
+ ]);
+
+ // Even though it's already completed, you can still use a promise
+ $response->then(function ($response) {
+ echo $response['status']; // 200
+ });
+
+ // Or access the response using the future interface
+ echo $response['status']; // 200
+
+cURL Handler
+~~~~~~~~~~~~
+
+The ``GuzzleHttp\Ring\Client\CurlHandler`` can be used with PHP 5.5+ to send
+requests using cURL easy handles. This handler is great for sending requests
+one at a time because the execute and select loop is implemented in C code
+which executes faster and consumes less memory than using PHP's
+``curl_multi_*`` interface.
+
+.. note::
+
+ This handler cannot send requests concurrently.
+
+When using the CurlHandler, custom curl options can be specified as an
+associative array of `cURL option constants `_
+mapping to values in the ``client`` option of a requst using the **curl** key.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlHandler;
+
+ $handler = new CurlHandler();
+
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['curl' => [CURLOPT_LOW_SPEED_LIMIT => 10]]
+ ];
+
+ $response = $handler($request);
+
+ // The response can be used directly as an array.
+ echo $response['status']; // 200
+
+ // Or, it can be used as a promise (that has already fulfilled).
+ $response->then(function ($response) {
+ echo $response['status']; // 200
+ });
+
+cURL Multi Handler
+~~~~~~~~~~~~~~~~~~
+
+The ``GuzzleHttp\Ring\Client\CurlMultiHandler`` transfers requests using
+cURL's `multi API `_. The
+``CurlMultiHandler`` is great for sending requests concurrently.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlMultiHandler;
+
+ $handler = new CurlMultiHandler();
+
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]]
+ ];
+
+ // this call returns a future array immediately.
+ $response = $handler($request);
+
+ // Ideally, you should use the promise API to not block.
+ $response
+ ->then(function ($response) {
+ // Got the response at some point in the future
+ echo $response['status']; // 200
+ // Don't break the chain
+ return $response;
+ })->then(function ($response) {
+ // ...
+ });
+
+ // If you really need to block, then you can use the response as an
+ // associative array. This will block until it has completed.
+ echo $response['status']; // 200
+
+Just like the ``CurlHandler``, the ``CurlMultiHandler`` accepts custom curl
+option in the ``curl`` key of the ``client`` request option.
+
+Mock Handler
+~~~~~~~~~~~~
+
+The ``GuzzleHttp\Ring\Client\MockHandler`` is used to return mock responses.
+When constructed, the handler can be configured to return the same response
+array over and over, a future response, or a the evaluation of a callback
+function.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\MockHandler;
+
+ // Return a canned response.
+ $mock = new MockHandler(['status' => 200]);
+ $response = $mock([]);
+ assert(200 == $response['status']);
+ assert([] == $response['headers']);
+
+Implementing Handlers
+---------------------
+
+Client handlers are just PHP callables (functions or classes that have the
+``__invoke`` magic method). The callable accepts a request array and MUST
+return an instance of ``GuzzleHttp\Ring\Future\FutureArrayInterface`` so that
+the response can be used by both blocking and non-blocking consumers.
+
+Handlers need to follow a few simple rules:
+
+1. Do not throw exceptions. If an error is encountered, return an array that
+ contains the ``error`` key that maps to an ``\Exception`` value.
+2. If the request has a ``delay`` client option, then the handler should only
+ send the request after the specified delay time in seconds. Blocking
+ handlers may find it convenient to just let the
+ ``GuzzleHttp\Ring\Core::doSleep($request)`` function handle this for them.
+3. Always return an instance of ``GuzzleHttp\Ring\Future\FutureArrayInterface``.
+4. Complete any outstanding requests when the handler is destructed.
diff --git a/vendor/guzzlehttp/ringphp/docs/client_middleware.rst b/vendor/guzzlehttp/ringphp/docs/client_middleware.rst
new file mode 100644
index 0000000..5a2c1a8
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/client_middleware.rst
@@ -0,0 +1,165 @@
+=================
+Client Middleware
+=================
+
+Middleware intercepts requests before they are sent over the wire and can be
+used to add functionality to handlers.
+
+Modifying Requests
+------------------
+
+Let's say you wanted to modify requests before they are sent over the wire
+so that they always add specific headers. This can be accomplished by creating
+a function that accepts a handler and returns a new function that adds the
+composed behavior.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlHandler;
+
+ $handler = new CurlHandler();
+
+ $addHeaderHandler = function (callable $handler, array $headers = []) {
+ return function (array $request) use ($handler, $headers) {
+ // Add our custom headers
+ foreach ($headers as $key => $value) {
+ $request['headers'][$key] = $value;
+ }
+
+ // Send the request using the handler and return the response.
+ return $handler($request);
+ }
+ };
+
+ // Create a new handler that adds headers to each request.
+ $handler = $addHeaderHandler($handler, [
+ 'X-AddMe' => 'hello',
+ 'Authorization' => 'Basic xyz'
+ ]);
+
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => ['httpbin.org']]
+ ]);
+
+Modifying Responses
+-------------------
+
+You can change a response as it's returned from a middleware. Remember that
+responses returned from an handler (including middleware) must implement
+``GuzzleHttp\Ring\Future\FutureArrayInterface``. In order to be a good citizen,
+you should not expect that the responses returned through your middleware will
+be completed synchronously. Instead, you should use the
+``GuzzleHttp\Ring\Core::proxy()`` function to modify the response when the
+underlying promise is resolved. This function is a helper function that makes it
+easy to create a new instance of ``FutureArrayInterface`` that wraps an existing
+``FutureArrayInterface`` object.
+
+Let's say you wanted to add headers to a response as they are returned from
+your middleware, but you want to make sure you aren't causing future
+responses to be dereferenced right away. You can achieve this by modifying the
+incoming request and using the ``Core::proxy`` function.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Core;
+ use GuzzleHttp\Ring\Client\CurlHandler;
+
+ $handler = new CurlHandler();
+
+ $responseHeaderHandler = function (callable $handler, array $headers) {
+ return function (array $request) use ($handler, $headers) {
+ // Send the request using the wrapped handler.
+ return Core::proxy($handler($request), function ($response) use ($headers) {
+ // Add the headers to the response when it is available.
+ foreach ($headers as $key => $value) {
+ $response['headers'][$key] = (array) $value;
+ }
+ // Note that you can return a regular response array when using
+ // the proxy method.
+ return $response;
+ });
+ }
+ };
+
+ // Create a new handler that adds headers to each response.
+ $handler = $responseHeaderHandler($handler, ['X-Header' => 'hello!']);
+
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => ['httpbin.org']]
+ ]);
+
+ assert($response['headers']['X-Header'] == 'hello!');
+
+Built-In Middleware
+-------------------
+
+RingPHP comes with a few basic client middlewares that modify requests
+and responses.
+
+Streaming Middleware
+~~~~~~~~~~~~~~~~~~~~
+
+If you want to send all requests with the ``streaming`` option to a specific
+handler but other requests to a different handler, then use the streaming
+middleware.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlHandler;
+ use GuzzleHttp\Ring\Client\StreamHandler;
+ use GuzzleHttp\Ring\Client\Middleware;
+
+ $defaultHandler = new CurlHandler();
+ $streamingHandler = new StreamHandler();
+ $streamingHandler = Middleware::wrapStreaming(
+ $defaultHandler,
+ $streamingHandler
+ );
+
+ // Send the request using the streaming handler.
+ $response = $streamingHandler([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => ['www.google.com']],
+ 'stream' => true
+ ]);
+
+ // Send the request using the default handler.
+ $response = $streamingHandler([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => ['www.google.com']]
+ ]);
+
+Future Middleware
+~~~~~~~~~~~~~~~~~
+
+If you want to send all requests with the ``future`` option to a specific
+handler but other requests to a different handler, then use the future
+middleware.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlHandler;
+ use GuzzleHttp\Ring\Client\CurlMultiHandler;
+ use GuzzleHttp\Ring\Client\Middleware;
+
+ $defaultHandler = new CurlHandler();
+ $futureHandler = new CurlMultiHandler();
+ $futureHandler = Middleware::wrapFuture(
+ $defaultHandler,
+ $futureHandler
+ );
+
+ // Send the request using the blocking CurlHandler.
+ $response = $futureHandler([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => ['www.google.com']]
+ ]);
+
+ // Send the request using the non-blocking CurlMultiHandler.
+ $response = $futureHandler([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => ['www.google.com']],
+ 'future' => true
+ ]);
diff --git a/vendor/guzzlehttp/ringphp/docs/conf.py b/vendor/guzzlehttp/ringphp/docs/conf.py
new file mode 100644
index 0000000..c6404aa
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/conf.py
@@ -0,0 +1,23 @@
+import sys, os
+import sphinx_rtd_theme
+from sphinx.highlighting import lexers
+from pygments.lexers.web import PhpLexer
+
+
+lexers['php'] = PhpLexer(startinline=True, linenos=1)
+lexers['php-annotations'] = PhpLexer(startinline=True, linenos=1)
+primary_domain = 'php'
+
+extensions = []
+templates_path = ['_templates']
+source_suffix = '.rst'
+master_doc = 'index'
+project = u'RingPHP'
+copyright = u'2014, Michael Dowling'
+version = '1.0.0-alpha'
+exclude_patterns = ['_build']
+
+html_title = "RingPHP"
+html_short_title = "RingPHP"
+html_theme = "sphinx_rtd_theme"
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
diff --git a/vendor/guzzlehttp/ringphp/docs/futures.rst b/vendor/guzzlehttp/ringphp/docs/futures.rst
new file mode 100644
index 0000000..af29cb3
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/futures.rst
@@ -0,0 +1,164 @@
+=======
+Futures
+=======
+
+Futures represent a computation that may have not yet completed. RingPHP
+uses hybrid of futures and promises to provide a consistent API that can be
+used for both blocking and non-blocking consumers.
+
+Promises
+--------
+
+You can get the result of a future when it is ready using the promise interface
+of a future. Futures expose a promise API via a ``then()`` method that utilizes
+`React's promise library `_. You should
+use this API when you do not wish to block.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlMultiHandler;
+
+ $request = [
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => ['httpbin.org']]
+ ];
+
+ $response = $handler($request);
+
+ // Use the then() method to use the promise API of the future.
+ $response->then(function ($response) {
+ echo $response['status'];
+ });
+
+You can get the promise used by a future, an instance of
+``React\Promise\PromiseInterface``, by calling the ``promise()`` method.
+
+.. code-block:: php
+
+ $response = $handler($request);
+ $promise = $response->promise();
+ $promise->then(function ($response) {
+ echo $response['status'];
+ });
+
+This promise value can be used with React's
+`aggregate promise functions `_.
+
+Waiting
+-------
+
+You can wait on a future to complete and retrieve the value, or *dereference*
+the future, using the ``wait()`` method. Calling the ``wait()`` method of a
+future will block until the result is available. The result is then returned or
+an exception is thrown if and exception was encountered while waiting on the
+the result. Subsequent calls to dereference a future will return the previously
+completed result or throw the previously encountered exception. Futures can be
+cancelled, which stops the computation if possible.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlMultiHandler;
+
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => ['httpbin.org']]
+ ]);
+
+ // You can explicitly call block to wait on a result.
+ $realizedResponse = $response->wait();
+
+ // Future responses can be used like a regular PHP array.
+ echo $response['status'];
+
+In addition to explicitly calling the ``wait()`` function, using a future like
+a normal value will implicitly trigger the ``wait()`` function.
+
+Future Responses
+----------------
+
+RingPHP uses futures to return asynchronous responses immediately. Client
+handlers always return future responses that implement
+``GuzzleHttp\Ring\Future\ArrayFutureInterface``. These future responses act
+just like normal PHP associative arrays for blocking access and provide a
+promise interface for non-blocking access.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlMultiHandler;
+
+ $handler = new CurlMultiHandler();
+
+ $request = [
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['Host' => ['www.google.com']]
+ ];
+
+ $response = $handler($request);
+
+ // Use the promise API for non-blocking access to the response. The actual
+ // response value will be delivered to the promise.
+ $response->then(function ($response) {
+ echo $response['status'];
+ });
+
+ // You can wait (block) until the future is completed.
+ $response->wait();
+
+ // This will implicitly call wait(), and will block too!
+ $response['status'];
+
+.. important::
+
+ Futures that are not completed by the time the underlying handler is
+ destructed will be completed when the handler is shutting down.
+
+Cancelling
+----------
+
+Futures can be cancelled if they have not already been dereferenced.
+
+RingPHP futures are typically implemented with the
+``GuzzleHttp\Ring\Future\BaseFutureTrait``. This trait provides the cancellation
+functionality that should be common to most implementations. Cancelling a
+future response will try to prevent the request from sending over the wire.
+
+When a future is cancelled, the cancellation function is invoked and performs
+the actual work needed to cancel the request from sending if possible
+(e.g., telling an event loop to stop sending a request or to close a socket).
+If no cancellation function is provided, then a request cannot be cancelled. If
+a cancel function is provided, then it should accept the future as an argument
+and return true if the future was successfully cancelled or false if it could
+not be cancelled.
+
+Wrapping an existing Promise
+----------------------------
+
+You can easily create a future from any existing promise using the
+``GuzzleHttp\Ring\Future\FutureValue`` class. This class's constructor
+accepts a promise as the first argument, a wait function as the second
+argument, and a cancellation function as the third argument. The dereference
+function is used to force the promise to resolve (for example, manually ticking
+an event loop). The cancel function is optional and is used to tell the thing
+that created the promise that it can stop computing the result (for example,
+telling an event loop to stop transferring a request).
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Future\FutureValue;
+ use React\Promise\Deferred;
+
+ $deferred = new Deferred();
+ $promise = $deferred->promise();
+
+ $f = new FutureValue(
+ $promise,
+ function () use ($deferred) {
+ // This function is responsible for blocking and resolving the
+ // promise. Here we pass in a reference to the deferred so that
+ // it can be resolved or rejected.
+ $deferred->resolve('foo');
+ }
+ );
diff --git a/vendor/guzzlehttp/ringphp/docs/index.rst b/vendor/guzzlehttp/ringphp/docs/index.rst
new file mode 100644
index 0000000..4bbce63
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/index.rst
@@ -0,0 +1,50 @@
+=======
+RingPHP
+=======
+
+Provides a simple API and specification that abstracts away the details of HTTP
+into a single PHP function. RingPHP be used to power HTTP clients and servers
+through a PHP function that accepts a request hash and returns a response hash
+that is fulfilled using a `promise `_,
+allowing RingPHP to support both synchronous and asynchronous workflows.
+
+By abstracting the implementation details of different HTTP clients and
+servers, RingPHP allows you to utilize pluggable HTTP clients and servers
+without tying your application to a specific implementation.
+
+.. toctree::
+ :maxdepth: 2
+
+ spec
+ futures
+ client_middleware
+ client_handlers
+ testing
+
+.. code-block:: php
+
+ 'GET',
+ 'uri' => '/',
+ 'headers' => [
+ 'host' => ['www.google.com'],
+ 'x-foo' => ['baz']
+ ]
+ ]);
+
+ $response->then(function (array $response) {
+ echo $response['status'];
+ });
+
+ $response->wait();
+
+RingPHP is inspired by Clojure's `Ring `_,
+which, in turn, was inspired by Python's WSGI and Ruby's Rack. RingPHP is
+utilized as the handler layer in `Guzzle `_ 5.0+ to send
+HTTP requests.
diff --git a/vendor/guzzlehttp/ringphp/docs/requirements.txt b/vendor/guzzlehttp/ringphp/docs/requirements.txt
new file mode 100644
index 0000000..483a4e9
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/requirements.txt
@@ -0,0 +1 @@
+sphinx_rtd_theme
diff --git a/vendor/guzzlehttp/ringphp/docs/spec.rst b/vendor/guzzlehttp/ringphp/docs/spec.rst
new file mode 100644
index 0000000..bc91078
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/spec.rst
@@ -0,0 +1,311 @@
+=============
+Specification
+=============
+
+RingPHP applications consist of handlers, requests, responses, and
+middleware.
+
+Handlers
+--------
+
+Handlers are implemented as a PHP ``callable`` that accept a request array
+and return a response array (``GuzzleHttp\Ring\Future\FutureArrayInterface``).
+
+For example:
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Future\CompletedFutureArray;
+
+ $mockHandler = function (array $request) {
+ return new CompletedFutureArray([
+ 'status' => 200,
+ 'headers' => ['X-Foo' => ['Bar']],
+ 'body' => 'Hello!'
+ ]);
+ };
+
+This handler returns the same response each time it is invoked. All RingPHP
+handlers must return a ``GuzzleHttp\Ring\Future\FutureArrayInterface``. Use
+``GuzzleHttp\Ring\Future\CompletedFutureArray`` when returning a response that
+has already completed.
+
+Requests
+--------
+
+A request array is a PHP associative array that contains the configuration
+settings need to send a request.
+
+.. code-block:: php
+
+ $request = [
+ 'http_method' => 'GET',
+ 'scheme' => 'http',
+ 'uri' => '/',
+ 'body' => 'hello!',
+ 'client' => ['timeout' => 1.0],
+ 'headers' => [
+ 'host' => ['httpbin.org'],
+ 'X-Foo' => ['baz', 'bar']
+ ]
+ ];
+
+The request array contains the following key value pairs:
+
+request_method
+ (string, required) The HTTP request method, must be all caps corresponding
+ to a HTTP request method, such as ``GET`` or ``POST``.
+
+scheme
+ (string) The transport protocol, must be one of ``http`` or ``https``.
+ Defaults to ``http``.
+
+uri
+ (string, required) The request URI excluding the query string. Must
+ start with "/".
+
+query_string
+ (string) The query string, if present (e.g., ``foo=bar``).
+
+version
+ (string) HTTP protocol version. Defaults to ``1.1``.
+
+headers
+ (required, array) Associative array of headers. Each key represents the
+ header name. Each value contains an array of strings where each entry of
+ the array SHOULD be sent over the wire on a separate header line.
+
+body
+ (string, fopen resource, ``Iterator``, ``GuzzleHttp\Stream\StreamInterface``)
+ The body of the request, if present. Can be a string, resource returned
+ from fopen, an ``Iterator`` that yields chunks of data, an object that
+ implemented ``__toString``, or a ``GuzzleHttp\Stream\StreamInterface``.
+
+future
+ (bool, string) Controls the asynchronous behavior of a response.
+
+ Set to ``true`` or omit the ``future`` option to *request* that a request
+ will be completed asynchronously. Keep in mind that your request might not
+ necessarily be completed asynchronously based on the handler you are using.
+ Set the ``future`` option to ``false`` to request that a synchronous
+ response be provided.
+
+ You can provide a string value to specify fine-tuned future behaviors that
+ may be specific to the underlying handlers you are using. There are,
+ however, some common future options that handlers should implement if
+ possible.
+
+ lazy
+ Requests that the handler does not open and send the request
+ immediately, but rather only opens and sends the request once the
+ future is dereferenced. This option is often useful for sending a large
+ number of requests concurrently to allow handlers to take better
+ advantage of non-blocking transfers by first building up a pool of
+ requests.
+
+ If an handler does not implement or understand a provided string value,
+ then the request MUST be treated as if the user provided ``true`` rather
+ than the string value.
+
+ Future responses created by asynchronous handlers MUST attempt to complete
+ any outstanding future responses when they are destructed. Asynchronous
+ handlers MAY choose to automatically complete responses when the number
+ of outstanding requests reaches an handler-specific threshold.
+
+Client Specific Options
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The following options are only used in ring client handlers.
+
+.. _client-options:
+
+client
+ (array) Associative array of client specific transfer options. The
+ ``client`` request key value pair can contain the following keys:
+
+ cert
+ (string, array) Set to a string to specify the path to a file
+ containing a PEM formatted SSL client side certificate. If a password
+ is required, then set ``cert`` to an array containing the path to the
+ PEM file in the first array element followed by the certificate
+ password in the second array element.
+
+ connect_timeout
+ (float) Float describing the number of seconds to wait while trying to
+ connect to a server. Use ``0`` to wait indefinitely (the default
+ behavior).
+
+ debug
+ (bool, fopen() resource) Set to true or set to a PHP stream returned by
+ fopen() to enable debug output with the handler used to send a request.
+ If set to ``true``, the output is written to PHP's STDOUT. If a PHP
+ ``fopen`` resource handle is provided, the output is written to the
+ stream.
+
+ "Debug output" is handler specific: different handlers will yield
+ different output and various various level of detail. For example, when
+ using cURL to transfer requests, cURL's `CURLOPT_VERBOSE `_
+ will be used. When using the PHP stream wrapper, `stream notifications `_
+ will be emitted.
+
+ decode_content
+ (bool) Specify whether or not ``Content-Encoding`` responses
+ (gzip, deflate, etc.) are automatically decoded. Set to ``true`` to
+ automatically decode encoded responses. Set to ``false`` to not decode
+ responses. By default, content is *not* decoded automatically.
+
+ delay
+ (int) The number of milliseconds to delay before sending the request.
+ This is often used for delaying before retrying a request. Handlers
+ SHOULD implement this if possible, but it is not a strict requirement.
+
+ progress
+ (function) Defines a function to invoke when transfer progress is made.
+ The function accepts the following arguments:
+
+ 1. The total number of bytes expected to be downloaded
+ 2. The number of bytes downloaded so far
+ 3. The number of bytes expected to be uploaded
+ 4. The number of bytes uploaded so far
+
+ proxy
+ (string, array) Pass a string to specify an HTTP proxy, or an
+ associative array to specify different proxies for different protocols
+ where the scheme is the key and the value is the proxy address.
+
+ .. code-block:: php
+
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['httpbin.org']],
+ 'client' => [
+ // Use different proxies for different URI schemes.
+ 'proxy' => [
+ 'http' => 'http://proxy.example.com:5100',
+ 'https' => 'https://proxy.example.com:6100'
+ ]
+ ]
+ ];
+
+ ssl_key
+ (string, array) Specify the path to a file containing a private SSL key
+ in PEM format. If a password is required, then set to an array
+ containing the path to the SSL key in the first array element followed
+ by the password required for the certificate in the second element.
+
+ save_to
+ (string, fopen resource, ``GuzzleHttp\Stream\StreamInterface``)
+ Specifies where the body of the response is downloaded. Pass a string to
+ open a local file on disk and save the output to the file. Pass an fopen
+ resource to save the output to a PHP stream resource. Pass a
+ ``GuzzleHttp\Stream\StreamInterface`` to save the output to a Guzzle
+ StreamInterface. Omitting this option will typically save the body of a
+ response to a PHP temp stream.
+
+ stream
+ (bool) Set to true to stream a response rather than download it all
+ up-front. This option will only be utilized when the corresponding
+ handler supports it.
+
+ timeout
+ (float) Float describing the timeout of the request in seconds. Use 0 to
+ wait indefinitely (the default behavior).
+
+ verify
+ (bool, string) Describes the SSL certificate verification behavior of a
+ request. Set to true to enable SSL certificate verification using the
+ system CA bundle when available (the default). Set to false to disable
+ certificate verification (this is insecure!). Set to a string to provide
+ the path to a CA bundle on disk to enable verification using a custom
+ certificate.
+
+ version
+ (string) HTTP protocol version to use with the request.
+
+Server Specific Options
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The following options are only used in ring server handlers.
+
+server_port
+ (integer) The port on which the request is being handled. This is only
+ used with ring servers, and is required.
+
+server_name
+ (string) The resolved server name, or the server IP address. Required when
+ using a Ring server.
+
+remote_addr
+ (string) The IP address of the client or the last proxy that sent the
+ request. Required when using a Ring server.
+
+Responses
+---------
+
+A response is an array-like object that implements
+``GuzzleHttp\Ring\Future\FutureArrayInterface``. Responses contain the
+following key value pairs:
+
+body
+ (string, fopen resource, ``Iterator``, ``GuzzleHttp\Stream\StreamInterface``)
+ The body of the response, if present. Can be a string, resource returned
+ from fopen, an ``Iterator`` that yields chunks of data, an object that
+ implemented ``__toString``, or a ``GuzzleHttp\Stream\StreamInterface``.
+
+effective_url
+ (string) The URL that returned the resulting response.
+
+error
+ (``\Exception``) Contains an exception describing any errors that were
+ encountered during the transfer.
+
+headers
+ (Required, array) Associative array of headers. Each key represents the
+ header name. Each value contains an array of strings where each entry of
+ the array is a header line. The headers array MAY be an empty array in the
+ event an error occurred before a response was received.
+
+reason
+ (string) Optional reason phrase. This option should be provided when the
+ reason phrase does not match the typical reason phrase associated with the
+ ``status`` code. See `RFC 7231 `_
+ for a list of HTTP reason phrases mapped to status codes.
+
+status
+ (Required, integer) The HTTP status code. The status code MAY be set to
+ ``null`` in the event an error occurred before a response was received
+ (e.g., a networking error).
+
+transfer_stats
+ (array) Provides an associative array of arbitrary transfer statistics if
+ provided by the underlying handler.
+
+version
+ (string) HTTP protocol version. Defaults to ``1.1``.
+
+Middleware
+----------
+
+Ring middleware augments the functionality of handlers by invoking them in the
+process of generating responses. Middleware is typically implemented as a
+higher-order function that takes one or more handlers as arguments followed by
+an optional associative array of options as the last argument, returning a new
+handler with the desired compound behavior.
+
+Here's an example of a middleware that adds a Content-Type header to each
+request.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\CurlHandler;
+ use GuzzleHttp\Ring\Core;
+
+ $contentTypeHandler = function(callable $handler, $contentType) {
+ return function (array $request) use ($handler, $contentType) {
+ return $handler(Core::setHeader('Content-Type', $contentType));
+ };
+ };
+
+ $baseHandler = new CurlHandler();
+ $wrappedHandler = $contentTypeHandler($baseHandler, 'text/html');
+ $response = $wrappedHandler([/** request hash **/]);
diff --git a/vendor/guzzlehttp/ringphp/docs/testing.rst b/vendor/guzzlehttp/ringphp/docs/testing.rst
new file mode 100644
index 0000000..9df2562
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/docs/testing.rst
@@ -0,0 +1,74 @@
+=======
+Testing
+=======
+
+RingPHP tests client handlers using `PHPUnit `_ and a
+built-in node.js web server.
+
+Running Tests
+-------------
+
+First, install the dependencies using `Composer `_.
+
+ composer.phar install
+
+Next, run the unit tests using ``Make``.
+
+ make test
+
+The tests are also run on Travis-CI on each commit: https://travis-ci.org/guzzle/guzzle-ring
+
+Test Server
+-----------
+
+Testing client handlers usually involves actually sending HTTP requests.
+RingPHP provides a node.js web server that returns canned responses and
+keep a list of the requests that have been received. The server can then
+be queried to get a list of the requests that were sent by the client so that
+you can ensure that the client serialized and transferred requests as intended.
+
+The server keeps a list of queued responses and returns responses that are
+popped off of the queue as HTTP requests are received. When there are not
+more responses to serve, the server returns a 500 error response.
+
+The test server uses the ``GuzzleHttp\Tests\Ring\Client\Server`` class to
+control the server.
+
+.. code-block:: php
+
+ use GuzzleHttp\Ring\Client\StreamHandler;
+ use GuzzleHttp\Tests\Ring\Client\Server;
+
+ // First return a 200 followed by a 404 response.
+ Server::enqueue([
+ ['status' => 200],
+ ['status' => 404]
+ ]);
+
+ $handler = new StreamHandler();
+
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'uri' => '/'
+ ]);
+
+ assert(200 == $response['status']);
+
+ $response = $handler([
+ 'http_method' => 'HEAD',
+ 'headers' => ['host' => [Server::$host]],
+ 'uri' => '/'
+ ]);
+
+ assert(404 == $response['status']);
+
+After requests have been sent, you can get a list of the requests as they
+were sent over the wire to ensure they were sent correctly.
+
+.. code-block:: php
+
+ $received = Server::received();
+
+ assert('GET' == $received[0]['http_method']);
+ assert('HEAD' == $received[1]['http_method']);
diff --git a/vendor/guzzlehttp/ringphp/phpunit.xml.dist b/vendor/guzzlehttp/ringphp/phpunit.xml.dist
new file mode 100644
index 0000000..1d19290
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+
+ tests
+
+
+
+
+ src
+
+
+
diff --git a/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php b/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php
new file mode 100644
index 0000000..27d5fe7
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php
@@ -0,0 +1,74 @@
+getDefaultOptions($request, $headers);
+ $this->applyMethod($request, $options);
+
+ if (isset($request['client'])) {
+ $this->applyHandlerOptions($request, $options);
+ }
+
+ $this->applyHeaders($request, $options);
+ unset($options['_headers']);
+
+ // Add handler options from the request's configuration options
+ if (isset($request['client']['curl'])) {
+ $options = $this->applyCustomCurlOptions(
+ $request['client']['curl'],
+ $options
+ );
+ }
+
+ if (!$handle) {
+ $handle = curl_init();
+ }
+
+ $body = $this->getOutputBody($request, $options);
+ curl_setopt_array($handle, $options);
+
+ return [$handle, &$headers, $body];
+ }
+
+ /**
+ * Creates a response hash from a cURL result.
+ *
+ * @param callable $handler Handler that was used.
+ * @param array $request Request that sent.
+ * @param array $response Response hash to update.
+ * @param array $headers Headers received during transfer.
+ * @param resource $body Body fopen response.
+ *
+ * @return array
+ */
+ public static function createResponse(
+ callable $handler,
+ array $request,
+ array $response,
+ array $headers,
+ $body
+ ) {
+ if (isset($response['transfer_stats']['url'])) {
+ $response['effective_url'] = $response['transfer_stats']['url'];
+ }
+
+ if (!empty($headers)) {
+ $startLine = explode(' ', array_shift($headers), 3);
+ $headerList = Core::headersFromLines($headers);
+ $response['headers'] = $headerList;
+ $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null;
+ $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null;
+ $response['reason'] = isset($startLine[2]) ? $startLine[2] : null;
+ $response['body'] = $body;
+ Core::rewindBody($response);
+ }
+
+ return !empty($response['curl']['errno']) || !isset($response['status'])
+ ? self::createErrorResponse($handler, $request, $response)
+ : $response;
+ }
+
+ private static function createErrorResponse(
+ callable $handler,
+ array $request,
+ array $response
+ ) {
+ static $connectionErrors = [
+ CURLE_OPERATION_TIMEOUTED => true,
+ CURLE_COULDNT_RESOLVE_HOST => true,
+ CURLE_COULDNT_CONNECT => true,
+ CURLE_SSL_CONNECT_ERROR => true,
+ CURLE_GOT_NOTHING => true,
+ ];
+
+ // Retry when nothing is present or when curl failed to rewind.
+ if (!isset($response['err_message'])
+ && (empty($response['curl']['errno'])
+ || $response['curl']['errno'] == 65)
+ ) {
+ return self::retryFailedRewind($handler, $request, $response);
+ }
+
+ $message = isset($response['err_message'])
+ ? $response['err_message']
+ : sprintf('cURL error %s: %s',
+ $response['curl']['errno'],
+ isset($response['curl']['error'])
+ ? $response['curl']['error']
+ : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html');
+
+ $error = isset($response['curl']['errno'])
+ && isset($connectionErrors[$response['curl']['errno']])
+ ? new ConnectException($message)
+ : new RingException($message);
+
+ return $response + [
+ 'status' => null,
+ 'reason' => null,
+ 'body' => null,
+ 'headers' => [],
+ 'error' => $error,
+ ];
+ }
+
+ private function getOutputBody(array $request, array &$options)
+ {
+ // Determine where the body of the response (if any) will be streamed.
+ if (isset($options[CURLOPT_WRITEFUNCTION])) {
+ return $request['client']['save_to'];
+ }
+
+ if (isset($options[CURLOPT_FILE])) {
+ return $options[CURLOPT_FILE];
+ }
+
+ if ($request['http_method'] != 'HEAD') {
+ // Create a default body if one was not provided
+ return $options[CURLOPT_FILE] = fopen('php://temp', 'w+');
+ }
+
+ return null;
+ }
+
+ private function getDefaultOptions(array $request, array &$headers)
+ {
+ $url = Core::url($request);
+ $startingResponse = false;
+
+ $options = [
+ '_headers' => $request['headers'],
+ CURLOPT_CUSTOMREQUEST => $request['http_method'],
+ CURLOPT_URL => $url,
+ CURLOPT_RETURNTRANSFER => false,
+ CURLOPT_HEADER => false,
+ CURLOPT_CONNECTTIMEOUT => 150,
+ CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) {
+ $value = trim($h);
+ if ($value === '') {
+ $startingResponse = true;
+ } elseif ($startingResponse) {
+ $startingResponse = false;
+ $headers = [$value];
+ } else {
+ $headers[] = $value;
+ }
+ return strlen($h);
+ },
+ ];
+
+ if (isset($request['version'])) {
+ if ($request['version'] == 2.0) {
+ $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0;
+ } else if ($request['version'] == 1.1) {
+ $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1;
+ } else {
+ $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
+ }
+ }
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
+ }
+
+ return $options;
+ }
+
+ private function applyMethod(array $request, array &$options)
+ {
+ if (isset($request['body'])) {
+ $this->applyBody($request, $options);
+ return;
+ }
+
+ switch ($request['http_method']) {
+ case 'PUT':
+ case 'POST':
+ // See http://tools.ietf.org/html/rfc7230#section-3.3.2
+ if (!Core::hasHeader($request, 'Content-Length')) {
+ $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0';
+ }
+ break;
+ case 'HEAD':
+ $options[CURLOPT_NOBODY] = true;
+ unset(
+ $options[CURLOPT_WRITEFUNCTION],
+ $options[CURLOPT_READFUNCTION],
+ $options[CURLOPT_FILE],
+ $options[CURLOPT_INFILE]
+ );
+ }
+ }
+
+ private function applyBody(array $request, array &$options)
+ {
+ $contentLength = Core::firstHeader($request, 'Content-Length');
+ $size = $contentLength !== null ? (int) $contentLength : null;
+
+ // Send the body as a string if the size is less than 1MB OR if the
+ // [client][curl][body_as_string] request value is set.
+ if (($size !== null && $size < 1000000) ||
+ isset($request['client']['curl']['body_as_string']) ||
+ is_string($request['body'])
+ ) {
+ $options[CURLOPT_POSTFIELDS] = Core::body($request);
+ // Don't duplicate the Content-Length header
+ $this->removeHeader('Content-Length', $options);
+ $this->removeHeader('Transfer-Encoding', $options);
+ } else {
+ $options[CURLOPT_UPLOAD] = true;
+ if ($size !== null) {
+ // Let cURL handle setting the Content-Length header
+ $options[CURLOPT_INFILESIZE] = $size;
+ $this->removeHeader('Content-Length', $options);
+ }
+ $this->addStreamingBody($request, $options);
+ }
+
+ // If the Expect header is not present, prevent curl from adding it
+ if (!Core::hasHeader($request, 'Expect')) {
+ $options[CURLOPT_HTTPHEADER][] = 'Expect:';
+ }
+
+ // cURL sometimes adds a content-type by default. Prevent this.
+ if (!Core::hasHeader($request, 'Content-Type')) {
+ $options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
+ }
+ }
+
+ private function addStreamingBody(array $request, array &$options)
+ {
+ $body = $request['body'];
+
+ if ($body instanceof StreamInterface) {
+ $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) {
+ return (string) $body->read($length);
+ };
+ if (!isset($options[CURLOPT_INFILESIZE])) {
+ if ($size = $body->getSize()) {
+ $options[CURLOPT_INFILESIZE] = $size;
+ }
+ }
+ } elseif (is_resource($body)) {
+ $options[CURLOPT_INFILE] = $body;
+ } elseif ($body instanceof \Iterator) {
+ $buf = '';
+ $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) {
+ if ($body->valid()) {
+ $buf .= $body->current();
+ $body->next();
+ }
+ $result = (string) substr($buf, 0, $length);
+ $buf = substr($buf, $length);
+ return $result;
+ };
+ } else {
+ throw new \InvalidArgumentException('Invalid request body provided');
+ }
+ }
+
+ private function applyHeaders(array $request, array &$options)
+ {
+ foreach ($options['_headers'] as $name => $values) {
+ foreach ($values as $value) {
+ $options[CURLOPT_HTTPHEADER][] = "$name: $value";
+ }
+ }
+
+ // Remove the Accept header if one was not set
+ if (!Core::hasHeader($request, 'Accept')) {
+ $options[CURLOPT_HTTPHEADER][] = 'Accept:';
+ }
+ }
+
+ /**
+ * Takes an array of curl options specified in the 'curl' option of a
+ * request's configuration array and maps them to CURLOPT_* options.
+ *
+ * This method is only called when a request has a 'curl' config setting.
+ *
+ * @param array $config Configuration array of custom curl option
+ * @param array $options Array of existing curl options
+ *
+ * @return array Returns a new array of curl options
+ */
+ private function applyCustomCurlOptions(array $config, array $options)
+ {
+ $curlOptions = [];
+ foreach ($config as $key => $value) {
+ if (is_int($key)) {
+ $curlOptions[$key] = $value;
+ }
+ }
+
+ return $curlOptions + $options;
+ }
+
+ /**
+ * Remove a header from the options array.
+ *
+ * @param string $name Case-insensitive header to remove
+ * @param array $options Array of options to modify
+ */
+ private function removeHeader($name, array &$options)
+ {
+ foreach (array_keys($options['_headers']) as $key) {
+ if (!strcasecmp($key, $name)) {
+ unset($options['_headers'][$key]);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Applies an array of request client options to a the options array.
+ *
+ * This method uses a large switch rather than double-dispatch to save on
+ * high overhead of calling functions in PHP.
+ */
+ private function applyHandlerOptions(array $request, array &$options)
+ {
+ foreach ($request['client'] as $key => $value) {
+ switch ($key) {
+ // Violating PSR-4 to provide more room.
+ case 'verify':
+
+ if ($value === false) {
+ unset($options[CURLOPT_CAINFO]);
+ $options[CURLOPT_SSL_VERIFYHOST] = 0;
+ $options[CURLOPT_SSL_VERIFYPEER] = false;
+ continue 2;
+ }
+
+ $options[CURLOPT_SSL_VERIFYHOST] = 2;
+ $options[CURLOPT_SSL_VERIFYPEER] = true;
+
+ if (is_string($value)) {
+ $options[CURLOPT_CAINFO] = $value;
+ if (!file_exists($value)) {
+ throw new \InvalidArgumentException(
+ "SSL CA bundle not found: $value"
+ );
+ }
+ }
+ break;
+
+ case 'decode_content':
+
+ if ($value === false) {
+ continue 2;
+ }
+
+ $accept = Core::firstHeader($request, 'Accept-Encoding');
+ if ($accept) {
+ $options[CURLOPT_ENCODING] = $accept;
+ } else {
+ $options[CURLOPT_ENCODING] = '';
+ // Don't let curl send the header over the wire
+ $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:';
+ }
+ break;
+
+ case 'save_to':
+
+ if (is_string($value)) {
+ if (!is_dir(dirname($value))) {
+ throw new \RuntimeException(sprintf(
+ 'Directory %s does not exist for save_to value of %s',
+ dirname($value),
+ $value
+ ));
+ }
+ $value = new LazyOpenStream($value, 'w+');
+ }
+
+ if ($value instanceof StreamInterface) {
+ $options[CURLOPT_WRITEFUNCTION] =
+ function ($ch, $write) use ($value) {
+ return $value->write($write);
+ };
+ } elseif (is_resource($value)) {
+ $options[CURLOPT_FILE] = $value;
+ } else {
+ throw new \InvalidArgumentException('save_to must be a '
+ . 'GuzzleHttp\Stream\StreamInterface or resource');
+ }
+ break;
+
+ case 'timeout':
+
+ if (defined('CURLOPT_TIMEOUT_MS')) {
+ $options[CURLOPT_TIMEOUT_MS] = $value * 1000;
+ } else {
+ $options[CURLOPT_TIMEOUT] = $value;
+ }
+ break;
+
+ case 'connect_timeout':
+
+ if (defined('CURLOPT_CONNECTTIMEOUT_MS')) {
+ $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000;
+ } else {
+ $options[CURLOPT_CONNECTTIMEOUT] = $value;
+ }
+ break;
+
+ case 'proxy':
+
+ if (!is_array($value)) {
+ $options[CURLOPT_PROXY] = $value;
+ } elseif (isset($request['scheme'])) {
+ $scheme = $request['scheme'];
+ if (isset($value[$scheme])) {
+ $options[CURLOPT_PROXY] = $value[$scheme];
+ }
+ }
+ break;
+
+ case 'cert':
+
+ if (is_array($value)) {
+ $options[CURLOPT_SSLCERTPASSWD] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \InvalidArgumentException(
+ "SSL certificate not found: {$value}"
+ );
+ }
+
+ $options[CURLOPT_SSLCERT] = $value;
+ break;
+
+ case 'ssl_key':
+
+ if (is_array($value)) {
+ $options[CURLOPT_SSLKEYPASSWD] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new \InvalidArgumentException(
+ "SSL private key not found: {$value}"
+ );
+ }
+
+ $options[CURLOPT_SSLKEY] = $value;
+ break;
+
+ case 'progress':
+
+ if (!is_callable($value)) {
+ throw new \InvalidArgumentException(
+ 'progress client option must be callable'
+ );
+ }
+
+ $options[CURLOPT_NOPROGRESS] = false;
+ $options[CURLOPT_PROGRESSFUNCTION] =
+ function () use ($value) {
+ $args = func_get_args();
+ // PHP 5.5 pushed the handle onto the start of the args
+ if (is_resource($args[0])) {
+ array_shift($args);
+ }
+ call_user_func_array($value, $args);
+ };
+ break;
+
+ case 'debug':
+
+ if ($value) {
+ $options[CURLOPT_STDERR] = Core::getDebugResource($value);
+ $options[CURLOPT_VERBOSE] = true;
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * This function ensures that a response was set on a transaction. If one
+ * was not set, then the request is retried if possible. This error
+ * typically means you are sending a payload, curl encountered a
+ * "Connection died, retrying a fresh connect" error, tried to rewind the
+ * stream, and then encountered a "necessary data rewind wasn't possible"
+ * error, causing the request to be sent through curl_multi_info_read()
+ * without an error status.
+ */
+ private static function retryFailedRewind(
+ callable $handler,
+ array $request,
+ array $response
+ ) {
+ // If there is no body, then there is some other kind of issue. This
+ // is weird and should probably never happen.
+ if (!isset($request['body'])) {
+ $response['err_message'] = 'No response was received for a request '
+ . 'with no body. This could mean that you are saturating your '
+ . 'network.';
+ return self::createErrorResponse($handler, $request, $response);
+ }
+
+ if (!Core::rewindBody($request)) {
+ $response['err_message'] = 'The connection unexpectedly failed '
+ . 'without providing an error. The request would have been '
+ . 'retried, but attempting to rewind the request body failed.';
+ return self::createErrorResponse($handler, $request, $response);
+ }
+
+ // Retry no more than 3 times before giving up.
+ if (!isset($request['curl']['retries'])) {
+ $request['curl']['retries'] = 1;
+ } elseif ($request['curl']['retries'] == 2) {
+ $response['err_message'] = 'The cURL request was retried 3 times '
+ . 'and did no succeed. cURL was unable to rewind the body of '
+ . 'the request and subsequent retries resulted in the same '
+ . 'error. Turn on the debug option to see what went wrong. '
+ . 'See https://bugs.php.net/bug.php?id=47204 for more information.';
+ return self::createErrorResponse($handler, $request, $response);
+ } else {
+ $request['curl']['retries']++;
+ }
+
+ return $handler($request);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php b/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php
new file mode 100644
index 0000000..e00aa4e
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php
@@ -0,0 +1,135 @@
+handles = $this->ownedHandles = [];
+ $this->factory = isset($options['handle_factory'])
+ ? $options['handle_factory']
+ : new CurlFactory();
+ $this->maxHandles = isset($options['max_handles'])
+ ? $options['max_handles']
+ : 5;
+ }
+
+ public function __destruct()
+ {
+ foreach ($this->handles as $handle) {
+ if (is_resource($handle)) {
+ curl_close($handle);
+ }
+ }
+ }
+
+ /**
+ * @param array $request
+ *
+ * @return CompletedFutureArray
+ */
+ public function __invoke(array $request)
+ {
+ return new CompletedFutureArray(
+ $this->_invokeAsArray($request)
+ );
+ }
+
+ /**
+ * @internal
+ *
+ * @param array $request
+ *
+ * @return array
+ */
+ public function _invokeAsArray(array $request)
+ {
+ $factory = $this->factory;
+
+ // Ensure headers are by reference. They're updated elsewhere.
+ $result = $factory($request, $this->checkoutEasyHandle());
+ $h = $result[0];
+ $hd =& $result[1];
+ $bd = $result[2];
+ Core::doSleep($request);
+ curl_exec($h);
+ $response = ['transfer_stats' => curl_getinfo($h)];
+ $response['curl']['error'] = curl_error($h);
+ $response['curl']['errno'] = curl_errno($h);
+ $response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']);
+ $this->releaseEasyHandle($h);
+
+ return CurlFactory::createResponse([$this, '_invokeAsArray'], $request, $response, $hd, $bd);
+ }
+
+ private function checkoutEasyHandle()
+ {
+ // Find an unused handle in the cache
+ if (false !== ($key = array_search(false, $this->ownedHandles, true))) {
+ $this->ownedHandles[$key] = true;
+ return $this->handles[$key];
+ }
+
+ // Add a new handle
+ $handle = curl_init();
+ $id = (int) $handle;
+ $this->handles[$id] = $handle;
+ $this->ownedHandles[$id] = true;
+
+ return $handle;
+ }
+
+ private function releaseEasyHandle($handle)
+ {
+ $id = (int) $handle;
+ if (count($this->ownedHandles) > $this->maxHandles) {
+ curl_close($this->handles[$id]);
+ unset($this->handles[$id], $this->ownedHandles[$id]);
+ } else {
+ // curl_reset doesn't clear these out for some reason
+ static $unsetValues = [
+ CURLOPT_HEADERFUNCTION => null,
+ CURLOPT_WRITEFUNCTION => null,
+ CURLOPT_READFUNCTION => null,
+ CURLOPT_PROGRESSFUNCTION => null,
+ ];
+ curl_setopt_array($handle, $unsetValues);
+ curl_reset($handle);
+ $this->ownedHandles[$id] = false;
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php b/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php
new file mode 100644
index 0000000..f84cf19
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php
@@ -0,0 +1,248 @@
+_mh = $options['mh'];
+ }
+ $this->factory = isset($options['handle_factory'])
+ ? $options['handle_factory'] : new CurlFactory();
+ $this->selectTimeout = isset($options['select_timeout'])
+ ? $options['select_timeout'] : 1;
+ $this->maxHandles = isset($options['max_handles'])
+ ? $options['max_handles'] : 100;
+ }
+
+ public function __get($name)
+ {
+ if ($name === '_mh') {
+ return $this->_mh = curl_multi_init();
+ }
+
+ throw new \BadMethodCallException();
+ }
+
+ public function __destruct()
+ {
+ // Finish any open connections before terminating the script.
+ if ($this->handles) {
+ $this->execute();
+ }
+
+ if (isset($this->_mh)) {
+ curl_multi_close($this->_mh);
+ unset($this->_mh);
+ }
+ }
+
+ public function __invoke(array $request)
+ {
+ $factory = $this->factory;
+ $result = $factory($request);
+ $entry = [
+ 'request' => $request,
+ 'response' => [],
+ 'handle' => $result[0],
+ 'headers' => &$result[1],
+ 'body' => $result[2],
+ 'deferred' => new Deferred(),
+ ];
+
+ $id = (int) $result[0];
+
+ $future = new FutureArray(
+ $entry['deferred']->promise(),
+ [$this, 'execute'],
+ function () use ($id) {
+ return $this->cancel($id);
+ }
+ );
+
+ $this->addRequest($entry);
+
+ // Transfer outstanding requests if there are too many open handles.
+ if (count($this->handles) >= $this->maxHandles) {
+ $this->execute();
+ }
+
+ return $future;
+ }
+
+ /**
+ * Runs until all outstanding connections have completed.
+ */
+ public function execute()
+ {
+ do {
+
+ if ($this->active &&
+ curl_multi_select($this->_mh, $this->selectTimeout) === -1
+ ) {
+ // Perform a usleep if a select returns -1.
+ // See: https://bugs.php.net/bug.php?id=61141
+ usleep(250);
+ }
+
+ // Add any delayed futures if needed.
+ if ($this->delays) {
+ $this->addDelays();
+ }
+
+ do {
+ $mrc = curl_multi_exec($this->_mh, $this->active);
+ } while ($mrc === CURLM_CALL_MULTI_PERFORM);
+
+ $this->processMessages();
+
+ // If there are delays but no transfers, then sleep for a bit.
+ if (!$this->active && $this->delays) {
+ usleep(500);
+ }
+
+ } while ($this->active || $this->handles);
+ }
+
+ private function addRequest(array &$entry)
+ {
+ $id = (int) $entry['handle'];
+ $this->handles[$id] = $entry;
+
+ // If the request is a delay, then add the reques to the curl multi
+ // pool only after the specified delay.
+ if (isset($entry['request']['client']['delay'])) {
+ $this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000);
+ } elseif (empty($entry['request']['future'])) {
+ curl_multi_add_handle($this->_mh, $entry['handle']);
+ } else {
+ curl_multi_add_handle($this->_mh, $entry['handle']);
+ // "lazy" futures are only sent once the pool has many requests.
+ if ($entry['request']['future'] !== 'lazy') {
+ do {
+ $mrc = curl_multi_exec($this->_mh, $this->active);
+ } while ($mrc === CURLM_CALL_MULTI_PERFORM);
+ $this->processMessages();
+ }
+ }
+ }
+
+ private function removeProcessed($id)
+ {
+ if (isset($this->handles[$id])) {
+ curl_multi_remove_handle(
+ $this->_mh,
+ $this->handles[$id]['handle']
+ );
+ curl_close($this->handles[$id]['handle']);
+ unset($this->handles[$id], $this->delays[$id]);
+ }
+ }
+
+ /**
+ * Cancels a handle from sending and removes references to it.
+ *
+ * @param int $id Handle ID to cancel and remove.
+ *
+ * @return bool True on success, false on failure.
+ */
+ private function cancel($id)
+ {
+ // Cannot cancel if it has been processed.
+ if (!isset($this->handles[$id])) {
+ return false;
+ }
+
+ $handle = $this->handles[$id]['handle'];
+ unset($this->delays[$id], $this->handles[$id]);
+ curl_multi_remove_handle($this->_mh, $handle);
+ curl_close($handle);
+
+ return true;
+ }
+
+ private function addDelays()
+ {
+ $currentTime = microtime(true);
+
+ foreach ($this->delays as $id => $delay) {
+ if ($currentTime >= $delay) {
+ unset($this->delays[$id]);
+ curl_multi_add_handle(
+ $this->_mh,
+ $this->handles[$id]['handle']
+ );
+ }
+ }
+ }
+
+ private function processMessages()
+ {
+ while ($done = curl_multi_info_read($this->_mh)) {
+ $id = (int) $done['handle'];
+
+ if (!isset($this->handles[$id])) {
+ // Probably was cancelled.
+ continue;
+ }
+
+ $entry = $this->handles[$id];
+ $entry['response']['transfer_stats'] = curl_getinfo($done['handle']);
+
+ if ($done['result'] !== CURLM_OK) {
+ $entry['response']['curl']['errno'] = $done['result'];
+ $entry['response']['curl']['error'] = curl_error($done['handle']);
+ }
+
+ $result = CurlFactory::createResponse(
+ $this,
+ $entry['request'],
+ $entry['response'],
+ $entry['headers'],
+ $entry['body']
+ );
+
+ $this->removeProcessed($id);
+ $entry['deferred']->resolve($result);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Client/Middleware.php b/vendor/guzzlehttp/ringphp/src/Client/Middleware.php
new file mode 100644
index 0000000..6fa7318
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Client/Middleware.php
@@ -0,0 +1,58 @@
+result = $result;
+ }
+
+ public function __invoke(array $request)
+ {
+ Core::doSleep($request);
+ $response = is_callable($this->result)
+ ? call_user_func($this->result, $request)
+ : $this->result;
+
+ if (is_array($response)) {
+ $response = new CompletedFutureArray($response + [
+ 'status' => null,
+ 'body' => null,
+ 'headers' => [],
+ 'reason' => null,
+ 'effective_url' => null,
+ ]);
+ } elseif (!$response instanceof FutureArrayInterface) {
+ throw new \InvalidArgumentException(
+ 'Response must be an array or FutureArrayInterface. Found '
+ . Core::describeType($request)
+ );
+ }
+
+ return $response;
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php b/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php
new file mode 100644
index 0000000..4bacec1
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php
@@ -0,0 +1,414 @@
+options = $options;
+ }
+
+ public function __invoke(array $request)
+ {
+ $url = Core::url($request);
+ Core::doSleep($request);
+
+ try {
+ // Does not support the expect header.
+ $request = Core::removeHeader($request, 'Expect');
+ $stream = $this->createStream($url, $request);
+ return $this->createResponse($request, $url, $stream);
+ } catch (RingException $e) {
+ return $this->createErrorResponse($url, $e);
+ }
+ }
+
+ private function createResponse(array $request, $url, $stream)
+ {
+ $hdrs = $this->lastHeaders;
+ $this->lastHeaders = null;
+ $parts = explode(' ', array_shift($hdrs), 3);
+ $response = [
+ 'version' => substr($parts[0], 5),
+ 'status' => $parts[1],
+ 'reason' => isset($parts[2]) ? $parts[2] : null,
+ 'headers' => Core::headersFromLines($hdrs),
+ 'effective_url' => $url,
+ ];
+
+ $stream = $this->checkDecode($request, $response, $stream);
+
+ // If not streaming, then drain the response into a stream.
+ if (empty($request['client']['stream'])) {
+ $dest = isset($request['client']['save_to'])
+ ? $request['client']['save_to']
+ : fopen('php://temp', 'r+');
+ $stream = $this->drain($stream, $dest);
+ }
+
+ $response['body'] = $stream;
+
+ return new CompletedFutureArray($response);
+ }
+
+ private function checkDecode(array $request, array $response, $stream)
+ {
+ // Automatically decode responses when instructed.
+ if (!empty($request['client']['decode_content'])) {
+ switch (Core::firstHeader($response, 'Content-Encoding', true)) {
+ case 'gzip':
+ case 'deflate':
+ $stream = new InflateStream(Stream::factory($stream));
+ break;
+ }
+ }
+
+ return $stream;
+ }
+
+ /**
+ * Drains the stream into the "save_to" client option.
+ *
+ * @param resource $stream
+ * @param string|resource|StreamInterface $dest
+ *
+ * @return Stream
+ * @throws \RuntimeException when the save_to option is invalid.
+ */
+ private function drain($stream, $dest)
+ {
+ if (is_resource($stream)) {
+ if (!is_resource($dest)) {
+ $stream = Stream::factory($stream);
+ } else {
+ stream_copy_to_stream($stream, $dest);
+ fclose($stream);
+ rewind($dest);
+ return $dest;
+ }
+ }
+
+ // Stream the response into the destination stream
+ $dest = is_string($dest)
+ ? new Stream(Utils::open($dest, 'r+'))
+ : Stream::factory($dest);
+
+ Utils::copyToStream($stream, $dest);
+ $dest->seek(0);
+ $stream->close();
+
+ return $dest;
+ }
+
+ /**
+ * Creates an error response for the given stream.
+ *
+ * @param string $url
+ * @param RingException $e
+ *
+ * @return array
+ */
+ private function createErrorResponse($url, RingException $e)
+ {
+ // Determine if the error was a networking error.
+ $message = $e->getMessage();
+
+ // This list can probably get more comprehensive.
+ if (strpos($message, 'getaddrinfo') // DNS lookup failed
+ || strpos($message, 'Connection refused')
+ ) {
+ $e = new ConnectException($e->getMessage(), 0, $e);
+ }
+
+ return new CompletedFutureArray([
+ 'status' => null,
+ 'body' => null,
+ 'headers' => [],
+ 'effective_url' => $url,
+ 'error' => $e
+ ]);
+ }
+
+ /**
+ * Create a resource and check to ensure it was created successfully
+ *
+ * @param callable $callback Callable that returns stream resource
+ *
+ * @return resource
+ * @throws \RuntimeException on error
+ */
+ private function createResource(callable $callback)
+ {
+ $errors = null;
+ set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
+ $errors[] = [
+ 'message' => $msg,
+ 'file' => $file,
+ 'line' => $line
+ ];
+ return true;
+ });
+
+ $resource = $callback();
+ restore_error_handler();
+
+ if (!$resource) {
+ $message = 'Error creating resource: ';
+ foreach ($errors as $err) {
+ foreach ($err as $key => $value) {
+ $message .= "[$key] $value" . PHP_EOL;
+ }
+ }
+ throw new RingException(trim($message));
+ }
+
+ return $resource;
+ }
+
+ private function createStream($url, array $request)
+ {
+ static $methods;
+ if (!$methods) {
+ $methods = array_flip(get_class_methods(__CLASS__));
+ }
+
+ // HTTP/1.1 streams using the PHP stream wrapper require a
+ // Connection: close header
+ if ((!isset($request['version']) || $request['version'] == '1.1')
+ && !Core::hasHeader($request, 'Connection')
+ ) {
+ $request['headers']['Connection'] = ['close'];
+ }
+
+ // Ensure SSL is verified by default
+ if (!isset($request['client']['verify'])) {
+ $request['client']['verify'] = true;
+ }
+
+ $params = [];
+ $options = $this->getDefaultOptions($request);
+
+ if (isset($request['client'])) {
+ foreach ($request['client'] as $key => $value) {
+ $method = "add_{$key}";
+ if (isset($methods[$method])) {
+ $this->{$method}($request, $options, $value, $params);
+ }
+ }
+ }
+
+ return $this->createStreamResource(
+ $url,
+ $request,
+ $options,
+ $this->createContext($request, $options, $params)
+ );
+ }
+
+ private function getDefaultOptions(array $request)
+ {
+ $headers = "";
+ foreach ($request['headers'] as $name => $value) {
+ foreach ((array) $value as $val) {
+ $headers .= "$name: $val\r\n";
+ }
+ }
+
+ $context = [
+ 'http' => [
+ 'method' => $request['http_method'],
+ 'header' => $headers,
+ 'protocol_version' => isset($request['version']) ? $request['version'] : 1.1,
+ 'ignore_errors' => true,
+ 'follow_location' => 0,
+ ],
+ ];
+
+ $body = Core::body($request);
+ if (isset($body)) {
+ $context['http']['content'] = $body;
+ // Prevent the HTTP handler from adding a Content-Type header.
+ if (!Core::hasHeader($request, 'Content-Type')) {
+ $context['http']['header'] .= "Content-Type:\r\n";
+ }
+ }
+
+ $context['http']['header'] = rtrim($context['http']['header']);
+
+ return $context;
+ }
+
+ private function add_proxy(array $request, &$options, $value, &$params)
+ {
+ if (!is_array($value)) {
+ $options['http']['proxy'] = $value;
+ } else {
+ $scheme = isset($request['scheme']) ? $request['scheme'] : 'http';
+ if (isset($value[$scheme])) {
+ $options['http']['proxy'] = $value[$scheme];
+ }
+ }
+ }
+
+ private function add_timeout(array $request, &$options, $value, &$params)
+ {
+ $options['http']['timeout'] = $value;
+ }
+
+ private function add_verify(array $request, &$options, $value, &$params)
+ {
+ if ($value === true) {
+ // PHP 5.6 or greater will find the system cert by default. When
+ // < 5.6, use the Guzzle bundled cacert.
+ if (PHP_VERSION_ID < 50600) {
+ $options['ssl']['cafile'] = ClientUtils::getDefaultCaBundle();
+ }
+ } elseif (is_string($value)) {
+ $options['ssl']['cafile'] = $value;
+ if (!file_exists($value)) {
+ throw new RingException("SSL CA bundle not found: $value");
+ }
+ } elseif ($value === false) {
+ $options['ssl']['verify_peer'] = false;
+ $options['ssl']['allow_self_signed'] = true;
+ return;
+ } else {
+ throw new RingException('Invalid verify request option');
+ }
+
+ $options['ssl']['verify_peer'] = true;
+ $options['ssl']['allow_self_signed'] = false;
+ }
+
+ private function add_cert(array $request, &$options, $value, &$params)
+ {
+ if (is_array($value)) {
+ $options['ssl']['passphrase'] = $value[1];
+ $value = $value[0];
+ }
+
+ if (!file_exists($value)) {
+ throw new RingException("SSL certificate not found: {$value}");
+ }
+
+ $options['ssl']['local_cert'] = $value;
+ }
+
+ private function add_progress(array $request, &$options, $value, &$params)
+ {
+ $fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) {
+ if ($code == STREAM_NOTIFY_PROGRESS) {
+ $value($total, $transferred, null, null);
+ }
+ };
+
+ // Wrap the existing function if needed.
+ $params['notification'] = isset($params['notification'])
+ ? Core::callArray([$params['notification'], $fn])
+ : $fn;
+ }
+
+ private function add_debug(array $request, &$options, $value, &$params)
+ {
+ if ($value === false) {
+ return;
+ }
+
+ static $map = [
+ STREAM_NOTIFY_CONNECT => 'CONNECT',
+ STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
+ STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
+ STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
+ STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
+ STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
+ STREAM_NOTIFY_PROGRESS => 'PROGRESS',
+ STREAM_NOTIFY_FAILURE => 'FAILURE',
+ STREAM_NOTIFY_COMPLETED => 'COMPLETED',
+ STREAM_NOTIFY_RESOLVE => 'RESOLVE',
+ ];
+
+ static $args = ['severity', 'message', 'message_code',
+ 'bytes_transferred', 'bytes_max'];
+
+ $value = Core::getDebugResource($value);
+ $ident = $request['http_method'] . ' ' . Core::url($request);
+ $fn = function () use ($ident, $value, $map, $args) {
+ $passed = func_get_args();
+ $code = array_shift($passed);
+ fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
+ foreach (array_filter($passed) as $i => $v) {
+ fwrite($value, $args[$i] . ': "' . $v . '" ');
+ }
+ fwrite($value, "\n");
+ };
+
+ // Wrap the existing function if needed.
+ $params['notification'] = isset($params['notification'])
+ ? Core::callArray([$params['notification'], $fn])
+ : $fn;
+ }
+
+ private function applyCustomOptions(array $request, array &$options)
+ {
+ if (!isset($request['client']['stream_context'])) {
+ return;
+ }
+
+ if (!is_array($request['client']['stream_context'])) {
+ throw new RingException('stream_context must be an array');
+ }
+
+ $options = array_replace_recursive(
+ $options,
+ $request['client']['stream_context']
+ );
+ }
+
+ private function createContext(array $request, array $options, array $params)
+ {
+ $this->applyCustomOptions($request, $options);
+ return $this->createResource(
+ function () use ($request, $options, $params) {
+ return stream_context_create($options, $params);
+ },
+ $request,
+ $options
+ );
+ }
+
+ private function createStreamResource(
+ $url,
+ array $request,
+ array $options,
+ $context
+ ) {
+ return $this->createResource(
+ function () use ($url, $context) {
+ if (false === strpos($url, 'http')) {
+ trigger_error("URL is invalid: {$url}", E_USER_WARNING);
+ return null;
+ }
+ $resource = fopen($url, 'r', null, $context);
+ $this->lastHeaders = $http_response_header;
+ return $resource;
+ },
+ $request,
+ $options
+ );
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Core.php b/vendor/guzzlehttp/ringphp/src/Core.php
new file mode 100644
index 0000000..dd7d1a0
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Core.php
@@ -0,0 +1,364 @@
+ $value) {
+ if (!strcasecmp($name, $header)) {
+ $result = array_merge($result, $value);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Gets a header value from a message as a string or null
+ *
+ * This method searches through the "headers" key of a message for a header
+ * using a case-insensitive search. The lines of the header are imploded
+ * using commas into a single string return value.
+ *
+ * @param array $message Request or response hash.
+ * @param string $header Header to retrieve
+ *
+ * @return string|null Returns the header string if found, or null if not.
+ */
+ public static function header($message, $header)
+ {
+ $match = self::headerLines($message, $header);
+ return $match ? implode(', ', $match) : null;
+ }
+
+ /**
+ * Returns the first header value from a message as a string or null. If
+ * a header line contains multiple values separated by a comma, then this
+ * function will return the first value in the list.
+ *
+ * @param array $message Request or response hash.
+ * @param string $header Header to retrieve
+ *
+ * @return string|null Returns the value as a string if found.
+ */
+ public static function firstHeader($message, $header)
+ {
+ if (!empty($message['headers'])) {
+ foreach ($message['headers'] as $name => $value) {
+ if (!strcasecmp($name, $header)) {
+ // Return the match itself if it is a single value.
+ $pos = strpos($value[0], ',');
+ return $pos ? substr($value[0], 0, $pos) : $value[0];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if a message has the provided case-insensitive header.
+ *
+ * @param array $message Request or response hash.
+ * @param string $header Header to check
+ *
+ * @return bool
+ */
+ public static function hasHeader($message, $header)
+ {
+ if (!empty($message['headers'])) {
+ foreach ($message['headers'] as $name => $value) {
+ if (!strcasecmp($name, $header)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses an array of header lines into an associative array of headers.
+ *
+ * @param array $lines Header lines array of strings in the following
+ * format: "Name: Value"
+ * @return array
+ */
+ public static function headersFromLines($lines)
+ {
+ $headers = [];
+
+ foreach ($lines as $line) {
+ $parts = explode(':', $line, 2);
+ $headers[trim($parts[0])][] = isset($parts[1])
+ ? trim($parts[1])
+ : null;
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Removes a header from a message using a case-insensitive comparison.
+ *
+ * @param array $message Message that contains 'headers'
+ * @param string $header Header to remove
+ *
+ * @return array
+ */
+ public static function removeHeader(array $message, $header)
+ {
+ if (isset($message['headers'])) {
+ foreach (array_keys($message['headers']) as $key) {
+ if (!strcasecmp($header, $key)) {
+ unset($message['headers'][$key]);
+ }
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * Replaces any existing case insensitive headers with the given value.
+ *
+ * @param array $message Message that contains 'headers'
+ * @param string $header Header to set.
+ * @param array $value Value to set.
+ *
+ * @return array
+ */
+ public static function setHeader(array $message, $header, array $value)
+ {
+ $message = self::removeHeader($message, $header);
+ $message['headers'][$header] = $value;
+
+ return $message;
+ }
+
+ /**
+ * Creates a URL string from a request.
+ *
+ * If the "url" key is present on the request, it is returned, otherwise
+ * the url is built up based on the scheme, host, uri, and query_string
+ * request values.
+ *
+ * @param array $request Request to get the URL from
+ *
+ * @return string Returns the request URL as a string.
+ * @throws \InvalidArgumentException if no Host header is present.
+ */
+ public static function url(array $request)
+ {
+ if (isset($request['url'])) {
+ return $request['url'];
+ }
+
+ $uri = (isset($request['scheme'])
+ ? $request['scheme'] : 'http') . '://';
+
+ if ($host = self::header($request, 'host')) {
+ $uri .= $host;
+ } else {
+ throw new \InvalidArgumentException('No Host header was provided');
+ }
+
+ if (isset($request['uri'])) {
+ $uri .= $request['uri'];
+ }
+
+ if (isset($request['query_string'])) {
+ $uri .= '?' . $request['query_string'];
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Reads the body of a message into a string.
+ *
+ * @param array|FutureArrayInterface $message Array containing a "body" key
+ *
+ * @return null|string Returns the body as a string or null if not set.
+ * @throws \InvalidArgumentException if a request body is invalid.
+ */
+ public static function body($message)
+ {
+ if (!isset($message['body'])) {
+ return null;
+ }
+
+ if ($message['body'] instanceof StreamInterface) {
+ return (string) $message['body'];
+ }
+
+ switch (gettype($message['body'])) {
+ case 'string':
+ return $message['body'];
+ case 'resource':
+ return stream_get_contents($message['body']);
+ case 'object':
+ if ($message['body'] instanceof \Iterator) {
+ return implode('', iterator_to_array($message['body']));
+ } elseif (method_exists($message['body'], '__toString')) {
+ return (string) $message['body'];
+ }
+ default:
+ throw new \InvalidArgumentException('Invalid request body: '
+ . self::describeType($message['body']));
+ }
+ }
+
+ /**
+ * Rewind the body of the provided message if possible.
+ *
+ * @param array $message Message that contains a 'body' field.
+ *
+ * @return bool Returns true on success, false on failure
+ */
+ public static function rewindBody($message)
+ {
+ if ($message['body'] instanceof StreamInterface) {
+ return $message['body']->seek(0);
+ }
+
+ if ($message['body'] instanceof \Generator) {
+ return false;
+ }
+
+ if ($message['body'] instanceof \Iterator) {
+ $message['body']->rewind();
+ return true;
+ }
+
+ if (is_resource($message['body'])) {
+ return rewind($message['body']);
+ }
+
+ return is_string($message['body'])
+ || (is_object($message['body'])
+ && method_exists($message['body'], '__toString'));
+ }
+
+ /**
+ * Debug function used to describe the provided value type and class.
+ *
+ * @param mixed $input
+ *
+ * @return string Returns a string containing the type of the variable and
+ * if a class is provided, the class name.
+ */
+ public static function describeType($input)
+ {
+ switch (gettype($input)) {
+ case 'object':
+ return 'object(' . get_class($input) . ')';
+ case 'array':
+ return 'array(' . count($input) . ')';
+ default:
+ ob_start();
+ var_dump($input);
+ // normalize float vs double
+ return str_replace('double(', 'float(', rtrim(ob_get_clean()));
+ }
+ }
+
+ /**
+ * Sleep for the specified amount of time specified in the request's
+ * ['client']['delay'] option if present.
+ *
+ * This function should only be used when a non-blocking sleep is not
+ * possible.
+ *
+ * @param array $request Request to sleep
+ */
+ public static function doSleep(array $request)
+ {
+ if (isset($request['client']['delay'])) {
+ usleep($request['client']['delay'] * 1000);
+ }
+ }
+
+ /**
+ * Returns a proxied future that modifies the dereferenced value of another
+ * future using a promise.
+ *
+ * @param FutureArrayInterface $future Future to wrap with a new future
+ * @param callable $onFulfilled Invoked when the future fulfilled
+ * @param callable $onRejected Invoked when the future rejected
+ * @param callable $onProgress Invoked when the future progresses
+ *
+ * @return FutureArray
+ */
+ public static function proxy(
+ FutureArrayInterface $future,
+ callable $onFulfilled = null,
+ callable $onRejected = null,
+ callable $onProgress = null
+ ) {
+ return new FutureArray(
+ $future->then($onFulfilled, $onRejected, $onProgress),
+ [$future, 'wait'],
+ [$future, 'cancel']
+ );
+ }
+
+ /**
+ * Returns a debug stream based on the provided variable.
+ *
+ * @param mixed $value Optional value
+ *
+ * @return resource
+ */
+ public static function getDebugResource($value = null)
+ {
+ if (is_resource($value)) {
+ return $value;
+ } elseif (defined('STDOUT')) {
+ return STDOUT;
+ } else {
+ return fopen('php://output', 'w');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php b/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php
new file mode 100644
index 0000000..95b353a
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php
@@ -0,0 +1,7 @@
+wrappedPromise = $promise;
+ $this->waitfn = $wait;
+ $this->cancelfn = $cancel;
+ }
+
+ public function wait()
+ {
+ if (!$this->isRealized) {
+ $this->addShadow();
+ if (!$this->isRealized && $this->waitfn) {
+ $this->invokeWait();
+ }
+ if (!$this->isRealized) {
+ $this->error = new RingException('Waiting did not resolve future');
+ }
+ }
+
+ if ($this->error) {
+ throw $this->error;
+ }
+
+ return $this->result;
+ }
+
+ public function promise()
+ {
+ return $this->wrappedPromise;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null,
+ callable $onProgress = null
+ ) {
+ return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress);
+ }
+
+ public function cancel()
+ {
+ if (!$this->isRealized) {
+ $cancelfn = $this->cancelfn;
+ $this->waitfn = $this->cancelfn = null;
+ $this->isRealized = true;
+ $this->error = new CancelledFutureAccessException();
+ if ($cancelfn) {
+ $cancelfn($this);
+ }
+ }
+ }
+
+ private function addShadow()
+ {
+ // Get the result and error when the promise is resolved. Note that
+ // calling this function might trigger the resolution immediately.
+ $this->wrappedPromise->then(
+ function ($value) {
+ $this->isRealized = true;
+ $this->result = $value;
+ $this->waitfn = $this->cancelfn = null;
+ },
+ function ($error) {
+ $this->isRealized = true;
+ $this->error = $error;
+ $this->waitfn = $this->cancelfn = null;
+ }
+ );
+ }
+
+ private function invokeWait()
+ {
+ try {
+ $wait = $this->waitfn;
+ $this->waitfn = null;
+ $wait();
+ } catch (\Exception $e) {
+ // Defer can throw to reject.
+ $this->error = $e;
+ $this->isRealized = true;
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php
new file mode 100644
index 0000000..0a90c93
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php
@@ -0,0 +1,43 @@
+result[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->result[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->result[$offset] = $value;
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->result[$offset]);
+ }
+
+ public function count()
+ {
+ return count($this->result);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->result);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php
new file mode 100644
index 0000000..0d25af7
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php
@@ -0,0 +1,57 @@
+result = $result;
+ $this->error = $e;
+ }
+
+ public function wait()
+ {
+ if ($this->error) {
+ throw $this->error;
+ }
+
+ return $this->result;
+ }
+
+ public function cancel() {}
+
+ public function promise()
+ {
+ if (!$this->cachedPromise) {
+ $this->cachedPromise = $this->error
+ ? new RejectedPromise($this->error)
+ : new FulfilledPromise($this->result);
+ }
+
+ return $this->cachedPromise;
+ }
+
+ public function then(
+ callable $onFulfilled = null,
+ callable $onRejected = null,
+ callable $onProgress = null
+ ) {
+ return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php b/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php
new file mode 100644
index 0000000..3d64c96
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php
@@ -0,0 +1,40 @@
+_value[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->_value[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ $this->_value[$offset] = $value;
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->_value[$offset]);
+ }
+
+ public function count()
+ {
+ return count($this->_value);
+ }
+
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->_value);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php b/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php
new file mode 100644
index 0000000..58f5f73
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php
@@ -0,0 +1,11 @@
+_value = $this->wait();
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php b/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php
new file mode 100644
index 0000000..ebde187
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php
@@ -0,0 +1,821 @@
+ 200,
+ 'headers' => [
+ 'Foo' => ['Bar'],
+ 'Baz' => ['bam'],
+ 'Content-Length' => [2],
+ ],
+ 'body' => 'hi',
+ ]]);
+
+ $stream = Stream::factory();
+
+ $request = [
+ 'http_method' => 'PUT',
+ 'headers' => [
+ 'host' => [Server::$url],
+ 'Hi' => [' 123'],
+ ],
+ 'body' => 'testing',
+ 'client' => ['save_to' => $stream],
+ ];
+
+ $f = new CurlFactory();
+ $result = $f($request);
+ $this->assertInternalType('array', $result);
+ $this->assertCount(3, $result);
+ $this->assertInternalType('resource', $result[0]);
+ $this->assertInternalType('array', $result[1]);
+ $this->assertSame($stream, $result[2]);
+ curl_close($result[0]);
+
+ $this->assertEquals('PUT', $_SERVER['_curl'][CURLOPT_CUSTOMREQUEST]);
+ $this->assertEquals(
+ 'http://http://127.0.0.1:8125/',
+ $_SERVER['_curl'][CURLOPT_URL]
+ );
+ // Sends via post fields when the request is small enough
+ $this->assertEquals('testing', $_SERVER['_curl'][CURLOPT_POSTFIELDS]);
+ $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_RETURNTRANSFER]);
+ $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_HEADER]);
+ $this->assertEquals(150, $_SERVER['_curl'][CURLOPT_CONNECTTIMEOUT]);
+ $this->assertInstanceOf('Closure', $_SERVER['_curl'][CURLOPT_HEADERFUNCTION]);
+
+ if (defined('CURLOPT_PROTOCOLS')) {
+ $this->assertEquals(
+ CURLPROTO_HTTP | CURLPROTO_HTTPS,
+ $_SERVER['_curl'][CURLOPT_PROTOCOLS]
+ );
+ }
+
+ $this->assertContains('Expect:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
+ $this->assertContains('Accept:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
+ $this->assertContains('Content-Type:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
+ $this->assertContains('Hi: 123', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
+ $this->assertContains('host: http://127.0.0.1:8125/', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
+ }
+
+ public function testSendsHeadRequests()
+ {
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'HEAD',
+ 'headers' => ['host' => [Server::$host]],
+ ]);
+ $response->wait();
+ $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_NOBODY]);
+ $checks = [CURLOPT_WRITEFUNCTION, CURLOPT_READFUNCTION, CURLOPT_FILE, CURLOPT_INFILE];
+ foreach ($checks as $check) {
+ $this->assertArrayNotHasKey($check, $_SERVER['_curl']);
+ }
+ $this->assertEquals('HEAD', Server::received()[0]['http_method']);
+ }
+
+ public function testCanAddCustomCurlOptions()
+ {
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['curl' => [CURLOPT_LOW_SPEED_LIMIT => 10]],
+ ]);
+ $this->assertEquals(10, $_SERVER['_curl'][CURLOPT_LOW_SPEED_LIMIT]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage SSL CA bundle not found: /does/not/exist
+ */
+ public function testValidatesVerify()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['verify' => '/does/not/exist'],
+ ]);
+ }
+
+ public function testCanSetVerifyToFile()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['verify' => __FILE__],
+ ]);
+ $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_CAINFO]);
+ $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]);
+ $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]);
+ }
+
+ public function testAddsVerifyAsTrue()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['verify' => true],
+ ]);
+ $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]);
+ $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]);
+ $this->assertArrayNotHasKey(CURLOPT_CAINFO, $_SERVER['_curl']);
+ }
+
+ public function testCanDisableVerify()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['verify' => false],
+ ]);
+ $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]);
+ $this->assertEquals(false, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]);
+ }
+
+ public function testAddsProxy()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['proxy' => 'http://bar.com'],
+ ]);
+ $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]);
+ }
+
+ public function testAddsViaScheme()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'scheme' => 'http',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => [
+ 'proxy' => ['http' => 'http://bar.com', 'https' => 'https://t'],
+ ],
+ ]);
+ $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage SSL private key not found: /does/not/exist
+ */
+ public function testValidatesSslKey()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['ssl_key' => '/does/not/exist'],
+ ]);
+ }
+
+ public function testAddsSslKey()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['ssl_key' => __FILE__],
+ ]);
+ $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]);
+ }
+
+ public function testAddsSslKeyWithPassword()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['ssl_key' => [__FILE__, 'test']],
+ ]);
+ $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]);
+ $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLKEYPASSWD]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage SSL certificate not found: /does/not/exist
+ */
+ public function testValidatesCert()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['cert' => '/does/not/exist'],
+ ]);
+ }
+
+ public function testAddsCert()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['cert' => __FILE__],
+ ]);
+ $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]);
+ }
+
+ public function testAddsCertWithPassword()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['cert' => [__FILE__, 'test']],
+ ]);
+ $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]);
+ $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLCERTPASSWD]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage progress client option must be callable
+ */
+ public function testValidatesProgress()
+ {
+ $f = new CurlFactory();
+ $f([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['foo.com']],
+ 'client' => ['progress' => 'foo'],
+ ]);
+ }
+
+ public function testEmitsDebugInfoToStream()
+ {
+ $res = fopen('php://memory', 'r+');
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'HEAD',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['debug' => $res],
+ ]);
+ $response->wait();
+ rewind($res);
+ $output = str_replace("\r", '', stream_get_contents($res));
+ $this->assertContains(
+ "> HEAD / HTTP/1.1\nhost: 127.0.0.1:8125\n\n",
+ $output
+ );
+ $this->assertContains("< HTTP/1.1 200", $output);
+ fclose($res);
+ }
+
+ public function testEmitsProgressToFunction()
+ {
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $called = [];
+ $response = $a([
+ 'http_method' => 'HEAD',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => [
+ 'progress' => function () use (&$called) {
+ $called[] = func_get_args();
+ },
+ ],
+ ]);
+ $response->wait();
+ $this->assertNotEmpty($called);
+ foreach ($called as $call) {
+ $this->assertCount(4, $call);
+ }
+ }
+
+ private function addDecodeResponse($withEncoding = true)
+ {
+ $content = gzencode('test');
+ $response = [
+ 'status' => 200,
+ 'reason' => 'OK',
+ 'headers' => ['Content-Length' => [strlen($content)]],
+ 'body' => $content,
+ ];
+
+ if ($withEncoding) {
+ $response['headers']['Content-Encoding'] = ['gzip'];
+ }
+
+ Server::flush();
+ Server::enqueue([$response]);
+
+ return $content;
+ }
+
+ public function testDecodesGzippedResponses()
+ {
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['decode_content' => true],
+ ]);
+ $response->wait();
+ $this->assertEquals('test', Core::body($response));
+ $this->assertEquals('', $_SERVER['_curl'][CURLOPT_ENCODING]);
+ $sent = Server::received()[0];
+ $this->assertNull(Core::header($sent, 'Accept-Encoding'));
+ }
+
+ public function testDecodesGzippedResponsesWithHeader()
+ {
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => [
+ 'host' => [Server::$host],
+ 'Accept-Encoding' => ['gzip'],
+ ],
+ 'client' => ['decode_content' => true],
+ ]);
+ $response->wait();
+ $this->assertEquals('gzip', $_SERVER['_curl'][CURLOPT_ENCODING]);
+ $sent = Server::received()[0];
+ $this->assertEquals('gzip', Core::header($sent, 'Accept-Encoding'));
+ $this->assertEquals('test', Core::body($response));
+ }
+
+ public function testDoesNotForceDecode()
+ {
+ $content = $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['decode_content' => false],
+ ]);
+ $response->wait();
+ $sent = Server::received()[0];
+ $this->assertNull(Core::header($sent, 'Accept-Encoding'));
+ $this->assertEquals($content, Core::body($response));
+ }
+
+ public function testProtocolVersion()
+ {
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'version' => 1.0,
+ ]);
+ $this->assertEquals(CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][CURLOPT_HTTP_VERSION]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesSaveTo()
+ {
+ $handler = new CurlMultiHandler();
+ $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['save_to' => true],
+ ]);
+ }
+
+ public function testSavesToStream()
+ {
+ $stream = fopen('php://memory', 'r+');
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => [
+ 'decode_content' => true,
+ 'save_to' => $stream,
+ ],
+ ]);
+ $response->wait();
+ rewind($stream);
+ $this->assertEquals('test', stream_get_contents($stream));
+ }
+
+ public function testSavesToGuzzleStream()
+ {
+ $stream = Stream::factory();
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => [
+ 'decode_content' => true,
+ 'save_to' => $stream,
+ ],
+ ]);
+ $response->wait();
+ $this->assertEquals('test', (string) $stream);
+ }
+
+ public function testSavesToFileOnDisk()
+ {
+ $tmpfile = tempnam(sys_get_temp_dir(), 'testfile');
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => [
+ 'decode_content' => true,
+ 'save_to' => $tmpfile,
+ ],
+ ]);
+ $response->wait();
+ $this->assertEquals('test', file_get_contents($tmpfile));
+ unlink($tmpfile);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesBody()
+ {
+ $handler = new CurlMultiHandler();
+ $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'body' => false,
+ ]);
+ }
+
+ public function testAddsLargePayloadFromStreamWithNoSizeUsingChunked()
+ {
+ $stream = Stream::factory('foo');
+ $stream = FnStream::decorate($stream, [
+ 'getSize' => function () {
+ return null;
+ }
+ ]);
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'body' => $stream,
+ ]);
+ $response->wait();
+ $sent = Server::received()[0];
+ $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding'));
+ $this->assertNull(Core::header($sent, 'Content-Length'));
+ $this->assertEquals('foo', $sent['body']);
+ }
+
+ public function testAddsPayloadFromIterator()
+ {
+ $iter = new \ArrayIterator(['f', 'o', 'o']);
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'body' => $iter,
+ ]);
+ $response->wait();
+ $sent = Server::received()[0];
+ $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding'));
+ $this->assertNull(Core::header($sent, 'Content-Length'));
+ $this->assertEquals('foo', $sent['body']);
+ }
+
+ public function testAddsPayloadFromResource()
+ {
+ $res = fopen('php://memory', 'r+');
+ $data = str_repeat('.', 1000000);
+ fwrite($res, $data);
+ rewind($res);
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => [
+ 'host' => [Server::$host],
+ 'content-length' => [1000000],
+ ],
+ 'body' => $res,
+ ]);
+ $response->wait();
+ $sent = Server::received()[0];
+ $this->assertNull(Core::header($sent, 'Transfer-Encoding'));
+ $this->assertEquals(1000000, Core::header($sent, 'Content-Length'));
+ $this->assertEquals($data, $sent['body']);
+ }
+
+ public function testAddsContentLengthFromStream()
+ {
+ $stream = Stream::factory('foo');
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'body' => $stream,
+ ]);
+ $response->wait();
+ $sent = Server::received()[0];
+ $this->assertEquals(3, Core::header($sent, 'Content-Length'));
+ $this->assertNull(Core::header($sent, 'Transfer-Encoding'));
+ $this->assertEquals('foo', $sent['body']);
+ }
+
+ public function testDoesNotAddMultipleContentLengthHeaders()
+ {
+ $this->addDecodeResponse();
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => [
+ 'host' => [Server::$host],
+ 'content-length' => [3],
+ ],
+ 'body' => 'foo',
+ ]);
+ $response->wait();
+ $sent = Server::received()[0];
+ $this->assertEquals(3, Core::header($sent, 'Content-Length'));
+ $this->assertNull(Core::header($sent, 'Transfer-Encoding'));
+ $this->assertEquals('foo', $sent['body']);
+ }
+
+ public function testSendsPostWithNoBodyOrDefaultContentType()
+ {
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $handler = new CurlMultiHandler();
+ $response = $handler([
+ 'http_method' => 'POST',
+ 'uri' => '/',
+ 'headers' => ['host' => [Server::$host]],
+ ]);
+ $response->wait();
+ $received = Server::received()[0];
+ $this->assertEquals('POST', $received['http_method']);
+ $this->assertNull(Core::header($received, 'content-type'));
+ $this->assertSame('0', Core::firstHeader($received, 'content-length'));
+ }
+
+ public function testParseProtocolVersion()
+ {
+ $res = CurlFactory::createResponse(
+ function () {},
+ [],
+ ['curl' => ['errno' => null]],
+ ['HTTP/1.1 200 Ok'],
+ null
+ );
+
+ $this->assertSame('1.1', $res['version']);
+ }
+
+ public function testFailsWhenNoResponseAndNoBody()
+ {
+ $res = CurlFactory::createResponse(function () {}, [], [], [], null);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']);
+ $this->assertContains(
+ 'No response was received for a request with no body',
+ $res['error']->getMessage()
+ );
+ }
+
+ public function testFailsWhenCannotRewindRetry()
+ {
+ $res = CurlFactory::createResponse(function () {}, [
+ 'body' => new NoSeekStream(Stream::factory('foo'))
+ ], [], [], null);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']);
+ $this->assertContains(
+ 'rewind the request body failed',
+ $res['error']->getMessage()
+ );
+ }
+
+ public function testRetriesWhenBodyCanBeRewound()
+ {
+ $callHandler = $called = false;
+ $res = CurlFactory::createResponse(function () use (&$callHandler) {
+ $callHandler = true;
+ return ['status' => 200];
+ }, [
+ 'body' => FnStream::decorate(Stream::factory('test'), [
+ 'seek' => function () use (&$called) {
+ $called = true;
+ return true;
+ }
+ ])
+ ], [], [], null);
+
+ $this->assertTrue($callHandler);
+ $this->assertTrue($called);
+ $this->assertEquals('200', $res['status']);
+ }
+
+ public function testFailsWhenRetryMoreThanThreeTimes()
+ {
+ $call = 0;
+ $mock = new MockHandler(function (array $request) use (&$mock, &$call) {
+ $call++;
+ return CurlFactory::createResponse($mock, $request, [], [], null);
+ });
+ $response = $mock([
+ 'http_method' => 'GET',
+ 'body' => 'test',
+ ]);
+ $this->assertEquals(3, $call);
+ $this->assertArrayHasKey('error', $response);
+ $this->assertContains(
+ 'The cURL request was retried 3 times',
+ $response['error']->getMessage()
+ );
+ }
+
+ public function testHandles100Continue()
+ {
+ Server::flush();
+ Server::enqueue([
+ [
+ 'status' => '200',
+ 'reason' => 'OK',
+ 'headers' => [
+ 'Test' => ['Hello'],
+ 'Content-Length' => ['4'],
+ ],
+ 'body' => 'test',
+ ],
+ ]);
+
+ $request = [
+ 'http_method' => 'PUT',
+ 'headers' => [
+ 'Host' => [Server::$host],
+ 'Expect' => ['100-Continue'],
+ ],
+ 'body' => 'test',
+ ];
+
+ $handler = new CurlMultiHandler();
+ $response = $handler($request)->wait();
+ $this->assertEquals(200, $response['status']);
+ $this->assertEquals('OK', $response['reason']);
+ $this->assertEquals(['Hello'], $response['headers']['Test']);
+ $this->assertEquals(['4'], $response['headers']['Content-Length']);
+ $this->assertEquals('test', Core::body($response));
+ }
+
+ public function testCreatesConnectException()
+ {
+ $m = new \ReflectionMethod('GuzzleHttp\Ring\Client\CurlFactory', 'createErrorResponse');
+ $m->setAccessible(true);
+ $response = $m->invoke(
+ null,
+ function () {},
+ [],
+ [
+ 'err_message' => 'foo',
+ 'curl' => [
+ 'errno' => CURLE_COULDNT_CONNECT,
+ ]
+ ]
+ );
+ $this->assertInstanceOf('GuzzleHttp\Ring\Exception\ConnectException', $response['error']);
+ }
+
+ public function testParsesLastResponseOnly()
+ {
+ $response1 = [
+ 'status' => 301,
+ 'headers' => [
+ 'Content-Length' => ['0'],
+ 'Location' => ['/foo']
+ ]
+ ];
+
+ $response2 = [
+ 'status' => 200,
+ 'headers' => [
+ 'Content-Length' => ['0'],
+ 'Foo' => ['bar']
+ ]
+ ];
+
+ Server::flush();
+ Server::enqueue([$response1, $response2]);
+
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => [Server::$host]],
+ 'client' => [
+ 'curl' => [
+ CURLOPT_FOLLOWLOCATION => true
+ ]
+ ]
+ ])->wait();
+
+ $this->assertEquals(1, $response['transfer_stats']['redirect_count']);
+ $this->assertEquals('http://127.0.0.1:8125/foo', $response['effective_url']);
+ $this->assertEquals(['bar'], $response['headers']['Foo']);
+ $this->assertEquals(200, $response['status']);
+ $this->assertFalse(Core::hasHeader($response, 'Location'));
+ }
+
+ public function testMaintainsMultiHeaderOrder()
+ {
+ Server::flush();
+ Server::enqueue([
+ [
+ 'status' => 200,
+ 'headers' => [
+ 'Content-Length' => ['0'],
+ 'Foo' => ['a', 'b'],
+ 'foo' => ['c', 'd'],
+ ]
+ ]
+ ]);
+
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['Host' => [Server::$host]]
+ ])->wait();
+
+ $this->assertEquals(
+ ['a', 'b', 'c', 'd'],
+ Core::headerLines($response, 'Foo')
+ );
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Directory /path/to/does/not does not exist for save_to value of /path/to/does/not/exist.txt
+ */
+ public function testThrowsWhenDirNotFound()
+ {
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$url]],
+ 'client' => ['save_to' => '/path/to/does/not/exist.txt'],
+ ];
+
+ $f = new CurlFactory();
+ $f($request);
+ }
+}
+
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php
new file mode 100644
index 0000000..ba03b8c
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php
@@ -0,0 +1,96 @@
+markTestSkipped('curl_reset() is not available');
+ }
+ }
+
+ protected function getHandler($factory = null, $options = [])
+ {
+ return new CurlHandler($options);
+ }
+
+ public function testCanSetMaxHandles()
+ {
+ $a = new CurlHandler(['max_handles' => 10]);
+ $this->assertEquals(10, $this->readAttribute($a, 'maxHandles'));
+ }
+
+ public function testCreatesCurlErrors()
+ {
+ $handler = new CurlHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => ['localhost:123']],
+ 'client' => ['timeout' => 0.001, 'connect_timeout' => 0.001],
+ ]);
+ $this->assertNull($response['status']);
+ $this->assertNull($response['reason']);
+ $this->assertEquals([], $response['headers']);
+ $this->assertInstanceOf(
+ 'GuzzleHttp\Ring\Exception\RingException',
+ $response['error']
+ );
+
+ $this->assertEquals(
+ 1,
+ preg_match('/^cURL error \d+: .*$/', $response['error']->getMessage())
+ );
+ }
+
+ public function testReleasesAdditionalEasyHandles()
+ {
+ Server::flush();
+ $response = [
+ 'status' => 200,
+ 'headers' => ['Content-Length' => [4]],
+ 'body' => 'test',
+ ];
+
+ Server::enqueue([$response, $response, $response, $response]);
+ $a = new CurlHandler(['max_handles' => 2]);
+
+ $fn = function () use (&$calls, $a, &$fn) {
+ if (++$calls < 4) {
+ $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['progress' => $fn],
+ ]);
+ }
+ };
+
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => [
+ 'progress' => $fn,
+ ],
+ ];
+
+ $a($request);
+ $this->assertCount(2, $this->readAttribute($a, 'handles'));
+ }
+
+ public function testReusesHandles()
+ {
+ Server::flush();
+ $response = ['status' => 200];
+ Server::enqueue([$response, $response]);
+ $a = new CurlHandler();
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ ];
+ $a($request);
+ $a($request);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php
new file mode 100644
index 0000000..530b239
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php
@@ -0,0 +1,181 @@
+ 200]]);
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ ]);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $this->assertEquals(200, $response['status']);
+ $this->assertArrayHasKey('transfer_stats', $response);
+ $realUrl = trim($response['transfer_stats']['url'], '/');
+ $this->assertEquals(trim(Server::$url, '/'), $realUrl);
+ $this->assertArrayHasKey('effective_url', $response);
+ $this->assertEquals(
+ trim(Server::$url, '/'),
+ trim($response['effective_url'], '/')
+ );
+ }
+
+ public function testCreatesErrorResponses()
+ {
+ $url = 'http://localhost:123/';
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['localhost:123']],
+ ]);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $this->assertNull($response['status']);
+ $this->assertNull($response['reason']);
+ $this->assertEquals([], $response['headers']);
+ $this->assertArrayHasKey('error', $response);
+ $this->assertContains('cURL error ', $response['error']->getMessage());
+ $this->assertArrayHasKey('transfer_stats', $response);
+ $this->assertEquals(
+ trim($url, '/'),
+ trim($response['transfer_stats']['url'], '/')
+ );
+ $this->assertArrayHasKey('effective_url', $response);
+ $this->assertEquals(
+ trim($url, '/'),
+ trim($response['effective_url'], '/')
+ );
+ }
+
+ public function testSendsFuturesWhenDestructed()
+ {
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ ]);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $a->__destruct();
+ $this->assertEquals(200, $response['status']);
+ }
+
+ public function testCanSetMaxHandles()
+ {
+ $a = new CurlMultiHandler(['max_handles' => 2]);
+ $this->assertEquals(2, $this->readAttribute($a, 'maxHandles'));
+ }
+
+ public function testCanSetSelectTimeout()
+ {
+ $a = new CurlMultiHandler(['select_timeout' => 2]);
+ $this->assertEquals(2, $this->readAttribute($a, 'selectTimeout'));
+ }
+
+ public function testSendsFuturesWhenMaxHandlesIsReached()
+ {
+ $request = [
+ 'http_method' => 'PUT',
+ 'headers' => ['host' => [Server::$host]],
+ 'future' => 'lazy', // passing this to control the test
+ ];
+ $response = ['status' => 200];
+ Server::flush();
+ Server::enqueue([$response, $response, $response]);
+ $a = new CurlMultiHandler(['max_handles' => 3]);
+ for ($i = 0; $i < 5; $i++) {
+ $responses[] = $a($request);
+ }
+ $this->assertCount(3, Server::received());
+ $responses[3]->cancel();
+ $responses[4]->cancel();
+ }
+
+ public function testCanCancel()
+ {
+ Server::flush();
+ $response = ['status' => 200];
+ Server::enqueue(array_fill_keys(range(0, 10), $response));
+ $a = new CurlMultiHandler();
+ $responses = [];
+
+ for ($i = 0; $i < 10; $i++) {
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'future' => 'lazy',
+ ]);
+ $response->cancel();
+ $responses[] = $response;
+ }
+
+ $this->assertCount(0, Server::received());
+
+ foreach ($responses as $response) {
+ $this->assertTrue($this->readAttribute($response, 'isRealized'));
+ }
+ }
+
+ public function testCannotCancelFinished()
+ {
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ ]);
+ $response->wait();
+ $response->cancel();
+ }
+
+ public function testDelaysInParallel()
+ {
+ Server::flush();
+ Server::enqueue([['status' => 200]]);
+ $a = new CurlMultiHandler();
+ $expected = microtime(true) + (100 / 1000);
+ $response = $a([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['delay' => 100],
+ ]);
+ $response->wait();
+ $this->assertGreaterThanOrEqual($expected, microtime(true));
+ }
+
+ public function testSendsNonLazyFutures()
+ {
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'future' => true,
+ ];
+ Server::flush();
+ Server::enqueue([['status' => 202]]);
+ $a = new CurlMultiHandler();
+ $response = $a($request);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $this->assertEquals(202, $response['status']);
+ }
+
+ public function testExtractsErrors()
+ {
+ $request = [
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['127.0.0.1:123']],
+ 'future' => true,
+ ];
+ Server::flush();
+ Server::enqueue([['status' => 202]]);
+ $a = new CurlMultiHandler();
+ $response = $a($request);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $this->assertEquals(CURLE_COULDNT_CONNECT, $response['curl']['errno']);
+ $this->assertNotEmpty($response['curl']['error']);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php b/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php
new file mode 100644
index 0000000..a47bb30
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php
@@ -0,0 +1,65 @@
+ 200]);
+ $calledA = false;
+ $a = function (array $req) use (&$calledA, $future) {
+ $calledA = true;
+ return $future;
+ };
+ $calledB = false;
+ $b = function (array $req) use (&$calledB) { $calledB = true; };
+ $s = Middleware::wrapFuture($a, $b);
+ $s([]);
+ $this->assertTrue($calledA);
+ $this->assertFalse($calledB);
+ }
+
+ public function testFutureCallsStreamingHandler()
+ {
+ $future = new CompletedFutureArray(['status' => 200]);
+ $calledA = false;
+ $a = function (array $req) use (&$calledA) { $calledA = true; };
+ $calledB = false;
+ $b = function (array $req) use (&$calledB, $future) {
+ $calledB = true;
+ return $future;
+ };
+ $s = Middleware::wrapFuture($a, $b);
+ $result = $s(['client' => ['future' => true]]);
+ $this->assertFalse($calledA);
+ $this->assertTrue($calledB);
+ $this->assertSame($future, $result);
+ }
+
+ public function testStreamingCallsDefaultHandler()
+ {
+ $calledA = false;
+ $a = function (array $req) use (&$calledA) { $calledA = true; };
+ $calledB = false;
+ $b = function (array $req) use (&$calledB) { $calledB = true; };
+ $s = Middleware::wrapStreaming($a, $b);
+ $s([]);
+ $this->assertTrue($calledA);
+ $this->assertFalse($calledB);
+ }
+
+ public function testStreamingCallsStreamingHandler()
+ {
+ $calledA = false;
+ $a = function (array $req) use (&$calledA) { $calledA = true; };
+ $calledB = false;
+ $b = function (array $req) use (&$calledB) { $calledB = true; };
+ $s = Middleware::wrapStreaming($a, $b);
+ $s(['client' => ['stream' => true]]);
+ $this->assertFalse($calledA);
+ $this->assertTrue($calledB);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php
new file mode 100644
index 0000000..26bcd6c
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php
@@ -0,0 +1,86 @@
+ 200]);
+ $response = $mock([]);
+ $this->assertEquals(200, $response['status']);
+ $this->assertEquals([], $response['headers']);
+ $this->assertNull($response['body']);
+ $this->assertNull($response['reason']);
+ $this->assertNull($response['effective_url']);
+ }
+
+ public function testReturnsFutures()
+ {
+ $deferred = new Deferred();
+ $future = new FutureArray(
+ $deferred->promise(),
+ function () use ($deferred) {
+ $deferred->resolve(['status' => 200]);
+ }
+ );
+ $mock = new MockHandler($future);
+ $response = $mock([]);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $this->assertEquals(200, $response['status']);
+ }
+
+ public function testReturnsFuturesWithThenCall()
+ {
+ $deferred = new Deferred();
+ $future = new FutureArray(
+ $deferred->promise(),
+ function () use ($deferred) {
+ $deferred->resolve(['status' => 200]);
+ }
+ );
+ $mock = new MockHandler($future);
+ $response = $mock([]);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $this->assertEquals(200, $response['status']);
+ $req = null;
+ $promise = $response->then(function ($value) use (&$req) {
+ $req = $value;
+ $this->assertEquals(200, $req['status']);
+ });
+ $this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
+ $this->assertEquals(200, $req['status']);
+ }
+
+ public function testReturnsFuturesAndProxiesCancel()
+ {
+ $c = null;
+ $deferred = new Deferred();
+ $future = new FutureArray(
+ $deferred->promise(),
+ function () {},
+ function () use (&$c) {
+ $c = true;
+ return true;
+ }
+ );
+ $mock = new MockHandler($future);
+ $response = $mock([]);
+ $this->assertInstanceOf('GuzzleHttp\Ring\Future\FutureArray', $response);
+ $response->cancel();
+ $this->assertTrue($c);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Response must be an array or FutureArrayInterface. Found
+ */
+ public function testEnsuresMockIsValid()
+ {
+ $mock = new MockHandler('foo');
+ $mock([]);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/Server.php b/vendor/guzzlehttp/ringphp/tests/Client/Server.php
new file mode 100644
index 0000000..14665a5
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/Server.php
@@ -0,0 +1,183 @@
+ [], 'reason' => '', 'body' => ''];
+ $data[] = $response;
+ }
+
+ self::send('PUT', '/guzzle-server/responses', json_encode($data));
+ }
+
+ /**
+ * Get all of the received requests as a RingPHP request structure.
+ *
+ * @return array
+ * @throws \RuntimeException
+ */
+ public static function received()
+ {
+ if (!self::$started) {
+ return [];
+ }
+
+ $response = self::send('GET', '/guzzle-server/requests');
+ $body = Core::body($response);
+ $result = json_decode($body, true);
+ if ($result === false) {
+ throw new \RuntimeException('Error decoding response: '
+ . json_last_error());
+ }
+
+ foreach ($result as &$res) {
+ if (isset($res['uri'])) {
+ $res['resource'] = $res['uri'];
+ }
+ if (isset($res['query_string'])) {
+ $res['resource'] .= '?' . $res['query_string'];
+ }
+ if (!isset($res['resource'])) {
+ $res['resource'] = '';
+ }
+ // Ensure that headers are all arrays
+ if (isset($res['headers'])) {
+ foreach ($res['headers'] as &$h) {
+ $h = (array) $h;
+ }
+ unset($h);
+ }
+ }
+
+ unset($res);
+ return $result;
+ }
+
+ /**
+ * Stop running the node.js server
+ */
+ public static function stop()
+ {
+ if (self::$started) {
+ self::send('DELETE', '/guzzle-server');
+ }
+
+ self::$started = false;
+ }
+
+ public static function wait($maxTries = 20)
+ {
+ $tries = 0;
+ while (!self::isListening() && ++$tries < $maxTries) {
+ usleep(100000);
+ }
+
+ if (!self::isListening()) {
+ throw new \RuntimeException('Unable to contact node.js server');
+ }
+ }
+
+ public static function start()
+ {
+ if (self::$started) {
+ return;
+ }
+
+ try {
+ self::wait();
+ } catch (\Exception $e) {
+ exec('node ' . __DIR__ . \DIRECTORY_SEPARATOR . 'server.js '
+ . self::$port . ' >> /tmp/server.log 2>&1 &');
+ self::wait();
+ }
+
+ self::$started = true;
+ }
+
+ private static function isListening()
+ {
+ $response = self::send('GET', '/guzzle-server/perf', null, [
+ 'connect_timeout' => 1,
+ 'timeout' => 1
+ ]);
+
+ return !isset($response['error']);
+ }
+
+ private static function send(
+ $method,
+ $path,
+ $body = null,
+ array $client = []
+ ) {
+ $handler = new StreamHandler();
+
+ $request = [
+ 'http_method' => $method,
+ 'uri' => $path,
+ 'request_port' => 8125,
+ 'headers' => ['host' => ['127.0.0.1:8125']],
+ 'body' => $body,
+ 'client' => $client,
+ ];
+
+ if ($body) {
+ $request['headers']['content-length'] = [strlen($body)];
+ }
+
+ return $handler($request);
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php
new file mode 100644
index 0000000..3cb9a8e
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php
@@ -0,0 +1,480 @@
+queueRes();
+ $handler = new StreamHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => [
+ 'host' => [Server::$host],
+ 'Foo' => ['Bar'],
+ ],
+ ]);
+
+ $this->assertEquals('1.1', $response['version']);
+ $this->assertEquals(200, $response['status']);
+ $this->assertEquals('OK', $response['reason']);
+ $this->assertEquals(['Bar'], $response['headers']['Foo']);
+ $this->assertEquals(['8'], $response['headers']['Content-Length']);
+ $this->assertEquals('hi there', Core::body($response));
+
+ $sent = Server::received()[0];
+ $this->assertEquals('GET', $sent['http_method']);
+ $this->assertEquals('/', $sent['resource']);
+ $this->assertEquals(['127.0.0.1:8125'], $sent['headers']['host']);
+ $this->assertEquals('Bar', Core::header($sent, 'foo'));
+ }
+
+ public function testAddsErrorToResponse()
+ {
+ $handler = new StreamHandler();
+ $result = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => ['localhost:123']],
+ 'client' => ['timeout' => 0.01],
+ ]);
+ $this->assertInstanceOf(
+ 'GuzzleHttp\Ring\Future\CompletedFutureArray',
+ $result
+ );
+ $this->assertNull($result['status']);
+ $this->assertNull($result['body']);
+ $this->assertEquals([], $result['headers']);
+ $this->assertInstanceOf(
+ 'GuzzleHttp\Ring\Exception\RingException',
+ $result['error']
+ );
+ }
+
+ public function testEnsuresTheHttpProtocol()
+ {
+ $handler = new StreamHandler();
+ $result = $handler([
+ 'http_method' => 'GET',
+ 'url' => 'ftp://localhost:123',
+ ]);
+ $this->assertArrayHasKey('error', $result);
+ $this->assertContains(
+ 'URL is invalid: ftp://localhost:123',
+ $result['error']->getMessage()
+ );
+ }
+
+ public function testStreamAttributeKeepsStreamOpen()
+ {
+ $this->queueRes();
+ $handler = new StreamHandler();
+ $response = $handler([
+ 'http_method' => 'PUT',
+ 'uri' => '/foo',
+ 'query_string' => 'baz=bar',
+ 'headers' => [
+ 'host' => [Server::$host],
+ 'Foo' => ['Bar'],
+ ],
+ 'body' => 'test',
+ 'client' => ['stream' => true],
+ ]);
+
+ $this->assertEquals(200, $response['status']);
+ $this->assertEquals('OK', $response['reason']);
+ $this->assertEquals('8', Core::header($response, 'Content-Length'));
+ $body = $response['body'];
+ $this->assertTrue(is_resource($body));
+ $this->assertEquals('http', stream_get_meta_data($body)['wrapper_type']);
+ $this->assertEquals('hi there', stream_get_contents($body));
+ fclose($body);
+ $sent = Server::received()[0];
+ $this->assertEquals('PUT', $sent['http_method']);
+ $this->assertEquals('/foo', $sent['uri']);
+ $this->assertEquals('baz=bar', $sent['query_string']);
+ $this->assertEquals('/foo?baz=bar', $sent['resource']);
+ $this->assertEquals('127.0.0.1:8125', Core::header($sent, 'host'));
+ $this->assertEquals('Bar', Core::header($sent, 'foo'));
+ }
+
+ public function testDrainsResponseIntoTempStream()
+ {
+ $this->queueRes();
+ $handler = new StreamHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => [Server::$host]],
+ ]);
+ $body = $response['body'];
+ $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']);
+ $this->assertEquals('hi', fread($body, 2));
+ fclose($body);
+ }
+
+ public function testDrainsResponseIntoSaveToBody()
+ {
+ $r = fopen('php://temp', 'r+');
+ $this->queueRes();
+ $handler = new StreamHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['save_to' => $r],
+ ]);
+ $body = $response['body'];
+ $this->assertEquals('php://temp', stream_get_meta_data($body)['uri']);
+ $this->assertEquals('hi', fread($body, 2));
+ $this->assertEquals(' there', stream_get_contents($r));
+ fclose($r);
+ }
+
+ public function testDrainsResponseIntoSaveToBodyAtPath()
+ {
+ $tmpfname = tempnam('/tmp', 'save_to_path');
+ $this->queueRes();
+ $handler = new StreamHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => ['save_to' => $tmpfname],
+ ]);
+ $body = $response['body'];
+ $this->assertInstanceOf('GuzzleHttp\Stream\StreamInterface', $body);
+ $this->assertEquals($tmpfname, $body->getMetadata('uri'));
+ $this->assertEquals('hi', $body->read(2));
+ $body->close();
+ unlink($tmpfname);
+ }
+
+ public function testAutomaticallyDecompressGzip()
+ {
+ Server::flush();
+ $content = gzencode('test');
+ Server::enqueue([
+ [
+ 'status' => 200,
+ 'reason' => 'OK',
+ 'headers' => [
+ 'Content-Encoding' => ['gzip'],
+ 'Content-Length' => [strlen($content)],
+ ],
+ 'body' => $content,
+ ],
+ ]);
+
+ $handler = new StreamHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'uri' => '/',
+ 'client' => ['decode_content' => true],
+ ]);
+ $this->assertEquals('test', Core::body($response));
+ }
+
+ public function testDoesNotForceGzipDecode()
+ {
+ Server::flush();
+ $content = gzencode('test');
+ Server::enqueue([
+ [
+ 'status' => 200,
+ 'reason' => 'OK',
+ 'headers' => [
+ 'Content-Encoding' => ['gzip'],
+ 'Content-Length' => [strlen($content)],
+ ],
+ 'body' => $content,
+ ],
+ ]);
+
+ $handler = new StreamHandler();
+ $response = $handler([
+ 'http_method' => 'GET',
+ 'headers' => ['host' => [Server::$host]],
+ 'uri' => '/',
+ 'client' => ['stream' => true, 'decode_content' => false],
+ ]);
+ $this->assertSame($content, Core::body($response));
+ }
+
+ public function testProtocolVersion()
+ {
+ $this->queueRes();
+ $handler = new StreamHandler();
+ $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => [Server::$host]],
+ 'version' => 1.0,
+ ]);
+
+ $this->assertEquals(1.0, Server::received()[0]['version']);
+ }
+
+ protected function getSendResult(array $opts)
+ {
+ $this->queueRes();
+ $handler = new StreamHandler();
+ $opts['stream'] = true;
+ return $handler([
+ 'http_method' => 'GET',
+ 'uri' => '/',
+ 'headers' => ['host' => [Server::$host]],
+ 'client' => $opts,
+ ]);
+ }
+
+ public function testAddsProxy()
+ {
+ $res = $this->getSendResult(['stream' => true, 'proxy' => '127.0.0.1:8125']);
+ $opts = stream_context_get_options($res['body']);
+ $this->assertEquals('127.0.0.1:8125', $opts['http']['proxy']);
+ }
+
+ public function testAddsTimeout()
+ {
+ $res = $this->getSendResult(['stream' => true, 'timeout' => 200]);
+ $opts = stream_context_get_options($res['body']);
+ $this->assertEquals(200, $opts['http']['timeout']);
+ }
+
+ public function testVerifiesVerifyIsValidIfPath()
+ {
+ $res = $this->getSendResult(['verify' => '/does/not/exist']);
+ $this->assertContains(
+ 'SSL CA bundle not found: /does/not/exist',
+ (string) $res['error']
+ );
+ }
+
+ public function testVerifyCanBeDisabled()
+ {
+ $res = $this->getSendResult(['verify' => false]);
+ $this->assertArrayNotHasKey('error', $res);
+ }
+
+ public function testVerifiesCertIfValidPath()
+ {
+ $res = $this->getSendResult(['cert' => '/does/not/exist']);
+ $this->assertContains(
+ 'SSL certificate not found: /does/not/exist',
+ (string) $res['error']
+ );
+ }
+
+ public function testVerifyCanBeSetToPath()
+ {
+ $path = $path = ClientUtils::getDefaultCaBundle();
+ $res = $this->getSendResult(['verify' => $path]);
+ $this->assertArrayNotHasKey('error', $res);
+ $opts = stream_context_get_options($res['body']);
+ $this->assertEquals(true, $opts['ssl']['verify_peer']);
+ $this->assertEquals($path, $opts['ssl']['cafile']);
+ $this->assertTrue(file_exists($opts['ssl']['cafile']));
+ }
+
+ public function testUsesSystemDefaultBundle()
+ {
+ $path = $path = ClientUtils::getDefaultCaBundle();
+ $res = $this->getSendResult(['verify' => true]);
+ $this->assertArrayNotHasKey('error', $res);
+ $opts = stream_context_get_options($res['body']);
+ if (PHP_VERSION_ID < 50600) {
+ $this->assertEquals($path, $opts['ssl']['cafile']);
+ }
+ }
+
+ public function testEnsuresVerifyOptionIsValid()
+ {
+ $res = $this->getSendResult(['verify' => 10]);
+ $this->assertContains(
+ 'Invalid verify request option',
+ (string) $res['error']
+ );
+ }
+
+ public function testCanSetPasswordWhenSettingCert()
+ {
+ $path = __FILE__;
+ $res = $this->getSendResult(['cert' => [$path, 'foo']]);
+ $opts = stream_context_get_options($res['body']);
+ $this->assertEquals($path, $opts['ssl']['local_cert']);
+ $this->assertEquals('foo', $opts['ssl']['passphrase']);
+ }
+
+ public function testDebugAttributeWritesToStream()
+ {
+ $this->queueRes();
+ $f = fopen('php://temp', 'w+');
+ $this->getSendResult(['debug' => $f]);
+ fseek($f, 0);
+ $contents = stream_get_contents($f);
+ $this->assertContains(' [CONNECT]', $contents);
+ $this->assertContains(' [FILE_SIZE_IS]', $contents);
+ $this->assertContains(' [PROGRESS]', $contents);
+ }
+
+ public function testDebugAttributeWritesStreamInfoToBuffer()
+ {
+ $called = false;
+ $this->queueRes();
+ $buffer = fopen('php://temp', 'r+');
+ $this->getSendResult([
+ 'progress' => function () use (&$called) { $called = true; },
+ 'debug' => $buffer,
+ ]);
+ fseek($buffer, 0);
+ $contents = stream_get_contents($buffer);
+ $this->assertContains(' [CONNECT]', $contents);
+ $this->assertContains(' [FILE_SIZE_IS] message: "Content-Length: 8"', $contents);
+ $this->assertContains(' [PROGRESS] bytes_max: "8"', $contents);
+ $this->assertTrue($called);
+ }
+
+ public function testEmitsProgressInformation()
+ {
+ $called = [];
+ $this->queueRes();
+ $this->getSendResult([
+ 'progress' => function () use (&$called) {
+ $called[] = func_get_args();
+ },
+ ]);
+ $this->assertNotEmpty($called);
+ $this->assertEquals(8, $called[0][0]);
+ $this->assertEquals(0, $called[0][1]);
+ }
+
+ public function testEmitsProgressInformationAndDebugInformation()
+ {
+ $called = [];
+ $this->queueRes();
+ $buffer = fopen('php://memory', 'w+');
+ $this->getSendResult([
+ 'debug' => $buffer,
+ 'progress' => function () use (&$called) {
+ $called[] = func_get_args();
+ },
+ ]);
+ $this->assertNotEmpty($called);
+ $this->assertEquals(8, $called[0][0]);
+ $this->assertEquals(0, $called[0][1]);
+ rewind($buffer);
+ $this->assertNotEmpty(stream_get_contents($buffer));
+ fclose($buffer);
+ }
+
+ public function testAddsProxyByProtocol()
+ {
+ $url = str_replace('http', 'tcp', Server::$url);
+ $res = $this->getSendResult(['proxy' => ['http' => $url]]);
+ $opts = stream_context_get_options($res['body']);
+ $this->assertEquals($url, $opts['http']['proxy']);
+ }
+
+ public function testPerformsShallowMergeOfCustomContextOptions()
+ {
+ $res = $this->getSendResult([
+ 'stream_context' => [
+ 'http' => [
+ 'request_fulluri' => true,
+ 'method' => 'HEAD',
+ ],
+ 'socket' => [
+ 'bindto' => '127.0.0.1:0',
+ ],
+ 'ssl' => [
+ 'verify_peer' => false,
+ ],
+ ],
+ ]);
+
+ $opts = stream_context_get_options($res['body']);
+ $this->assertEquals('HEAD', $opts['http']['method']);
+ $this->assertTrue($opts['http']['request_fulluri']);
+ $this->assertFalse($opts['ssl']['verify_peer']);
+ $this->assertEquals('127.0.0.1:0', $opts['socket']['bindto']);
+ }
+
+ public function testEnsuresThatStreamContextIsAnArray()
+ {
+ $res = $this->getSendResult(['stream_context' => 'foo']);
+ $this->assertContains(
+ 'stream_context must be an array',
+ (string) $res['error']
+ );
+ }
+
+ public function testDoesNotAddContentTypeByDefault()
+ {
+ $this->queueRes();
+ $handler = new StreamHandler();
+ $handler([
+ 'http_method' => 'PUT',
+ 'uri' => '/',
+ 'headers' => ['host' => [Server::$host], 'content-length' => [3]],
+ 'body' => 'foo',
+ ]);
+ $req = Server::received()[0];
+ $this->assertEquals('', Core::header($req, 'Content-Type'));
+ $this->assertEquals(3, Core::header($req, 'Content-Length'));
+ }
+
+ private function queueRes()
+ {
+ Server::flush();
+ Server::enqueue([
+ [
+ 'status' => 200,
+ 'reason' => 'OK',
+ 'headers' => [
+ 'Foo' => ['Bar'],
+ 'Content-Length' => [8],
+ ],
+ 'body' => 'hi there',
+ ],
+ ]);
+ }
+
+ public function testSupports100Continue()
+ {
+ Server::flush();
+ Server::enqueue([
+ [
+ 'status' => '200',
+ 'reason' => 'OK',
+ 'headers' => [
+ 'Test' => ['Hello'],
+ 'Content-Length' => ['4'],
+ ],
+ 'body' => 'test',
+ ],
+ ]);
+
+ $request = [
+ 'http_method' => 'PUT',
+ 'headers' => [
+ 'Host' => [Server::$host],
+ 'Expect' => ['100-Continue'],
+ ],
+ 'body' => 'test',
+ ];
+
+ $handler = new StreamHandler();
+ $response = $handler($request);
+ $this->assertEquals(200, $response['status']);
+ $this->assertEquals('OK', $response['reason']);
+ $this->assertEquals(['Hello'], $response['headers']['Test']);
+ $this->assertEquals(['4'], $response['headers']['Content-Length']);
+ $this->assertEquals('test', Core::body($response));
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Client/server.js b/vendor/guzzlehttp/ringphp/tests/Client/server.js
new file mode 100644
index 0000000..6a03e33
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Client/server.js
@@ -0,0 +1,241 @@
+/**
+ * Guzzle node.js test server to return queued responses to HTTP requests and
+ * expose a RESTful API for enqueueing responses and retrieving the requests
+ * that have been received.
+ *
+ * - Delete all requests that have been received:
+ * > DELETE /guzzle-server/requests
+ * > Host: 127.0.0.1:8125
+ *
+ * - Enqueue responses
+ * > PUT /guzzle-server/responses
+ * > Host: 127.0.0.1:8125
+ * >
+ * > [{'status': 200, 'reason': 'OK', 'headers': {}, 'body': '' }]
+ *
+ * - Get the received requests
+ * > GET /guzzle-server/requests
+ * > Host: 127.0.0.1:8125
+ *
+ * < HTTP/1.1 200 OK
+ * <
+ * < [{'http_method': 'GET', 'uri': '/', 'headers': {}, 'body': 'string'}]
+ *
+ * - Attempt access to the secure area
+ * > GET /secure/by-digest/qop-auth/guzzle-server/requests
+ * > Host: 127.0.0.1:8125
+ *
+ * < HTTP/1.1 401 Unauthorized
+ * < WWW-Authenticate: Digest realm="Digest Test", qop="auth", nonce="0796e98e1aeef43141fab2a66bf4521a", algorithm="MD5", stale="false"
+ * <
+ * < 401 Unauthorized
+ *
+ * - Shutdown the server
+ * > DELETE /guzzle-server
+ * > Host: 127.0.0.1:8125
+ *
+ * @package Guzzle PHP
+ * @license See the LICENSE file that was distributed with this source code.
+ */
+
+var http = require('http');
+var url = require('url');
+
+/**
+ * Guzzle node.js server
+ * @class
+ */
+var GuzzleServer = function(port, log) {
+
+ this.port = port;
+ this.log = log;
+ this.responses = [];
+ this.requests = [];
+ var that = this;
+
+ var md5 = function(input) {
+ var crypto = require('crypto');
+ var hasher = crypto.createHash('md5');
+ hasher.update(input);
+ return hasher.digest('hex');
+ }
+
+ /**
+ * Node.js HTTP server authentication module.
+ *
+ * It is only initialized on demand (by loadAuthentifier). This avoids
+ * requiring the dependency to http-auth on standard operations, and the
+ * performance hit at startup.
+ */
+ var auth;
+
+ /**
+ * Provides authentication handlers (Basic, Digest).
+ */
+ var loadAuthentifier = function(type, options) {
+ var typeId = type;
+ if (type == 'digest') {
+ typeId += '.'+(options && options.qop ? options.qop : 'none');
+ }
+ if (!loadAuthentifier[typeId]) {
+ if (!auth) {
+ try {
+ auth = require('http-auth');
+ } catch (e) {
+ if (e.code == 'MODULE_NOT_FOUND') {
+ return;
+ }
+ }
+ }
+ switch (type) {
+ case 'digest':
+ var digestParams = {
+ realm: 'Digest Test',
+ login: 'me',
+ password: 'test'
+ };
+ if (options && options.qop) {
+ digestParams.qop = options.qop;
+ }
+ loadAuthentifier[typeId] = auth.digest(digestParams, function(username, callback) {
+ callback(md5(digestParams.login + ':' + digestParams.realm + ':' + digestParams.password));
+ });
+ break
+ }
+ }
+ return loadAuthentifier[typeId];
+ };
+
+ var firewallRequest = function(request, req, res, requestHandlerCallback) {
+ var securedAreaUriParts = request.uri.match(/^\/secure\/by-(digest)(\/qop-([^\/]*))?(\/.*)$/);
+ if (securedAreaUriParts) {
+ var authentifier = loadAuthentifier(securedAreaUriParts[1], { qop: securedAreaUriParts[2] });
+ if (!authentifier) {
+ res.writeHead(501, 'HTTP authentication not implemented', { 'Content-Length': 0 });
+ res.end();
+ return;
+ }
+ authentifier.check(req, res, function(req, res) {
+ req.url = securedAreaUriParts[4];
+ requestHandlerCallback(request, req, res);
+ });
+ } else {
+ requestHandlerCallback(request, req, res);
+ }
+ };
+
+ var controlRequest = function(request, req, res) {
+ if (req.url == '/guzzle-server/perf') {
+ res.writeHead(200, 'OK', {'Content-Length': 16});
+ res.end('Body of response');
+ } else if (req.method == 'DELETE') {
+ if (req.url == '/guzzle-server/requests') {
+ // Clear the received requests
+ that.requests = [];
+ res.writeHead(200, 'OK', { 'Content-Length': 0 });
+ res.end();
+ if (that.log) {
+ console.log('Flushing requests');
+ }
+ } else if (req.url == '/guzzle-server') {
+ // Shutdown the server
+ res.writeHead(200, 'OK', { 'Content-Length': 0, 'Connection': 'close' });
+ res.end();
+ if (that.log) {
+ console.log('Shutting down');
+ }
+ that.server.close();
+ }
+ } else if (req.method == 'GET') {
+ if (req.url === '/guzzle-server/requests') {
+ if (that.log) {
+ console.log('Sending received requests');
+ }
+ // Get received requests
+ var body = JSON.stringify(that.requests);
+ res.writeHead(200, 'OK', { 'Content-Length': body.length });
+ res.end(body);
+ }
+ } else if (req.method == 'PUT' && req.url == '/guzzle-server/responses') {
+ if (that.log) {
+ console.log('Adding responses...');
+ }
+ if (!request.body) {
+ if (that.log) {
+ console.log('No response data was provided');
+ }
+ res.writeHead(400, 'NO RESPONSES IN REQUEST', { 'Content-Length': 0 });
+ } else {
+ that.responses = eval('(' + request.body + ')');
+ for (var i = 0; i < that.responses.length; i++) {
+ if (that.responses[i].body) {
+ that.responses[i].body = new Buffer(that.responses[i].body, 'base64');
+ }
+ }
+ if (that.log) {
+ console.log(that.responses);
+ }
+ res.writeHead(200, 'OK', { 'Content-Length': 0 });
+ }
+ res.end();
+ }
+ };
+
+ var receivedRequest = function(request, req, res) {
+ if (req.url.indexOf('/guzzle-server') === 0) {
+ controlRequest(request, req, res);
+ } else if (req.url.indexOf('/guzzle-server') == -1 && !that.responses.length) {
+ res.writeHead(500);
+ res.end('No responses in queue');
+ } else {
+ if (that.log) {
+ console.log('Returning response from queue and adding request');
+ }
+ that.requests.push(request);
+ var response = that.responses.shift();
+ res.writeHead(response.status, response.reason, response.headers);
+ res.end(response.body);
+ }
+ };
+
+ this.start = function() {
+
+ that.server = http.createServer(function(req, res) {
+
+ var parts = url.parse(req.url, false);
+ var request = {
+ http_method: req.method,
+ scheme: parts.scheme,
+ uri: parts.pathname,
+ query_string: parts.query,
+ headers: req.headers,
+ version: req.httpVersion,
+ body: ''
+ };
+
+ // Receive each chunk of the request body
+ req.addListener('data', function(chunk) {
+ request.body += chunk;
+ });
+
+ // Called when the request completes
+ req.addListener('end', function() {
+ firewallRequest(request, req, res, receivedRequest);
+ });
+ });
+
+ that.server.listen(this.port, '127.0.0.1');
+
+ if (this.log) {
+ console.log('Server running at http://127.0.0.1:8125/');
+ }
+ };
+};
+
+// Get the port from the arguments
+port = process.argv.length >= 3 ? process.argv[2] : 8125;
+log = process.argv.length >= 4 ? process.argv[3] : false;
+
+// Start the server
+server = new GuzzleServer(port, log);
+server.start();
diff --git a/vendor/guzzlehttp/ringphp/tests/CoreTest.php b/vendor/guzzlehttp/ringphp/tests/CoreTest.php
new file mode 100644
index 0000000..49522f2
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/CoreTest.php
@@ -0,0 +1,336 @@
+assertNull(Core::header([], 'Foo'));
+ $this->assertNull(Core::firstHeader([], 'Foo'));
+ }
+
+ public function testChecksIfHasHeader()
+ {
+ $message = [
+ 'headers' => [
+ 'Foo' => ['Bar', 'Baz'],
+ 'foo' => ['hello'],
+ 'bar' => ['1']
+ ]
+ ];
+ $this->assertTrue(Core::hasHeader($message, 'Foo'));
+ $this->assertTrue(Core::hasHeader($message, 'foo'));
+ $this->assertTrue(Core::hasHeader($message, 'FoO'));
+ $this->assertTrue(Core::hasHeader($message, 'bar'));
+ $this->assertFalse(Core::hasHeader($message, 'barr'));
+ }
+
+ public function testReturnsFirstHeaderWhenSimple()
+ {
+ $this->assertEquals('Bar', Core::firstHeader([
+ 'headers' => ['Foo' => ['Bar', 'Baz']],
+ ], 'Foo'));
+ }
+
+ public function testReturnsFirstHeaderWhenMultiplePerLine()
+ {
+ $this->assertEquals('Bar', Core::firstHeader([
+ 'headers' => ['Foo' => ['Bar, Baz']],
+ ], 'Foo'));
+ }
+
+ public function testExtractsCaseInsensitiveHeader()
+ {
+ $this->assertEquals(
+ 'hello',
+ Core::header(['headers' => ['foo' => ['hello']]], 'FoO')
+ );
+ }
+
+ public function testExtractsCaseInsensitiveHeaderLines()
+ {
+ $this->assertEquals(
+ ['a', 'b', 'c', 'd'],
+ Core::headerLines([
+ 'headers' => [
+ 'foo' => ['a', 'b'],
+ 'Foo' => ['c', 'd']
+ ]
+ ], 'foo')
+ );
+ }
+
+ public function testExtractsHeaderLines()
+ {
+ $this->assertEquals(
+ ['bar', 'baz'],
+ Core::headerLines([
+ 'headers' => [
+ 'Foo' => ['bar', 'baz'],
+ ],
+ ], 'Foo')
+ );
+ }
+
+ public function testExtractsHeaderAsString()
+ {
+ $this->assertEquals(
+ 'bar, baz',
+ Core::header([
+ 'headers' => [
+ 'Foo' => ['bar', 'baz'],
+ ],
+ ], 'Foo', true)
+ );
+ }
+
+ public function testReturnsNullWhenHeaderNotFound()
+ {
+ $this->assertNull(Core::header(['headers' => []], 'Foo'));
+ }
+
+ public function testRemovesHeaders()
+ {
+ $message = [
+ 'headers' => [
+ 'foo' => ['bar'],
+ 'Foo' => ['bam'],
+ 'baz' => ['123'],
+ ],
+ ];
+
+ $this->assertSame($message, Core::removeHeader($message, 'bam'));
+ $this->assertEquals([
+ 'headers' => ['baz' => ['123']],
+ ], Core::removeHeader($message, 'foo'));
+ }
+
+ public function testCreatesUrl()
+ {
+ $req = [
+ 'scheme' => 'http',
+ 'headers' => ['host' => ['foo.com']],
+ 'uri' => '/',
+ ];
+
+ $this->assertEquals('http://foo.com/', Core::url($req));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage No Host header was provided
+ */
+ public function testEnsuresHostIsAvailableWhenCreatingUrls()
+ {
+ Core::url([]);
+ }
+
+ public function testCreatesUrlWithQueryString()
+ {
+ $req = [
+ 'scheme' => 'http',
+ 'headers' => ['host' => ['foo.com']],
+ 'uri' => '/',
+ 'query_string' => 'foo=baz',
+ ];
+
+ $this->assertEquals('http://foo.com/?foo=baz', Core::url($req));
+ }
+
+ public function testUsesUrlIfSet()
+ {
+ $req = ['url' => 'http://foo.com'];
+ $this->assertEquals('http://foo.com', Core::url($req));
+ }
+
+ public function testReturnsNullWhenNoBody()
+ {
+ $this->assertNull(Core::body([]));
+ }
+
+ public function testReturnsStreamAsString()
+ {
+ $this->assertEquals(
+ 'foo',
+ Core::body(['body' => Stream::factory('foo')])
+ );
+ }
+
+ public function testReturnsString()
+ {
+ $this->assertEquals('foo', Core::body(['body' => 'foo']));
+ }
+
+ public function testReturnsResourceContent()
+ {
+ $r = fopen('php://memory', 'w+');
+ fwrite($r, 'foo');
+ rewind($r);
+ $this->assertEquals('foo', Core::body(['body' => $r]));
+ fclose($r);
+ }
+
+ public function testReturnsIteratorContent()
+ {
+ $a = new \ArrayIterator(['a', 'b', 'cd', '']);
+ $this->assertEquals('abcd', Core::body(['body' => $a]));
+ }
+
+ public function testReturnsObjectToString()
+ {
+ $this->assertEquals('foo', Core::body(['body' => new StrClass]));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testEnsuresBodyIsValid()
+ {
+ Core::body(['body' => false]);
+ }
+
+ public function testParsesHeadersFromLines()
+ {
+ $lines = ['Foo: bar', 'Foo: baz', 'Abc: 123', 'Def: a, b'];
+ $this->assertEquals([
+ 'Foo' => ['bar', 'baz'],
+ 'Abc' => ['123'],
+ 'Def' => ['a, b'],
+ ], Core::headersFromLines($lines));
+ }
+
+ public function testParsesHeadersFromLinesWithMultipleLines()
+ {
+ $lines = ['Foo: bar', 'Foo: baz', 'Foo: 123'];
+ $this->assertEquals([
+ 'Foo' => ['bar', 'baz', '123'],
+ ], Core::headersFromLines($lines));
+ }
+
+ public function testCreatesArrayCallFunctions()
+ {
+ $called = [];
+ $a = function ($a, $b) use (&$called) {
+ $called['a'] = func_get_args();
+ };
+ $b = function ($a, $b) use (&$called) {
+ $called['b'] = func_get_args();
+ };
+ $c = Core::callArray([$a, $b]);
+ $c(1, 2);
+ $this->assertEquals([1, 2], $called['a']);
+ $this->assertEquals([1, 2], $called['b']);
+ }
+
+ public function testRewindsGuzzleStreams()
+ {
+ $str = Stream::factory('foo');
+ $this->assertTrue(Core::rewindBody(['body' => $str]));
+ }
+
+ public function testRewindsStreams()
+ {
+ $str = Stream::factory('foo')->detach();
+ $this->assertTrue(Core::rewindBody(['body' => $str]));
+ }
+
+ public function testRewindsIterators()
+ {
+ $iter = new \ArrayIterator(['foo']);
+ $this->assertTrue(Core::rewindBody(['body' => $iter]));
+ }
+
+ public function testRewindsStrings()
+ {
+ $this->assertTrue(Core::rewindBody(['body' => 'hi']));
+ }
+
+ public function testRewindsToStrings()
+ {
+ $this->assertTrue(Core::rewindBody(['body' => new StrClass()]));
+ }
+
+ public function typeProvider()
+ {
+ return [
+ ['foo', 'string(3) "foo"'],
+ [true, 'bool(true)'],
+ [false, 'bool(false)'],
+ [10, 'int(10)'],
+ [1.0, 'float(1)'],
+ [new StrClass(), 'object(GuzzleHttp\Tests\Ring\StrClass)'],
+ [['foo'], 'array(1)']
+ ];
+ }
+
+ /**
+ * @dataProvider typeProvider
+ */
+ public function testDescribesType($input, $output)
+ {
+ $this->assertEquals($output, Core::describeType($input));
+ }
+
+ public function testDoesSleep()
+ {
+ $t = microtime(true);
+ $expected = $t + (100 / 1000);
+ Core::doSleep(['client' => ['delay' => 100]]);
+ $this->assertGreaterThanOrEqual($expected, microtime(true));
+ }
+
+ public function testProxiesFuture()
+ {
+ $f = new CompletedFutureArray(['status' => 200]);
+ $res = null;
+ $proxied = Core::proxy($f, function ($value) use (&$res) {
+ $value['foo'] = 'bar';
+ $res = $value;
+ return $value;
+ });
+ $this->assertNotSame($f, $proxied);
+ $this->assertEquals(200, $f->wait()['status']);
+ $this->assertArrayNotHasKey('foo', $f->wait());
+ $this->assertEquals('bar', $proxied->wait()['foo']);
+ $this->assertEquals(200, $proxied->wait()['status']);
+ }
+
+ public function testProxiesDeferredFuture()
+ {
+ $d = new Deferred();
+ $f = new FutureArray($d->promise());
+ $f2 = Core::proxy($f);
+ $d->resolve(['foo' => 'bar']);
+ $this->assertEquals('bar', $f['foo']);
+ $this->assertEquals('bar', $f2['foo']);
+ }
+
+ public function testProxiesDeferredFutureFailure()
+ {
+ $d = new Deferred();
+ $f = new FutureArray($d->promise());
+ $f2 = Core::proxy($f);
+ $d->reject(new \Exception('foo'));
+ try {
+ $f2['hello?'];
+ $this->fail('did not throw');
+ } catch (\Exception $e) {
+ $this->assertEquals('foo', $e->getMessage());
+ }
+
+ }
+}
+
+final class StrClass
+{
+ public function __toString()
+ {
+ return 'foo';
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php b/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php
new file mode 100644
index 0000000..82d7efb
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php
@@ -0,0 +1,21 @@
+ 'bar']);
+ $this->assertEquals('bar', $f['foo']);
+ $this->assertFalse(isset($f['baz']));
+ $f['abc'] = '123';
+ $this->assertTrue(isset($f['abc']));
+ $this->assertEquals(['foo' => 'bar', 'abc' => '123'], iterator_to_array($f));
+ $this->assertEquals(2, count($f));
+ unset($f['abc']);
+ $this->assertEquals(1, count($f));
+ $this->assertEquals(['foo' => 'bar'], iterator_to_array($f));
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php b/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php
new file mode 100644
index 0000000..6ded40d
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php
@@ -0,0 +1,46 @@
+assertEquals('hi', $f->wait());
+ $f->cancel();
+
+ $a = null;
+ $f->then(function ($v) use (&$a) {
+ $a = $v;
+ });
+ $this->assertSame('hi', $a);
+ }
+
+ public function testThrows()
+ {
+ $ex = new \Exception('foo');
+ $f = new CompletedFutureValue(null, $ex);
+ $f->cancel();
+ try {
+ $f->wait();
+ $this->fail('did not throw');
+ } catch (\Exception $e) {
+ $this->assertSame($e, $ex);
+ }
+ }
+
+ public function testMarksAsCancelled()
+ {
+ $ex = new CancelledFutureAccessException();
+ $f = new CompletedFutureValue(null, $ex);
+ try {
+ $f->wait();
+ $this->fail('did not throw');
+ } catch (\Exception $e) {
+ $this->assertSame($e, $ex);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php b/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php
new file mode 100644
index 0000000..0e09f5a
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php
@@ -0,0 +1,56 @@
+promise(),
+ function () use (&$c, $deferred) {
+ $c = true;
+ $deferred->resolve(['status' => 200]);
+ }
+ );
+ $this->assertFalse($c);
+ $this->assertFalse($this->readAttribute($f, 'isRealized'));
+ $this->assertEquals(200, $f['status']);
+ $this->assertTrue($c);
+ }
+
+ public function testActsLikeArray()
+ {
+ $deferred = new Deferred();
+ $f = new FutureArray(
+ $deferred->promise(),
+ function () use (&$c, $deferred) {
+ $deferred->resolve(['status' => 200]);
+ }
+ );
+
+ $this->assertTrue(isset($f['status']));
+ $this->assertEquals(200, $f['status']);
+ $this->assertEquals(['status' => 200], $f->wait());
+ $this->assertEquals(1, count($f));
+ $f['baz'] = 10;
+ $this->assertEquals(10, $f['baz']);
+ unset($f['baz']);
+ $this->assertFalse(isset($f['baz']));
+ $this->assertEquals(['status' => 200], iterator_to_array($f));
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testThrowsWhenAccessingInvalidProperty()
+ {
+ $deferred = new Deferred();
+ $f = new FutureArray($deferred->promise(), function () {});
+ $f->foo;
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php b/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php
new file mode 100644
index 0000000..d59c543
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php
@@ -0,0 +1,109 @@
+promise(),
+ function () use ($deferred, &$called) {
+ $called++;
+ $deferred->resolve('foo');
+ }
+ );
+
+ $this->assertEquals('foo', $f->wait());
+ $this->assertEquals(1, $called);
+ $this->assertEquals('foo', $f->wait());
+ $this->assertEquals(1, $called);
+ $f->cancel();
+ $this->assertTrue($this->readAttribute($f, 'isRealized'));
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Ring\Exception\CancelledFutureAccessException
+ */
+ public function testThrowsWhenAccessingCancelled()
+ {
+ $f = new FutureValue(
+ (new Deferred())->promise(),
+ function () {},
+ function () { return true; }
+ );
+ $f->cancel();
+ $f->wait();
+ }
+
+ /**
+ * @expectedException \OutOfBoundsException
+ */
+ public function testThrowsWhenDerefFailure()
+ {
+ $called = false;
+ $deferred = new Deferred();
+ $f = new FutureValue(
+ $deferred->promise(),
+ function () use(&$called) {
+ $called = true;
+ }
+ );
+ $deferred->reject(new \OutOfBoundsException());
+ $f->wait();
+ $this->assertFalse($called);
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Ring\Exception\RingException
+ * @expectedExceptionMessage Waiting did not resolve future
+ */
+ public function testThrowsWhenDerefDoesNotResolve()
+ {
+ $deferred = new Deferred();
+ $f = new FutureValue(
+ $deferred->promise(),
+ function () use(&$called) {
+ $called = true;
+ }
+ );
+ $f->wait();
+ }
+
+ public function testThrowingCancelledFutureAccessExceptionCancels()
+ {
+ $deferred = new Deferred();
+ $f = new FutureValue(
+ $deferred->promise(),
+ function () use ($deferred) {
+ throw new CancelledFutureAccessException();
+ }
+ );
+ try {
+ $f->wait();
+ $this->fail('did not throw');
+ } catch (CancelledFutureAccessException $e) {}
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage foo
+ */
+ public function testThrowingExceptionInDerefMarksAsFailed()
+ {
+ $deferred = new Deferred();
+ $f = new FutureValue(
+ $deferred->promise(),
+ function () {
+ throw new \Exception('foo');
+ }
+ );
+ $f->wait();
+ }
+}
diff --git a/vendor/guzzlehttp/ringphp/tests/bootstrap.php b/vendor/guzzlehttp/ringphp/tests/bootstrap.php
new file mode 100644
index 0000000..017610f
--- /dev/null
+++ b/vendor/guzzlehttp/ringphp/tests/bootstrap.php
@@ -0,0 +1,11 @@
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/guzzlehttp/streams/Makefile b/vendor/guzzlehttp/streams/Makefile
new file mode 100644
index 0000000..f4d4284
--- /dev/null
+++ b/vendor/guzzlehttp/streams/Makefile
@@ -0,0 +1,19 @@
+all: clean coverage
+
+release: tag
+ git push origin --tags
+
+tag:
+ chag tag --sign --debug CHANGELOG.rst
+
+test:
+ vendor/bin/phpunit
+
+coverage:
+ vendor/bin/phpunit --coverage-html=artifacts/coverage
+
+view-coverage:
+ open artifacts/coverage/index.html
+
+clean:
+ rm -rf artifacts/*
diff --git a/vendor/guzzlehttp/streams/README.rst b/vendor/guzzlehttp/streams/README.rst
new file mode 100644
index 0000000..baff63b
--- /dev/null
+++ b/vendor/guzzlehttp/streams/README.rst
@@ -0,0 +1,36 @@
+==============
+Guzzle Streams
+==============
+
+Provides a simple abstraction over streams of data.
+
+This library is used in `Guzzle 5 `_, and is
+(currently) compatible with the WIP PSR-7.
+
+Installation
+============
+
+This package can be installed easily using `Composer `_.
+Simply add the following to the composer.json file at the root of your project:
+
+.. code-block:: javascript
+
+ {
+ "require": {
+ "guzzlehttp/streams": "~3.0"
+ }
+ }
+
+Then install your dependencies using ``composer.phar install``.
+
+Documentation
+=============
+
+The documentation for this package can be found on the main Guzzle website at
+http://docs.guzzlephp.org/en/guzzle4/streams.html.
+
+Testing
+=======
+
+This library is tested using PHPUnit. You'll need to install the dependencies
+using `Composer `_ then run ``make test``.
diff --git a/vendor/guzzlehttp/streams/composer.json b/vendor/guzzlehttp/streams/composer.json
new file mode 100644
index 0000000..6d70343
--- /dev/null
+++ b/vendor/guzzlehttp/streams/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "guzzlehttp/streams",
+ "description": "Provides a simple abstraction over streams of data",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": ["stream", "guzzle"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "autoload": {
+ "psr-4": { "GuzzleHttp\\Stream\\": "src/" }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/streams/phpunit.xml.dist b/vendor/guzzlehttp/streams/phpunit.xml.dist
new file mode 100644
index 0000000..6e758c1
--- /dev/null
+++ b/vendor/guzzlehttp/streams/phpunit.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+ tests
+
+
+
+
+ src
+
+ src/functions.php
+
+
+
+
diff --git a/vendor/guzzlehttp/streams/src/AppendStream.php b/vendor/guzzlehttp/streams/src/AppendStream.php
new file mode 100644
index 0000000..94bda71
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/AppendStream.php
@@ -0,0 +1,220 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return $this->getContents();
+ } catch (\Exception $e) {
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream)
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents()
+ {
+ return Utils::copyToString($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ *
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->pos = $this->current = 0;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream
+ *
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $this->close();
+ $this->detached = true;
+ }
+
+ public function attach($stream)
+ {
+ throw new CannotAttachException();
+ }
+
+ public function tell()
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ *
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof()
+ {
+ return !$this->streams ||
+ ($this->current >= count($this->streams) - 1 &&
+ $this->streams[$this->current]->eof());
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ *
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if (!$this->seekable || $whence !== SEEK_SET) {
+ return false;
+ }
+
+ $success = true;
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $stream) {
+ if (!$stream->seek(0)) {
+ $success = false;
+ }
+ }
+
+ if (!$success) {
+ return false;
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $this->read(min(8096, $offset - $this->pos));
+ }
+
+ return $this->pos == $offset;
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ *
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+
+ while ($remaining > 0) {
+ // Progress to the next stream if needed.
+ if ($this->streams[$this->current]->eof()) {
+ if ($this->current == $total) {
+ break;
+ }
+ $this->current++;
+ }
+ $buffer .= $this->streams[$this->current]->read($remaining);
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function write($string)
+ {
+ return false;
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/AsyncReadStream.php b/vendor/guzzlehttp/streams/src/AsyncReadStream.php
new file mode 100644
index 0000000..25ad960
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/AsyncReadStream.php
@@ -0,0 +1,207 @@
+isReadable() || !$buffer->isWritable()) {
+ throw new \InvalidArgumentException(
+ 'Buffer must be readable and writable'
+ );
+ }
+
+ if (isset($config['size'])) {
+ $this->size = $config['size'];
+ }
+
+ static $callables = ['pump', 'drain'];
+ foreach ($callables as $check) {
+ if (isset($config[$check])) {
+ if (!is_callable($config[$check])) {
+ throw new \InvalidArgumentException(
+ $check . ' must be callable'
+ );
+ }
+ $this->{$check} = $config[$check];
+ }
+ }
+
+ $this->hwm = $buffer->getMetadata('hwm');
+
+ // Cannot drain when there's no high water mark.
+ if ($this->hwm === null) {
+ $this->drain = null;
+ }
+
+ $this->stream = $buffer;
+ }
+
+ /**
+ * Factory method used to create new async stream and an underlying buffer
+ * if no buffer is provided.
+ *
+ * This function accepts the same options as AsyncReadStream::__construct,
+ * but added the following key value pairs:
+ *
+ * - buffer: (StreamInterface) Buffer used to buffer data. If none is
+ * provided, a default buffer is created.
+ * - hwm: (int) High water mark to use if a buffer is created on your
+ * behalf.
+ * - max_buffer: (int) If provided, wraps the utilized buffer in a
+ * DroppingStream decorator to ensure that buffer does not exceed a given
+ * length. When exceeded, the stream will begin dropping data. Set the
+ * max_buffer to 0, to use a NullStream which does not store data.
+ * - write: (callable) A function that is invoked when data is written
+ * to the underlying buffer. The function accepts the buffer as the first
+ * argument, and the data being written as the second. The function MUST
+ * return the number of bytes that were written or false to let writers
+ * know to slow down.
+ * - drain: (callable) See constructor documentation.
+ * - pump: (callable) See constructor documentation.
+ *
+ * @param array $options Associative array of options.
+ *
+ * @return array Returns an array containing the buffer used to buffer
+ * data, followed by the ready to use AsyncReadStream object.
+ */
+ public static function create(array $options = [])
+ {
+ $maxBuffer = isset($options['max_buffer'])
+ ? $options['max_buffer']
+ : null;
+
+ if ($maxBuffer === 0) {
+ $buffer = new NullStream();
+ } elseif (isset($options['buffer'])) {
+ $buffer = $options['buffer'];
+ } else {
+ $hwm = isset($options['hwm']) ? $options['hwm'] : 16384;
+ $buffer = new BufferStream($hwm);
+ }
+
+ if ($maxBuffer > 0) {
+ $buffer = new DroppingStream($buffer, $options['max_buffer']);
+ }
+
+ // Call the on_write callback if an on_write function was provided.
+ if (isset($options['write'])) {
+ $onWrite = $options['write'];
+ $buffer = FnStream::decorate($buffer, [
+ 'write' => function ($string) use ($buffer, $onWrite) {
+ $result = $buffer->write($string);
+ $onWrite($buffer, $string);
+ return $result;
+ }
+ ]);
+ }
+
+ return [$buffer, new self($buffer, $options)];
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ return false;
+ }
+
+ public function read($length)
+ {
+ if (!$this->needsDrain && $this->drain) {
+ $this->needsDrain = $this->stream->getSize() >= $this->hwm;
+ }
+
+ $result = $this->stream->read($length);
+
+ // If we need to drain, then drain when the buffer is empty.
+ if ($this->needsDrain && $this->stream->getSize() === 0) {
+ $this->needsDrain = false;
+ $drainFn = $this->drain;
+ $drainFn($this->stream);
+ }
+
+ $resultLen = strlen($result);
+
+ // If a pump was provided, the buffer is still open, and not enough
+ // data was given, then block until the data is provided.
+ if ($this->pump && $resultLen < $length) {
+ $pumpFn = $this->pump;
+ $result .= $pumpFn($length - $resultLen);
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/BufferStream.php b/vendor/guzzlehttp/streams/src/BufferStream.php
new file mode 100644
index 0000000..0fffbd6
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/BufferStream.php
@@ -0,0 +1,138 @@
+hwm = $hwm;
+ }
+
+ public function __toString()
+ {
+ return $this->getContents();
+ }
+
+ public function getContents()
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close()
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+ }
+
+ public function attach($stream)
+ {
+ throw new CannotAttachException();
+ }
+
+ public function getSize()
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function isWritable()
+ {
+ return true;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return false;
+ }
+
+ public function eof()
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell()
+ {
+ return false;
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length)
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string)
+ {
+ $this->buffer .= $string;
+
+ if (strlen($this->buffer) >= $this->hwm) {
+ return false;
+ }
+
+ return strlen($string);
+ }
+
+ public function getMetadata($key = null)
+ {
+ if ($key == 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/CachingStream.php b/vendor/guzzlehttp/streams/src/CachingStream.php
new file mode 100644
index 0000000..60bb905
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/CachingStream.php
@@ -0,0 +1,122 @@
+remoteStream = $stream;
+ $this->stream = $target ?: new Stream(fopen('php://temp', 'r+'));
+ }
+
+ public function getSize()
+ {
+ return max($this->stream->getSize(), $this->remoteStream->getSize());
+ }
+
+ /**
+ * {@inheritdoc}
+ * @throws SeekException When seeking with SEEK_END or when seeking
+ * past the total size of the buffer stream
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence == SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence == SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } else {
+ return false;
+ }
+
+ // You cannot skip ahead past where you've read from the remote stream
+ if ($byte > $this->stream->getSize()) {
+ throw new SeekException(
+ $this,
+ $byte,
+ sprintf('Cannot seek to byte %d when the buffered stream only'
+ . ' contains %d bytes', $byte, $this->stream->getSize())
+ );
+ }
+
+ return $this->stream->seek($byte);
+ }
+
+ public function read($length)
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string)
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close()
+ {
+ $this->remoteStream->close() && $this->stream->close();
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/DroppingStream.php b/vendor/guzzlehttp/streams/src/DroppingStream.php
new file mode 100644
index 0000000..56ee80c
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/DroppingStream.php
@@ -0,0 +1,42 @@
+stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string)
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning false when the underlying stream is too large.
+ if ($diff <= 0) {
+ return false;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ $this->stream->write(substr($string, 0, $diff));
+
+ return false;
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php b/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php
new file mode 100644
index 0000000..e631b9f
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php
@@ -0,0 +1,4 @@
+stream = $stream;
+ $msg = $msg ?: 'Could not seek the stream to position ' . $pos;
+ parent::__construct($msg);
+ }
+
+ /**
+ * @return StreamInterface
+ */
+ public function getStream()
+ {
+ return $this->stream;
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/FnStream.php b/vendor/guzzlehttp/streams/src/FnStream.php
new file mode 100644
index 0000000..6b5872d
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/FnStream.php
@@ -0,0 +1,147 @@
+methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_' . $name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ * @throws \BadMethodCallException
+ */
+ public function __get($name)
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ . '() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ call_user_func($this->_fn_close);
+ }
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::$slots, array_keys($methods)) as $diff) {
+ $methods[$diff] = [$stream, $diff];
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString()
+ {
+ return call_user_func($this->_fn___toString);
+ }
+
+ public function close()
+ {
+ return call_user_func($this->_fn_close);
+ }
+
+ public function detach()
+ {
+ return call_user_func($this->_fn_detach);
+ }
+
+ public function attach($stream)
+ {
+ return call_user_func($this->_fn_attach, $stream);
+ }
+
+ public function getSize()
+ {
+ return call_user_func($this->_fn_getSize);
+ }
+
+ public function tell()
+ {
+ return call_user_func($this->_fn_tell);
+ }
+
+ public function eof()
+ {
+ return call_user_func($this->_fn_eof);
+ }
+
+ public function isSeekable()
+ {
+ return call_user_func($this->_fn_isSeekable);
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return call_user_func($this->_fn_seek, $offset, $whence);
+ }
+
+ public function isWritable()
+ {
+ return call_user_func($this->_fn_isWritable);
+ }
+
+ public function write($string)
+ {
+ return call_user_func($this->_fn_write, $string);
+ }
+
+ public function isReadable()
+ {
+ return call_user_func($this->_fn_isReadable);
+ }
+
+ public function read($length)
+ {
+ return call_user_func($this->_fn_read, $length);
+ }
+
+ public function getContents()
+ {
+ return call_user_func($this->_fn_getContents);
+ }
+
+ public function getMetadata($key = null)
+ {
+ return call_user_func($this->_fn_getMetadata, $key);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php b/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php
new file mode 100644
index 0000000..4d049a6
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php
@@ -0,0 +1,117 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ . 'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, null, stream_context_create([
+ 'guzzle' => ['stream' => $stream]
+ ]));
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register()
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open($path, $mode, $options, &$opened_path)
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read($count)
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write($data)
+ {
+ return (int) $this->stream->write($data);
+ }
+
+ public function stream_tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ return $this->stream->seek($offset, $whence);
+ }
+
+ public function stream_stat()
+ {
+ static $modeMap = [
+ 'r' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0
+ ];
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/InflateStream.php b/vendor/guzzlehttp/streams/src/InflateStream.php
new file mode 100644
index 0000000..978af21
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/InflateStream.php
@@ -0,0 +1,27 @@
+stream = new Stream($resource);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/LazyOpenStream.php b/vendor/guzzlehttp/streams/src/LazyOpenStream.php
new file mode 100644
index 0000000..6242ee7
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/LazyOpenStream.php
@@ -0,0 +1,37 @@
+filename = $filename;
+ $this->mode = $mode;
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ *
+ * @return StreamInterface
+ */
+ protected function createStream()
+ {
+ return Stream::factory(Utils::open($this->filename, $this->mode));
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/LimitStream.php b/vendor/guzzlehttp/streams/src/LimitStream.php
new file mode 100644
index 0000000..e9fad98
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/LimitStream.php
@@ -0,0 +1,161 @@
+stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof()
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit == -1) {
+ return false;
+ }
+
+ $tell = $this->stream->tell();
+ if ($tell === false) {
+ return false;
+ }
+
+ return $tell >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit == -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ return false;
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ return $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @return self
+ * @throws SeekException
+ */
+ public function setOffset($offset)
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if (!$this->stream->seek($offset)) {
+ if ($current > $offset) {
+ throw new SeekException($this, $offset);
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+ }
+
+ $this->offset = $offset;
+
+ return $this;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ * @return self
+ */
+ public function setLimit($limit)
+ {
+ $this->limit = $limit;
+
+ return $this;
+ }
+
+ public function read($length)
+ {
+ if ($this->limit == -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php b/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php
new file mode 100644
index 0000000..c1433ad
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php
@@ -0,0 +1,11 @@
+stream->attach($stream);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/NullStream.php b/vendor/guzzlehttp/streams/src/NullStream.php
new file mode 100644
index 0000000..41ee776
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/NullStream.php
@@ -0,0 +1,78 @@
+source = $source;
+ $this->size = isset($options['size']) ? $options['size'] : null;
+ $this->metadata = isset($options['metadata']) ? $options['metadata'] : [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString()
+ {
+ return Utils::copyToString($this);
+ }
+
+ public function close()
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = false;
+ $this->source = null;
+ }
+
+ public function attach($stream)
+ {
+ throw new CannotAttachException();
+ }
+
+ public function getSize()
+ {
+ return $this->size;
+ }
+
+ public function tell()
+ {
+ return $this->tellPos;
+ }
+
+ public function eof()
+ {
+ return !$this->source;
+ }
+
+ public function isSeekable()
+ {
+ return false;
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return false;
+ }
+
+ public function isWritable()
+ {
+ return false;
+ }
+
+ public function write($string)
+ {
+ return false;
+ }
+
+ public function isReadable()
+ {
+ return true;
+ }
+
+ public function read($length)
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents()
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return isset($this->metadata[$key]) ? $this->metadata[$key] : null;
+ }
+
+ private function pump($length)
+ {
+ if ($this->source) {
+ do {
+ $data = call_user_func($this->source, $length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/Stream.php b/vendor/guzzlehttp/streams/src/Stream.php
new file mode 100644
index 0000000..7adbc5e
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/Stream.php
@@ -0,0 +1,261 @@
+ [
+ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
+ 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
+ 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a+' => true
+ ],
+ 'write' => [
+ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
+ 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
+ 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
+ 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
+ ]
+ ];
+
+ /**
+ * Create a new stream based on the input type.
+ *
+ * This factory accepts the same associative array of options as described
+ * in the constructor.
+ *
+ * @param resource|string|StreamInterface $resource Entity body data
+ * @param array $options Additional options
+ *
+ * @return Stream
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+ public static function factory($resource = '', array $options = [])
+ {
+ $type = gettype($resource);
+
+ if ($type == 'string') {
+ $stream = fopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, $resource);
+ fseek($stream, 0);
+ }
+ return new self($stream, $options);
+ }
+
+ if ($type == 'resource') {
+ return new self($resource, $options);
+ }
+
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ }
+
+ if ($type == 'object' && method_exists($resource, '__toString')) {
+ return self::factory((string) $resource, $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ if ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+ return $result;
+ }, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: ' . $type);
+ }
+
+ /**
+ * This constructor accepts an associative array of options.
+ *
+ * - size: (int) If a read stream would otherwise have an indeterminate
+ * size, but the size is known due to foreknownledge, then you can
+ * provide that size, in bytes.
+ * - metadata: (array) Any additional metadata to return when the metadata
+ * of the stream is accessed.
+ *
+ * @param resource $stream Stream resource to wrap.
+ * @param array $options Associative array of options.
+ *
+ * @throws \InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream, $options = [])
+ {
+ if (!is_resource($stream)) {
+ throw new \InvalidArgumentException('Stream must be a resource');
+ }
+
+ if (isset($options['size'])) {
+ $this->size = $options['size'];
+ }
+
+ $this->customMetadata = isset($options['metadata'])
+ ? $options['metadata']
+ : [];
+
+ $this->attach($stream);
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString()
+ {
+ if (!$this->stream) {
+ return '';
+ }
+
+ $this->seek(0);
+
+ return (string) stream_get_contents($this->stream);
+ }
+
+ public function getContents()
+ {
+ return $this->stream ? stream_get_contents($this->stream) : '';
+ }
+
+ public function close()
+ {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $result = $this->stream;
+ $this->stream = $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function attach($stream)
+ {
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
+ $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ public function getSize()
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!$this->stream) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (isset($stats['size'])) {
+ $this->size = $stats['size'];
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable()
+ {
+ return $this->readable;
+ }
+
+ public function isWritable()
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable()
+ {
+ return $this->seekable;
+ }
+
+ public function eof()
+ {
+ return !$this->stream || feof($this->stream);
+ }
+
+ public function tell()
+ {
+ return $this->stream ? ftell($this->stream) : false;
+ }
+
+ public function setSize($size)
+ {
+ $this->size = $size;
+
+ return $this;
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return $this->seekable
+ ? fseek($this->stream, $offset, $whence) === 0
+ : false;
+ }
+
+ public function read($length)
+ {
+ return $this->readable ? fread($this->stream, $length) : false;
+ }
+
+ public function write($string)
+ {
+ // We can't know the size after writing anything
+ $this->size = null;
+
+ return $this->writable ? fwrite($this->stream, $string) : false;
+ }
+
+ public function getMetadata($key = null)
+ {
+ if (!$this->stream) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return isset($meta[$key]) ? $meta[$key] : null;
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php
new file mode 100644
index 0000000..39c19c5
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php
@@ -0,0 +1,143 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ */
+ public function __get($name)
+ {
+ if ($name == 'stream') {
+ $this->stream = $this->createStream();
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString()
+ {
+ try {
+ $this->seek(0);
+ return $this->getContents();
+ } catch (\Exception $e) {
+ // Really, PHP? https://bugs.php.net/bug.php?id=53648
+ trigger_error('StreamDecorator::__toString exception: '
+ . (string) $e, E_USER_ERROR);
+ return '';
+ }
+ }
+
+ public function getContents()
+ {
+ return Utils::copyToString($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @param string $method Missing method name
+ * @param array $args Method arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, array $args)
+ {
+ $result = call_user_func_array(array($this->stream, $method), $args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close()
+ {
+ $this->stream->close();
+ }
+
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function attach($stream)
+ {
+ throw new CannotAttachException();
+ }
+
+ public function getSize()
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof()
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell()
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable()
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable()
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable()
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ return $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length)
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string)
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @return StreamInterface
+ * @throws \BadMethodCallException
+ */
+ protected function createStream()
+ {
+ throw new \BadMethodCallException('createStream() not implemented in '
+ . get_class($this));
+ }
+}
diff --git a/vendor/guzzlehttp/streams/src/StreamInterface.php b/vendor/guzzlehttp/streams/src/StreamInterface.php
new file mode 100644
index 0000000..fd19c6f
--- /dev/null
+++ b/vendor/guzzlehttp/streams/src/StreamInterface.php
@@ -0,0 +1,159 @@
+eof()) {
+ $buf = $stream->read(1048576);
+ if ($buf === false) {
+ break;
+ }
+ $buffer .= $buf;
+ }
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ if ($buf === false) {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ */
+ public static function copyToStream(
+ StreamInterface $source,
+ StreamInterface $dest,
+ $maxLen = -1
+ ) {
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read(1048576))) {
+ break;
+ }
+ }
+ return;
+ }
+
+ $bytes = 0;
+ while (!$source->eof()) {
+ $buf = $source->read($maxLen - $bytes);
+ if (!($len = strlen($buf))) {
+ break;
+ }
+ $bytes += $len;
+ $dest->write($buf);
+ if ($bytes == $maxLen) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Calculate a hash of a Stream
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @return string Returns the hash of the stream
+ * @throws SeekException
+ */
+ public static function hash(
+ StreamInterface $stream,
+ $algo,
+ $rawOutput = false
+ ) {
+ $pos = $stream->tell();
+
+ if ($pos > 0 && !$stream->seek(0)) {
+ throw new SeekException($stream);
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, (bool) $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+ }
+
+ /**
+ * Read a line from the stream up to the maximum allowed buffer length
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int $maxLength Maximum buffer length
+ *
+ * @return string|bool
+ */
+ public static function readline(StreamInterface $stream, $maxLength = null)
+ {
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ if (false === ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte == PHP_EOL || ++$size == $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Alias of GuzzleHttp\Stream\Stream::factory.
+ *
+ * @param mixed $resource Resource to create
+ * @param array $options Associative array of stream options defined in
+ * {@see \GuzzleHttp\Stream\Stream::__construct}
+ *
+ * @return StreamInterface
+ *
+ * @see GuzzleHttp\Stream\Stream::factory
+ * @see GuzzleHttp\Stream\Stream::__construct
+ */
+ public static function create($resource, array $options = [])
+ {
+ return Stream::factory($resource, $options);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/AppendStreamTest.php b/vendor/guzzlehttp/streams/tests/AppendStreamTest.php
new file mode 100644
index 0000000..78798d9
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/AppendStreamTest.php
@@ -0,0 +1,178 @@
+getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['isReadable'])
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $a->addStream($s);
+ }
+
+ public function testValidatesSeekType()
+ {
+ $a = new AppendStream();
+ $this->assertFalse($a->seek(100, SEEK_CUR));
+ }
+
+ public function testTriesToRewindOnSeek()
+ {
+ $a = new AppendStream();
+ $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['isReadable', 'seek', 'isSeekable'])
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(true));
+ $s->expects($this->once())
+ ->method('seek')
+ ->will($this->returnValue(false));
+ $a->addStream($s);
+ $this->assertFalse($a->seek(10));
+ }
+
+ public function testSeeksToPositionByReading()
+ {
+ $a = new AppendStream([
+ Stream::factory('foo'),
+ Stream::factory('bar'),
+ Stream::factory('baz'),
+ ]);
+
+ $this->assertTrue($a->seek(3));
+ $this->assertEquals(3, $a->tell());
+ $this->assertEquals('bar', $a->read(3));
+ $a->seek(6);
+ $this->assertEquals(6, $a->tell());
+ $this->assertEquals('baz', $a->read(3));
+ }
+
+ public function testDetachesEachStream()
+ {
+ $s1 = Stream::factory('foo');
+ $s2 = Stream::factory('foo');
+ $a = new AppendStream([$s1, $s2]);
+ $this->assertSame('foofoo', (string) $a);
+ $a->detach();
+ $this->assertSame('', (string) $a);
+ $this->assertSame(0, $a->getSize());
+ }
+
+ public function testClosesEachStream()
+ {
+ $s1 = Stream::factory('foo');
+ $a = new AppendStream([$s1]);
+ $a->close();
+ $this->assertSame('', (string) $a);
+ }
+
+ public function testIsNotWritable()
+ {
+ $a = new AppendStream([Stream::factory('foo')]);
+ $this->assertFalse($a->isWritable());
+ $this->assertTrue($a->isSeekable());
+ $this->assertTrue($a->isReadable());
+ $this->assertFalse($a->write('foo'));
+ }
+
+ public function testDoesNotNeedStreams()
+ {
+ $a = new AppendStream();
+ $this->assertEquals('', (string) $a);
+ }
+
+ public function testCanReadFromMultipleStreams()
+ {
+ $a = new AppendStream([
+ Stream::factory('foo'),
+ Stream::factory('bar'),
+ Stream::factory('baz'),
+ ]);
+ $this->assertFalse($a->eof());
+ $this->assertSame(0, $a->tell());
+ $this->assertEquals('foo', $a->read(3));
+ $this->assertEquals('bar', $a->read(3));
+ $this->assertEquals('baz', $a->read(3));
+ $this->assertTrue($a->eof());
+ $this->assertSame(9, $a->tell());
+ $this->assertEquals('foobarbaz', (string) $a);
+ }
+
+ public function testCanDetermineSizeFromMultipleStreams()
+ {
+ $a = new AppendStream([
+ Stream::factory('foo'),
+ Stream::factory('bar')
+ ]);
+ $this->assertEquals(6, $a->getSize());
+
+ $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['isSeekable', 'isReadable'])
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('isSeekable')
+ ->will($this->returnValue(null));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $a->addStream($s);
+ $this->assertNull($a->getSize());
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['read', 'isReadable', 'eof'])
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \RuntimeException('foo')));
+ $s->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(true));
+ $s->expects($this->any())
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $a = new AppendStream([$s]);
+ $this->assertFalse($a->eof());
+ $this->assertSame('', (string) $a);
+ }
+
+ public function testCanDetach()
+ {
+ $s = new AppendStream();
+ $s->detach();
+ }
+
+ public function testReturnsEmptyMetadata()
+ {
+ $s = new AppendStream();
+ $this->assertEquals([], $s->getMetadata());
+ $this->assertNull($s->getMetadata('foo'));
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException
+ */
+ public function testCannotAttach()
+ {
+ $p = new AppendStream();
+ $p->attach('a');
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php b/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php
new file mode 100644
index 0000000..8c78995
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php
@@ -0,0 +1,186 @@
+ function () { return false; }]
+ ));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage Buffer must be readable and writable
+ */
+ public function testValidatesWritableBuffer()
+ {
+ new AsyncReadStream(FnStream::decorate(
+ Stream::factory(),
+ ['isWritable' => function () { return false; }]
+ ));
+ }
+
+ public function testValidatesHwmMetadata()
+ {
+ $a = new AsyncReadStream(Stream::factory(), [
+ 'drain' => function() {}
+ ]);
+ $this->assertNull($this->readAttribute($a, 'drain'));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage pump must be callable
+ */
+ public function testValidatesPumpIsCallable()
+ {
+ new AsyncReadStream(new BufferStream(), ['pump' => true]);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage drain must be callable
+ */
+ public function testValidatesDrainIsCallable()
+ {
+ new AsyncReadStream(new BufferStream(), ['drain' => true]);
+ }
+
+ public function testCanInitialize()
+ {
+ $buffer = new BufferStream();
+ $a = new AsyncReadStream($buffer, [
+ 'size' => 10,
+ 'drain' => function () {},
+ 'pump' => function () {},
+ ]);
+ $this->assertSame($buffer, $this->readAttribute($a, 'stream'));
+ $this->assertTrue(is_callable($this->readAttribute($a, 'drain')));
+ $this->assertTrue(is_callable($this->readAttribute($a, 'pump')));
+ $this->assertTrue($a->isReadable());
+ $this->assertFalse($a->isSeekable());
+ $this->assertFalse($a->isWritable());
+ $this->assertFalse($a->write('foo'));
+ $this->assertEquals(10, $a->getSize());
+ }
+
+ public function testReadsFromBufferWithNoDrainOrPump()
+ {
+ $buffer = new BufferStream();
+ $a = new AsyncReadStream($buffer);
+ $buffer->write('foo');
+ $this->assertNull($a->getSize());
+ $this->assertEquals('foo', $a->read(10));
+ $this->assertEquals('', $a->read(10));
+ }
+
+ public function testCallsPumpForMoreDataWhenRequested()
+ {
+ $called = 0;
+ $buffer = new BufferStream();
+ $a = new AsyncReadStream($buffer, [
+ 'pump' => function ($size) use (&$called) {
+ $called++;
+ return str_repeat('.', $size);
+ }
+ ]);
+ $buffer->write('foobar');
+ $this->assertEquals('foo', $a->read(3));
+ $this->assertEquals(0, $called);
+ $this->assertEquals('bar.....', $a->read(8));
+ $this->assertEquals(1, $called);
+ $this->assertEquals('..', $a->read(2));
+ $this->assertEquals(2, $called);
+ }
+
+ public function testCallsDrainWhenNeeded()
+ {
+ $called = 0;
+ $buffer = new BufferStream(5);
+ $a = new AsyncReadStream($buffer, [
+ 'drain' => function (BufferStream $b) use (&$called, $buffer) {
+ $this->assertSame($b, $buffer);
+ $called++;
+ }
+ ]);
+
+ $buffer->write('foobar');
+ $this->assertEquals(6, $buffer->getSize());
+ $this->assertEquals(0, $called);
+
+ $a->read(3);
+ $this->assertTrue($this->readAttribute($a, 'needsDrain'));
+ $this->assertEquals(3, $buffer->getSize());
+ $this->assertEquals(0, $called);
+
+ $a->read(3);
+ $this->assertEquals(0, $buffer->getSize());
+ $this->assertFalse($this->readAttribute($a, 'needsDrain'));
+ $this->assertEquals(1, $called);
+ }
+
+ public function testCreatesBufferWithNoConfig()
+ {
+ list($buffer, $async) = AsyncReadStream::create();
+ $this->assertInstanceOf('GuzzleHttp\Stream\BufferStream', $buffer);
+ $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async);
+ }
+
+ public function testCreatesBufferWithSpecifiedBuffer()
+ {
+ $buf = new BufferStream();
+ list($buffer, $async) = AsyncReadStream::create(['buffer' => $buf]);
+ $this->assertSame($buf, $buffer);
+ $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async);
+ }
+
+ public function testCreatesNullStream()
+ {
+ list($buffer, $async) = AsyncReadStream::create(['max_buffer' => 0]);
+ $this->assertInstanceOf('GuzzleHttp\Stream\NullStream', $buffer);
+ $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async);
+ }
+
+ public function testCreatesDroppingStream()
+ {
+ list($buffer, $async) = AsyncReadStream::create(['max_buffer' => 5]);
+ $this->assertInstanceOf('GuzzleHttp\Stream\DroppingStream', $buffer);
+ $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async);
+ $buffer->write('12345678910');
+ $this->assertEquals(5, $buffer->getSize());
+ }
+
+ public function testCreatesOnWriteStream()
+ {
+ $c = 0;
+ $b = new BufferStream();
+ list($buffer, $async) = AsyncReadStream::create([
+ 'buffer' => $b,
+ 'write' => function (BufferStream $buf, $data) use (&$c, $b) {
+ $this->assertSame($buf, $b);
+ $this->assertEquals('foo', $data);
+ $c++;
+ }
+ ]);
+ $this->assertInstanceOf('GuzzleHttp\Stream\FnStream', $buffer);
+ $this->assertInstanceOf('GuzzleHttp\Stream\AsyncReadStream', $async);
+ $this->assertEquals(0, $c);
+ $this->assertEquals(3, $buffer->write('foo'));
+ $this->assertEquals(1, $c);
+ $this->assertEquals(3, $buffer->write('foo'));
+ $this->assertEquals(2, $c);
+ $this->assertEquals('foofoo', (string) $buffer);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/BufferStreamTest.php b/vendor/guzzlehttp/streams/tests/BufferStreamTest.php
new file mode 100644
index 0000000..f9bfea2
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/BufferStreamTest.php
@@ -0,0 +1,69 @@
+assertTrue($b->isReadable());
+ $this->assertTrue($b->isWritable());
+ $this->assertFalse($b->isSeekable());
+ $this->assertEquals(null, $b->getMetadata('foo'));
+ $this->assertEquals(10, $b->getMetadata('hwm'));
+ $this->assertEquals([], $b->getMetadata());
+ }
+
+ public function testRemovesReadDataFromBuffer()
+ {
+ $b = new BufferStream();
+ $this->assertEquals(3, $b->write('foo'));
+ $this->assertEquals(3, $b->getSize());
+ $this->assertFalse($b->eof());
+ $this->assertEquals('foo', $b->read(10));
+ $this->assertTrue($b->eof());
+ $this->assertEquals('', $b->read(10));
+ }
+
+ public function testCanCastToStringOrGetContents()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->write('baz');
+ $this->assertEquals('foo', $b->read(3));
+ $b->write('bar');
+ $this->assertEquals('bazbar', (string) $b);
+ $this->assertFalse($b->tell());
+ }
+
+ public function testDetachClearsBuffer()
+ {
+ $b = new BufferStream();
+ $b->write('foo');
+ $b->detach();
+ $this->assertEquals(0, $b->tell());
+ $this->assertTrue($b->eof());
+ $this->assertEquals(3, $b->write('abc'));
+ $this->assertEquals('abc', $b->read(10));
+ }
+
+ public function testExceedingHighwaterMarkReturnsFalseButStillBuffers()
+ {
+ $b = new BufferStream(5);
+ $this->assertEquals(3, $b->write('hi '));
+ $this->assertFalse($b->write('hello'));
+ $this->assertEquals('hi hello', (string) $b);
+ $this->assertEquals(4, $b->write('test'));
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException
+ */
+ public function testCannotAttach()
+ {
+ $p = new BufferStream();
+ $p->attach('a');
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/CachingStreamTest.php b/vendor/guzzlehttp/streams/tests/CachingStreamTest.php
new file mode 100644
index 0000000..ea969b3
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/CachingStreamTest.php
@@ -0,0 +1,136 @@
+decorated = Stream::factory('testing');
+ $this->body = new CachingStream($this->decorated);
+ }
+
+ public function tearDown()
+ {
+ $this->decorated->close();
+ $this->body->close();
+ }
+
+ public function testUsesRemoteSizeIfPossible()
+ {
+ $body = Stream::factory('test');
+ $caching = new CachingStream($body);
+ $this->assertEquals(4, $caching->getSize());
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Cannot seek to byte 10
+ */
+ public function testCannotSeekPastWhatHasBeenRead()
+ {
+ $this->body->seek(10);
+ }
+
+ public function testCannotUseSeekEnd()
+ {
+ $this->assertFalse($this->body->seek(2, SEEK_END));
+ }
+
+ public function testRewindUsesSeek()
+ {
+ $a = Stream::factory('foo');
+ $d = $this->getMockBuilder('GuzzleHttp\Stream\CachingStream')
+ ->setMethods(array('seek'))
+ ->setConstructorArgs(array($a))
+ ->getMock();
+ $d->expects($this->once())
+ ->method('seek')
+ ->with(0)
+ ->will($this->returnValue(true));
+ $d->seek(0);
+ }
+
+ public function testCanSeekToReadBytes()
+ {
+ $this->assertEquals('te', $this->body->read(2));
+ $this->body->seek(0);
+ $this->assertEquals('test', $this->body->read(4));
+ $this->assertEquals(4, $this->body->tell());
+ $this->body->seek(2);
+ $this->assertEquals(2, $this->body->tell());
+ $this->body->seek(2, SEEK_CUR);
+ $this->assertEquals(4, $this->body->tell());
+ $this->assertEquals('ing', $this->body->read(3));
+ }
+
+ public function testWritesToBufferStream()
+ {
+ $this->body->read(2);
+ $this->body->write('hi');
+ $this->body->seek(0);
+ $this->assertEquals('tehiing', (string) $this->body);
+ }
+
+ public function testSkipsOverwrittenBytes()
+ {
+ $decorated = Stream::factory(
+ implode("\n", array_map(function ($n) {
+ return str_pad($n, 4, '0', STR_PAD_LEFT);
+ }, range(0, 25)))
+ );
+
+ $body = new CachingStream($decorated);
+
+ $this->assertEquals("0000\n", Utils::readline($body));
+ $this->assertEquals("0001\n", Utils::readline($body));
+ // Write over part of the body yet to be read, so skip some bytes
+ $this->assertEquals(5, $body->write("TEST\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+ // Read, which skips bytes, then reads
+ $this->assertEquals("0003\n", Utils::readline($body));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("0004\n", Utils::readline($body));
+ $this->assertEquals("0005\n", Utils::readline($body));
+
+ // Overwrite part of the cached body (so don't skip any bytes)
+ $body->seek(5);
+ $this->assertEquals(5, $body->write("ABCD\n"));
+ $this->assertEquals(0, $this->readAttribute($body, 'skipReadBytes'));
+ $this->assertEquals("TEST\n", Utils::readline($body));
+ $this->assertEquals("0003\n", Utils::readline($body));
+ $this->assertEquals("0004\n", Utils::readline($body));
+ $this->assertEquals("0005\n", Utils::readline($body));
+ $this->assertEquals("0006\n", Utils::readline($body));
+ $this->assertEquals(5, $body->write("1234\n"));
+ $this->assertEquals(5, $this->readAttribute($body, 'skipReadBytes'));
+
+ // Seek to 0 and ensure the overwritten bit is replaced
+ $body->seek(0);
+ $this->assertEquals("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", $body->read(50));
+
+ // Ensure that casting it to a string does not include the bit that was overwritten
+ $this->assertContains("0000\nABCD\nTEST\n0003\n0004\n0005\n0006\n1234\n0008\n0009\n", (string) $body);
+ }
+
+ public function testClosesBothStreams()
+ {
+ $s = fopen('php://temp', 'r');
+ $a = Stream::factory($s);
+ $d = new CachingStream($a);
+ $d->close();
+ $this->assertFalse(is_resource($s));
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php b/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php
new file mode 100644
index 0000000..bb2cb22
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php
@@ -0,0 +1,26 @@
+assertEquals(3, $drop->write('hel'));
+ $this->assertFalse($drop->write('lo'));
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('hello', $drop->read(5));
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('12345678910');
+ $this->assertEquals(5, $stream->getSize());
+ $this->assertEquals(5, $drop->getSize());
+ $this->assertEquals('12345', (string) $drop);
+ $this->assertEquals(0, $drop->getSize());
+ $drop->write('hello');
+ $this->assertFalse($drop->write('test'));
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php b/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php
new file mode 100644
index 0000000..fd8cd1a
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php
@@ -0,0 +1,16 @@
+assertSame($s, $e->getStream());
+ $this->assertContains('10', $e->getMessage());
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/FnStreamTest.php b/vendor/guzzlehttp/streams/tests/FnStreamTest.php
new file mode 100644
index 0000000..6cc336b
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/FnStreamTest.php
@@ -0,0 +1,89 @@
+seek(1);
+ }
+
+ public function testProxiesToFunction()
+ {
+ $s = new FnStream([
+ 'read' => function ($len) {
+ $this->assertEquals(3, $len);
+ return 'foo';
+ }
+ ]);
+
+ $this->assertEquals('foo', $s->read(3));
+ }
+
+ public function testCanCloseOnDestruct()
+ {
+ $called = false;
+ $s = new FnStream([
+ 'close' => function () use (&$called) {
+ $called = true;
+ }
+ ]);
+ unset($s);
+ $this->assertTrue($called);
+ }
+
+ public function testDoesNotRequireClose()
+ {
+ $s = new FnStream([]);
+ unset($s);
+ }
+
+ public function testDecoratesStream()
+ {
+ $a = Stream::factory('foo');
+ $b = FnStream::decorate($a, []);
+ $this->assertEquals(3, $b->getSize());
+ $this->assertEquals($b->isWritable(), true);
+ $this->assertEquals($b->isReadable(), true);
+ $this->assertEquals($b->isSeekable(), true);
+ $this->assertEquals($b->read(3), 'foo');
+ $this->assertEquals($b->tell(), 3);
+ $this->assertEquals($a->tell(), 3);
+ $this->assertEquals($b->eof(), true);
+ $this->assertEquals($a->eof(), true);
+ $b->seek(0);
+ $this->assertEquals('foo', (string) $b);
+ $b->seek(0);
+ $this->assertEquals('foo', $b->getContents());
+ $this->assertEquals($a->getMetadata(), $b->getMetadata());
+ $b->seek(0, SEEK_END);
+ $b->write('bar');
+ $this->assertEquals('foobar', (string) $b);
+ $this->assertInternalType('resource', $b->detach());
+ $b->close();
+ }
+
+ public function testDecoratesWithCustomizations()
+ {
+ $called = false;
+ $a = Stream::factory('foo');
+ $b = FnStream::decorate($a, [
+ 'read' => function ($len) use (&$called, $a) {
+ $called = true;
+ return $a->read($len);
+ }
+ ]);
+ $this->assertEquals('foo', $b->read(3));
+ $this->assertTrue($called);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php b/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php
new file mode 100644
index 0000000..33c3ecc
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php
@@ -0,0 +1,99 @@
+assertSame('foo', fread($handle, 3));
+ $this->assertSame(3, ftell($handle));
+ $this->assertSame(3, fwrite($handle, 'bar'));
+ $this->assertSame(0, fseek($handle, 0));
+ $this->assertSame('foobar', fread($handle, 6));
+ $this->assertTrue(feof($handle));
+
+ // This fails on HHVM for some reason
+ if (!defined('HHVM_VERSION')) {
+ $this->assertEquals([
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 33206,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 6,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ 0 => 0,
+ 1 => 0,
+ 2 => 33206,
+ 3 => 0,
+ 4 => 0,
+ 5 => 0,
+ 6 => 0,
+ 7 => 6,
+ 8 => 0,
+ 9 => 0,
+ 10 => 0,
+ 11 => 0,
+ 12 => 0,
+ ], fstat($handle));
+ }
+
+ $this->assertTrue(fclose($handle));
+ $this->assertSame('foobar', (string) $stream);
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testValidatesStream()
+ {
+ $stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['isReadable', 'isWritable'])
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(false));
+ GuzzleStreamWrapper::getResource($stream);
+ }
+
+ /**
+ * @expectedException \PHPUnit_Framework_Error_Warning
+ */
+ public function testReturnsFalseWhenStreamDoesNotExist()
+ {
+ fopen('guzzle://foo', 'r');
+ }
+
+ public function testCanOpenReadonlyStream()
+ {
+ $stream = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['isReadable', 'isWritable'])
+ ->getMockForAbstractClass();
+ $stream->expects($this->once())
+ ->method('isReadable')
+ ->will($this->returnValue(false));
+ $stream->expects($this->once())
+ ->method('isWritable')
+ ->will($this->returnValue(true));
+ $r = GuzzleStreamWrapper::getResource($stream);
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/InflateStreamTest.php b/vendor/guzzlehttp/streams/tests/InflateStreamTest.php
new file mode 100644
index 0000000..ead9356
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/InflateStreamTest.php
@@ -0,0 +1,16 @@
+assertEquals('test', (string) $b);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php b/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php
new file mode 100644
index 0000000..79e0078
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php
@@ -0,0 +1,64 @@
+fname = tempnam('/tmp', 'tfile');
+
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function tearDown()
+ {
+ if (file_exists($this->fname)) {
+ unlink($this->fname);
+ }
+ }
+
+ public function testOpensLazily()
+ {
+ $l = new LazyOpenStream($this->fname, 'w+');
+ $l->write('foo');
+ $this->assertInternalType('array', $l->getMetadata());
+ $this->assertFileExists($this->fname);
+ $this->assertEquals('foo', file_get_contents($this->fname));
+ $this->assertEquals('foo', (string) $l);
+ }
+
+ public function testProxiesToFile()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $this->assertEquals('foo', $l->read(4));
+ $this->assertTrue($l->eof());
+ $this->assertEquals(3, $l->tell());
+ $this->assertTrue($l->isReadable());
+ $this->assertTrue($l->isSeekable());
+ $this->assertFalse($l->isWritable());
+ $l->seek(1);
+ $this->assertEquals('oo', $l->getContents());
+ $this->assertEquals('foo', (string) $l);
+ $this->assertEquals(3, $l->getSize());
+ $this->assertInternalType('array', $l->getMetadata());
+ $l->close();
+ }
+
+ public function testDetachesUnderlyingStream()
+ {
+ file_put_contents($this->fname, 'foo');
+ $l = new LazyOpenStream($this->fname, 'r');
+ $r = $l->detach();
+ $this->assertInternalType('resource', $r);
+ fseek($r, 0);
+ $this->assertEquals('foo', stream_get_contents($r));
+ fclose($r);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/LimitStreamTest.php b/vendor/guzzlehttp/streams/tests/LimitStreamTest.php
new file mode 100644
index 0000000..efb1dc5
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/LimitStreamTest.php
@@ -0,0 +1,133 @@
+decorated = Stream::factory(fopen(__FILE__, 'r'));
+ $this->body = new LimitStream($this->decorated, 10, 3);
+ }
+
+ public function testReturnsSubset()
+ {
+ $body = new LimitStream(Stream::factory('foo'), -1, 1);
+ $this->assertEquals('oo', (string) $body);
+ $this->assertTrue($body->eof());
+ $body->seek(0);
+ $this->assertFalse($body->eof());
+ $this->assertEquals('oo', $body->read(100));
+ $this->assertTrue($body->eof());
+ }
+
+ public function testReturnsSubsetWhenCastToString()
+ {
+ $body = Stream::factory('foo_baz_bar');
+ $limited = new LimitStream($body, 3, 4);
+ $this->assertEquals('baz', (string) $limited);
+ }
+
+ public function testReturnsSubsetOfEmptyBodyWhenCastToString()
+ {
+ $body = Stream::factory('');
+ $limited = new LimitStream($body, 0, 10);
+ $this->assertEquals('', (string) $limited);
+ }
+
+ public function testSeeksWhenConstructed()
+ {
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ }
+
+ public function testAllowsBoundedSeek()
+ {
+ $this->assertEquals(true, $this->body->seek(100));
+ $this->assertEquals(10, $this->body->tell());
+ $this->assertEquals(13, $this->decorated->tell());
+ $this->assertEquals(true, $this->body->seek(0));
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ $this->assertEquals(false, $this->body->seek(-10));
+ $this->assertEquals(0, $this->body->tell());
+ $this->assertEquals(3, $this->decorated->tell());
+ $this->assertEquals(true, $this->body->seek(5));
+ $this->assertEquals(5, $this->body->tell());
+ $this->assertEquals(8, $this->decorated->tell());
+ $this->assertEquals(false, $this->body->seek(1000, SEEK_END));
+ }
+
+ public function testReadsOnlySubsetOfData()
+ {
+ $data = $this->body->read(100);
+ $this->assertEquals(10, strlen($data));
+ $this->assertFalse($this->body->read(1000));
+
+ $this->body->setOffset(10);
+ $newData = $this->body->read(100);
+ $this->assertEquals(10, strlen($newData));
+ $this->assertNotSame($data, $newData);
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Stream\Exception\SeekException
+ * @expectedExceptionMessage Could not seek the stream to position 2
+ */
+ public function testThrowsWhenCurrentGreaterThanOffsetSeek()
+ {
+ $a = Stream::factory('foo_bar');
+ $b = new NoSeekStream($a);
+ $c = new LimitStream($b);
+ $a->getContents();
+ $c->setOffset(2);
+ }
+
+ public function testClaimsConsumedWhenReadLimitIsReached()
+ {
+ $this->assertFalse($this->body->eof());
+ $this->body->read(1000);
+ $this->assertTrue($this->body->eof());
+ }
+
+ public function testContentLengthIsBounded()
+ {
+ $this->assertEquals(10, $this->body->getSize());
+ }
+
+ public function testGetContentsIsBasedOnSubset()
+ {
+ $body = new LimitStream(Stream::factory('foobazbar'), 3, 3);
+ $this->assertEquals('baz', $body->getContents());
+ }
+
+ public function testReturnsNullIfSizeCannotBeDetermined()
+ {
+ $a = new FnStream([
+ 'getSize' => function () { return null; },
+ 'tell' => function () { return 0; },
+ ]);
+ $b = new LimitStream($a);
+ $this->assertNull($b->getSize());
+ }
+
+ public function testLengthLessOffsetWhenNoLimitSize()
+ {
+ $a = Stream::factory('foo_bar');
+ $b = new LimitStream($a, -1, 4);
+ $this->assertEquals(3, $b->getSize());
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php b/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php
new file mode 100644
index 0000000..21b7c6d
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php
@@ -0,0 +1,41 @@
+getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['isSeekable', 'seek'])
+ ->getMockForAbstractClass();
+ $s->expects($this->never())->method('seek');
+ $s->expects($this->never())->method('isSeekable');
+ $wrapped = new NoSeekStream($s);
+ $this->assertFalse($wrapped->isSeekable());
+ $this->assertFalse($wrapped->seek(2));
+ }
+
+ public function testHandlesClose()
+ {
+ $s = Stream::factory('foo');
+ $wrapped = new NoSeekStream($s);
+ $wrapped->close();
+ $this->assertFalse($wrapped->write('foo'));
+ }
+
+ public function testCanAttach()
+ {
+ $s1 = Stream::factory('foo');
+ $s2 = Stream::factory('bar');
+ $wrapped = new NoSeekStream($s1);
+ $wrapped->attach($s2->detach());
+ $this->assertEquals('bar', (string) $wrapped);
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/NullStreamTest.php b/vendor/guzzlehttp/streams/tests/NullStreamTest.php
new file mode 100644
index 0000000..8e41431
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/NullStreamTest.php
@@ -0,0 +1,39 @@
+assertEquals('', $b->read(10));
+ $this->assertEquals(4, $b->write('test'));
+ $this->assertEquals('', (string) $b);
+ $this->assertNull($b->getMetadata('a'));
+ $this->assertEquals([], $b->getMetadata());
+ $this->assertEquals(0, $b->getSize());
+ $this->assertEquals('', $b->getContents());
+ $this->assertEquals(0, $b->tell());
+
+ $this->assertTrue($b->isReadable());
+ $this->assertTrue($b->isWritable());
+ $this->assertTrue($b->isSeekable());
+ $this->assertFalse($b->seek(10));
+
+ $this->assertTrue($b->eof());
+ $b->detach();
+ $this->assertTrue($b->eof());
+ $b->close();
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException
+ */
+ public function testCannotAttach()
+ {
+ $p = new NullStream();
+ $p->attach('a');
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/PumpStreamTest.php b/vendor/guzzlehttp/streams/tests/PumpStreamTest.php
new file mode 100644
index 0000000..2d20ce9
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/PumpStreamTest.php
@@ -0,0 +1,77 @@
+ ['foo' => 'bar'],
+ 'size' => 100
+ ]);
+
+ $this->assertEquals('bar', $p->getMetadata('foo'));
+ $this->assertEquals(['foo' => 'bar'], $p->getMetadata());
+ $this->assertEquals(100, $p->getSize());
+ }
+
+ public function testCanReadFromCallable()
+ {
+ $p = Stream::factory(function ($size) {
+ return 'a';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals(1, $p->tell());
+ $this->assertEquals('aaaaa', $p->read(5));
+ $this->assertEquals(6, $p->tell());
+ }
+
+ public function testStoresExcessDataInBuffer()
+ {
+ $called = [];
+ $p = Stream::factory(function ($size) use (&$called) {
+ $called[] = $size;
+ return 'abcdef';
+ });
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('cdef', $p->read(4));
+ $this->assertEquals('abcdefabc', $p->read(9));
+ $this->assertEquals([1, 9, 3], $called);
+ }
+
+ public function testInifiniteStreamWrappedInLimitStream()
+ {
+ $p = Stream::factory(function () { return 'a'; });
+ $s = new LimitStream($p, 5);
+ $this->assertEquals('aaaaa', (string) $s);
+ }
+
+ public function testDescribesCapabilities()
+ {
+ $p = Stream::factory(function () {});
+ $this->assertTrue($p->isReadable());
+ $this->assertFalse($p->isSeekable());
+ $this->assertFalse($p->isWritable());
+ $this->assertNull($p->getSize());
+ $this->assertFalse($p->write('aa'));
+ $this->assertEquals('', $p->getContents());
+ $this->assertEquals('', (string) $p);
+ $p->close();
+ $this->assertEquals('', $p->read(10));
+ $this->assertTrue($p->eof());
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException
+ */
+ public function testCannotAttach()
+ {
+ $p = Stream::factory(function () {});
+ $p->attach('a');
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php b/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php
new file mode 100644
index 0000000..2ba79ad
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php
@@ -0,0 +1,147 @@
+c = fopen('php://temp', 'r+');
+ fwrite($this->c, 'foo');
+ fseek($this->c, 0);
+ $this->a = Stream::factory($this->c);
+ $this->b = new Str($this->a);
+ }
+
+ public function testCatchesExceptionsWhenCastingToString()
+ {
+ $s = $this->getMockBuilder('GuzzleHttp\Stream\StreamInterface')
+ ->setMethods(['read'])
+ ->getMockForAbstractClass();
+ $s->expects($this->once())
+ ->method('read')
+ ->will($this->throwException(new \Exception('foo')));
+ $msg = '';
+ set_error_handler(function ($errNo, $str) use (&$msg) { $msg = $str; });
+ echo new Str($s);
+ restore_error_handler();
+ $this->assertContains('foo', $msg);
+ }
+
+ public function testToString()
+ {
+ $this->assertEquals('foo', (string) $this->b);
+ }
+
+ public function testHasSize()
+ {
+ $this->assertEquals(3, $this->b->getSize());
+ $this->assertSame($this->b, $this->b->setSize(2));
+ $this->assertEquals(2, $this->b->getSize());
+ }
+
+ public function testReads()
+ {
+ $this->assertEquals('foo', $this->b->read(10));
+ }
+
+ public function testCheckMethods()
+ {
+ $this->assertEquals($this->a->isReadable(), $this->b->isReadable());
+ $this->assertEquals($this->a->isWritable(), $this->b->isWritable());
+ $this->assertEquals($this->a->isSeekable(), $this->b->isSeekable());
+ }
+
+ public function testSeeksAndTells()
+ {
+ $this->assertTrue($this->b->seek(1));
+ $this->assertEquals(1, $this->a->tell());
+ $this->assertEquals(1, $this->b->tell());
+ $this->assertTrue($this->b->seek(0));
+ $this->assertEquals(0, $this->a->tell());
+ $this->assertEquals(0, $this->b->tell());
+ $this->assertTrue($this->b->seek(0, SEEK_END));
+ $this->assertEquals(3, $this->a->tell());
+ $this->assertEquals(3, $this->b->tell());
+ }
+
+ public function testGetsContents()
+ {
+ $this->assertEquals('foo', $this->b->getContents());
+ $this->assertEquals('', $this->b->getContents());
+ $this->b->seek(1);
+ $this->assertEquals('oo', $this->b->getContents(1));
+ }
+
+ public function testCloses()
+ {
+ $this->b->close();
+ $this->assertFalse(is_resource($this->c));
+ }
+
+ public function testDetaches()
+ {
+ $this->b->detach();
+ $this->assertFalse($this->b->isReadable());
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Stream\Exception\CannotAttachException
+ */
+ public function testCannotAttachByDefault()
+ {
+ $this->b->attach('a');
+ }
+
+ public function testWrapsMetadata()
+ {
+ $this->assertSame($this->b->getMetadata(), $this->a->getMetadata());
+ $this->assertSame($this->b->getMetadata('uri'), $this->a->getMetadata('uri'));
+ }
+
+ public function testWrapsWrites()
+ {
+ $this->b->seek(0, SEEK_END);
+ $this->b->write('foo');
+ $this->assertEquals('foofoo', (string) $this->a);
+ }
+
+ /**
+ * @expectedException \UnexpectedValueException
+ */
+ public function testThrowsWithInvalidGetter()
+ {
+ $this->b->foo;
+ }
+
+ /**
+ * @expectedException \BadMethodCallException
+ */
+ public function testThrowsWhenGetterNotImplemented()
+ {
+ $s = new BadStream();
+ $s->stream;
+ }
+}
+
+class BadStream
+{
+ use StreamDecoratorTrait;
+
+ public function __construct() {}
+}
diff --git a/vendor/guzzlehttp/streams/tests/StreamTest.php b/vendor/guzzlehttp/streams/tests/StreamTest.php
new file mode 100644
index 0000000..2985bfb
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/StreamTest.php
@@ -0,0 +1,252 @@
+assertTrue($stream->isReadable());
+ $this->assertTrue($stream->isWritable());
+ $this->assertTrue($stream->isSeekable());
+ $this->assertEquals('php://temp', $stream->getMetadata('uri'));
+ $this->assertInternalType('array', $stream->getMetadata());
+ $this->assertEquals(4, $stream->getSize());
+ $this->assertFalse($stream->eof());
+ $stream->close();
+ }
+
+ public function testStreamClosesHandleOnDestruct()
+ {
+ $handle = fopen('php://temp', 'r');
+ $stream = new Stream($handle);
+ unset($stream);
+ $this->assertFalse(is_resource($handle));
+ }
+
+ public function testConvertsToString()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('data', (string) $stream);
+ $this->assertEquals('data', (string) $stream);
+ $stream->close();
+ }
+
+ public function testGetsContents()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertEquals('', $stream->getContents());
+ $stream->seek(0);
+ $this->assertEquals('data', $stream->getContents());
+ $this->assertEquals('', $stream->getContents());
+ }
+
+ public function testChecksEof()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $this->assertFalse($stream->eof());
+ $stream->read(4);
+ $this->assertTrue($stream->eof());
+ $stream->close();
+ }
+
+ public function testAllowsSettingManualSize()
+ {
+ $handle = fopen('php://temp', 'w+');
+ fwrite($handle, 'data');
+ $stream = new Stream($handle);
+ $stream->setSize(10);
+ $this->assertEquals(10, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testGetSize()
+ {
+ $size = filesize(__FILE__);
+ $handle = fopen(__FILE__, 'r');
+ $stream = new Stream($handle);
+ $this->assertEquals($size, $stream->getSize());
+ // Load from cache
+ $this->assertEquals($size, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testEnsuresSizeIsConsistent()
+ {
+ $h = fopen('php://temp', 'w+');
+ $this->assertEquals(3, fwrite($h, 'foo'));
+ $stream = new Stream($h);
+ $this->assertEquals(3, $stream->getSize());
+ $this->assertEquals(4, $stream->write('test'));
+ $this->assertEquals(7, $stream->getSize());
+ $this->assertEquals(7, $stream->getSize());
+ $stream->close();
+ }
+
+ public function testProvidesStreamPosition()
+ {
+ $handle = fopen('php://temp', 'w+');
+ $stream = new Stream($handle);
+ $this->assertEquals(0, $stream->tell());
+ $stream->write('foo');
+ $this->assertEquals(3, $stream->tell());
+ $stream->seek(1);
+ $this->assertEquals(1, $stream->tell());
+ $this->assertSame(ftell($handle), $stream->tell());
+ $stream->close();
+ }
+
+ public function testKeepsPositionOfResource()
+ {
+ $h = fopen(__FILE__, 'r');
+ fseek($h, 10);
+ $stream = Stream::factory($h);
+ $this->assertEquals(10, $stream->tell());
+ $stream->close();
+ }
+
+ public function testCanDetachAndAttachStream()
+ {
+ $r = fopen('php://temp', 'w+');
+ $stream = new Stream($r);
+ $stream->write('foo');
+ $this->assertTrue($stream->isReadable());
+ $this->assertSame($r, $stream->detach());
+ $this->assertNull($stream->detach());
+
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->read(10));
+ $this->assertFalse($stream->isWritable());
+ $this->assertFalse($stream->write('bar'));
+ $this->assertFalse($stream->isSeekable());
+ $this->assertFalse($stream->seek(10));
+ $this->assertFalse($stream->tell());
+ $this->assertTrue($stream->eof());
+ $this->assertNull($stream->getSize());
+ $this->assertSame('', (string) $stream);
+ $this->assertSame('', $stream->getContents());
+
+ $stream->attach($r);
+ $stream->seek(0);
+ $this->assertEquals('foo', $stream->getContents());
+ $this->assertTrue($stream->isReadable());
+ $this->assertTrue($stream->isWritable());
+ $this->assertTrue($stream->isSeekable());
+
+ $stream->close();
+ }
+
+ public function testCloseClearProperties()
+ {
+ $handle = fopen('php://temp', 'r+');
+ $stream = new Stream($handle);
+ $stream->close();
+
+ $this->assertEmpty($stream->getMetadata());
+ $this->assertFalse($stream->isSeekable());
+ $this->assertFalse($stream->isReadable());
+ $this->assertFalse($stream->isWritable());
+ $this->assertNull($stream->getSize());
+ }
+
+ public function testCreatesWithFactory()
+ {
+ $stream = Stream::factory('foo');
+ $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $stream);
+ $this->assertEquals('foo', $stream->getContents());
+ $stream->close();
+ }
+
+ public function testFactoryCreatesFromEmptyString()
+ {
+ $s = Stream::factory();
+ $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s);
+ }
+
+ public function testFactoryCreatesFromResource()
+ {
+ $r = fopen(__FILE__, 'r');
+ $s = Stream::factory($r);
+ $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s);
+ $this->assertSame(file_get_contents(__FILE__), (string) $s);
+ }
+
+ public function testFactoryCreatesFromObjectWithToString()
+ {
+ $r = new HasToString();
+ $s = Stream::factory($r);
+ $this->assertInstanceOf('GuzzleHttp\Stream\Stream', $s);
+ $this->assertEquals('foo', (string) $s);
+ }
+
+ public function testCreatePassesThrough()
+ {
+ $s = Stream::factory('foo');
+ $this->assertSame($s, Stream::factory($s));
+ }
+
+ /**
+ * @expectedException \InvalidArgumentException
+ */
+ public function testThrowsExceptionForUnknown()
+ {
+ Stream::factory(new \stdClass());
+ }
+
+ public function testReturnsCustomMetadata()
+ {
+ $s = Stream::factory('foo', ['metadata' => ['hwm' => 3]]);
+ $this->assertEquals(3, $s->getMetadata('hwm'));
+ $this->assertArrayHasKey('hwm', $s->getMetadata());
+ }
+
+ public function testCanSetSize()
+ {
+ $s = Stream::factory('', ['size' => 10]);
+ $this->assertEquals(10, $s->getSize());
+ }
+
+ public function testCanCreateIteratorBasedStream()
+ {
+ $a = new \ArrayIterator(['foo', 'bar', '123']);
+ $p = Stream::factory($a);
+ $this->assertInstanceOf('GuzzleHttp\Stream\PumpStream', $p);
+ $this->assertEquals('foo', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('b', $p->read(1));
+ $this->assertEquals('a', $p->read(1));
+ $this->assertEquals('r12', $p->read(3));
+ $this->assertFalse($p->eof());
+ $this->assertEquals('3', $p->getContents());
+ $this->assertTrue($p->eof());
+ $this->assertEquals(9, $p->tell());
+ }
+}
+
+class HasToString
+{
+ public function __toString() {
+ return 'foo';
+ }
+}
diff --git a/vendor/guzzlehttp/streams/tests/UtilsTest.php b/vendor/guzzlehttp/streams/tests/UtilsTest.php
new file mode 100644
index 0000000..6e3e3b2
--- /dev/null
+++ b/vendor/guzzlehttp/streams/tests/UtilsTest.php
@@ -0,0 +1,155 @@
+assertEquals('foobaz', Utils::copyToString($s));
+ $s->seek(0);
+ $this->assertEquals('foo', Utils::copyToString($s, 3));
+ $this->assertEquals('baz', Utils::copyToString($s, 3));
+ $this->assertEquals('', Utils::copyToString($s));
+ }
+
+ public function testCopiesToStringStopsWhenReadFails()
+ {
+ $s1 = Stream::factory('foobaz');
+ $s1 = FnStream::decorate($s1, [
+ 'read' => function () {
+ return false;
+ }
+ ]);
+ $result = Utils::copyToString($s1);
+ $this->assertEquals('', $result);
+ }
+
+ public function testCopiesToStream()
+ {
+ $s1 = Stream::factory('foobaz');
+ $s2 = Stream::factory('');
+ Utils::copyToStream($s1, $s2);
+ $this->assertEquals('foobaz', (string) $s2);
+ $s2 = Stream::factory('');
+ $s1->seek(0);
+ Utils::copyToStream($s1, $s2, 3);
+ $this->assertEquals('foo', (string) $s2);
+ Utils::copyToStream($s1, $s2, 3);
+ $this->assertEquals('foobaz', (string) $s2);
+ }
+
+ public function testStopsCopyToStreamWhenWriteFails()
+ {
+ $s1 = Stream::factory('foobaz');
+ $s2 = Stream::factory('');
+ $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
+ Utils::copyToStream($s1, $s2);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenWriteFailsWithMaxLen()
+ {
+ $s1 = Stream::factory('foobaz');
+ $s2 = Stream::factory('');
+ $s2 = FnStream::decorate($s2, ['write' => function () { return 0; }]);
+ Utils::copyToStream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testStopsCopyToSteamWhenReadFailsWithMaxLen()
+ {
+ $s1 = Stream::factory('foobaz');
+ $s1 = FnStream::decorate($s1, ['read' => function () { return ''; }]);
+ $s2 = Stream::factory('');
+ Utils::copyToStream($s1, $s2, 10);
+ $this->assertEquals('', (string) $s2);
+ }
+
+ public function testReadsLines()
+ {
+ $s = Stream::factory("foo\nbaz\nbar");
+ $this->assertEquals("foo\n", Utils::readline($s));
+ $this->assertEquals("baz\n", Utils::readline($s));
+ $this->assertEquals("bar", Utils::readline($s));
+ }
+
+ public function testReadsLinesUpToMaxLength()
+ {
+ $s = Stream::factory("12345\n");
+ $this->assertEquals("123", Utils::readline($s, 4));
+ $this->assertEquals("45\n", Utils::readline($s));
+ }
+
+ public function testReadsLineUntilFalseReturnedFromRead()
+ {
+ $s = $this->getMockBuilder('GuzzleHttp\Stream\Stream')
+ ->setMethods(['read', 'eof'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $s->expects($this->exactly(2))
+ ->method('read')
+ ->will($this->returnCallback(function () {
+ static $c = false;
+ if ($c) {
+ return false;
+ }
+ $c = true;
+ return 'h';
+ }));
+ $s->expects($this->exactly(2))
+ ->method('eof')
+ ->will($this->returnValue(false));
+ $this->assertEquals("h", Utils::readline($s));
+ }
+
+ public function testCalculatesHash()
+ {
+ $s = Stream::factory('foobazbar');
+ $this->assertEquals(md5('foobazbar'), Utils::hash($s, 'md5'));
+ }
+
+ /**
+ * @expectedException \GuzzleHttp\Stream\Exception\SeekException
+ */
+ public function testCalculatesHashThrowsWhenSeekFails()
+ {
+ $s = new NoSeekStream(Stream::factory('foobazbar'));
+ $s->read(2);
+ Utils::hash($s, 'md5');
+ }
+
+ public function testCalculatesHashSeeksToOriginalPosition()
+ {
+ $s = Stream::factory('foobazbar');
+ $s->seek(4);
+ $this->assertEquals(md5('foobazbar'), Utils::hash($s, 'md5'));
+ $this->assertEquals(4, $s->tell());
+ }
+
+ public function testOpensFilesSuccessfully()
+ {
+ $r = Utils::open(__FILE__, 'r');
+ $this->assertInternalType('resource', $r);
+ fclose($r);
+ }
+
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessage Unable to open /path/to/does/not/exist using mode r
+ */
+ public function testThrowsExceptionNotWarning()
+ {
+ Utils::open('/path/to/does/not/exist', 'r');
+ }
+
+ public function testProxiesToFactory()
+ {
+ $this->assertEquals('foo', (string) Utils::create('foo'));
+ }
+}
diff --git a/vendor/overtrue/socialite/.github/FUNDING.yml b/vendor/overtrue/socialite/.github/FUNDING.yml
new file mode 100644
index 0000000..5ea9ebf
--- /dev/null
+++ b/vendor/overtrue/socialite/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [overtrue]
diff --git a/vendor/overtrue/socialite/.github/dependabot.yml b/vendor/overtrue/socialite/.github/dependabot.yml
new file mode 100644
index 0000000..a51bb0b
--- /dev/null
+++ b/vendor/overtrue/socialite/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "composer" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "daily"
diff --git a/vendor/overtrue/socialite/.gitignore b/vendor/overtrue/socialite/.gitignore
index d6eb268..73e1359 100644
--- a/vendor/overtrue/socialite/.gitignore
+++ b/vendor/overtrue/socialite/.gitignore
@@ -6,4 +6,5 @@ composer.lock
Thumbs.db
/*.php
sftp-config.json
-.php_cs.cache
\ No newline at end of file
+.php_cs.cache
+.php-cs-fixer.cache
diff --git a/vendor/overtrue/socialite/.phpunit.result.cache b/vendor/overtrue/socialite/.phpunit.result.cache
new file mode 100644
index 0000000..1eab539
--- /dev/null
+++ b/vendor/overtrue/socialite/.phpunit.result.cache
@@ -0,0 +1 @@
+{"version":1,"defects":{"OAuthTest::test_it_can_get_token":4,"OAuthTest::test_it_can_get_user_by_code":4,"WechatTest::testWeChatProviderHasCorrectlyRedirectResponse":6},"times":{"OAuthTest::test_it_can_get_auth_url_without_redirect":0.001,"OAuthTest::test_it_can_get_auth_url_with_redirect":0,"OAuthTest::test_it_can_get_auth_url_with_scopes":0,"OAuthTest::test_it_can_get_auth_url_with_state":0,"OAuthTest::test_it_can_get_token":0.004,"OAuthTest::test_it_can_get_user_by_token":0,"OAuthTest::test_it_can_get_user_by_code":0,"Providers\\FeiShuTest::testProviderCanCreateCorrect":0,"Providers\\FeiShuTest::testProviderWithInternalAppModeWork":0,"Providers\\FeiShuTest::testProviderWithAppTicketWork":0,"Providers\\FeiShuTest::testConfigAppAccessTokenWithDefaultModeNoAppTicketWork":0.002,"Providers\\FeiShuTest::testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInBadResponse":0.001,"Providers\\FeiShuTest::testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInGoodResponse":0,"Providers\\FeiShuTest::testConfigAppAccessTokenWithInternalInBadResponse":0,"Providers\\FeiShuTest::testConfigAppAccessTokenWithInternalInGoodResponse":0,"WeWorkTest::testQrConnect":0,"WeWorkTest::testOAuthWithAgentId":0,"WeWorkTest::testOAuthWithoutAgentId":0,"WechatTest::testWeChatProviderHasCorrectlyRedirectResponse":0,"WechatTest::testWeChatProviderTokenUrlAndRequestFields":0,"WechatTest::testOpenPlatformComponent":0,"WechatTest::testOpenPlatformComponentWithCustomParameters":0,"SocialiteManagerTest::test_it_can_create_from_config":0.001,"SocialiteManagerTest::test_it_can_create_from_custom_creator":0,"SocialiteManagerTest::test_it_can_create_from_custom_provider_class":0,"UserTest::testJsonserialize":0,"UserTest::test_it_can_get_refresh_token":0,"UserTest::test_it_can_set_raw_data":0,"UserTest::test_ie_can_get_attribute_by_magic_method":0}}
\ No newline at end of file
diff --git a/vendor/overtrue/socialite/.travis.yml b/vendor/overtrue/socialite/.travis.yml
index 2969e3a..39912f9 100644
--- a/vendor/overtrue/socialite/.travis.yml
+++ b/vendor/overtrue/socialite/.travis.yml
@@ -1,11 +1,9 @@
language: php
php:
- - 5.5
- - 5.6
- 7.0
- 7.1
- - hhvm
+ - 7.2
sudo: false
dist: trusty
diff --git a/vendor/overtrue/socialite/README.md b/vendor/overtrue/socialite/README.md
index b6c8c20..62e5e81 100644
--- a/vendor/overtrue/socialite/README.md
+++ b/vendor/overtrue/socialite/README.md
@@ -1,47 +1,82 @@
Socialite
-
-
-
-Socialite is an OAuth2 Authentication tool. It is inspired by laravel/socialite , You can easily use it in any PHP project.
+Socialite is an OAuth2 Authentication tool. It is inspired by laravel/socialite , You can easily use it in any PHP project. 中文文档
+
+This tool now supports platforms such as Facebook, GitHub, Google, LinkedIn, Outlook, QQ, Tapd, Alipay, Taobao, Baidu, DingTalk, Weibo, WeChat, Douyin, Feishu, Douban, WeWork, Tencent Cloud, Line, Gitee.
+
+[](https://github.com/sponsors/overtrue)
+
+如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue)
-
-
- 创造不息,交付不止
-
-
-
-
-
+- [Requirement](#requirement)
+- [Installation](#installation)
+- [Usage](#usage)
+ - [Configuration](#configuration)
+ - [Custom app name](#custom-app-name)
+ - [Extends custom provider](#extends-custom-provider)
+ - [Platform](#platform)
+ - [Alipay](#alipay)
+ - [DingTalk](#dingtalk)
+ - [Douyin](#douyin)
+ - [Baidu](#baidu)
+ - [Feishu](#feishu)
+ - [Taobao](#taobao)
+ - [WeChat](#wechat)
+ - [Some Skill](#some-skill)
+ - [Scopes](#scopes)
+ - [Redirect URL](#redirect-url)
+ - [State](#state)
+ - [Redirect with `state` parameter](#redirect-with-state-parameter)
+ - [Validate the callback `state`](#validate-the-callback-state)
+ - [Additional parameters](#additional-parameters)
+ - [User interface](#user-interface)
+ - [Standard user api:](#standard-user-api)
+ - [Get raw response from OAuth API](#get-raw-response-from-oauth-api)
+ - [Get the token response when you use userFromCode()](#get-the-token-response-when-you-use-userfromcode)
+ - [Get user with access token](#get-user-with-access-token)
+- [Enjoy it! :heart:](#enjoy-it-heart)
+- [Reference](#reference)
+- [PHP 扩展包开发](#php-扩展包开发)
+- [License](#license)
# Requirement
```
-PHP >= 5.4
+PHP >= 7.4
```
# Installation
```shell
-$ composer require "overtrue/socialite:~1.1"
+$ composer require "overtrue/socialite" -vvv
```
# Usage
-For Laravel 5: [overtrue/laravel-socialite](https://github.com/overtrue/laravel-socialite)
+Users just need to create the corresponding configuration variables, then create the authentication application for each platform through the tool, and easily obtain the access_token and user information for that platform. The implementation logic of the tool is referred to OAuth2 documents of major platforms for details.
+
+The tool is used in the following steps:
+
+1. Configurate platform config
+2. Use this tool to create a platform application
+3. Let the user redirect to platform authentication
+4. The server receives a Code callback from the platform, and uses the Code to exchange the user information on the platform (including access_token).
+
+Packages created for Laravel users are easier to integrate: [overtrue/laravel-socialite](https://github.com/overtrue/laravel-socialite)
`authorize.php`:
```php
driver('github')->redirect();
+$url = $socialite->create('github')->redirect();
-echo $response;// or $response->send();
+return redirect($url);
```
`callback.php`:
@@ -64,87 +99,389 @@ echo $response;// or $response->send();
```php
driver('github')->user();
+use Overtrue\Socialite\SocialiteManager;
+
+$config = [
+ 'github' => [
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+
+$socialite = new SocialiteManager($config);
+
+$code = request()->query('code');
+
+$user = $socialite->create('github')->userFromCode($code);
$user->getId(); // 1472352
$user->getNickname(); // "overtrue"
+$user->getUsername(); // "overtrue"
$user->getName(); // "安正超"
$user->getEmail(); // "anzhengchao@gmail.com"
-$user->getProviderName(); // GitHub
...
```
-### Configuration
+## Configuration
-Now we support the following sites:
-
-`facebook`, `github`, `google`, `linkedin`, `weibo`, `qq`, `wechat`, `wechat_open`, and `douban`.
-
-Each drive uses the same configuration keys: `client_id`, `client_secret`, `redirect`.
+Each create uses the same configuration keys: `client_id`, `client_secret`, `redirect`.
Example:
-```
-...
+```php
+$config = [
'weibo' => [
'client_id' => 'your-app-id',
'client_secret' => 'your-app-secret',
'redirect' => 'http://localhost/socialite/callback.php',
],
+ 'facebook' => [
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+```
+
+### Custom app name
+
+You can use any name you like as the name of the application, such as `foo`, and set provider using `provider` key:
+
+```php
+$config = [
+ 'foo' => [
+ 'provider' => 'github', // <-- provider name
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+
+ // another github app
+ 'bar' => [
+ 'provider' => 'github', // <-- provider name
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+ //...
+];
+```
+
+### Extends custom provider
+
+You can create application from you custom provider easily,you have to ways to do this:
+
+1. Using custom creator:
+ As shown in the following code, the service provider name is defined for the Foo application, but the tool itself does not support it, so use the creator `extend()` to create an instance of the service provider as a closure function.
+
+```php
+$config = [
+ 'foo' => [
+ 'provider' => 'myprovider', // <-- provider name
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+
+$socialite = new SocialiteManager($config);
+
+$socialite->extend('myprovider', function(array $config) {
+ return new MyCustomProvider($config);
+});
+
+$app = $socialite->create('foo');
+```
+
+2. Using provider:
+
+>👋🏻 Your custom provider class must be implements of `Overtrue\Socialite\Contracts\ProviderInterface`.
+
+```php
+class MyCustomProvider implements \Overtrue\Socialite\Contracts\ProviderInterface
+{
+ //...
+}
+```
+
+then set `provider` with the class name:
+
+```php
+$config = [
+ 'foo' => [
+ 'provider' => MyCustomProvider::class, // <-- class name
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+
+$socialite = new SocialiteManager($config);
+$app = $socialite->create('foo');
+```
+
+
+
+## Platform
+
+Different platforms have different configuration methods, so please check the platform Settings you are using.
+
+### [Alipay](https://opendocs.alipay.com/open/200/105310#s2)
+
+You must have the following configuration.
+```php
+$config = [
+ 'alipay' => [
+ // This can also be named as 'app_id' like the official documentation.
+ 'client_id' => 'your-app-id',
+
+ // Please refer to the official documentation, in the official management background configuration RSA2.
+ // Note: This is your own private key.
+ // Note: Do not allow the private key content to have extra characters.
+ // Recommendation: For security, you can read directly from the file. But here as long as the value, please remember to remove the head and tail of the decoration.
+ 'rsa_private_key' => 'your-rsa-private-key',
+
+ // Be sure to set this value and make sure that it is the same address value as set in the official admin system.
+ // This can also be named as 'redirect_url' like the official documentation.
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ]
+ ...
+];
+
+$socialite = new SocialiteManager($config);
+
+$user = $socialite->create('alipay')->userFromCode('here is auth code');
+
+// See this documents "User interface"
+$user->getId(); // 1472352
+$user->getNickname(); // "overtrue"
+$user->getUsername(); // "overtrue"
+$user->getName(); // "安正超"
+...
+```
+Only RSA2 personal private keys are supported, so stay tuned if you want to log in with a certificate.
+
+### [DingTalk](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3)
+
+Follow the documentation and configure it like following.
+
+> Note: It only supported QR code access to third-part websites. i.e exchange for user information(opendid, unionid and nickname)
+
+```php
+$config = [
+ 'dingtalk' => [
+ // or 'app_id'
+ 'client_id' => 'your app id',
+
+ // or 'app_secret'
+ 'client_secret' => 'your app secret',
+
+ // or 'redirect_url'
+ 'redirect' => 'redirect URL'
+ ]
+];
+
+$socialite = new SocialiteManager($config);
+
+$user = $socialite->create('dingtalk')->userFromCode('here is auth code');
+
+// See this documents "User interface"
+$user->getId(); // 1472352
+$user->getNickname(); // "overtrue"
+$user->getUsername(); // "overtrue"
+$user->getName(); // "安正超"
...
```
-Special configuration options for [WeChat Open Platform](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN)
-```
-'wechat_open' => [
- 'client_id' => 'your-app-id',
- 'client_secret' => ['your-component-appid', 'your-component-access-token'],
- 'redirect' => 'http://localhost/socialite/callback.php',
-]
-```
+### [Douyin](https://open.douyin.com/platform/doc/OpenAPI-oauth2)
-### Scope
-
-Before redirecting the user, you may also set "scopes" on the request using the scope method. This method will overwrite all existing scopes:
+> Note: using the Douyin create that if you get user information directly using access token, set up the openid first. the openid can be obtained by code when access is obtained, so call `userFromCode()` automatically configured for you openid, if call `userFromToken()` first call `withOpenId()`
```php
-$response = $socialite->driver('github')
- ->scopes(['scope1', 'scope2'])->redirect();
+$config = [
+ 'douyin' => [
+ 'client_id' => 'your app id',
+ 'client_secret' => 'your app secret',
+
+ 'redirect' => 'redirect URL'
+ ]
+];
+
+$socialite = new SocialiteManager($config);
+
+$user = $socialite->create('douyin')->userFromCode('here is auth code');
+
+$user = $socialite->create('douyin')->withOpenId('openId')->userFromToken('here is the access token');
+```
+
+
+### [Baidu](https://developer.baidu.com/wiki/index.php?title=docs/oauth)
+
+You can choose the form you want display by using `withDisplay()`.
+
+- **page**
+- **popup**
+- **dialog**
+- **mobile**
+- **tv**
+- **pad**
+
+```php
+$authUrl = $socialite->create('baidu')->withDisplay('mobile')->redirect();
+
+```
+`popup` mode is the default setting with display. `basic` is the default with scopes.
+
+### [Feishu](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)
+
+Some simple way to use by internal app mode and config app_ticket.
+
+```php
+$config = [
+ 'feishu' => [
+ // or 'app_id'
+ 'client_id' => 'your app id',
+
+ // or 'app_secret'
+ 'client_secret' => 'your app secret',
+
+ // or 'redirect_url'
+ 'redirect' => 'redirect URL',
+
+ // if you want to use internal way to get app_access_token
+ // set this key by 'internal' then you already turn on the internal app mode
+ 'app_mode' => 'internal'
+ ]
+];
+
+$socialite = new SocialiteManager($config);
+
+$feishuDriver = $socialite->create('feishu');
+
+$feishuDriver->withInternalAppMode()->userFromCode('here is code');
+$feishuDriver->withDefaultMode()->withAppTicket('app_ticket')->userFromCode('here is code');
+```
+
+### [Taobao](https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search)
+
+You can choose the form you want display by using `withView()`.
+
+```php
+$authUrl = $socialite->create('taobao')->withView('wap')->redirect();
+```
+`web` mode is the default setting with display. `user_info` is the default with scopes.
+
+### [WeChat](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Official_Accounts/official_account_website_authorization.html)
+
+We support Open Platform Third-party Platform webpage authorizations on behalf of Official Account.
+
+You just need input your config like below config. Official Accounts authorizations only doesn't need.
+```php
+...
+[
+ 'wechat' =>
+ [
+ 'client_id' => 'client_id',
+ 'client_secret' => 'client_secret',
+ 'redirect' => 'redirect-url',
+
+ // Open Platform - Third-party Platform Need
+ 'component' => [
+ 'id' => 'component-app-id',
+ 'token' => 'component-access-token', // or Using a callable as value.
+ ]
+ ]
+],
+...
+```
+
+## Some Skill
+
+### Scopes
+
+Before redirecting the user, you may also set "scopes" on the request using the `scopes()` method. This method will overwrite all existing scopes:
+
+```php
+$response = $socialite->create('github')
+ ->scopes(['scope1', 'scope2'])->redirect();
```
### Redirect URL
-You may also want to dynamic set `redirect`,you can use the following methods to change the `redirect` URL:
+You may also want to dynamically set `redirect_uri`,you can use the following methods to change the `redirect_uri` URL:
```php
+$url = 'your callback url.';
+
$socialite->redirect($url);
// or
$socialite->withRedirectUrl($url)->redirect();
-// or
-$socialite->setRedirectUrl($url)->redirect();
```
-> WeChat scopes:
-- `snsapi_base`, `snsapi_userinfo` - Used to Media Platform Authentication.
-- `snsapi_login` - Used to web Authentication.
+### State
+
+Your app can use a state parameter for making sure the response belongs to a request initiated by the same user, therefore preventing cross-site request forgery (CSFR) attacks. A CSFR attack occurs when a malicious attacker tricks the user into performing unwanted actions that only the user is authorized to perform on a trusted web application, and all will be done without involving or alerting the user.
+
+Here's the simplest example of how providing the state can make your app more secure. in this example, we use the session ID as the state parameter, but you can use whatever logic you want to create value for the state.
+
+### Redirect with `state` parameter
+```php
+create('github')->withState($state)->redirect();
+
+return redirect($url);
+```
+
+### Validate the callback `state`
+
+Once the user has authorized your app, the user will be redirected back to your app's redirect_uri. The OAuth server will return the state parameter unchanged. Check if the state provided in the redirect_uri matches the state generated by your app:
+
+```php
+query('state');
+$code = request()->query('code');
+
+// Check the state received with current session id
+if ($state != hash('sha256', session_id())) {
+ exit('State does not match!');
+}
+$user = $socialite->create('github')->userFromCode($code);
+
+// authorized
+```
+
+[Read more about `state` parameter](https://auth0.com/docs/protocols/oauth2/oauth-state)
### Additional parameters
-To include any optional parameters in the request, call the with method with an associative array:
+To include any optional parameters in the request, call the `with()` method with an associative array:
```php
-$response = $socialite->driver('google')
+$response = $socialite->create('google')
->with(['hd' => 'example.com'])->redirect();
```
-### User interface
-#### Standard user api:
+## User interface
+
+### Standard user api:
```php
-
-$user = $socialite->driver('weibo')->user();
+$user = $socialite->create('github')->userFromCode($code);
```
```json
@@ -154,7 +491,7 @@ $user = $socialite->driver('weibo')->user();
"name": "安正超",
"email": "anzhengchao@gmail.com",
"avatar": "https://avatars.githubusercontent.com/u/1472352?v=3",
- "original": {
+ "raw": {
"login": "overtrue",
"id": 1472352,
"avatar_url": "https://avatars.githubusercontent.com/u/1472352?v=3",
@@ -163,7 +500,7 @@ $user = $socialite->driver('weibo')->user();
"html_url": "https://github.com/overtrue",
...
},
- "token": {
+ "token_response": {
"access_token": "5b1dc56d64fffbd052359f032716cc4e0a1cb9a0",
"token_type": "bearer",
"scope": "user:email"
@@ -171,7 +508,7 @@ $user = $socialite->driver('weibo')->user();
}
```
-You can fetch the user attribute as a array key like this:
+You can fetch the user attribute as a array keys like these:
```php
$user['id']; // 1472352
@@ -181,84 +518,81 @@ $user['email']; // "anzhengchao@gmail.com"
...
```
-Or using method:
+Or using the method:
```php
-$user->getId();
-$user->getNickname();
-$user->getName();
-$user->getEmail();
-$user->getAvatar();
-$user->getOriginal();
-$user->getToken();// or $user->getAccessToken()
-$user->getProviderName(); // GitHub/Google/Facebook...
+mixed $user->getId();
+?string $user->getNickname();
+?string $user->getName();
+?string $user->getEmail();
+?string $user->getAvatar();
+?string $user->getRaw();
+?string $user->getAccessToken();
+?string $user->getRefreshToken();
+?int $user->getExpiresIn();
+?array $user->getTokenResponse();
+
+
```
-#### Get original response from OAuth API
+### Get raw response from OAuth API
-The `$user->getOriginal()` method will return an array of the API raw response.
+The `$user->getRaw()` method will return an **array** of the API raw response.
-#### Get access token Object
+### Get the token response when you use userFromCode()
-You can get the access token instance of current session by call `$user->getToken()` or `$user->getAccessToken()` or `$user['token']` .
+The `$user->getTokenResponse()` method will return an **array** of the get token(access token) API response.
+> Note: This method only return a **valid array** when you use `userFromCode()`, else will return **null** because use `userFromToken()` have no token response.
### Get user with access token
```php
-$accessToken = new AccessToken(['access_token' => $accessToken]);
-$user = $socialite->user($accessToken);
+$accessToken = 'xxxxxxxxxxx';
+$user = $socialite->userFromToken($accessToken);
```
-### Custom Session or Request instance.
-You can set the request with your custom `Request` instance which instanceof `Symfony\Component\HttpFoundation\Request` before you call `driver` method.
-
-
-```php
-
-$request = new Request(); // or use AnotherCustomRequest.
-
-$socialite = new SocialiteManager($config, $request);
-```
-
-Or set request to `SocialiteManager` instance:
-
-```php
-$socialite->setRequest($request);
-```
-
-You can get the request from `SocialiteManager` instance by `getRequest()`:
-
-```php
-$request = $socialite->getRequest();
-```
-
-#### Set custom session manager.
-
-By default, the `SocialiteManager` use `Symfony\Component\HttpFoundation\Session\Session` instance as session manager, you can change it as following lines:
-
-```php
-$session = new YourCustomSessionManager();
-$socialite->getRequest()->setSession($session);
-```
-
-> Your custom session manager must be implement the [`Symfony\Component\HttpFoundation\Session\SessionInterface`](http://api.symfony.com/3.0/Symfony/Component/HttpFoundation/Session/SessionInterface.html).
-
-Enjoy it! :heart:
+# Enjoy it! :heart:
# Reference
+- [Alipay - 用户信息授权](https://opendocs.alipay.com/open/289/105656)
+- [DingTalk - 扫码登录第三方网站](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3)
- [Google - OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect)
+- [Github - Authorizing OAuth Apps](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)
- [Facebook - Graph API](https://developers.facebook.com/docs/graph-api)
- [Linkedin - Authenticating with OAuth 2.0](https://developer.linkedin.com/docs/oauth2)
- [微博 - OAuth 2.0 授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E)
-- [QQ - OAuth 2.0 登录QQ](http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B)
-- [微信公众平台 - OAuth文档](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html)
+- [QQ - OAuth 2.0 登录 QQ](http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B)
+- [腾讯云 - OAuth2.0](https://cloud.tencent.com/document/product/306/37730#.E6.8E.A5.E5.85.A5.E8.85.BE.E8.AE.AF.E4.BA.91-oauth)
+- [微信公众平台 - OAuth 文档](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html)
- [微信开放平台 - 网站应用微信登录开发指南](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN)
- [微信开放平台 - 代公众号发起网页授权](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN)
+- [企业微信 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90000/90135/91020)
+- [企业微信第三方应用 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90001/90143/91118)
- [豆瓣 - OAuth 2.0 授权机制说明](http://developers.douban.com/wiki/?title=oauth2)
+- [抖音 - 网站应用开发指南](http://open.douyin.com/platform/doc)
+- [飞书 - 授权说明](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)
+- [Tapd - 用户授权说明](https://www.tapd.cn/help/show#1120003271001000093)
+- [Line - OAuth 2.0](https://developers.line.biz/en/docs/line-login/integrate-line-login/)
+- [Gitee - OAuth文档](https://gitee.com/api/v5/oauth_doc#/)
+
+[](https://github.com/sponsors/overtrue)
+
+## Project supported by JetBrains
+
+Many thanks to Jetbrains for kindly providing a license for me to work on this and other open-source projects.
+
+[](https://www.jetbrains.com/?from=https://github.com/overtrue)
+
+
+# PHP 扩展包开发
+
+> 想知道如何从零开始构建 PHP 扩展包?
+>
+> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
# License
diff --git a/vendor/overtrue/socialite/README_CN.md b/vendor/overtrue/socialite/README_CN.md
new file mode 100644
index 0000000..8456e92
--- /dev/null
+++ b/vendor/overtrue/socialite/README_CN.md
@@ -0,0 +1,610 @@
+ Socialite
+
+
+
+
+
+
+
+
+
+
+
+Socialite 是一个 OAuth2 认证工具。 它的灵感来源于 laravel/socialite , 你可以很轻易的在任何 PHP 项目中使用它。
+
+该工具现已支持平台有:Facebook,Github,Google,Linkedin,Outlook,QQ,TAPD,支付宝,淘宝,百度,钉钉,微博,微信,抖音,飞书,豆瓣,企业微信,腾讯云,Line,Gitee。
+
+- [版本要求](#版本要求)
+- [安装](#安装)
+- [使用指南](#使用指南)
+ - [配置](#配置)
+ - [自定义应用名](#自定义应用名)
+ - [扩展自定义服务提供程序](#扩展自定义服务提供程序)
+ - [平台](#平台)
+ - [支付宝](#支付宝)
+ - [钉钉](#钉钉)
+ - [抖音](#抖音)
+ - [百度](#百度)
+ - [飞书](#飞书)
+ - [淘宝](#淘宝)
+ - [微信](#微信)
+ - [其他一些技巧](#其他一些技巧)
+ - [Scopes](#scopes)
+ - [Redirect URL](#redirect-url)
+ - [State](#state)
+ - [带着 `state` 参数的重定向](#带着-state-参数的重定向)
+ - [检验回调的 `state`](#检验回调的-state)
+ - [其他的一些参数](#其他的一些参数)
+ - [User interface](#user-interface)
+ - [标准的 user api:](#标准的-user-api)
+ - [从 OAuth API 响应中取得原始数据](#从-oauth-api-响应中取得原始数据)
+ - [当你使用 userFromCode() 想要获取 token 响应的原始数据](#当你使用-userfromcode-想要获取-token-响应的原始数据)
+ - [通过 access token 获取用户信息](#通过-access-token-获取用户信息)
+- [Enjoy it! :heart:](#enjoy-it-heart)
+- [参照](#参照)
+- [PHP 扩展包开发](#php-扩展包开发)
+- [License](#license)
+
+# 版本要求
+
+```
+PHP >= 7.4
+```
+
+# 安装
+
+```shell
+$ composer require "overtrue/socialite" -vvv
+```
+
+# 使用指南
+
+用户只需要创建相应配置变量,然后通过工具为各个平台创建认证应用,并轻松获取该平台的 access_token 和用户相关信息。工具实现逻辑详见参照各大平台 OAuth2 文档。
+
+工具使用大致分为以下几步:
+
+1. 配置平台设置
+2. 创建对应平台应用
+3. 让用户跳转至平台认证
+4. 服务器收到平台回调 Code,使用 Code 换取平台处用户信息(包括 access_token)
+
+为 Laravel 用户创建的更方便的整合的包: [overtrue/laravel-socialite](https://github.com/overtrue/laravel-socialite)
+
+`authorize.php`: 让用户跳转至平台认证
+
+```php
+ [
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+
+$socialite = new SocialiteManager($config);
+
+$url = $socialite->create('github')->redirect();
+
+return redirect($url);
+```
+
+`callback.php`:
+
+```php
+ [
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+
+$socialite = new SocialiteManager($config);
+
+$code = request()->query('code');
+
+$user = $socialite->create('github')->userFromCode($code);
+
+$user->getId(); // 1472352
+$user->getNickname(); // "overtrue"
+$user->getUsername(); // "overtrue"
+$user->getName(); // "安正超"
+$user->getEmail(); // "anzhengchao@gmail.com"
+...
+```
+
+## 配置
+
+为每个平台设置相同的键值对后就能开箱即用:`client_id`, `client_secret`, `redirect`.
+
+示例:
+
+```php
+$config = [
+ 'weibo' => [
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+ 'facebook' => [
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+```
+
+### 自定义应用名
+
+你可以使用任意你喜欢的名字对每个平台进行命名,比如说 `foo`, 采用别名的方法后需要在配置中多设置一个 `provider` 键,这样才能告诉工具包如何正确找到你想要的程序:
+
+```php
+$config = [
+ // 为 github 应用起别名为 foo
+ 'foo' => [
+ 'provider' => 'github', // <-- provider name
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+
+ // 另外一个名字叫做 bar 的 github 应用
+ 'bar' => [
+ 'provider' => 'github', // <-- provider name
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+
+ //...
+];
+
+$socialite = new SocialiteManager($config);
+
+$appFoo = $socialite->create('foo');
+$appBar = $socialite->create('bar');
+```
+
+### 扩展自定义服务提供程序
+
+你可以很容易的从自定义的服务提供中创建应用,只需要遵循如下两点:
+
+1. 使用自定义创建器
+
+ 如下代码所示,为 foo 应用定义了服务提供名,但是工具本身还未支持,所以使用创建器 `extend()`,以闭包函数的形式为该服务提供创建一个实例。
+
+```php
+$config = [
+ 'foo' => [
+ 'provider' => 'myprovider', // <-- 一个工具还未支持的服务提供程序
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+
+$socialite = new SocialiteManager($config);
+
+$socialite->extend('myprovider', function(array $config) {
+ return new MyCustomProvider($config);
+});
+
+$app = $socialite->create('foo');
+```
+
+2. 使用服务提供类
+
+>👋🏻 你的自定义服务提供类必须实现`Overtrue\Socialite\Contracts\ProviderInterface` 接口
+
+```php
+class MyCustomProvider implements \Overtrue\Socialite\Contracts\ProviderInterface
+{
+ //...
+}
+```
+
+接下来为 `provider` 设置该类名让工具可以找到该类并实例化:
+
+```php
+$config = [
+ 'foo' => [
+ 'provider' => MyCustomProvider::class, // <-- 类名
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+];
+
+$socialite = new SocialiteManager($config);
+$app = $socialite->create('foo');
+```
+
+
+
+## 平台
+
+不同的平台有不同的配置方法,为了确保工具的正常运行,所以请确保你所使用的平台的配置都是如期设置的。
+
+### [支付宝](https://opendocs.alipay.com/open/200/105310#s2)
+
+请按如下方式配置
+
+```php
+$config = [
+ 'alipay' => [
+ // 这个键名还能像官方文档那样叫做 'app_id'
+ 'client_id' => 'your-app-id',
+
+ // 请根据官方文档,在官方管理后台配置 RSA2
+ // 注意: 这是你自己的私钥
+ // 注意: 不允许私钥内容有其他字符
+ // 建议: 为了保证安全,你可以将文本信息从磁盘文件中读取,而不是在这里明文
+ 'rsa_private_key' => 'your-rsa-private-key',
+
+ // 确保这里的值与你在服务后台绑定的地址值一致
+ // 这个键名还能像官方文档那样叫做 'redirect_url'
+ 'redirect' => 'http://localhost/socialite/callback.php',
+
+ // 沙箱模式接入地址见 https://opendocs.alipay.com/open/220/105337#%E5%85%B3%E4%BA%8E%E6%B2%99%E7%AE%B1
+ 'sandbox' => false,
+ ]
+ ...
+];
+
+$socialite = new SocialiteManager($config);
+
+$user = $socialite->create('alipay')->userFromCode('here is auth code');
+
+// 详见文档后面 "User interface"
+$user->getId(); // 1472352
+$user->getNickname(); // "overtrue"
+$user->getUsername(); // "overtrue"
+$user->getName(); // "安正超"
+...
+```
+
+本工具暂时只支持 RSA2 个人私钥认证方式。
+
+### [钉钉](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3)
+
+如文档所示
+
+> 注意:该工具仅支持 QR code 连接到第三方网站,用来获取用户信息(opeid, unionid 和 nickname)
+
+```php
+$config = [
+ 'dingtalk' => [
+ // or 'app_id'
+ 'client_id' => 'your app id',
+
+ // or 'app_secret'
+ 'client_secret' => 'your app secret',
+
+ // or 'redirect_url'
+ 'redirect' => 'redirect URL'
+ ]
+];
+
+$socialite = new SocialiteManager($config);
+
+$user = $socialite->create('dingtalk')->userFromCode('here is auth code');
+
+// 详见文档后面 "User interface"
+$user->getId(); // 1472352
+$user->getNickname(); // "overtrue"
+$user->getUsername(); // "overtrue"
+$user->getName(); // "安正超"
+...
+```
+
+### [抖音](https://open.douyin.com/platform/doc/OpenAPI-oauth2)
+
+> 注意: 使用抖音服务提供的时候,如果你想直接使用 access_token 获取用户信息时,请先设置 openid。 先调用 `withOpenId()` 再调用 `userFromToken()`
+
+```php
+$config = [
+ 'douyin' => [
+ 'client_id' => 'your app id',
+
+ 'client_secret' => 'your app secret',
+
+ 'redirect' => 'redirect URL'
+ ]
+];
+
+$socialite = new SocialiteManager($config);
+
+$user = $socialite->create('douyin')->userFromCode('here is auth code');
+
+$user = $socialite->create('douyin')->withOpenId('openId')->userFromToken('here is the access token');
+```
+
+
+### [百度](https://developer.baidu.com/wiki/index.php?title=docs/oauth)
+
+其他配置没啥区别,在用法上,可以很轻易的选择重定向登录页面的模式,通过 `withDisplay()`
+
+- **page:**全屏形式的授权页面 (默认),适用于 web 应用。
+- **popup:** 弹框形式的授权页面,适用于桌面软件应用和 web 应用。
+- **dialog:** 浮层形式的授权页面,只能用于站内 web 应用。
+- **mobile:** Iphone/Android 等智能移动终端上用的授权页面,适用于 Iphone/Android 等智能移动终端上的应用。
+- **tv:** 电视等超大显示屏使用的授权页面。
+- **pad:** IPad/Android 等智能平板电脑使用的授权页面。
+
+```php
+$authUrl = $socialite->create('baidu')->withDisplay('mobile')->redirect();
+
+```
+
+`popup` 模式是工具内默认的使用模式。`basic` 是默认使用的 scopes 值。
+
+### [飞书](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)
+
+通过一些简单的方法配置 app_ticket 就能使用内部应用模式
+
+```php
+$config = [
+ 'feishu' => [
+ // or 'app_id'
+ 'client_id' => 'your app id',
+
+ // or 'app_secret'
+ 'client_secret' => 'your app secret',
+
+ // or 'redirect_url'
+ 'redirect' => 'redirect URL',
+
+ // 如果你想使用使用内部应用的方式获取 app_access_token
+ // 对这个键设置了 'internal' 值那么你已经开启了内部应用模式
+ 'app_mode' => 'internal'
+ ]
+];
+
+$socialite = new SocialiteManager($config);
+
+$feishuDriver = $socialite->create('feishu');
+
+$feishuDriver->withInternalAppMode()->userFromCode('here is code');
+$feishuDriver->withDefaultMode()->withAppTicket('app_ticket')->userFromCode('here is code');
+```
+
+### [淘宝](https://open.taobao.com/doc.htm?docId=102635&docType=1&source=search)
+
+其他配置与其他平台的一样,你能选择你想要展示的重定向页面类型通过使用 `withView()`
+
+```php
+$authUrl = $socialite->create('taobao')->withView('wap')->redirect();
+```
+
+`web` 模式是工具默认使用的展示方式, `user_info` 是默认使用的 scopes 范围值。
+
+### [微信](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Official_Accounts/official_account_website_authorization.html)
+
+我们支持开放平台代表公众号进行第三方平台网页授权。
+
+你只需要像下面这样输入你的配置。官方账号不需要授权。
+
+```php
+...
+[
+ 'wechat' =>
+ [
+ 'client_id' => 'client_id',
+ 'client_secret' => 'client_secret',
+ 'redirect' => 'redirect-url',
+
+ // 开放平台 - 第三方平台所需
+ 'component' => [
+ // or 'app_id', 'component_app_id' as key
+ 'id' => 'component-app-id',
+ // or 'app_token', 'access_token', 'component_access_token' as key
+ 'token' => 'component-access-token',
+ ]
+ ]
+],
+...
+```
+
+## 其他一些技巧
+
+### Scopes
+
+在重定向用户之前,您还可以使用 `scopes()` 方法在请求上设置 “范围”。此方法将覆盖所有现有的作用域:
+
+```php
+$response = $socialite->create('github')
+ ->scopes(['scope1', 'scope2'])->redirect();
+```
+
+### Redirect URL
+
+你也可以动态设置' redirect_uri ',你可以使用以下方法来改变 `redirect_uri` URL:
+
+```php
+$url = 'your callback url.';
+
+$socialite->redirect($url);
+// or
+$socialite->withRedirectUrl($url)->redirect();
+```
+
+### State
+
+你的应用程序可以使用一个状态参数来确保响应属于同一个用户发起的请求,从而防止跨站请求伪造 (CSFR) 攻击。当恶意攻击者欺骗用户执行不需要的操作 (只有用户有权在受信任的 web 应用程序上执行) 时,就会发生 CSFR 攻击,所有操作都将在不涉及或警告用户的情况下完成。
+
+这里有一个最简单的例子,说明了如何提供状态可以让你的应用程序更安全。在本例中,我们使用会话 ID 作为状态参数,但是您可以使用您想要为状态创建值的任何逻辑。
+
+### 带着 `state` 参数的重定向
+
+```php
+create('github')->withState($state)->redirect();
+
+return redirect($url);
+```
+
+### 检验回调的 `state`
+
+一旦用户授权你的应用程序,用户将被重定向回你的应用程序的 redirect_uri。OAuth 服务器将不加修改地返回状态参数。检查 redirect_uri 中提供的状态是否与应用程序生成的状态相匹配:
+
+```php
+query('state');
+$code = request()->query('code');
+
+// Check the state received with current session id
+if ($state != hash('sha256', session_id())) {
+ exit('State does not match!');
+}
+$user = $socialite->create('github')->userFromCode($code);
+
+// authorized
+```
+
+[查看更多关于 `state` 参数的文档](https://auth0.com/docs/protocols/oauth2/oauth-state)
+
+### 其他的一些参数
+
+要在请求中包含任何可选参数,调用 `with()` 方法传入一个你想要设置的关联数组:
+
+```php
+$response = $socialite->create('google')
+ ->with(['hd' => 'example.com'])->redirect();
+```
+
+
+## User interface
+
+### 标准的 user api:
+
+```php
+$user = $socialite->create('github')->userFromCode($code);
+```
+
+```json
+{
+ "id": 1472352,
+ "nickname": "overtrue",
+ "name": "安正超",
+ "email": "anzhengchao@gmail.com",
+ "avatar": "https://avatars.githubusercontent.com/u/1472352?v=3",
+ "raw": {
+ "login": "overtrue",
+ "id": 1472352,
+ "avatar_url": "https://avatars.githubusercontent.com/u/1472352?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/overtrue",
+ "html_url": "https://github.com/overtrue",
+ ...
+ },
+ "token_response": {
+ "access_token": "5b1dc56d64fffbd052359f032716cc4e0a1cb9a0",
+ "token_type": "bearer",
+ "scope": "user:email"
+ }
+}
+```
+
+你可以像这样以数组键的形式获取 user 属性:
+
+```php
+$user['id']; // 1472352
+$user['nickname']; // "overtrue"
+$user['name']; // "安正超"
+$user['email']; // "anzhengchao@gmail.com"
+...
+```
+
+或者使用该 `User` 对象的方法:
+
+```php
+mixed $user->getId();
+?string $user->getNickname();
+?string $user->getName();
+?string $user->getEmail();
+?string $user->getAvatar();
+?string $user->getRaw();
+?string $user->getAccessToken();
+?string $user->getRefreshToken();
+?int $user->getExpiresIn();
+?array $user->getTokenResponse();
+
+
+```
+
+### 从 OAuth API 响应中取得原始数据
+
+`$user->getRaw()` 方法会返回一个 **array**。
+
+### 当你使用 userFromCode() 想要获取 token 响应的原始数据
+
+`$user->getTokenResponse()` 方法会返回一个 **array** 里面是响应从获取 token 时候 API 返回的响应。
+
+> 注意:当你使用 `userFromCode()` 时,这个方法只返回一个 **有效的数组**,否则将返回 **null**,因为 `userFromToken() ` 没有 token 的 HTTP 响应。
+
+### 通过 access token 获取用户信息
+
+```php
+$accessToken = 'xxxxxxxxxxx';
+$user = $socialite->userFromToken($accessToken);
+```
+
+
+
+# Enjoy it! :heart:
+
+# 参照
+
+- [Alipay - 用户信息授权](https://opendocs.alipay.com/open/289/105656)
+- [DingTalk - 扫码登录第三方网站](https://ding-doc.dingtalk.com/doc#/serverapi3/mrugr3)
+- [Google - OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect)
+- [Github - Authorizing OAuth Apps](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)
+- [Facebook - Graph API](https://developers.facebook.com/docs/graph-api)
+- [Linkedin - Authenticating with OAuth 2.0](https://developer.linkedin.com/docs/oauth2)
+- [微博 - OAuth 2.0 授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E)
+- [QQ - OAuth 2.0 登录 QQ](http://wiki.connect.qq.com/oauth2-0%E7%AE%80%E4%BB%8B)
+- [腾讯云 - OAuth2.0](https://cloud.tencent.com/document/product/306/37730#.E6.8E.A5.E5.85.A5.E8.85.BE.E8.AE.AF.E4.BA.91-oauth)
+- [微信公众平台 - OAuth 文档](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html)
+- [微信开放平台 - 网站应用微信登录开发指南](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN)
+- [微信开放平台 - 代公众号发起网页授权](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318590&token=&lang=zh_CN)
+- [企业微信 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90000/90135/91020)
+- [企业微信第三方应用 - OAuth 文档](https://open.work.weixin.qq.com/api/doc/90001/90143/91118)
+- [豆瓣 - OAuth 2.0 授权机制说明](http://developers.douban.com/wiki/?title=oauth2)
+- [抖音 - 网站应用开发指南](http://open.douyin.com/platform/doc)
+- [飞书 - 授权说明](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)
+- [Tapd - 用户授权说明](https://www.tapd.cn/help/show#1120003271001000093)
+- [Line - OAuth 2.0](https://developers.line.biz/en/docs/line-login/integrate-line-login/)
+- [Gitee - OAuth文档](https://gitee.com/api/v5/oauth_doc#/)
+
+
+
+# PHP 扩展包开发
+
+> 想知道如何从零开始构建 PHP 扩展包?
+>
+> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
+
+# License
+
+MIT
\ No newline at end of file
diff --git a/vendor/overtrue/socialite/composer.json b/vendor/overtrue/socialite/composer.json
index e2b30e5..abdc1fb 100644
--- a/vendor/overtrue/socialite/composer.json
+++ b/vendor/overtrue/socialite/composer.json
@@ -1,26 +1,44 @@
{
- "name": "overtrue/socialite",
- "description": "A collection of OAuth 2 packages that extracts from laravel/socialite.",
- "keywords": ["OAuth", "social", "login", "Weibo", "WeChat", "QQ"],
- "autoload": {
- "psr-4": {
- "Overtrue\\Socialite\\": "src/"
- }
- },
- "require": {
- "php": ">=5.4.0",
- "guzzlehttp/guzzle": "~5.0|~6.0",
- "symfony/http-foundation": "~2.6|~2.7|~2.8|~3.0"
- },
- "require-dev": {
- "mockery/mockery": "~0.9",
- "phpunit/phpunit": "~4.0"
- },
- "license": "MIT",
- "authors": [
- {
- "name": "overtrue",
- "email": "anzhengchao@gmail.com"
- }
- ]
+ "name": "overtrue/socialite",
+ "description": "A collection of OAuth 2 packages.",
+ "keywords": [
+ "oauth",
+ "social",
+ "login",
+ "weibo",
+ "wechat",
+ "qq",
+ "feishu",
+ "qcloud"
+ ],
+ "autoload": {
+ "psr-4": {
+ "Overtrue\\Socialite\\": "src/"
+ }
+ },
+ "require": {
+ "php": ">=7.4",
+ "symfony/http-foundation": "^5.0",
+ "guzzlehttp/guzzle": "~6.0|~7.0",
+ "ext-json": "*",
+ "symfony/psr-http-message-bridge": "^2.0",
+ "ext-openssl": "*"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.2",
+ "phpunit/phpunit": "~9.0",
+ "friendsofphp/php-cs-fixer": "^3.0"
+ },
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "overtrue",
+ "email": "anzhengchao@gmail.com"
+ }
+ ],
+ "scripts": {
+ "check-style": "php-cs-fixer fix --using-cache=no --diff --dry-run --ansi",
+ "fix-style": "php-cs-fixer fix --using-cache=no --ansi",
+ "test": "vendor/bin/phpunit --colors=always"
+ }
}
diff --git a/vendor/overtrue/socialite/phpunit.xml b/vendor/overtrue/socialite/phpunit.xml
index 3347b75..422eeac 100644
--- a/vendor/overtrue/socialite/phpunit.xml
+++ b/vendor/overtrue/socialite/phpunit.xml
@@ -8,7 +8,6 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
- syntaxCheck="false"
>
diff --git a/vendor/overtrue/socialite/src/Config.php b/vendor/overtrue/socialite/src/Config.php
index c538560..35c9dc2 100644
--- a/vendor/overtrue/socialite/src/Config.php
+++ b/vendor/overtrue/socialite/src/Config.php
@@ -1,32 +1,18 @@
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Overtrue\Socialite;
use ArrayAccess;
use InvalidArgumentException;
-/**
- * Class Config.
- */
-class Config implements ArrayAccess
+class Config implements ArrayAccess, \JsonSerializable
{
/**
* @var array
*/
- protected $config;
+ protected array $config;
/**
- * Config constructor.
- *
* @param array $config
*/
public function __construct(array $config)
@@ -35,23 +21,23 @@ class Config implements ArrayAccess
}
/**
- * Get an item from an array using "dot" notation.
- *
* @param string $key
* @param mixed $default
*
* @return mixed
*/
- public function get($key, $default = null)
+ public function get(string $key, $default = null)
{
$config = $this->config;
if (is_null($key)) {
return $config;
}
+
if (isset($config[$key])) {
return $config[$key];
}
+
foreach (explode('.', $key) as $segment) {
if (!is_array($config) || !array_key_exists($segment, $config)) {
return $default;
@@ -63,117 +49,74 @@ class Config implements ArrayAccess
}
/**
- * Set an array item to a given value using "dot" notation.
- *
* @param string $key
* @param mixed $value
*
* @return array
*/
- public function set($key, $value)
+ public function set(string $key, $value)
{
if (is_null($key)) {
throw new InvalidArgumentException('Invalid config key.');
}
$keys = explode('.', $key);
+ $config = &$this->config;
while (count($keys) > 1) {
$key = array_shift($keys);
- if (!isset($this->config[$key]) || !is_array($this->config[$key])) {
- $this->config[$key] = [];
+ if (!isset($config[$key]) || !is_array($config[$key])) {
+ $config[$key] = [];
}
- $this->config = &$this->config[$key];
+ $config = &$config[$key];
}
- $this->config[array_shift($keys)] = $value;
+ $config[array_shift($keys)] = $value;
- return $this->config;
+ return $config;
}
/**
- * Determine if the given configuration value exists.
- *
* @param string $key
*
* @return bool
*/
- public function has($key)
+ public function has(string $key): bool
{
return (bool) $this->get($key);
}
- /**
- * Whether a offset exists.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetexists.php
- *
- * @param mixed $offset
- * An offset to check for.
- *
- *
- * @return bool true on success or false on failure.
- *
- *
- * The return value will be casted to boolean if non-boolean was returned
- *
- * @since 5.0.0
- */
- public function offsetExists($offset)
+ public function offsetExists($offset): bool
{
return array_key_exists($offset, $this->config);
}
- /**
- * Offset to retrieve.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetget.php
- *
- * @param mixed $offset
- * The offset to retrieve.
- *
- *
- * @return mixed Can return all value types
- *
- * @since 5.0.0
- */
+ #[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->get($offset);
}
- /**
- * Offset to set.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetset.php
- *
- * @param mixed $offset
- * The offset to assign the value to.
- *
- * @param mixed $value
- * The value to set.
- *
- *
- * @since 5.0.0
- */
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
- /**
- * Offset to unset.
- *
- * @see http://php.net/manual/en/arrayaccess.offsetunset.php
- *
- * @param mixed $offset
- * The offset to unset.
- *
- *
- * @since 5.0.0
- */
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
$this->set($offset, null);
}
+
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return $this->config;
+ }
+
+ public function __toString()
+ {
+ return \json_encode($this, \JSON_UNESCAPED_UNICODE);
+ }
}
diff --git a/vendor/overtrue/socialite/src/Contracts/FactoryInterface.php b/vendor/overtrue/socialite/src/Contracts/FactoryInterface.php
new file mode 100644
index 0000000..943c11c
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Contracts/FactoryInterface.php
@@ -0,0 +1,13 @@
+body = (array) $body;
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Exceptions/BadRequestException.php b/vendor/overtrue/socialite/src/Exceptions/BadRequestException.php
new file mode 100644
index 0000000..05baa7e
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Exceptions/BadRequestException.php
@@ -0,0 +1,10 @@
+token = $token;
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Exceptions/MethodDoesNotSupportException.php b/vendor/overtrue/socialite/src/Exceptions/MethodDoesNotSupportException.php
new file mode 100644
index 0000000..397b290
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Exceptions/MethodDoesNotSupportException.php
@@ -0,0 +1,8 @@
+sandbox = $this->config->get('sandbox', false);
+ if ($this->sandbox) {
+ $this->baseUrl = 'https://openapi.alipaydev.com/gateway.do';
+ $this->authUrl = 'https://openauth.alipaydev.com/oauth2/publicAppAuthorize.htm';
+ }
+ }
+
+ protected function getAuthUrl(): string
+ {
+ return $this->buildAuthUrlFromBase($this->authUrl);
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl;
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $params = $this->getPublicFields('alipay.user.info.share');
+ $params += ['auth_token' => $token];
+ $params['sign'] = $this->generateSign($params);
+
+ $response = $this->getHttpClient()->post(
+ $this->baseUrl,
+ [
+ 'form_params' => $params,
+ 'headers' => [
+ "content-type" => "application/x-www-form-urlencoded;charset=utf-8",
+ ],
+ ]
+ );
+
+ $response = json_decode($response->getBody()->getContents(), true);
+
+ if (!empty($response['error_response']) || empty($response['alipay_user_info_share_response'])) {
+ throw new \InvalidArgumentException('You have error! ' . \json_encode($response, JSON_UNESCAPED_UNICODE));
+ }
+
+ return $response['alipay_user_info_share_response'];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User(
+ [
+ 'id' => $user['user_id'] ?? null,
+ 'name' => $user['nick_name'] ?? null,
+ 'avatar' => $user['avatar'] ?? null,
+ 'email' => $user['email'] ?? null,
+ ]
+ );
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ $response = $this->getHttpClient()->post(
+ $this->getTokenUrl(),
+ [
+ 'form_params' => $this->getTokenFields($code),
+ 'headers' => [
+ "content-type" => "application/x-www-form-urlencoded;charset=utf-8",
+ ],
+ ]
+ );
+ $response = json_decode($response->getBody()->getContents(), true);
+
+ if (!empty($response['error_response'])) {
+ throw new \InvalidArgumentException('You have error! ' . json_encode($response, JSON_UNESCAPED_UNICODE));
+ }
+
+ return $this->normalizeAccessTokenResponse($response['alipay_system_oauth_token_response']);
+ }
+
+ /**
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
+ */
+ protected function getCodeFields(): array
+ {
+ if (empty($this->redirectUrl)) {
+ throw new InvalidArgumentException('Please set same redirect URL like your Alipay Official Admin');
+ }
+
+ $fields = array_merge(
+ [
+ 'app_id' => $this->getConfig()->get('client_id') ?? $this->getConfig()->get('app_id'),
+ 'scope' => implode(',', $this->scopes),
+ 'redirect_uri' => $this->redirectUrl,
+ ],
+ $this->parameters
+ );
+
+ return $fields;
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array|string[]
+ * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
+ */
+ protected function getTokenFields(string $code): array
+ {
+ $params = $this->getPublicFields('alipay.system.oauth.token');
+ $params += [
+ 'code' => $code,
+ 'grant_type' => 'authorization_code',
+ ];
+ $params['sign'] = $this->generateSign($params);
+
+ return $params;
+ }
+
+ /**
+ * @param string $method
+ *
+ * @return array
+ */
+ public function getPublicFields(string $method): array
+ {
+ return [
+ 'app_id' => $this->getConfig()->get('client_id') ?? $this->getConfig()->get('app_id'),
+ 'format' => $this->format,
+ 'charset' => $this->postCharset,
+ 'sign_type' => $this->signType,
+ 'method' => $method,
+ 'timestamp' => date('Y-m-d H:m:s'),
+ 'version' => $this->apiVersion,
+ ];
+ }
+
+ /**
+ * @param $params
+ *
+ * @return string
+ * @throws InvalidArgumentException
+ *
+ * @see https://opendocs.alipay.com/open/289/105656
+ */
+ protected function generateSign($params)
+ {
+ ksort($params);
+
+ $signContent = $this->buildParams($params);
+ $key = $this->getConfig()->get('rsa_private_key');
+ $signValue = $this->signWithSHA256RSA($signContent, $key);
+
+ return $signValue;
+ }
+
+ /**
+ * @param string $signContent
+ * @param string $key
+ *
+ * @return string
+ * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
+ */
+ protected function signWithSHA256RSA(string $signContent, string $key)
+ {
+ if (empty($key)) {
+ throw new InvalidArgumentException('no RSA private key set.');
+ }
+
+ $key = "-----BEGIN RSA PRIVATE KEY-----\n" .
+ chunk_split($key, 64, "\n") .
+ "-----END RSA PRIVATE KEY-----";
+
+ openssl_sign($signContent, $signValue, $key, OPENSSL_ALGO_SHA256);
+
+ return base64_encode($signValue);
+ }
+
+ /**
+ * @param array $params
+ * @param bool $urlencode
+ * @param array|string[] $except
+ *
+ * @return string
+ */
+ public static function buildParams(array $params, bool $urlencode = false, array $except = ['sign'])
+ {
+ $param_str = '';
+ foreach ($params as $k => $v) {
+ if (in_array($k, $except)) {
+ continue;
+ }
+ $param_str .= $k . '=';
+ $param_str .= $urlencode ? rawurlencode($v) : $v;
+ $param_str .= '&';
+ }
+
+ return rtrim($param_str, '&');
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Baidu.php b/vendor/overtrue/socialite/src/Providers/Baidu.php
new file mode 100644
index 0000000..e8a9b2c
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Baidu.php
@@ -0,0 +1,119 @@
+display = $display;
+
+ return $this;
+ }
+
+ /**
+ * @param array $scopes
+ *
+ * @return self
+ */
+ public function withScopes(array $scopes): self
+ {
+ $this->scopes = $scopes;
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getAuthUrl(): string
+ {
+ return $this->buildAuthUrlFromBase($this->baseUrl . '/oauth/' . $this->version . '/authorize');
+ }
+
+ protected function getCodeFields(): array
+ {
+ return [
+ 'response_type' => 'code',
+ 'client_id' => $this->getClientId(),
+ 'redirect_uri' => $this->redirectUrl,
+ 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+ 'display' => $this->display,
+ ] + $this->parameters;
+ }
+
+ /**
+ * @return string
+ */
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl . '/oauth/' . $this->version . '/token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields($code): array
+ {
+ return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $response = $this->getHttpClient()->get(
+ $this->baseUrl . '/rest/' . $this->version . '/passport/users/getInfo',
+ [
+ 'query' => [
+ 'access_token' => $token,
+ ],
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]
+ );
+
+ return json_decode($response->getBody(), true) ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User(
+ [
+ 'id' => $user['userid'] ?? null,
+ 'nickname' => $user['realname'] ?? null,
+ 'name' => $user['username'] ?? null,
+ 'email' => '',
+ 'avatar' => $user['portrait'] ? 'http://tb.himg.baidu.com/sys/portraitn/item/' . $user['portrait'] : null,
+ ]
+ );
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Base.php b/vendor/overtrue/socialite/src/Providers/Base.php
new file mode 100644
index 0000000..e4bab50
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Base.php
@@ -0,0 +1,333 @@
+config = new Config($config);
+
+ // set scopes
+ if ($this->config->has('scopes') && is_array($this->config->get('scopes'))) {
+ $this->scopes = $this->getConfig()->get('scopes');
+ } elseif ($this->config->has('scope') && is_string($this->getConfig()->get('scope'))) {
+ $this->scopes = array($this->getConfig()->get('scope'));
+ }
+
+ // normalize 'client_id'
+ if (!$this->config->has('client_id')) {
+ $id = $this->config->get('app_id');
+ if (null != $id) {
+ $this->config->set('client_id', $id);
+ }
+ }
+
+ // normalize 'client_secret'
+ if (!$this->config->has('client_secret')) {
+ $secret = $this->config->get('app_secret');
+ if (null != $secret) {
+ $this->config->set('client_secret', $secret);
+ }
+ }
+
+ // normalize 'redirect_url'
+ if (!$this->config->has('redirect_url')) {
+ $this->config->set('redirect_url', $this->config->get('redirect'));
+ }
+ $this->redirectUrl = $this->config->get('redirect_url');
+ }
+
+ abstract protected function getAuthUrl(): string;
+
+ abstract protected function getTokenUrl(): string;
+
+ abstract protected function getUserByToken(string $token): array;
+
+ abstract protected function mapUserToObject(array $user): User;
+
+ /**
+ * @param string|null $redirectUrl
+ *
+ * @return string
+ */
+ public function redirect(?string $redirectUrl = null): string
+ {
+ if (!empty($redirectUrl)) {
+ $this->withRedirectUrl($redirectUrl);
+ }
+
+ return $this->getAuthUrl();
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return \Overtrue\Socialite\User
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function userFromCode(string $code): User
+ {
+ $tokenResponse = $this->tokenFromCode($code);
+ $user = $this->userFromToken($tokenResponse[$this->accessTokenKey]);
+
+ return $user->setRefreshToken($tokenResponse[$this->refreshTokenKey] ?? null)
+ ->setExpiresIn($tokenResponse[$this->expiresInKey] ?? null)
+ ->setTokenResponse($tokenResponse);
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ public function userFromToken(string $token): User
+ {
+ $user = $this->getUserByToken($token);
+
+ return $this->mapUserToObject($user)->setProvider($this)->setRaw($user)->setAccessToken($token);
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ $response = $this->getHttpClient()->post(
+ $this->getTokenUrl(),
+ [
+ 'form_params' => $this->getTokenFields($code),
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]
+ );
+
+ return $this->normalizeAccessTokenResponse($response->getBody()->getContents());
+ }
+
+ /**
+ * @param string $refreshToken
+ *
+ * @throws \Overtrue\Socialite\Exceptions\MethodDoesNotSupportException
+ */
+ public function refreshToken(string $refreshToken)
+ {
+ throw new MethodDoesNotSupportException('refreshToken does not support.');
+ }
+
+ /**
+ * @param string $redirectUrl
+ *
+ * @return $this|\Overtrue\Socialite\Contracts\ProviderInterface
+ */
+ public function withRedirectUrl(string $redirectUrl): ProviderInterface
+ {
+ $this->redirectUrl = $redirectUrl;
+
+ return $this;
+ }
+
+ /**
+ * @param string $state
+ *
+ * @return \Overtrue\Socialite\Contracts\ProviderInterface
+ */
+ public function withState(string $state): ProviderInterface
+ {
+ $this->state = $state;
+
+ return $this;
+ }
+
+ /**
+ * @param array $scopes
+ *
+ * @return $this
+ */
+ public function scopes(array $scopes): self
+ {
+ $this->scopes = $scopes;
+
+ return $this;
+ }
+
+ /**
+ * @param array $parameters
+ *
+ * @return $this
+ */
+ public function with(array $parameters): self
+ {
+ $this->parameters = $parameters;
+
+ return $this;
+ }
+
+ public function getConfig(): Config
+ {
+ return $this->config;
+ }
+
+ /**
+ * @param string $scopeSeparator
+ *
+ * @return self
+ */
+ public function withScopeSeparator(string $scopeSeparator): self
+ {
+ $this->scopeSeparator = $scopeSeparator;
+
+ return $this;
+ }
+
+ public function getClientId(): ?string
+ {
+ return $this->config->get('client_id');
+ }
+
+ public function getClientSecret(): ?string
+ {
+ return $this->config->get('client_secret');
+ }
+
+ public function getHttpClient(): GuzzleClient
+ {
+ return $this->httpClient ?? new GuzzleClient($this->guzzleOptions);
+ }
+
+ /**
+ * @param array $config
+ *
+ * @return \Overtrue\Socialite\Contracts\ProviderInterface
+ */
+ public function setGuzzleOptions($config = []): ProviderInterface
+ {
+ $this->guzzleOptions = $config;
+
+ return $this;
+ }
+
+ public function getGuzzleOptions(): array
+ {
+ return $this->guzzleOptions;
+ }
+
+ /**
+ * @param array $scopes
+ * @param string $scopeSeparator
+ *
+ * @return string
+ */
+ protected function formatScopes(array $scopes, $scopeSeparator): string
+ {
+ return implode($scopeSeparator, $scopes);
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields(string $code): array
+ {
+ return [
+ 'client_id' => $this->getClientId(),
+ 'client_secret' => $this->getClientSecret(),
+ 'code' => $code,
+ 'redirect_uri' => $this->redirectUrl,
+ ];
+ }
+
+ /**
+ * @param string $url
+ *
+ * @return string
+ */
+ protected function buildAuthUrlFromBase(string $url): string
+ {
+ $query = $this->getCodeFields() + ($this->state ? ['state' => $this->state] : []);
+
+ return $url . '?' . \http_build_query($query, '', '&', $this->encodingType);
+ }
+
+ protected function getCodeFields(): array
+ {
+ $fields = array_merge(
+ [
+ 'client_id' => $this->getClientId(),
+ 'redirect_uri' => $this->redirectUrl,
+ 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+ 'response_type' => 'code',
+ ],
+ $this->parameters
+ );
+
+ if ($this->state) {
+ $fields['state'] = $this->state;
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @param array|string $response
+ *
+ * @return mixed
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ *
+ */
+ protected function normalizeAccessTokenResponse($response): array
+ {
+ if ($response instanceof Stream) {
+ $response->rewind();
+ $response = $response->getContents();
+ }
+
+ if (\is_string($response)) {
+ $response = json_decode($response, true) ?? [];
+ }
+
+ if (!\is_array($response)) {
+ throw new AuthorizeFailedException('Invalid token response', [$response]);
+ }
+
+ if (empty($response[$this->accessTokenKey])) {
+ throw new AuthorizeFailedException('Authorize Failed: ' . json_encode($response, JSON_UNESCAPED_UNICODE), $response);
+ }
+
+ return $response + [
+ 'access_token' => $response[$this->accessTokenKey],
+ 'refresh_token' => $response[$this->refreshTokenKey] ?? null,
+ 'expires_in' => \intval($response[$this->expiresInKey] ?? 0),
+ ];
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/DingTalk.php b/vendor/overtrue/socialite/src/Providers/DingTalk.php
new file mode 100644
index 0000000..548f652
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/DingTalk.php
@@ -0,0 +1,126 @@
+buildAuthUrlFromBase('https://oapi.dingtalk.com/connect/qrconnect');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ throw new \InvalidArgumentException('not supported to get access token.');
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ */
+ protected function getUserByToken(string $token): array
+ {
+ throw new \InvalidArgumentException('Unable to use token get User.');
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User(
+ [
+ 'name' => $user['nick'] ?? null,
+ 'nickname' => $user['nick'] ?? null,
+ 'id' => $user['openid'] ?? null,
+ 'email' => null,
+ 'avatar' => null,
+ ]
+ );
+ }
+
+ protected function getCodeFields(): array
+ {
+ return array_merge(
+ [
+ 'appid' => $this->getClientId(),
+ 'response_type' => 'code',
+ 'scope' => implode($this->scopes),
+ 'redirect_uri' => $this->redirectUrl,
+ ],
+ $this->parameters
+ );
+ }
+
+ public function getClientId(): ?string
+ {
+ return $this->getConfig()->get('app_id') ?? $this->getConfig()->get('appid') ?? $this->getConfig()->get('appId')
+ ?? $this->getConfig()->get('client_id');
+ }
+
+ public function getClientSecret(): ?string
+ {
+ return $this->getConfig()->get('app_secret') ?? $this->getConfig()->get('appSecret')
+ ?? $this->getConfig()->get('client_secret');
+ }
+
+ protected function createSignature(int $time)
+ {
+ return base64_encode(hash_hmac('sha256', $time, $this->getClientSecret(), true));
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return User
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @see https://ding-doc.dingtalk.com/doc#/personnal/tmudue
+ */
+ public function userFromCode(string $code): User
+ {
+ $time = (int)microtime(true) * 1000;
+ $queryParams = [
+ 'accessKey' => $this->getClientId(),
+ 'timestamp' => $time,
+ 'signature' => $this->createSignature($time),
+ ];
+
+ $response = $this->getHttpClient()->post(
+ $this->getUserByCode . '?' . http_build_query($queryParams),
+ [
+ 'json' => ['tmp_auth_code' => $code],
+ ]
+ );
+ $response = \json_decode($response->getBody()->getContents(), true);
+
+ if (0 != $response['errcode'] ?? 1) {
+ throw new \InvalidArgumentException('You get error: ' . json_encode($response, JSON_UNESCAPED_UNICODE));
+ }
+
+ return new User(
+ [
+ 'name' => $response['user_info']['nick'],
+ 'nickname' => $response['user_info']['nick'],
+ 'id' => $response['user_info']['openid'],
+ ]
+ );
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/DouYin.php b/vendor/overtrue/socialite/src/Providers/DouYin.php
new file mode 100644
index 0000000..4b6dc84
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/DouYin.php
@@ -0,0 +1,120 @@
+buildAuthUrlFromBase($this->baseUrl . '/platform/oauth/connect/');
+ }
+
+ public function getCodeFields(): array
+ {
+ return [
+ 'client_key' => $this->getClientId(),
+ 'redirect_uri' => $this->redirectUrl,
+ 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+ 'response_type' => 'code',
+ ];
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl . '/oauth/access_token/';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ *
+ */
+ public function tokenFromCode($code): array
+ {
+ $response = $this->getHttpClient()->get(
+ $this->getTokenUrl(),
+ [
+ 'query' => $this->getTokenFields($code),
+ ]
+ );
+
+ $body = \json_decode($response->getBody()->getContents(), true) ?? [];
+
+ if (empty($body['data']) || $body['data']['error_code'] != 0) {
+ throw new AuthorizeFailedException('Invalid token response', $body);
+ }
+
+ $this->withOpenId($body['data']['open_id']);
+
+ return $this->normalizeAccessTokenResponse($body['data']);
+ }
+
+ protected function getTokenFields(string $code): array
+ {
+ return [
+ 'client_key' => $this->getClientId(),
+ 'client_secret' => $this->getClientSecret(),
+ 'code' => $code,
+ 'grant_type' => 'authorization_code',
+ ];
+ }
+
+ protected function getUserByToken(string $token): array
+ {
+ $userUrl = $this->baseUrl . '/oauth/userinfo/';
+
+ if (empty($this->openId)) {
+ throw new InvalidArgumentException('please set open_id before your query.');
+ }
+
+ $response = $this->getHttpClient()->get(
+ $userUrl,
+ [
+ 'query' => [
+ 'access_token' => $token,
+ 'open_id' => $this->openId,
+ ],
+ ]
+ );
+
+ $body = \json_decode($response->getBody()->getContents(), true);
+
+ return $body['data'] ?? [];
+ }
+
+ protected function mapUserToObject(array $user): User
+ {
+ return new User(
+ [
+ 'id' => $user['open_id'] ?? null,
+ 'name' => $user['nickname'] ?? null,
+ 'nickname' => $user['nickname'] ?? null,
+ 'avatar' => $user['avatar'] ?? null,
+ 'email' => $user['email'] ?? null,
+ ]
+ );
+ }
+
+ public function withOpenId(string $openId): self
+ {
+ $this->openId = $openId;
+
+ return $this;
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Douban.php b/vendor/overtrue/socialite/src/Providers/Douban.php
new file mode 100644
index 0000000..06f71e1
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Douban.php
@@ -0,0 +1,83 @@
+buildAuthUrlFromBase('https://www.douban.com/service/auth2/auth');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return 'https://www.douban.com/service/auth2/token';
+ }
+
+ /**
+ * @param string $token
+ * @param array|null $query
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token, ?array $query = []): array
+ {
+ $response = $this->getHttpClient()->get('https://api.douban.com/v2/user/~me', [
+ 'headers' => [
+ 'Authorization' => 'Bearer '.$token,
+ ],
+ ]);
+
+ return json_decode($response->getBody()->getContents(), true) ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['id'] ?? null,
+ 'nickname' => $user['name'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'avatar' => $user['avatar'] ?? null,
+ 'email' => null,
+ ]);
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array|string[]
+ */
+ protected function getTokenFields(string $code): array
+ {
+ return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ $response = $this->getHttpClient()->post($this->getTokenUrl(), [
+ 'form_params' => $this->getTokenFields($code),
+ ]);
+
+ return $this->normalizeAccessTokenResponse($response->getBody()->getContents());
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Facebook.php b/vendor/overtrue/socialite/src/Providers/Facebook.php
new file mode 100644
index 0000000..a7b5f33
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Facebook.php
@@ -0,0 +1,125 @@
+buildAuthUrlFromBase('https://www.facebook.com/' . $this->version . '/dialog/oauth');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->graphUrl . '/oauth/access_token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ $response = $this->getHttpClient()->get(
+ $this->getTokenUrl(),
+ [
+ 'query' => $this->getTokenFields($code),
+ ]
+ );
+
+ return $this->normalizeAccessTokenResponse($response->getBody());
+ }
+
+ /**
+ * @param string $token
+ * @param array|null $query
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token, ?array $query = []): array
+ {
+ $appSecretProof = hash_hmac('sha256', $token, $this->getConfig()->get('client_secret'));
+ $endpoint = $this->graphUrl . '/' . $this->version . '/me?access_token=' . $token . '&appsecret_proof=' . $appSecretProof . '&fields=' .
+ implode(',', $this->fields);
+
+ $response = $this->getHttpClient()->get(
+ $endpoint,
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]
+ );
+
+ return \json_decode($response->getBody(), true) ?? [];
+ }
+
+ protected function mapUserToObject(array $user): User
+ {
+ $userId = $user['id'] ?? null;
+ $avatarUrl = $this->graphUrl . '/' . $this->version . '/' . $userId . '/picture';
+
+ $firstName = $user['first_name'] ?? null;
+ $lastName = $user['last_name'] ?? null;
+
+ return new User(
+ [
+ 'id' => $user['id'] ?? null,
+ 'nickname' => null,
+ 'name' => $firstName . ' ' . $lastName,
+ 'email' => $user['email'] ?? null,
+ 'avatar' => $userId ? $avatarUrl . '?type=normal' : null,
+ 'avatar_original' => $userId ? $avatarUrl . '?width=1920' : null,
+ ]
+ );
+ }
+
+ protected function getCodeFields(): array
+ {
+ $fields = parent::getCodeFields();
+
+ if ($this->popup) {
+ $fields['display'] = 'popup';
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @param array $fields
+ *
+ * @return $this
+ */
+ public function fields(array $fields)
+ {
+ $this->fields = $fields;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function asPopup()
+ {
+ $this->popup = true;
+
+ return $this;
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/FeiShu.php b/vendor/overtrue/socialite/src/Providers/FeiShu.php
new file mode 100644
index 0000000..9cb1f15
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/FeiShu.php
@@ -0,0 +1,239 @@
+isInternalApp = ($this->config->get('app_mode') ?? $this->config->get('mode')) == 'internal';
+ }
+
+ protected function getAuthUrl(): string
+ {
+ return $this->buildAuthUrlFromBase($this->baseUrl . '/authen/v1/index');
+ }
+
+ protected function getCodeFields(): array
+ {
+ return [
+ 'redirect_uri' => $this->redirectUrl,
+ 'app_id' => $this->getClientId(),
+ ];
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl . '/authen/v1/access_token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws AuthorizeFailedException
+ * @throws GuzzleException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ return $this->normalizeAccessTokenResponse($this->getTokenFromCode($code));
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws AuthorizeFailedException
+ *
+ * @throws AuthorizeFailedException
+ * @throws GuzzleException
+ */
+ protected function getTokenFromCode(string $code): array
+ {
+ $this->configAppAccessToken();
+ $response = $this->getHttpClient()->post(
+ $this->getTokenUrl(),
+ [
+ 'json' => [
+ 'app_access_token' => $this->config->get('app_access_token'),
+ 'code' => $code,
+ 'grant_type' => 'authorization_code',
+ ],
+ ]
+ );
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (empty($response['data'])) {
+ throw new AuthorizeFailedException('Invalid token response', $response);
+ }
+
+ return $this->normalizeAccessTokenResponse($response['data']);
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $response = $this->getHttpClient()->get(
+ $this->baseUrl . '/authen/v1/user_info',
+ [
+ 'headers' => ['Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $token],
+ 'query' => array_filter(
+ [
+ 'user_access_token' => $token,
+ ]
+ ),
+ ]
+ );
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (empty($response['data'])) {
+ throw new \InvalidArgumentException('You have error! ' . json_encode($response, JSON_UNESCAPED_UNICODE));
+ }
+
+ return $response['data'];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User(
+ [
+ 'id' => $user['user_id'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'nickname' => $user['name'] ?? null,
+ 'avatar' => $user['avatar_url'] ?? null,
+ 'email' => $user['email'] ?? null,
+ ]
+ );
+ }
+
+ public function withInternalAppMode(): self
+ {
+ $this->isInternalApp = true;
+ return $this;
+ }
+
+ public function withDefaultMode(): self
+ {
+ $this->isInternalApp = false;
+ return $this;
+ }
+
+ /**
+ * set 'app_ticket' in config attribute
+ *
+ * @param string $appTicket
+ *
+ * @return FeiShu
+ */
+ public function withAppTicket(string $appTicket): self
+ {
+ $this->config->set('app_ticket', $appTicket);
+ return $this;
+ }
+
+ /**
+ * 设置 app_access_token 到 config 设置中
+ * 应用维度授权凭证,开放平台可据此识别调用方的应用身份
+ * 分内建和自建
+ */
+ protected function configAppAccessToken()
+ {
+ $url = $this->baseUrl . '/auth/v3/app_access_token/';
+ $params = [
+ 'json' => [
+ 'app_id' => $this->config->get('client_id'),
+ 'app_secret' => $this->config->get('client_secret'),
+ 'app_ticket' => $this->config->get('app_ticket'),
+ ],
+ ];
+
+ if ($this->isInternalApp) {
+ $url = $this->baseUrl . '/auth/v3/app_access_token/internal/';
+ $params = [
+ 'json' => [
+ 'app_id' => $this->config->get('client_id'),
+ 'app_secret' => $this->config->get('client_secret'),
+ ],
+ ];
+ }
+
+ if (!$this->isInternalApp && !$this->config->has('app_ticket')) {
+ throw new InvalidTicketException('You are using default mode, please config \'app_ticket\' first');
+ }
+
+ $response = $this->getHttpClient()->post($url, $params);
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (empty($response['app_access_token'])) {
+ throw new InvalidTokenException('Invalid \'app_access_token\' response', json_encode($response));
+ }
+
+ $this->config->set('app_access_token', $response['app_access_token']);
+ }
+
+ /**
+ * 设置 tenant_access_token 到 config 属性中
+ * 应用的企业授权凭证,开放平台据此识别调用方的应用身份和企业身份
+ * 分内建和自建
+ */
+ protected function configTenantAccessToken()
+ {
+ $url = $this->baseUrl . '/auth/v3/tenant_access_token/';
+ $params = [
+ 'json' => [
+ 'app_id' => $this->config->get('client_id'),
+ 'app_secret' => $this->config->get('client_secret'),
+ 'app_ticket' => $this->config->get('app_ticket'),
+ ],
+ ];
+
+ if ($this->isInternalApp) {
+ $url = $this->baseUrl . '/auth/v3/tenant_access_token/internal/';
+ $params = [
+ 'json' => [
+ 'app_id' => $this->config->get('client_id'),
+ 'app_secret' => $this->config->get('client_secret'),
+ ],
+ ];
+ }
+
+ if (!$this->isInternalApp && !$this->config->has('app_ticket')) {
+ throw new BadRequestException('You are using default mode, please config \'app_ticket\' first');
+ }
+
+ $response = $this->getHttpClient()->post($url, $params);
+ $response = \json_decode($response->getBody(), true) ?? [];
+ if (empty($response['tenant_access_token'])) {
+ throw new AuthorizeFailedException('Invalid tenant_access_token response', $response);
+ }
+
+ $this->config->set('tenant_access_token', $response['tenant_access_token']);
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/GitHub.php b/vendor/overtrue/socialite/src/Providers/GitHub.php
new file mode 100644
index 0000000..d0e1771
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/GitHub.php
@@ -0,0 +1,97 @@
+buildAuthUrlFromBase('https://github.com/login/oauth/authorize');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return 'https://github.com/login/oauth/access_token';
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $userUrl = 'https://api.github.com/user';
+
+ $response = $this->getHttpClient()->get(
+ $userUrl,
+ $this->createAuthorizationHeaders($token)
+ );
+
+ $user = json_decode($response->getBody(), true);
+
+ if (in_array('user:email', $this->scopes)) {
+ $user['email'] = $this->getEmailByToken($token);
+ }
+
+ return $user;
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return string
+ */
+ protected function getEmailByToken(string $token)
+ {
+ $emailsUrl = 'https://api.github.com/user/emails';
+
+ try {
+ $response = $this->getHttpClient()->get(
+ $emailsUrl,
+ $this->createAuthorizationHeaders($token)
+ );
+ } catch (\Throwable $e) {
+ return '';
+ }
+
+ foreach (json_decode($response->getBody(), true) as $email) {
+ if ($email['primary'] && $email['verified']) {
+ return $email['email'];
+ }
+ }
+ }
+
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['id'] ?? null,
+ 'nickname' => $user['login'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'email' => $user['email'] ?? null,
+ 'avatar' => $user['avatar_url'] ?? null,
+ ]);
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ */
+ protected function createAuthorizationHeaders(string $token)
+ {
+ return [
+ 'headers' => [
+ 'Accept' => 'application/vnd.github.v3+json',
+ 'Authorization' => sprintf('token %s', $token),
+ ],
+ ];
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Gitee.php b/vendor/overtrue/socialite/src/Providers/Gitee.php
new file mode 100644
index 0000000..6a1a93e
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Gitee.php
@@ -0,0 +1,65 @@
+buildAuthUrlFromBase('https://gitee.com/oauth/authorize');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return 'https://gitee.com/oauth/token';
+ }
+
+ protected function getUserByToken(string $token): array
+ {
+ $userUrl = 'https://gitee.com/api/v5/user';
+ $response = $this->getHttpClient()->get(
+ $userUrl,
+ [
+ 'query' => ['access_token' => $token],
+ ]
+ );
+ return \json_decode($response->getBody()->getContents(), true) ?? [];
+ }
+
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['id'] ?? null,
+ 'nickname' => $user['login'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'email' => $user['email'] ?? null,
+ 'avatar' => $user['avatar_url'] ?? null,
+ ]);
+ }
+
+ protected function getTokenFields(string $code): array
+ {
+ return [
+ 'client_id' => $this->getClientId(),
+ 'client_secret' => $this->getClientSecret(),
+ 'code' => $code,
+ 'redirect_uri' => $this->redirectUrl,
+ 'grant_type' => 'authorization_code',
+ ];
+ }
+}
\ No newline at end of file
diff --git a/vendor/overtrue/socialite/src/Providers/Google.php b/vendor/overtrue/socialite/src/Providers/Google.php
new file mode 100644
index 0000000..35430c3
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Google.php
@@ -0,0 +1,90 @@
+buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/v2/auth');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return 'https://www.googleapis.com/oauth2/v4/token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ public function tokenFromCode($code): array
+ {
+ $response = $this->getHttpClient()->post($this->getTokenUrl(), [
+ 'form_params' => $this->getTokenFields($code),
+ ]);
+
+ return $this->normalizeAccessTokenResponse($response->getBody());
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields($code): array
+ {
+ return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
+ }
+
+ /**
+ * @param string $token
+ * @param array|null $query
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token, ?array $query = []): array
+ {
+ $response = $this->getHttpClient()->get('https://www.googleapis.com/userinfo/v2/me', [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Bearer '.$token,
+ ],
+ ]);
+
+ return \json_decode($response->getBody(), true) ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['id'] ?? null,
+ 'username' => $user['email'] ?? null,
+ 'nickname' => $user['name'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'email' => $user['email'] ?? null,
+ 'avatar' => $user['picture'] ?? null,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Line.php b/vendor/overtrue/socialite/src/Providers/Line.php
new file mode 100644
index 0000000..844b1e3
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Line.php
@@ -0,0 +1,77 @@
+state = $this->state ?: md5(uniqid());
+ return $this->buildAuthUrlFromBase('https://access.line.me/oauth2/'.$this->version.'/authorize');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl.$this->version.'/token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields(string $code): array
+ {
+ return [
+ 'client_id' => $this->getClientId(),
+ 'client_secret' => $this->getClientSecret(),
+ 'code' => $code,
+ 'grant_type' => 'authorization_code',
+ 'redirect_uri' => $this->redirectUrl,
+ ];
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $response = $this->getHttpClient()->get(
+ 'https://api.line.me/v2/profile',
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Bearer '.$token,
+ ],
+ ]
+ );
+
+ return \json_decode($response->getBody(), true) ?? [];
+ }
+
+ protected function mapUserToObject(array $user): User
+ {
+ return new User(
+ [
+ 'id' => $user['userId'] ?? null,
+ 'name' => $user['displayName'] ?? null,
+ 'nickname' => $user['displayName'] ?? null,
+ 'avatar' => $user['pictureUrl'] ?? null,
+ 'email' => null,
+ ]
+ );
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Linkedin.php b/vendor/overtrue/socialite/src/Providers/Linkedin.php
new file mode 100644
index 0000000..ae6df2c
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Linkedin.php
@@ -0,0 +1,121 @@
+buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return 'https://www.linkedin.com/oauth/v2/accessToken';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields($code): array
+ {
+ return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
+ }
+
+ /**
+ * @param string $token
+ * @param array|null $query
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token, ?array $query = []): array
+ {
+ $basicProfile = $this->getBasicProfile($token);
+ $emailAddress = $this->getEmailAddress($token);
+
+ return array_merge($basicProfile, $emailAddress);
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getBasicProfile(string $token)
+ {
+ $url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))';
+
+ $response = $this->getHttpClient()->get($url, [
+ 'headers' => [
+ 'Authorization' => 'Bearer '.$token,
+ 'X-RestLi-Protocol-Version' => '2.0.0',
+ ],
+ ]);
+
+ return \json_decode($response->getBody(), true) ?? [];
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getEmailAddress(string $token)
+ {
+ $url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))';
+
+ $response = $this->getHttpClient()->get($url, [
+ 'headers' => [
+ 'Authorization' => 'Bearer '.$token,
+ 'X-RestLi-Protocol-Version' => '2.0.0',
+ ],
+ ]);
+
+ return \json_decode($response->getBody(), true)['elements.0.handle~'] ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ $preferredLocale = ($user['firstName.preferredLocale.language'] ?? null).'_'.($user['firstName.preferredLocale.country']) ?? null;
+ $firstName = $user['firstName.localized.'.$preferredLocale] ?? null;
+ $lastName = $user['lastName.localized.'.$preferredLocale] ?? null;
+ $name = $firstName.' '.$lastName;
+
+ $images = $user['profilePicture.displayImage~.elements'] ?? [];
+ $avatars = array_filter($images, function ($image) {
+ return ($image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] ?? 0) === 100;
+ });
+ $avatar = array_shift($avatars);
+ $originalAvatars = array_filter($images, function ($image) {
+ return ($image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] ?? 0) === 800;
+ });
+ $originalAvatar = array_shift($originalAvatars);
+
+ return new User([
+ 'id' => $user['id'] ?? null,
+ 'nickname' => $name,
+ 'name' => $name,
+ 'email' => $user['emailAddress'] ?? null,
+ 'avatar' => $avatar['identifiers.0.identifier'] ?? null,
+ 'avatar_original' => $originalAvatar['identifiers.0.identifier'] ?? null,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/OpenWeWork.php b/vendor/overtrue/socialite/src/Providers/OpenWeWork.php
new file mode 100644
index 0000000..f4eee68
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/OpenWeWork.php
@@ -0,0 +1,217 @@
+getConfig()->has('base_url')) {
+ $this->baseUrl = $this->getConfig()->get('base_url');
+ }
+ }
+
+ public function withAgentId(int $agentId): OpenWeWork
+ {
+ $this->agentId = $agentId;
+
+ return $this;
+ }
+
+ public function userFromCode(string $code): User
+ {
+ $user = $this->getUser($this->getSuiteAccessToken(), $code);
+
+ if ($this->detailed) {
+ $user = array_merge($user, $this->getUserByTicket($user['user_ticket']));
+ }
+
+ return $this->mapUserToObject($user)->setProvider($this)->setRaw($user);
+ }
+
+ public function withSuiteTicket(string $suiteTicket): OpenWeWork
+ {
+ $this->suiteTicket = $suiteTicket;
+
+ return $this;
+ }
+
+ public function withSuiteAccessToken(string $suiteAccessToken): OpenWeWork
+ {
+ $this->suiteAccessToken = $suiteAccessToken;
+
+ return $this;
+ }
+
+ public function getAuthUrl(): string
+ {
+ $queries = \array_filter([
+ 'appid' => $this->getClientId(),
+ 'redirect_uri' => $this->redirectUrl,
+ 'response_type' => 'code',
+ 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+ 'state' => $this->state,
+ 'agentid' => $this->agentId,
+ ]);
+
+ if ((\in_array('snsapi_userinfo', $this->scopes) || \in_array('snsapi_privateinfo', $this->scopes)) && empty($this->agentId)) {
+ throw new InvalidArgumentException('agentid is required when scopes is snsapi_userinfo or snsapi_privateinfo.');
+ }
+
+ return sprintf('https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect', http_build_query($queries));
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\MethodDoesNotSupportException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ throw new MethodDoesNotSupportException('Open WeWork doesn\'t support access_token mode');
+ }
+
+ /**
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ protected function getSuiteAccessToken(): string
+ {
+ return $this->suiteAccessToken ?? $this->suiteAccessToken = $this->requestSuiteAccessToken();
+ }
+
+ /**
+ * @param string $token
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUser(string $token, string $code): array
+ {
+ $response = $this->getHttpClient()->get(
+ $this->baseUrl . '/cgi-bin/service/getuserinfo3rd',
+ [
+ 'query' => array_filter(
+ [
+ 'suite_access_token' => $token,
+ 'code' => $code,
+ ]
+ ),
+ ]
+ );
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (($response['errcode'] ?? 1) > 0 || (empty($response['UserId']) && empty($response['open_userid']))) {
+ throw new AuthorizeFailedException('Failed to get user openid:' . $response['errmsg'] ?? 'Unknown.', $response);
+ }
+
+ return $response;
+ }
+
+ /**
+ * @param string $userTicket
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByTicket(string $userTicket): array
+ {
+ $response = $this->getHttpClient()->post(
+ $this->baseUrl . '/cgi-bin/user/get',
+ [
+ 'query' => [
+ 'suite_access_token' => $this->getSuiteAccessToken(),
+ 'user_ticket' => $userTicket,
+ ],
+ ]
+ );
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (($response['errcode'] ?? 1) > 0 || empty($response['userid'])) {
+ throw new AuthorizeFailedException('Failed to get user:' . $response['errmsg'] ?? 'Unknown.', $response);
+ }
+
+ return $response;
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ if ($this->detailed) {
+ return new User(
+ [
+ 'id' => $user['userid'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'avatar' => $user['avatar'] ?? null,
+ 'email' => $user['email'] ?? null,
+ ]
+ );
+ }
+
+ return new User(
+ [
+ 'id' => $user['UserId'] ?? null ?: $user['OpenId'] ?? null,
+ ]
+ );
+ }
+
+ /**
+ * @return string
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function requestSuiteAccessToken(): string
+ {
+ $response = $this->getHttpClient()->post(
+ $this->baseUrl . '/cgi-bin/service/get_suite_token',
+ [
+ 'json' =>
+ [
+ 'suite_id' => $this->config->get('suite_id') ?? $this->config->get('client_id'),
+ 'suite_secret' => $this->config->get('suite_secret') ?? $this->config->get('client_secret'),
+ 'suite_ticket' => $this->suiteTicket,
+ ]
+ ]
+ );
+
+ $response = \json_decode($response->getBody()->getContents(), true) ?? [];
+
+ if (isset($response['errcode']) && $response['errcode'] > 0) {
+ throw new AuthorizeFailedException('Failed to get api access_token:' . $response['errmsg'] ?? 'Unknown.', $response);
+ }
+
+ return $response['suite_access_token'];
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return '';
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Outlook.php b/vendor/overtrue/socialite/src/Providers/Outlook.php
new file mode 100644
index 0000000..55cdc96
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Outlook.php
@@ -0,0 +1,71 @@
+buildAuthUrlFromBase('https://login.microsoftonline.com/common/oauth2/v2.0/authorize');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
+ }
+
+ /**
+ * @param string $token
+ * @param array|null $query
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token, ?array $query = []): array
+ {
+ $response = $this->getHttpClient()->get(
+ 'https://graph.microsoft.com/v1.0/me',
+ ['headers' => [
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Bearer '.$token,
+ ],
+ ]
+ );
+
+ return \json_decode($response->getBody()->getContents(), true) ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['id'] ?? null,
+ 'nickname' => null,
+ 'name' => $user['displayName'] ?? null,
+ 'email' => $user['userPrincipalName'] ?? null,
+ 'avatar' => null,
+ ]);
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array|string[]
+ */
+ protected function getTokenFields(string $code): array
+ {
+ return parent::getTokenFields($code) + [
+ 'grant_type' => 'authorization_code',
+ ];
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/QCloud.php b/vendor/overtrue/socialite/src/Providers/QCloud.php
new file mode 100644
index 0000000..898898d
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/QCloud.php
@@ -0,0 +1,260 @@
+buildAuthUrlFromBase('https://cloud.tencent.com/open/authorize');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return '';
+ }
+
+ protected function getAppId(): string
+ {
+ return $this->config->get('app_id') ?? $this->getClientId();
+ }
+
+ protected function getSecretId(): string
+ {
+ return $this->config->get('secret_id');
+ }
+
+ protected function getSecretKey(): string
+ {
+ return $this->config->get('secret_key');
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ public function TokenFromCode(string $code): array
+ {
+ $response = $this->performRequest(
+ 'GET',
+ 'open.tencentcloudapi.com',
+ 'GetUserAccessToken',
+ '2018-12-25',
+ [
+ 'query' => [
+ 'UserAuthCode' => $code,
+ ],
+ ]
+ );
+
+ return $this->parseAccessToken($response);
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $secret = $this->getFederationToken($token);
+
+ return $this->performRequest(
+ 'GET',
+ 'open.tencentcloudapi.com',
+ 'GetUserBaseInfo',
+ '2018-12-25',
+ [
+ 'headers' => [
+ 'X-TC-Token' => $secret['Token'],
+ ],
+ ],
+ $secret['TmpSecretId'],
+ $secret['TmpSecretKey'],
+ );
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User(
+ [
+ 'id' => $this->openId ?? null,
+ 'name' => $user['Nickname'] ?? null,
+ 'nickname' => $user['Nickname'] ?? null,
+ ]
+ );
+ }
+
+ public function performRequest(string $method, string $host, string $action, string $version, array $options = [], ?string $secretId = null, ?string $secretKey = null)
+ {
+ $method = \strtoupper($method);
+ $timestamp = \time();
+ $credential = \sprintf('%s/%s/tc3_request', \gmdate('Y-m-d', $timestamp), $this->getServiceFromHost($host));
+ $options['headers'] = \array_merge(
+ $options['headers'] ?? [],
+ [
+ 'X-TC-Action' => $action,
+ 'X-TC-Timestamp' => $timestamp,
+ 'X-TC-Version' => $version,
+ 'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
+ ]
+ );
+
+ $signature = $this->sign($method, $host, $options['query'] ?? [], '', $options['headers'], $credential, $secretKey);
+ $options['headers']['Authorization'] =
+ \sprintf(
+ 'TC3-HMAC-SHA256 Credential=%s/%s, SignedHeaders=content-type;host, Signature=%s',
+ $secretId ?? $this->getSecretId(),
+ $credential,
+ $signature
+ );
+ $options['debug'] = \fopen(storage_path('logs/laravel-2020-07-15.log'), 'w+');
+ $response = $this->getHttpClient()->get("https://{$host}/", $options);
+
+ $response = json_decode($response->getBody()->getContents(), true) ?? [];
+
+ if (!empty($response['Response']['Error'])) {
+ throw new AuthorizeFailedException(
+ \sprintf('%s: %s', $response['Response']['Error']['Code'], $response['Response']['Error']['Message']),
+ $response
+ );
+ }
+
+ return $response['Response'] ?? [];
+ }
+
+ protected function sign(string $requestMethod, string $host, array $query, string $payload, $headers, $credential, ?string $secretKey = null)
+ {
+ $canonicalRequestString = \join(
+ "\n",
+ [
+ $requestMethod,
+ '/',
+ \http_build_query($query),
+ "content-type:{$headers['Content-Type']}\nhost:{$host}\n",
+ "content-type;host",
+ hash('SHA256', $payload),
+ ]
+ );
+
+ $signString = \join(
+ "\n",
+ [
+ 'TC3-HMAC-SHA256',
+ $headers['X-TC-Timestamp'],
+ $credential,
+ hash('SHA256', $canonicalRequestString),
+ ]
+ );
+
+ $secretKey = $secretKey ?? $this->getSecretKey();
+ $secretDate = hash_hmac('SHA256', \gmdate('Y-m-d', $headers['X-TC-Timestamp']), "TC3{$secretKey}", true);
+ $secretService = hash_hmac('SHA256', $this->getServiceFromHost($host), $secretDate, true);
+ $secretSigning = hash_hmac('SHA256', "tc3_request", $secretService, true);
+
+ return hash_hmac('SHA256', $signString, $secretSigning);
+ }
+
+ /**
+ * @param string|array $body
+ *
+ * @return array
+ * @throws AuthorizeFailedException
+ */
+ protected function parseAccessToken($body)
+ {
+ if (!is_array($body)) {
+ $body = json_decode($body, true);
+ }
+
+ if (empty($body['UserOpenId'])) {
+ throw new AuthorizeFailedException('Authorize Failed: ' . json_encode($body, JSON_UNESCAPED_UNICODE), $body);
+ }
+
+ $this->openId = $body['UserOpenId'] ?? null;
+ $this->unionId = $body['UserUnionId'] ?? null;
+
+ return $body;
+ }
+
+ /**
+ * @param string $accessToken
+ *
+ * @return mixed
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ protected function getFederationToken(string $accessToken)
+ {
+ $response = $this->performRequest(
+ 'GET',
+ 'sts.tencentcloudapi.com',
+ 'GetThirdPartyFederationToken',
+ '2018-08-13',
+ [
+ 'query' => [
+ 'UserAccessToken' => $accessToken,
+ 'Duration' => 7200,
+ 'ApiAppId' => 0,
+ ],
+ 'headers' => [
+ 'X-TC-Region' => 'ap-guangzhou', // 官方人员说写死
+ ]
+ ]
+ );
+
+ if (empty($response['Credentials'])) {
+ throw new AuthorizeFailedException('Get Federation Token failed.', $response);
+ }
+
+ return $response['Credentials'];
+ }
+
+ protected function getCodeFields(): array
+ {
+ $fields = array_merge(
+ [
+ 'app_id' => $this->getAppId(),
+ 'redirect_url' => $this->redirectUrl,
+ 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+ 'response_type' => 'code',
+ ],
+ $this->parameters
+ );
+
+ if ($this->state) {
+ $fields['state'] = $this->state;
+ }
+
+ return $fields;
+ }
+
+ /**
+ * @param string $host
+ *
+ * @return mixed|string
+ */
+ protected function getServiceFromHost(string $host)
+ {
+ return explode('.', $host)[0];
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/QQ.php b/vendor/overtrue/socialite/src/Providers/QQ.php
new file mode 100644
index 0000000..b9c5ce5
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/QQ.php
@@ -0,0 +1,109 @@
+buildAuthUrlFromBase($this->baseUrl.'/oauth2.0/authorize');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl.'/oauth2.0/token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields(string $code): array
+ {
+ return parent::getTokenFields($code) + [
+ 'grant_type' => 'authorization_code',
+ ];
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ $response = $this->getHttpClient()->get($this->getTokenUrl(), [
+ 'query' => $this->getTokenFields($code),
+ ]);
+
+ \parse_str($response->getBody()->getContents(), $token);
+
+ return $this->normalizeAccessTokenResponse($token);
+ }
+
+ public function withUnionId(): self
+ {
+ $this->withUnionId = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $url = $this->baseUrl.'/oauth2.0/me?fmt=json&access_token='.$token;
+ $this->withUnionId && $url .= '&unionid=1';
+
+ $response = $this->getHttpClient()->get($url);
+
+ $me = \json_decode($response->getBody()->getContents(), true);
+
+ $queries = [
+ 'access_token' => $token,
+ 'fmt' => 'json',
+ 'openid' => $me['openid'],
+ 'oauth_consumer_key' => $this->getClientId(),
+ ];
+
+ $response = $this->getHttpClient()->get($this->baseUrl.'/user/get_user_info?'.http_build_query($queries));
+
+ return (\json_decode($response->getBody()->getContents(), true) ?? []) + [
+ 'unionid' => $me['unionid'] ?? null,
+ 'openid' => $me['openid'] ?? null,
+ ];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['openid'] ?? null,
+ 'name' => $user['nickname'] ?? null,
+ 'nickname' => $user['nickname'] ?? null,
+ 'email' => $user['email'] ?? null,
+ 'avatar' => $user['figureurl_qq_2'] ?? null,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Taobao.php b/vendor/overtrue/socialite/src/Providers/Taobao.php
new file mode 100644
index 0000000..80da804
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Taobao.php
@@ -0,0 +1,160 @@
+view = $view;
+
+ return $this;
+ }
+
+ protected function getAuthUrl(): string
+ {
+ return $this->buildAuthUrlFromBase($this->baseUrl.'/authorize');
+ }
+
+ public function getCodeFields(): array
+ {
+ return [
+ 'client_id' => $this->getClientId(),
+ 'redirect_uri' => $this->redirectUrl,
+ 'view' => $this->view,
+ 'response_type' => 'code',
+ ];
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl.'/token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields($code): array
+ {
+ return parent::getTokenFields($code) + ['grant_type' => 'authorization_code', 'view' => $this->view];
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ $response = $this->getHttpClient()->post($this->getTokenUrl(), [
+ 'query' => $this->getTokenFields($code),
+ ]);
+
+ return $this->normalizeAccessTokenResponse($response->getBody()->getContents());
+ }
+
+ /**
+ * @param string $token
+ * @param array|null $query
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token, ?array $query = []): array
+ {
+ $response = $this->getHttpClient()->post($this->getUserInfoUrl($this->gatewayUrl, $token));
+
+ return \json_decode($response->getBody()->getContents(), true) ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['open_id'] ?? null,
+ 'nickname' => $user['nick'] ?? null,
+ 'name' => $user['nick'] ?? null,
+ 'avatar' => $user['avatar'] ?? null,
+ 'email' => $user['email'] ?? null,
+ ]);
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return string
+ */
+ protected function generateSign(array $params)
+ {
+ ksort($params);
+
+ $stringToBeSigned = $this->getConfig()->get('client_secret');
+
+ foreach ($params as $k => $v) {
+ if (!is_array($v) && '@' != substr($v, 0, 1)) {
+ $stringToBeSigned .= "$k$v";
+ }
+ }
+
+ $stringToBeSigned .= $this->getConfig()->get('client_secret');
+
+ return strtoupper(md5($stringToBeSigned));
+ }
+
+ /**
+ * @param string $token
+ * @param array $apiFields
+ *
+ * @return array
+ */
+ protected function getPublicFields(string $token, array $apiFields = [])
+ {
+ $fields = [
+ 'app_key' => $this->getClientId(),
+ 'sign_method' => 'md5',
+ 'session' => $token,
+ 'timestamp' => \date('Y-m-d H:i:s'),
+ 'v' => '2.0',
+ 'format' => 'json',
+ ];
+
+ $fields = array_merge($apiFields, $fields);
+ $fields['sign'] = $this->generateSign($fields);
+
+ return $fields;
+ }
+
+ /**
+ * @param string $url
+ * @param string $token
+ *
+ * @return string
+ */
+ protected function getUserInfoUrl(string $url, string $token)
+ {
+ $apiFields = ['method' => 'taobao.miniapp.userInfo.get'];
+
+ $query = http_build_query($this->getPublicFields($token, $apiFields), '', '&', $this->encodingType);
+
+ return $url.'?'.$query;
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Tapd.php b/vendor/overtrue/socialite/src/Providers/Tapd.php
new file mode 100644
index 0000000..48624ab
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Tapd.php
@@ -0,0 +1,185 @@
+buildAuthUrlFromBase($this->baseUrl . '/quickstart/testauth');
+ }
+
+ /**
+ * @return string
+ */
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl . '/tokens/request_token';
+ }
+
+ /**
+ * @return string
+ */
+ protected function getRefreshTokenUrl(): string
+ {
+ return $this->baseUrl . '/tokens/refresh_token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function tokenFromCode($code): array
+ {
+ $response = $this->getHttpClient()->post($this->getTokenUrl(), [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Basic ' . \base64_encode(\sprintf('%s:%s', $this->getClientId(), $this->getClientSecret()))
+ ],
+ 'form_params' => $this->getTokenFields($code),
+ ]);
+
+ return $this->normalizeAccessTokenResponse($response->getBody()->getContents());
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields(string $code): array
+ {
+ return [
+ 'grant_type' => 'authorization_code',
+ 'redirect_uri' => $this->redirectUrl,
+ 'code' => $code,
+ ];
+ }
+
+ /**
+ * @param $refreshToken
+ *
+ * @return array
+ */
+ protected function getRefreshTokenFields($refreshToken): array
+ {
+ return [
+ 'grant_type' => 'refresh_token',
+ 'redirect_uri' => $this->redirectUrl,
+ 'refresh_token' => $refreshToken,
+ ];
+ }
+
+ /**
+ * @param string $refreshToken
+ *
+ * @return array
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ public function tokenFromRefreshToken(string $refreshToken): array
+ {
+ $response = $this->getHttpClient()->post($this->getRefreshTokenUrl(), [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Basic ' . \base64_encode(\sprintf('%s:%s', $this->getClientId(), $this->getClientSecret()))
+ ],
+ 'form_params' => $this->getRefreshTokenFields($refreshToken),
+ ]);
+
+ return $this->normalizeAccessTokenResponse($response->getBody()->getContents());
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $response = $this->getHttpClient()->get($this->baseUrl . '/users/info', [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ 'Authorization' => 'Bearer ' . $token,
+ ],
+ ]);
+
+ return \json_decode($response->getBody(), true) ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ *
+ * @throws \Overtrue\Socialite\Exceptions\BadRequestException
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ if (!isset($user['status']) && $user['status'] != 1) {
+ throw new BadRequestException("用户信息获取失败");
+ }
+
+ return new User([
+ 'id' => $user['data']['id'] ?? null,
+ 'nickname' => $user['data']['nick'] ?? null,
+ 'name' => $user['data']['name'] ?? null,
+ 'email' => $user['data']['email'] ?? null,
+ 'avatar' => $user['data']['avatar'] ?? null,
+ ]);
+ }
+
+ /**
+ * @param array|string $response
+ *
+ * @return mixed
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ *
+ */
+ protected function normalizeAccessTokenResponse($response): array
+ {
+ if ($response instanceof Stream) {
+ $response->rewind();
+ $response = $response->getContents();
+ }
+
+ if (\is_string($response)) {
+ $response = json_decode($response, true) ?? [];
+ }
+
+ if (!\is_array($response)) {
+ throw new AuthorizeFailedException('Invalid token response', [$response]);
+ }
+
+ if (empty($response['data'][$this->accessTokenKey])) {
+ throw new AuthorizeFailedException('Authorize Failed: ' . json_encode($response, JSON_UNESCAPED_UNICODE), $response);
+ }
+
+ return $response + [
+ 'access_token' => $response['data'][$this->accessTokenKey],
+ 'refresh_token' => $response['data'][$this->refreshTokenKey] ?? null,
+ 'expires_in' => \intval($response['data'][$this->expiresInKey] ?? 0),
+ ];
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/WeChat.php b/vendor/overtrue/socialite/src/Providers/WeChat.php
new file mode 100644
index 0000000..e9ce827
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/WeChat.php
@@ -0,0 +1,262 @@
+getConfig()->has('component')) {
+ $this->prepareForComponent((array) $this->getConfig()->get('component'));
+ }
+ }
+
+ /**
+ * @param string $openid
+ *
+ * @return $this
+ */
+ public function withOpenid(string $openid): self
+ {
+ $this->openid = $openid;
+
+ return $this;
+ }
+
+ public function withCountryCode()
+ {
+ $this->withCountryCode = true;
+
+ return $this;
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException
+ */
+ public function tokenFromCode(string $code): array
+ {
+ $response = $this->getTokenFromCode($code);
+
+ return $this->normalizeAccessTokenResponse($response->getBody()->getContents());
+ }
+
+ /**
+ * @param array $componentConfig ['id' => xxx, 'token' => xxx]
+ *
+ * @return \Overtrue\Socialite\Providers\WeChat
+ * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
+ */
+ public function withComponent(array $componentConfig)
+ {
+ $this->prepareForComponent($componentConfig);
+
+ return $this;
+ }
+
+ public function getComponent()
+ {
+ return $this->component;
+ }
+
+ protected function getAuthUrl(): string
+ {
+ $path = 'oauth2/authorize';
+
+ if (in_array('snsapi_login', $this->scopes)) {
+ $path = 'qrconnect';
+ }
+
+ return $this->buildAuthUrlFromBase("https://open.weixin.qq.com/connect/{$path}");
+ }
+
+ /**
+ * @param string $url
+ *
+ * @return string
+ */
+ protected function buildAuthUrlFromBase(string $url): string
+ {
+ $query = http_build_query($this->getCodeFields(), '', '&', $this->encodingType);
+
+ return $url . '?' . $query . '#wechat_redirect';
+ }
+
+ protected function getCodeFields(): array
+ {
+ if (!empty($this->component)) {
+ $this->with(array_merge($this->parameters, ['component_appid' => $this->component['id']]));
+ }
+
+ return array_merge([
+ 'appid' => $this->getClientId(),
+ 'redirect_uri' => $this->redirectUrl,
+ 'response_type' => 'code',
+ 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+ 'state' => $this->state ?: md5(uniqid()),
+ 'connect_redirect' => 1,
+ ], $this->parameters);
+ }
+
+ protected function getTokenUrl(): string
+ {
+ if (!empty($this->component)) {
+ return $this->baseUrl . '/oauth2/component/access_token';
+ }
+
+ return $this->baseUrl . '/oauth2/access_token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return \Overtrue\Socialite\User
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException|\GuzzleHttp\Exception\GuzzleException
+ */
+ public function userFromCode(string $code): User
+ {
+ if (in_array('snsapi_base', $this->scopes)) {
+ return $this->mapUserToObject(\json_decode($this->getTokenFromCode($code)->getBody()->getContents(), true) ?? []);
+ }
+
+ $token = $this->tokenFromCode($code);
+
+ $this->withOpenid($token['openid']);
+
+ $user = $this->userFromToken($token[$this->accessTokenKey]);
+
+ return $user->setRefreshToken($token['refresh_token'])
+ ->setExpiresIn($token['expires_in'])
+ ->setTokenResponse($token);
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $language = $this->withCountryCode ? null : (isset($this->parameters['lang']) ? $this->parameters['lang'] : 'zh_CN');
+
+ $response = $this->getHttpClient()->get($this->baseUrl . '/userinfo', [
+ 'query' => array_filter([
+ 'access_token' => $token,
+ 'openid' => $this->openid,
+ 'lang' => $language,
+ ]),
+ ]);
+
+ return \json_decode($response->getBody()->getContents(), true) ?? [];
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['openid'] ?? null,
+ 'name' => $user['nickname'] ?? null,
+ 'nickname' => $user['nickname'] ?? null,
+ 'avatar' => $user['headimgurl'] ?? null,
+ 'email' => null,
+ ]);
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields(string $code): array
+ {
+ if (!empty($this->component)) {
+ return [
+ 'appid' => $this->getClientId(),
+ 'component_appid' => $this->component['id'],
+ 'component_access_token' => $this->component['token'],
+ 'code' => $code,
+ 'grant_type' => 'authorization_code',
+ ];
+ }
+
+ return [
+ 'appid' => $this->getClientId(),
+ 'secret' => $this->getClientSecret(),
+ 'code' => $code,
+ 'grant_type' => 'authorization_code',
+ ];
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return \Psr\Http\Message\ResponseInterface
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getTokenFromCode(string $code): ResponseInterface
+ {
+ return $this->getHttpClient()->get($this->getTokenUrl(), [
+ 'headers' => ['Accept' => 'application/json'],
+ 'query' => $this->getTokenFields($code),
+ ]);
+ }
+
+ protected function prepareForComponent(array $component)
+ {
+ $config = [];
+ foreach ($component as $key => $value) {
+ if (\is_callable($value)) {
+ $value = \call_user_func($value, $this);
+ }
+
+ switch ($key) {
+ case 'id':
+ case 'app_id':
+ case 'component_app_id':
+ $config['id'] = $value;
+ break;
+ case 'token':
+ case 'app_token':
+ case 'access_token':
+ case 'component_access_token':
+ $config['token'] = $value;
+ break;
+ }
+ }
+
+ if (2 !== count($config)) {
+ throw new InvalidArgumentException('Please check your config arguments is available.');
+ }
+
+ if (1 === count($this->scopes) && in_array('snsapi_login', $this->scopes)) {
+ $this->scopes = ['snsapi_base'];
+ }
+
+ $this->component = $config;
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/WeWork.php b/vendor/overtrue/socialite/src/Providers/WeWork.php
new file mode 100644
index 0000000..392c9c5
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/WeWork.php
@@ -0,0 +1,274 @@
+getConfig()->has('base_url')) {
+ $this->baseUrl = $this->getConfig()->get('base_url');
+ }
+ }
+
+ /**
+ * @deprecated will remove at 4.0
+ */
+ public function setAgentId(int $agentId): WeWork
+ {
+ $this->agentId = $agentId;
+
+ return $this;
+ }
+
+ /**
+ * @deprecated will remove at 4.0
+ */
+ public function withAgentId(int $agentId): WeWork
+ {
+ $this->agentId = $agentId;
+
+ return $this;
+ }
+
+ public function getBaseUrl()
+ {
+ return $this->baseUrl;
+ }
+
+ public function userFromCode(string $code): User
+ {
+ $token = $this->getApiAccessToken();
+ $user = $this->getUser($token, $code);
+
+ if ($this->detailed) {
+ $userTicket = $user['user_ticket'] ?? '';
+ $user = $this->getUserById($user['UserId']);
+ if ($userTicket) {
+ $user += $this->getUserDetail($userTicket);
+ }
+ }
+
+ return $this->mapUserToObject($user)->setProvider($this)->setRaw($user);
+ }
+
+ public function detailed(): self
+ {
+ $this->detailed = true;
+
+ return $this;
+ }
+
+ public function withApiAccessToken(string $apiAccessToken): WeWork
+ {
+ $this->apiAccessToken = $apiAccessToken;
+
+ return $this;
+ }
+
+ public function getAuthUrl(): string
+ {
+ // 网页授权登录
+ if (empty($this->agentId)) {
+ $queries = [
+ 'appid' => $this->getClientId(),
+ 'redirect_uri' => $this->redirectUrl,
+ 'response_type' => 'code',
+ 'scope' => $this->formatScopes($this->scopes, $this->scopeSeparator),
+ 'state' => $this->state,
+ ];
+
+ return sprintf('https://open.weixin.qq.com/connect/oauth2/authorize?%s#wechat_redirect', http_build_query($queries));
+ }
+
+ // 第三方网页应用登录(扫码登录)
+ return $this->getQrConnectUrl();
+ }
+
+ /**
+ * @deprecated will remove at 4.0
+ */
+ public function getQrConnectUrl()
+ {
+ $queries = [
+ 'appid' => $this->getClientId(),
+ 'agentid' => $this->agentId ?? $this->config->get('agentid'),
+ 'redirect_uri' => $this->redirectUrl,
+ 'state' => $this->state,
+ ];
+
+ if (empty($queries['agentid'])) {
+ throw new InvalidArgumentException('You must config the `agentid` in configuration or using `setAgentid($agentId)`.');
+ }
+
+ return sprintf('https://open.work.weixin.qq.com/wwopen/sso/qrConnect?%s#wechat_redirect', http_build_query($queries));
+ }
+
+ /**
+ * @throws \Overtrue\Socialite\Exceptions\MethodDoesNotSupportException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ throw new MethodDoesNotSupportException('WeWork doesn\'t support access_token mode');
+ }
+
+ protected function getApiAccessToken(): string
+ {
+ return $this->apiAccessToken ?? $this->apiAccessToken = $this->requestApiAccessToken();
+ }
+
+ protected function getUser(string $token, string $code): array
+ {
+ $response = $this->getHttpClient()->get(
+ $this->baseUrl . '/cgi-bin/user/getuserinfo',
+ [
+ 'query' => array_filter(
+ [
+ 'access_token' => $token,
+ 'code' => $code,
+ ]
+ ),
+ ]
+ );
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (($response['errcode'] ?? 1) > 0 || (empty($response['UserId']) && empty($response['OpenId']))) {
+ throw new AuthorizeFailedException('Failed to get user openid:' . $response['errmsg'] ?? 'Unknown.', $response);
+ } elseif (empty($response['UserId'])) {
+ $this->detailed = false;
+ }
+
+ return $response;
+ }
+
+ /**
+ * 获取访问用户敏感信息
+ * see:https://developer.work.weixin.qq.com/document/path/95833
+ * @param string $userTicket
+ *
+ * @return array
+ * @throws AuthorizeFailedException
+ */
+ protected function getUserDetail(string $userTicket): array
+ {
+ $response = $this->getHttpClient()->post(
+ $this->baseUrl.'/cgi-bin/user/getuserdetail',
+ [
+ 'query' => [
+ 'access_token' => $this->getApiAccessToken(),
+ ],
+ 'json' => [
+ 'user_ticket' => $userTicket,
+ ]
+ ]
+ );
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (($response['errcode'] ?? 1) > 0 || empty($response['userid'])) {
+ throw new AuthorizeFailedException('Failed to get user detail:' . $response['errmsg'] ?? 'Unknown.', $response);
+ }
+
+ return $response;
+ }
+
+ /**
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ */
+ protected function getUserById(string $userId): array
+ {
+ $response = $this->getHttpClient()->post(
+ $this->baseUrl . '/cgi-bin/user/get',
+ [
+ 'query' => [
+ 'access_token' => $this->getApiAccessToken(),
+ 'userid' => $userId,
+ ],
+ ]
+ );
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (($response['errcode'] ?? 1) > 0 || empty($response['userid'])) {
+ throw new AuthorizeFailedException('Failed to get user:' . $response['errmsg'] ?? 'Unknown.', $response);
+ }
+
+ return $response;
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ if ($this->detailed) {
+ return new User(
+ [
+ 'id' => $user['userid'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'avatar' => $user['avatar'] ?? null,
+ 'email' => $user['email'] ?? null,
+ ]
+ );
+ }
+
+ return new User(
+ [
+ 'id' => $user['UserId'] ?? null ?: $user['OpenId'] ?? null,
+ ]
+ );
+ }
+
+ /**
+ * @return string
+ * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function requestApiAccessToken(): string
+ {
+ $response = $this->getHttpClient()->get(
+ $this->baseUrl . '/cgi-bin/gettoken',
+ [
+ 'query' => array_filter(
+ [
+ 'corpid' => $this->config->get('corp_id') ?? $this->config->get('corpid') ?? $this->config->get('client_id'),
+ 'corpsecret' => $this->config->get('corp_secret') ?? $this->config->get('corpsecret') ?? $this->config->get('client_secret'),
+ ]
+ ),
+ ]
+ );
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (($response['errcode'] ?? 1) > 0) {
+ throw new AuthorizeFailedException('Failed to get api access_token:' . $response['errmsg'] ?? 'Unknown.', $response);
+ }
+
+ return $response['access_token'];
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return '';
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Providers/Weibo.php b/vendor/overtrue/socialite/src/Providers/Weibo.php
new file mode 100644
index 0000000..cb7f55b
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Providers/Weibo.php
@@ -0,0 +1,108 @@
+buildAuthUrlFromBase($this->baseUrl.'/oauth2/authorize');
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return $this->baseUrl.'/2/oauth2/access_token';
+ }
+
+ /**
+ * @param string $code
+ *
+ * @return array
+ */
+ protected function getTokenFields(string $code): array
+ {
+ return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ *
+ * @throws \Overtrue\Socialite\Exceptions\InvalidTokenException
+ */
+ protected function getUserByToken(string $token): array
+ {
+ $uid = $this->getTokenPayload($token)['uid'] ?? null;
+
+ if (empty($uid)) {
+ throw new InvalidTokenException('Invalid token.', $token);
+ }
+
+ $response = $this->getHttpClient()->get($this->baseUrl.'/2/users/show.json', [
+ 'query' => [
+ 'uid' => $uid,
+ 'access_token' => $token,
+ ],
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]);
+
+ return \json_decode($response->getBody(), true) ?? [];
+ }
+
+ /**
+ * @param string $token
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Overtrue\Socialite\Exceptions\InvalidTokenException
+ */
+ protected function getTokenPayload(string $token): array
+ {
+ $response = $this->getHttpClient()->post($this->baseUrl.'/oauth2/get_token_info', [
+ 'query' => [
+ 'access_token' => $token,
+ ],
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]);
+
+ $response = \json_decode($response->getBody(), true) ?? [];
+
+ if (empty($response['uid'])) {
+ throw new InvalidTokenException(\sprintf('Invalid token %s', $token), $token);
+ }
+
+ return $response;
+ }
+
+ /**
+ * @param array $user
+ *
+ * @return \Overtrue\Socialite\User
+ */
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([
+ 'id' => $user['id'] ?? null,
+ 'nickname' => $user['screen_name'] ?? null,
+ 'name' => $user['name'] ?? null,
+ 'email' => $user['email'] ?? null,
+ 'avatar' => $user['avatar_large'] ?? null,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/socialite/src/Traits/HasAttributes.php b/vendor/overtrue/socialite/src/Traits/HasAttributes.php
new file mode 100644
index 0000000..70ea00d
--- /dev/null
+++ b/vendor/overtrue/socialite/src/Traits/HasAttributes.php
@@ -0,0 +1,91 @@
+attributes;
+ }
+
+ /**
+ * @param string $name
+ * @param string $default
+ *
+ * @return mixed
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function setAttribute($name, $value)
+ {
+ $this->attributes[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param array $attributes
+ *
+ * @return $this
+ */
+ public function merge(array $attributes)
+ {
+ $this->attributes = array_merge($this->attributes, $attributes);
+
+ return $this;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->attributes);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->getAttribute($offset);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ $this->setAttribute($offset, $value);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ unset($this->attributes[$offset]);
+ }
+
+ public function __get($property)
+ {
+ return $this->getAttribute($property);
+ }
+
+ public function toArray(): array
+ {
+ return $this->getAttributes();
+ }
+
+ public function toJSON(): string
+ {
+ return \json_encode($this->getAttributes(), JSON_UNESCAPED_UNICODE);
+ }
+}
diff --git a/vendor/overtrue/socialite/src/User.php b/vendor/overtrue/socialite/src/User.php
index 3d9f18f..e081b13 100644
--- a/vendor/overtrue/socialite/src/User.php
+++ b/vendor/overtrue/socialite/src/User.php
@@ -1,165 +1,145 @@
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
-
namespace Overtrue\Socialite;
use ArrayAccess;
use JsonSerializable;
+use Overtrue\Socialite\Contracts\ProviderInterface;
+use Overtrue\Socialite\Contracts\UserInterface;
+use Overtrue\Socialite\Traits\HasAttributes;
-/**
- * Class User.
- */
class User implements ArrayAccess, UserInterface, JsonSerializable
{
use HasAttributes;
/**
- * User constructor.
- *
- * @param array $attributes
+ * @var \Overtrue\Socialite\Contracts\ProviderInterface|null
*/
- public function __construct(array $attributes)
+ protected ?ProviderInterface $provider;
+
+ public function __construct(array $attributes, ProviderInterface $provider = null)
{
$this->attributes = $attributes;
+ $this->provider = $provider;
}
- /**
- * Get the unique identifier for the user.
- *
- * @return string
- */
public function getId()
{
- return $this->getAttribute('id');
+ return $this->getAttribute('id') ?? $this->getEmail();
}
- /**
- * Get the username for the user.
- *
- * @return string
- */
- public function getUsername()
+ public function getNickname(): ?string
{
- return $this->getAttribute('username', $this->getId());
+ return $this->getAttribute('nickname') ?? $this->getName();
}
- /**
- * Get the nickname / username for the user.
- *
- * @return string
- */
- public function getNickname()
- {
- return $this->getAttribute('nickname');
- }
-
- /**
- * Get the full name of the user.
- *
- * @return string
- */
- public function getName()
+ public function getName(): ?string
{
return $this->getAttribute('name');
}
- /**
- * Get the e-mail address of the user.
- *
- * @return string
- */
- public function getEmail()
+ public function getEmail(): ?string
{
return $this->getAttribute('email');
}
- /**
- * Get the avatar / image URL for the user.
- *
- * @return string
- */
- public function getAvatar()
+ public function getAvatar(): ?string
{
return $this->getAttribute('avatar');
}
- /**
- * Set the token on the user.
- *
- * @param \Overtrue\Socialite\AccessTokenInterface $token
- *
- * @return $this
- */
- public function setToken(AccessTokenInterface $token)
+ public function setAccessToken(string $token): self
{
- $this->setAttribute('token', $token);
+ $this->setAttribute('access_token', $token);
return $this;
}
- /**
- * @param string $provider
- *
- * @return $this
- */
- public function setProviderName($provider)
+ public function getAccessToken(): ?string
{
- $this->setAttribute('provider', $provider);
+ return $this->getAttribute('access_token');
+ }
+
+ public function setRefreshToken(?string $refreshToken): self
+ {
+ $this->setAttribute('refresh_token', $refreshToken);
return $this;
}
- /**
- * @return string
- */
- public function getProviderName()
+ public function getRefreshToken(): ?string
{
- return $this->getAttribute('provider');
+ return $this->getAttribute('refresh_token');
+ }
+
+ public function setExpiresIn(int $expiresIn): self
+ {
+ $this->setAttribute('expires_in', $expiresIn);
+
+ return $this;
+ }
+
+ public function getExpiresIn(): ?int
+ {
+ return $this->getAttribute('expires_in');
+ }
+
+ public function setRaw(array $user): self
+ {
+ $this->setAttribute('raw', $user);
+
+ return $this;
+ }
+
+ public function getRaw(): array
+ {
+ return $this->getAttribute('raw');
+ }
+
+ public function setTokenResponse(array $response)
+ {
+ $this->setAttribute('token_response', $response);
+
+ return $this;
+ }
+
+ public function getTokenResponse()
+ {
+ return $this->getAttribute('token_response');
+ }
+
+ public function jsonSerialize(): array
+ {
+ return $this->attributes;
+ }
+
+ public function __serialize(): array
+ {
+ return $this->attributes;
+ }
+
+ public function __unserialize(array $serialized): void
+ {
+ $this->attributes = $serialized ?: [];
}
/**
- * Get the authorized token.
+ * @return \Overtrue\Socialite\Contracts\ProviderInterface
+ */
+ public function getProvider(): \Overtrue\Socialite\Contracts\ProviderInterface
+ {
+ return $this->provider;
+ }
+
+ /**
+ * @param \Overtrue\Socialite\Contracts\ProviderInterface $provider
*
- * @return \Overtrue\Socialite\AccessToken
+ * @return $this
*/
- public function getToken()
+ public function setProvider(\Overtrue\Socialite\Contracts\ProviderInterface $provider)
{
- return $this->getAttribute('token');
- }
+ $this->provider = $provider;
- /**
- * Alias of getToken().
- *
- * @return \Overtrue\Socialite\AccessToken
- */
- public function getAccessToken()
- {
- return $this->getToken();
- }
-
- /**
- * Get the original attributes.
- *
- * @return array
- */
- public function getOriginal()
- {
- return $this->getAttribute('original');
- }
-
- /**
- * {@inheritdoc}
- */
- public function jsonSerialize()
- {
- return array_merge($this->attributes, ['token' => $this->token->getAttributes()]);
+ return $this;
}
}
diff --git a/vendor/overtrue/socialite/tests/Providers/FeiShuTest.php b/vendor/overtrue/socialite/tests/Providers/FeiShuTest.php
new file mode 100644
index 0000000..59cc82f
--- /dev/null
+++ b/vendor/overtrue/socialite/tests/Providers/FeiShuTest.php
@@ -0,0 +1,246 @@
+ 'xxxxx',
+ 'app_secret' => 'yyyyy',
+ 'app_mode' => 'internal'
+ ];
+ $f = new FeiShu($config);
+ $rf = new \ReflectionObject($f);
+
+ $this->assertEquals('xxxxx', $f->getClientId());
+ $this->assertEquals('yyyyy', $f->getClientSecret());
+
+ $rfProperty = $rf->getProperty('isInternalApp');
+ $rfProperty->setAccessible(true);
+ $this->assertEquals(true, $rfProperty->getValue($f));
+
+ // diff filed way
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ 'mode' => 'internal'
+ ];
+
+ $f = new FeiShu($config);
+ $rf = new \ReflectionObject($f);
+
+ $this->assertEquals('xxxxx', $f->getClientId());
+ $this->assertEquals('yyyyy', $f->getClientSecret());
+ $rfProperty = $rf->getProperty('isInternalApp');
+ $rfProperty->setAccessible(true);
+ $this->assertEquals(true, $rfProperty->getValue($f));
+
+ // no mode config way
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ ];
+
+ $f = new FeiShu($config);
+ $rf = new \ReflectionObject($f);
+
+ $this->assertEquals('xxxxx', $f->getClientId());
+ $this->assertEquals('yyyyy', $f->getClientSecret());
+ $rfProperty = $rf->getProperty('isInternalApp');
+ $rfProperty->setAccessible(true);
+ $this->assertEquals(false, $rfProperty->getValue($f));
+ }
+
+ public function testProviderWithInternalAppModeWork()
+ {
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ ];
+
+ $f = new FeiShu($config);
+ $rf = new \ReflectionObject($f);
+
+ $rfProperty = $rf->getProperty('isInternalApp');
+ $rfProperty->setAccessible(true);
+
+ $f->withInternalAppMode();
+ $this->assertEquals(true, $rfProperty->getValue($f));
+
+ $f->withDefaultMode();
+ $this->assertEquals(false, $rfProperty->getValue($f));
+ }
+
+ public function testProviderWithAppTicketWork()
+ {
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ ];
+
+ $f = new FeiShu($config);
+ $f->withAppTicket('app_ticket');
+ $this->assertEquals('app_ticket', $f->getConfig()->get('app_ticket'));
+ }
+
+ public function testConfigAppAccessTokenWithDefaultModeNoAppTicketWork()
+ {
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ ];
+
+ $f = new FeiShu($config);
+ $fr = new \ReflectionObject($f);
+ $frClient = $fr->getProperty('httpClient');
+ $frClient->setAccessible(true);
+ $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken');
+
+ $mock = new MockHandler([
+ new Response(403, []),
+ new Response(200, [], json_encode([
+ 'app_access_token' => 'app_access_token'
+ ]))
+ ]);
+
+ $handler = HandlerStack::create($mock);
+ $client = new Client(['handler' => $handler]);
+ $frClient->setValue($f, $client);
+ $ff->setAccessible(true);
+
+ // 默认模式下没有 app_ticket
+ $this->expectException(InvalidTicketException::class);
+ $ff->invoke($f);
+
+ $ff->invoke($f);
+ $f->withAppTicket('app_ticket');
+ $this->assertEquals('app_access_token', $f->getConfig()->get('app_access_token'));
+
+ $this->expectException(InvalidTokenException::class);
+ $ff->invoke($f);
+ }
+
+ public function testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInBadResponse()
+ {
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ ];
+
+ $f = new FeiShu($config);
+ $fr = new \ReflectionObject($f);
+ $frClient = $fr->getProperty('httpClient');
+ $frClient->setAccessible(true);
+ $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken');
+
+ $mock = new MockHandler([
+ new Response(200, []),
+ ]);
+
+ $handler = HandlerStack::create($mock);
+ $client = new Client(['handler' => $handler]);
+ $frClient->setValue($f, $client);
+ $ff->setAccessible(true);
+
+ $this->expectException(InvalidTokenException::class);
+ $ff->invoke($f->withAppTicket('app_ticket'));
+ }
+
+ public function testConfigAppAccessTokenWithDefaultModeAndAppTicketWorkInGoodResponse()
+ {
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ ];
+
+ $f = new FeiShu($config);
+ $fr = new \ReflectionObject($f);
+ $frClient = $fr->getProperty('httpClient');
+ $frClient->setAccessible(true);
+ $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken');
+
+ $mock = new MockHandler([
+ new Response(200, [], json_encode([
+ 'app_access_token' => 'app_access_token'
+ ]))
+ ]);
+
+ $handler = HandlerStack::create($mock);
+ $client = new Client(['handler' => $handler]);
+ $frClient->setValue($f, $client);
+ $ff->setAccessible(true);
+
+ $this->assertEquals(null, $f->getConfig()->get('app_access_token'));
+ $ff->invoke($f->withAppTicket('app_ticket'));
+ $this->assertEquals('app_access_token', $f->getConfig()->get('app_access_token'));
+ }
+
+ public function testConfigAppAccessTokenWithInternalInBadResponse()
+ {
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ 'mode' => 'internal'
+ ];
+
+ $f = new FeiShu($config);
+ $fr = new \ReflectionObject($f);
+ $frClient = $fr->getProperty('httpClient');
+ $frClient->setAccessible(true);
+ $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken');
+
+ $mock = new MockHandler([
+ new Response(200, []),
+ ]);
+
+ $handler = HandlerStack::create($mock);
+ $client = new Client(['handler' => $handler]);
+ $frClient->setValue($f, $client);
+ $ff->setAccessible(true);
+
+ $this->expectException(InvalidTokenException::class);
+ $ff->invoke($f);
+ }
+
+ public function testConfigAppAccessTokenWithInternalInGoodResponse()
+ {
+ $config = [
+ 'client_id' => 'xxxxx',
+ 'client_secret' => 'yyyyy',
+ 'mode' => 'internal'
+ ];
+
+ $f = new FeiShu($config);
+ $fr = new \ReflectionObject($f);
+ $frClient = $fr->getProperty('httpClient');
+ $frClient->setAccessible(true);
+ $ff = new \ReflectionMethod(FeiShu::class, 'configAppAccessToken');
+
+ $mock = new MockHandler([
+ new Response(200, [], json_encode([
+ 'app_access_token' => 'app_access_token'
+ ]))
+ ]);
+
+ $handler = HandlerStack::create($mock);
+ $client = new Client(['handler' => $handler]);
+ $frClient->setValue($f, $client);
+ $ff->setAccessible(true);
+
+ $this->assertEquals(null, $f->getConfig()->get('app_access_token'));
+ $ff->invoke($f);
+ $this->assertEquals('app_access_token', $f->getConfig()->get('app_access_token'));
+ }
+}
diff --git a/vendor/overtrue/socialite/tests/Providers/WeWorkTest.php b/vendor/overtrue/socialite/tests/Providers/WeWorkTest.php
new file mode 100644
index 0000000..22e6f34
--- /dev/null
+++ b/vendor/overtrue/socialite/tests/Providers/WeWorkTest.php
@@ -0,0 +1,46 @@
+ 'ww100000a5f2191',
+ 'client_secret' => 'client_secret',
+ 'redirect' => 'http://www.oa.com',
+ ]))
+ ->setAgentId('1000000')
+ ->redirect();
+
+ $this->assertSame('https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=ww100000a5f2191&agentid=1000000&redirect_uri=http%3A%2F%2Fwww.oa.com#wechat_redirect', $response);
+ }
+
+ public function testOAuthWithAgentId()
+ {
+ $response = (new WeWork([
+ 'client_id' => 'CORPID',
+ 'client_secret' => 'client_secret',
+ 'redirect' => 'REDIRECT_URI',
+ ]))
+ ->scopes(['snsapi_base'])
+ ->redirect();
+
+ $this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base#wechat_redirect', $response);
+ }
+
+ public function testOAuthWithoutAgentId()
+ {
+ $response = (new WeWork([
+ 'client_id' => 'CORPID',
+ 'client_secret' => 'client_secret',
+ 'redirect' => 'REDIRECT_URI',
+ ]))
+ ->scopes(['snsapi_base'])
+ ->redirect();
+
+ $this->assertSame('https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base#wechat_redirect', $response);
+ }
+}
diff --git a/vendor/overtrue/socialite/tests/Providers/WechatTest.php b/vendor/overtrue/socialite/tests/Providers/WechatTest.php
new file mode 100644
index 0000000..2d95eb0
--- /dev/null
+++ b/vendor/overtrue/socialite/tests/Providers/WechatTest.php
@@ -0,0 +1,118 @@
+ 'client_id',
+ 'client_secret' => 'client_secret',
+ 'redirect_url' => 'http://localhost/socialite/callback.php',
+ ]))->redirect();
+
+ $this->assertStringStartsWith('https://open.weixin.qq.com/connect/qrconnect', $response);
+ $this->assertMatchesRegularExpression('/redirect_uri=http%3A%2F%2Flocalhost%2Fsocialite%2Fcallback.php/', $response);
+ }
+
+ public function testWeChatProviderTokenUrlAndRequestFields()
+ {
+ $provider = new WeChat([
+ 'client_id' => 'client_id',
+ 'client_secret' => 'client_secret',
+ 'redirect_url' => 'http://localhost/socialite/callback.php',
+ ]);
+
+ $getTokenUrl = new ReflectionMethod(WeChat::class, 'getTokenUrl');
+ $getTokenUrl->setAccessible(true);
+
+ $getTokenFields = new ReflectionMethod(WeChat::class, 'getTokenFields');
+ $getTokenFields->setAccessible(true);
+
+ $getCodeFields = new ReflectionMethod(WeChat::class, 'getCodeFields');
+ $getCodeFields->setAccessible(true);
+
+ $this->assertSame('https://api.weixin.qq.com/sns/oauth2/access_token', $getTokenUrl->invoke($provider));
+ $this->assertSame([
+ 'appid' => 'client_id',
+ 'secret' => 'client_secret',
+ 'code' => 'iloveyou',
+ 'grant_type' => 'authorization_code',
+ ], $getTokenFields->invoke($provider, 'iloveyou'));
+
+ $this->assertSame([
+ 'appid' => 'client_id',
+ 'redirect_uri' => 'http://localhost/socialite/callback.php',
+ 'response_type' => 'code',
+ 'scope' => 'snsapi_login',
+ 'state' => 'wechat-state',
+ 'connect_redirect' => 1,
+ ], $getCodeFields->invoke($provider->withState('wechat-state')));
+ }
+
+ public function testOpenPlatformComponent()
+ {
+ $provider = new WeChat([
+ 'client_id' => 'client_id',
+ 'client_secret' => null,
+ 'redirect' => 'redirect-url',
+ 'component' => [
+ 'id' => 'component-app-id',
+ 'token' => 'token',
+ ],
+ ]);
+ $getTokenUrl = new ReflectionMethod(WeChat::class, 'getTokenUrl');
+ $getTokenUrl->setAccessible(true);
+
+ $getTokenFields = new ReflectionMethod(WeChat::class, 'getTokenFields');
+ $getTokenFields->setAccessible(true);
+
+ $getCodeFields = new ReflectionMethod(WeChat::class, 'getCodeFields');
+ $getCodeFields->setAccessible(true);
+
+
+ $this->assertSame([
+ 'appid' => 'client_id',
+ 'redirect_uri' => 'redirect-url',
+ 'response_type' => 'code',
+ 'scope' => 'snsapi_base',
+ 'state' => 'state',
+ 'connect_redirect' => 1,
+ 'component_appid' => 'component-app-id',
+ ], $getCodeFields->invoke($provider->withState('state')));
+
+ $this->assertSame([
+ 'appid' => 'client_id',
+ 'component_appid' => 'component-app-id',
+ 'component_access_token' => 'token',
+ 'code' => 'simcode',
+ 'grant_type' => 'authorization_code',
+ ], $getTokenFields->invoke($provider, 'simcode'));
+
+ $this->assertSame('https://api.weixin.qq.com/sns/oauth2/component/access_token', $getTokenUrl->invoke($provider));
+ }
+
+ public function testOpenPlatformComponentWithCustomParameters()
+ {
+ $provider = new WeChat([
+ 'client_id' => 'client_id',
+ 'client_secret' => null,
+ 'redirect' => 'redirect-url',
+ 'component' => [
+ 'id' => 'component-app-id',
+ 'token' => 'token',
+ ],
+ ]);
+
+ $getCodeFields = new ReflectionMethod(WeChat::class, 'getCodeFields');
+ $getCodeFields->setAccessible(true);
+
+ $provider->with(['foo' => 'bar']);
+
+ $fields = $getCodeFields->invoke($provider->withState('wechat-state'));
+ $this->assertArrayHasKey('foo', $fields);
+ $this->assertSame('bar', $fields['foo']);
+ }
+}
diff --git a/vendor/overtrue/socialite/tests/SocialiteManagerTest.php b/vendor/overtrue/socialite/tests/SocialiteManagerTest.php
new file mode 100644
index 0000000..26dc1e1
--- /dev/null
+++ b/vendor/overtrue/socialite/tests/SocialiteManagerTest.php
@@ -0,0 +1,109 @@
+ [
+ 'provider' => 'github',
+ 'client_id' => 'foo-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+ 'bar' => [
+ 'provider' => 'github',
+ 'client_id' => 'bar-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+ ];
+
+ $manager = new SocialiteManager($config);
+
+ $this->assertInstanceOf(GitHub::class, $manager->create('foo'));
+ $this->assertSame('foo-app-id', $manager->create('foo')->getClientId());
+
+ $this->assertInstanceOf(GitHub::class, $manager->create('bar'));
+ $this->assertSame('bar-app-id', $manager->create('bar')->getClientId());
+
+ // from name
+ $config = [
+ 'github' => [
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+ ];
+
+ $manager = new SocialiteManager($config);
+
+ $this->assertInstanceOf(GitHub::class, $manager->create('github'));
+ $this->assertSame('your-app-id', $manager->create('github')->getClientId());
+ }
+
+ public function test_it_can_create_from_custom_creator()
+ {
+ $config = [
+ 'foo' => [
+ 'provider' => 'myprovider',
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+ ];
+
+ $manager = new SocialiteManager($config);
+
+ $manager->extend('myprovider', function ($config) {
+ return new DummyProviderForCustomProviderTest($config);
+ });
+
+ $this->assertInstanceOf(DummyProviderForCustomProviderTest::class, $manager->create('foo'));
+ }
+
+ public function test_it_can_create_from_custom_provider_class()
+ {
+ $config = [
+ 'foo' => [
+ 'provider' => DummyProviderForCustomProviderTest::class,
+ 'client_id' => 'your-app-id',
+ 'client_secret' => 'your-app-secret',
+ 'redirect' => 'http://localhost/socialite/callback.php',
+ ],
+ ];
+
+ $manager = new SocialiteManager($config);
+
+ $this->assertInstanceOf(DummyProviderForCustomProviderTest::class, $manager->create('foo'));
+ }
+}
+
+class DummyProviderForCustomProviderTest extends Base
+{
+ protected function getAuthUrl(): string
+ {
+ return '';
+ }
+
+ protected function getTokenUrl(): string
+ {
+ return '';
+ }
+
+ protected function getUserByToken(string $token): array
+ {
+ return [];
+ }
+
+ protected function mapUserToObject(array $user): User
+ {
+ return new User([]);
+ }
+}
diff --git a/vendor/overtrue/socialite/tests/UserTest.php b/vendor/overtrue/socialite/tests/UserTest.php
new file mode 100644
index 0000000..a8226e8
--- /dev/null
+++ b/vendor/overtrue/socialite/tests/UserTest.php
@@ -0,0 +1,51 @@
+assertSame('[]', json_encode(new User([])));
+ $this->assertSame('{"access_token":"mock-token"}', json_encode(new User(['access_token' => 'mock-token'])));
+ }
+
+ public function test_it_can_get_refresh_token()
+ {
+ $user = new User([
+ 'name' => 'fake_name',
+ 'access_token' => 'mock-token',
+ 'refresh_token' => 'fake_refresh',
+ ]);
+
+ // 能通过用 User 对象获取 refresh token
+ $this->assertSame('fake_refresh', $user->getRefreshToken());
+ $this->assertSame('{"name":"fake_name","access_token":"mock-token","refresh_token":"fake_refresh"}', json_encode($user));
+
+ // 无 refresh_token 属性返回 null
+ $user = new User([]);
+ $this->assertSame(null, $user->getRefreshToken());
+ // 能通过 setRefreshToken() 设置
+ $user->setRefreshToken('fake_refreshToken');
+ $this->assertSame('fake_refreshToken', $user->getRefreshToken());
+ $this->assertSame('{"refresh_token":"fake_refreshToken"}', json_encode($user));
+ }
+
+ public function test_it_can_set_raw_data()
+ {
+ $user = new User([]);
+ $data = ['data' => 'raw'];
+
+ $user->setRaw($data);
+ $this->assertSame($data, $user->getRaw());
+ $this->assertSame(json_encode(['raw' => ['data' => 'raw']]), json_encode($user));
+ }
+
+ public function test_ie_can_get_attribute_by_magic_method()
+ {
+ $user = new User(['xx' => 'data']);
+
+ $this->assertSame('data', $user->xx);
+ }
+}
diff --git a/vendor/overtrue/wechat/CHANGELOG.md b/vendor/overtrue/wechat/CHANGELOG.md
new file mode 100644
index 0000000..df42aa6
--- /dev/null
+++ b/vendor/overtrue/wechat/CHANGELOG.md
@@ -0,0 +1,1401 @@
+# Change Log
+
+## [Unreleased](https://github.com/overtrue/wechat/tree/HEAD)
+
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0...HEAD)
+
+**Closed issues:**
+
+- 能否增加对symfony4的支持 [\#1044](https://github.com/overtrue/wechat/issues/1044)
+
+## [4.0.0](https://github.com/overtrue/wechat/tree/4.0.0) (2017-12-11)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.21...4.0.0)
+
+**Closed issues:**
+
+- filecache.php 文件 createPathIfNeeded\(string $path\) : bool [\#1046](https://github.com/overtrue/wechat/issues/1046)
+- 沙箱模式的Notify总是出错:Invalid request payloads. [\#1045](https://github.com/overtrue/wechat/issues/1045)
+- 你好我是SwooleDistributed框架的作者 [\#1040](https://github.com/overtrue/wechat/issues/1040)
+
+## [3.3.21](https://github.com/overtrue/wechat/tree/3.3.21) (2017-12-10)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.4...3.3.21)
+
+**Closed issues:**
+
+- 开启开放平台自动路由后报错 [\#1042](https://github.com/overtrue/wechat/issues/1042)
+- 关于3.x升级4.x的问题 [\#1041](https://github.com/overtrue/wechat/issues/1041)
+- 获取不了unionid [\#1038](https://github.com/overtrue/wechat/issues/1038)
+- authorizer\_refresh\_token刷新问题 [\#1033](https://github.com/overtrue/wechat/issues/1033)
+- lumen+swoole无法获取request信息。 [\#1032](https://github.com/overtrue/wechat/issues/1032)
+- 上传素材报错, empty post data hint [\#1031](https://github.com/overtrue/wechat/issues/1031)
+- 开放平台不支持全网发布接入检测(第三方) [\#1029](https://github.com/overtrue/wechat/issues/1029)
+- 公众号模板消息不兼容跳转小程序 [\#1025](https://github.com/overtrue/wechat/issues/1025)
+- swoole下无法使用 [\#1017](https://github.com/overtrue/wechat/issues/1017)
+- 请教有没有高清素材下载方法? [\#997](https://github.com/overtrue/wechat/issues/997)
+- 自动回复多图文素材,错误 [\#996](https://github.com/overtrue/wechat/issues/996)
+- xml解释失败 [\#989](https://github.com/overtrue/wechat/issues/989)
+- Curl error 77 [\#982](https://github.com/overtrue/wechat/issues/982)
+- 3.1.10 H5支付不晓得算不算BUG的BUG [\#968](https://github.com/overtrue/wechat/issues/968)
+- 请问是否有遇到微信扫码或内部打开外部网站出现请求2次的情况 [\#963](https://github.com/overtrue/wechat/issues/963)
+- 请问4.0何时正式发布? [\#962](https://github.com/overtrue/wechat/issues/962)
+- dev-master 不能用于laravel5.1 [\#952](https://github.com/overtrue/wechat/issues/952)
+- 请教小程序的模板消息是否支持 [\#920](https://github.com/overtrue/wechat/issues/920)
+- 模板消息的颜色设置问题 [\#914](https://github.com/overtrue/wechat/issues/914)
+- 英文文档跳转问题 [\#854](https://github.com/overtrue/wechat/issues/854)
+- \[4.0\] 功能测试 [\#849](https://github.com/overtrue/wechat/issues/849)
+- \[4.0\] 命名变更 [\#743](https://github.com/overtrue/wechat/issues/743)
+
+**Merged pull requests:**
+
+- Scrutinizer Auto-Fixes [\#1043](https://github.com/overtrue/wechat/pull/1043) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 修复解密小程序转发信息数据\(wx.getShareInfo\)失败的问题 [\#1037](https://github.com/overtrue/wechat/pull/1037) ([yyqqing](https://github.com/yyqqing))
+- 修復微信支付沙盒模式的通知結果本地校驗失敗錯誤。 [\#1036](https://github.com/overtrue/wechat/pull/1036) ([amyuki](https://github.com/amyuki))
+- 修复 verifyTicket 使用不了自定义缓存的问题 [\#1034](https://github.com/overtrue/wechat/pull/1034) ([mingyoung](https://github.com/mingyoung))
+- 🚧 Auto discover extensions. [\#1027](https://github.com/overtrue/wechat/pull/1027) ([mingyoung](https://github.com/mingyoung))
+
+## [4.0.0-beta.4](https://github.com/overtrue/wechat/tree/4.0.0-beta.4) (2017-11-21)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.20...4.0.0-beta.4)
+
+**Closed issues:**
+
+- Order对象的$attributes中不能传入device\_info参数 [\#1030](https://github.com/overtrue/wechat/issues/1030)
+- 默认文件缓存的路径是否可以简单修改? [\#1023](https://github.com/overtrue/wechat/issues/1023)
+- 3.3.17 版本获取 token 的问题 [\#1022](https://github.com/overtrue/wechat/issues/1022)
+- \[V3\] AccessToken.php:243 [\#1021](https://github.com/overtrue/wechat/issues/1021)
+
+**Merged pull requests:**
+
+- more detailed cache key. [\#1028](https://github.com/overtrue/wechat/pull/1028) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#1026](https://github.com/overtrue/wechat/pull/1026) ([mingyoung](https://github.com/mingyoung))
+- Specify the request instance. [\#1024](https://github.com/overtrue/wechat/pull/1024) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.20](https://github.com/overtrue/wechat/tree/3.3.20) (2017-11-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.18...3.3.20)
+
+## [3.3.18](https://github.com/overtrue/wechat/tree/3.3.18) (2017-11-11)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.17...3.3.18)
+
+**Closed issues:**
+
+- 临时二维码接口无法生成以字符串为参数的二维码 [\#1020](https://github.com/overtrue/wechat/issues/1020)
+- 现金红包出现了500错误,跟进显示http\_error:true [\#1016](https://github.com/overtrue/wechat/issues/1016)
+- 4.0 企业微信agent OA的错误 [\#1015](https://github.com/overtrue/wechat/issues/1015)
+- 求thinkphp框架demo [\#1010](https://github.com/overtrue/wechat/issues/1010)
+- 沙箱模式获取验签 key 时产生无限循环 , 无法正常获取 [\#1009](https://github.com/overtrue/wechat/issues/1009)
+- JSSDK里面url导致的invalid signature错误 [\#1002](https://github.com/overtrue/wechat/issues/1002)
+- 微信支付沙箱模式下,回调验签错误 [\#998](https://github.com/overtrue/wechat/issues/998)
+- 有微信退款回调接口吗? [\#985](https://github.com/overtrue/wechat/issues/985)
+- 希望兼容新出的微信H5支付 [\#966](https://github.com/overtrue/wechat/issues/966)
+- 小程序生成无限量二维码接口缺少参数 [\#965](https://github.com/overtrue/wechat/issues/965)
+
+**Merged pull requests:**
+
+- 查询企业付款接口参数调整,加入企业付款到银行卡接口(RSA 参数加密待完成) [\#1019](https://github.com/overtrue/wechat/pull/1019) ([tianyong90](https://github.com/tianyong90))
+- Token AESKey can be null. [\#1013](https://github.com/overtrue/wechat/pull/1013) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#1012](https://github.com/overtrue/wechat/pull/1012) ([mingyoung](https://github.com/mingyoung))
+- Add Mini-program tester's binding/unbinding feature [\#1011](https://github.com/overtrue/wechat/pull/1011) ([caikeal](https://github.com/caikeal))
+- Apply fixes from StyleCI [\#1008](https://github.com/overtrue/wechat/pull/1008) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#1007](https://github.com/overtrue/wechat/pull/1007) ([overtrue](https://github.com/overtrue))
+- Added open-platform's mini-program code management [\#1003](https://github.com/overtrue/wechat/pull/1003) ([caikeal](https://github.com/caikeal))
+- Cleanup payment [\#1001](https://github.com/overtrue/wechat/pull/1001) ([mingyoung](https://github.com/mingyoung))
+- Unify get stream. [\#995](https://github.com/overtrue/wechat/pull/995) ([mingyoung](https://github.com/mingyoung))
+- Add appCode `page` param. [\#991](https://github.com/overtrue/wechat/pull/991) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.17](https://github.com/overtrue/wechat/tree/3.3.17) (2017-10-27)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.3...3.3.17)
+
+**Closed issues:**
+
+- open platform component\_verify\_ticket 错误 [\#984](https://github.com/overtrue/wechat/issues/984)
+- 请教下载语音后的文件不完整怎么处理? [\#980](https://github.com/overtrue/wechat/issues/980)
+- 微信支付 API 调用下单解析缓缓 [\#977](https://github.com/overtrue/wechat/issues/977)
+- 是否可以加入微信收款(个人转账版)服务接口 [\#970](https://github.com/overtrue/wechat/issues/970)
+- 微信公众号消息加解密方式‘兼容模式’也需要填写‘aes\_key’参数,不能为空 [\#967](https://github.com/overtrue/wechat/issues/967)
+- 第三方平台 接收消息一直报错 但是能回复消息 也会提示错误 [\#961](https://github.com/overtrue/wechat/issues/961)
+- 中文官网无法访问 [\#960](https://github.com/overtrue/wechat/issues/960)
+- laravel队列中使用了SDK报Component verify ticket does not exists. [\#958](https://github.com/overtrue/wechat/issues/958)
+- 接口调用次数每日限额清零方法没有? [\#953](https://github.com/overtrue/wechat/issues/953)
+- 获取access\_toekn失败之后抛出异常的地方,能够与其他地方统一使用下述这个 resolveResponse 返回数据 [\#951](https://github.com/overtrue/wechat/issues/951)
+- 官网挂了 [\#950](https://github.com/overtrue/wechat/issues/950)
+- 无法接收到菜单点击事件推送的消息 [\#949](https://github.com/overtrue/wechat/issues/949)
+- 请教这个sdk是否可用于android 或者ios 登录? [\#948](https://github.com/overtrue/wechat/issues/948)
+- 关于access token 后端分布式部署的中控服务器的问题 [\#947](https://github.com/overtrue/wechat/issues/947)
+- 4.0 不支持laravel 5.2? [\#946](https://github.com/overtrue/wechat/issues/946)
+- log不能打印出来 [\#945](https://github.com/overtrue/wechat/issues/945)
+- EasyWeChat.org域名挂了?? [\#940](https://github.com/overtrue/wechat/issues/940)
+- 微信静默授权的时候,页面上老是会显示一段很长的英文Redirecting to http://xxxx,很影响用户体验,有没有什么方法可以去掉,保留空白页,或者允许自定义显示内容 [\#939](https://github.com/overtrue/wechat/issues/939)
+- 微信小程序生成二维码(接口B)微信扫描不出来结果 [\#938](https://github.com/overtrue/wechat/issues/938)
+- 官网可否支持看老版本的文档? [\#937](https://github.com/overtrue/wechat/issues/937)
+- 客服发送消息 收到的中文信息被unicode 编码 [\#935](https://github.com/overtrue/wechat/issues/935)
+- 有多个商户时,订单通知的 $payment 怎么创建 [\#934](https://github.com/overtrue/wechat/issues/934)
+- console中使用$app-\>user-\>get报错 [\#932](https://github.com/overtrue/wechat/issues/932)
+- PC端扫描登录的问题 [\#930](https://github.com/overtrue/wechat/issues/930)
+- 关于小程序支付的疑问 [\#912](https://github.com/overtrue/wechat/issues/912)
+- 服务商api模式使用可以更加详细吗 [\#653](https://github.com/overtrue/wechat/issues/653)
+
+**Merged pull requests:**
+
+- 修正 微信公众号要求 所有接口使用 HTTPS 方式访问 [\#988](https://github.com/overtrue/wechat/pull/988) ([drogjh](https://github.com/drogjh))
+- Apply fixes from StyleCI [\#987](https://github.com/overtrue/wechat/pull/987) ([mingyoung](https://github.com/mingyoung))
+- 修复微信收款(个人转账版)商户添加、查询含有多余字段导致签名失败的问题 [\#986](https://github.com/overtrue/wechat/pull/986) ([chenhaizano](https://github.com/chenhaizano))
+- Add merchant client. [\#983](https://github.com/overtrue/wechat/pull/983) ([mingyoung](https://github.com/mingyoung))
+- Fix PKCS7 unpad issue. [\#981](https://github.com/overtrue/wechat/pull/981) ([mingyoung](https://github.com/mingyoung))
+- 💯 Add unit tests. [\#979](https://github.com/overtrue/wechat/pull/979) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#978](https://github.com/overtrue/wechat/pull/978) ([overtrue](https://github.com/overtrue))
+- Add sub-merchant support. [\#976](https://github.com/overtrue/wechat/pull/976) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#974](https://github.com/overtrue/wechat/pull/974) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#973](https://github.com/overtrue/wechat/pull/973) ([mingyoung](https://github.com/mingyoung))
+- Refactoring payment [\#972](https://github.com/overtrue/wechat/pull/972) ([mingyoung](https://github.com/mingyoung))
+- Fix request method. [\#964](https://github.com/overtrue/wechat/pull/964) ([mingyoung](https://github.com/mingyoung))
+- MiniProgram template. [\#959](https://github.com/overtrue/wechat/pull/959) ([mingyoung](https://github.com/mingyoung))
+- 企业微信 jssdk ticket [\#954](https://github.com/overtrue/wechat/pull/954) ([mingyoung](https://github.com/mingyoung))
+- Scrutinizer Auto-Fixes [\#944](https://github.com/overtrue/wechat/pull/944) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 简化子商户js config [\#943](https://github.com/overtrue/wechat/pull/943) ([HanSon](https://github.com/HanSon))
+- Apply fixes from StyleCI [\#942](https://github.com/overtrue/wechat/pull/942) ([overtrue](https://github.com/overtrue))
+- 支持子商户JS CONFIG生成 [\#941](https://github.com/overtrue/wechat/pull/941) ([HanSon](https://github.com/HanSon))
+
+## [4.0.0-beta.3](https://github.com/overtrue/wechat/tree/4.0.0-beta.3) (2017-09-23)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.16...4.0.0-beta.3)
+
+**Closed issues:**
+
+- 退款结果通知 [\#858](https://github.com/overtrue/wechat/issues/858)
+
+**Merged pull requests:**
+
+- Update Application.php [\#936](https://github.com/overtrue/wechat/pull/936) ([HanSon](https://github.com/HanSon))
+
+## [3.3.16](https://github.com/overtrue/wechat/tree/3.3.16) (2017-09-20)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.15...3.3.16)
+
+**Closed issues:**
+
+- 希望能增加获取回复数据的方法 [\#929](https://github.com/overtrue/wechat/issues/929)
+- 3.3 版本 数据类型不对导致无法运行 [\#928](https://github.com/overtrue/wechat/issues/928)
+
+**Merged pull requests:**
+
+- 增加退款回调处理 [\#931](https://github.com/overtrue/wechat/pull/931) ([leo108](https://github.com/leo108))
+
+## [3.3.15](https://github.com/overtrue/wechat/tree/3.3.15) (2017-09-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.14...3.3.15)
+
+**Closed issues:**
+
+- 微信 for windows 发送文件的时候报错 [\#927](https://github.com/overtrue/wechat/issues/927)
+
+## [3.3.14](https://github.com/overtrue/wechat/tree/3.3.14) (2017-09-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.2...3.3.14)
+
+**Closed issues:**
+
+- 请教授权的时候什么方法拿到用户是否关注了本公众号? [\#926](https://github.com/overtrue/wechat/issues/926)
+
+## [4.0.0-beta.2](https://github.com/overtrue/wechat/tree/4.0.0-beta.2) (2017-09-12)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-beta.1...4.0.0-beta.2)
+
+**Closed issues:**
+
+- readme.md写错了? [\#923](https://github.com/overtrue/wechat/issues/923)
+- token验证成功,但还是回复暂时不可用,困扰1个星期多了,真心求助!!!有偿都可以!! [\#922](https://github.com/overtrue/wechat/issues/922)
+- 条件判断错了,stripos返回的是“返回在字符串 haystack 中 needle 首次出现的数字位置。”,所以不能直接作为条件判断 [\#915](https://github.com/overtrue/wechat/issues/915)
+- README中的链接是否错误 [\#913](https://github.com/overtrue/wechat/issues/913)
+- 测试公众号无法接受用户信息 [\#911](https://github.com/overtrue/wechat/issues/911)
+- ReadMe文件过期 [\#910](https://github.com/overtrue/wechat/issues/910)
+- 开放平台服务,取消授权会有哪些参数过来? [\#909](https://github.com/overtrue/wechat/issues/909)
+- token无法验证 [\#908](https://github.com/overtrue/wechat/issues/908)
+- laravel 5.4 composer 失败 [\#907](https://github.com/overtrue/wechat/issues/907)
+- 开放平台:组件ticket无法通过 [\#904](https://github.com/overtrue/wechat/issues/904)
+- 官方网站一直登陆不了,浙江丽水地区 [\#903](https://github.com/overtrue/wechat/issues/903)
+- \[4.0\] Pimple\Exception\UnknownIdentifierException [\#901](https://github.com/overtrue/wechat/issues/901)
+- 4.0 报错“Your requirements could not be resolved to an installable set of packages.” [\#898](https://github.com/overtrue/wechat/issues/898)
+
+**Merged pull requests:**
+
+- 修改通过ticket换取二维码图片地址的逻辑 [\#925](https://github.com/overtrue/wechat/pull/925) ([Gwill](https://github.com/Gwill))
+- make domain more flexible [\#924](https://github.com/overtrue/wechat/pull/924) ([HanSon](https://github.com/HanSon))
+- add code & domain comment [\#921](https://github.com/overtrue/wechat/pull/921) ([HanSon](https://github.com/HanSon))
+- Apply fixes from StyleCI [\#919](https://github.com/overtrue/wechat/pull/919) ([overtrue](https://github.com/overtrue))
+- \[3.1\] Custom PreAuthCode Support [\#918](https://github.com/overtrue/wechat/pull/918) ([freyo](https://github.com/freyo))
+- 修改acess\_token无效时微信返回错误码的判断 [\#916](https://github.com/overtrue/wechat/pull/916) ([blackjune](https://github.com/blackjune))
+- \[4.0\] Add optional 'request' parameter to notify handler methods [\#905](https://github.com/overtrue/wechat/pull/905) ([edwardaa](https://github.com/edwardaa))
+- Apply fixes from StyleCI [\#902](https://github.com/overtrue/wechat/pull/902) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#897](https://github.com/overtrue/wechat/pull/897) ([overtrue](https://github.com/overtrue))
+- 增加OAuth中Guzzle\Client的配置项的设置 [\#893](https://github.com/overtrue/wechat/pull/893) ([khsing](https://github.com/khsing))
+- Apply fixes from StyleCI [\#887](https://github.com/overtrue/wechat/pull/887) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#884](https://github.com/overtrue/wechat/pull/884) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+
+## [4.0.0-beta.1](https://github.com/overtrue/wechat/tree/4.0.0-beta.1) (2017-08-31)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.2...4.0.0-beta.1)
+
+**Closed issues:**
+
+- http://easywechat.org/ 网站访问不了了? [\#896](https://github.com/overtrue/wechat/issues/896)
+- 关于缓存,请问为什么key中包含appId \* 2,有什么讲究吗? [\#892](https://github.com/overtrue/wechat/issues/892)
+- 小程序调用解密程序报-41003错误 [\#891](https://github.com/overtrue/wechat/issues/891)
+- 小程序调用加密数据解密时报错,不存在方法 [\#890](https://github.com/overtrue/wechat/issues/890)
+- 有关4.0使用文档的问题 [\#883](https://github.com/overtrue/wechat/issues/883)
+- \[4.0\] PHP最低版本能否降到7.0 [\#880](https://github.com/overtrue/wechat/issues/880)
+
+**Merged pull requests:**
+
+- \[4.0\] Pass proper arguments to the Response constructor [\#895](https://github.com/overtrue/wechat/pull/895) ([edwardaa](https://github.com/edwardaa))
+- Fix baseUrl and json issues. [\#894](https://github.com/overtrue/wechat/pull/894) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#889](https://github.com/overtrue/wechat/pull/889) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#885](https://github.com/overtrue/wechat/pull/885) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Apply fixes from StyleCI [\#882](https://github.com/overtrue/wechat/pull/882) ([overtrue](https://github.com/overtrue))
+- 补充通用卡接口 [\#881](https://github.com/overtrue/wechat/pull/881) ([XiaoLer](https://github.com/XiaoLer))
+- Apply fixes from StyleCI [\#879](https://github.com/overtrue/wechat/pull/879) ([overtrue](https://github.com/overtrue))
+- \[3.1\] Payment/API 没有使用全局的 cache [\#878](https://github.com/overtrue/wechat/pull/878) ([edwardaa](https://github.com/edwardaa))
+- Add JSON\_UNESCAPED\_UNICODE option. [\#874](https://github.com/overtrue/wechat/pull/874) ([mingyoung](https://github.com/mingyoung))
+- update \_\_set\_state magic method to static [\#872](https://github.com/overtrue/wechat/pull/872) ([8090Lambert](https://github.com/8090Lambert))
+
+## [4.0.0-alpha.2](https://github.com/overtrue/wechat/tree/4.0.0-alpha.2) (2017-08-20)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha.1...4.0.0-alpha.2)
+
+**Closed issues:**
+
+- 你好,怎么用的 [\#869](https://github.com/overtrue/wechat/issues/869)
+
+**Merged pull requests:**
+
+- Tweak dir [\#871](https://github.com/overtrue/wechat/pull/871) ([mingyoung](https://github.com/mingyoung))
+- Fix mini-program guard. [\#870](https://github.com/overtrue/wechat/pull/870) ([mingyoung](https://github.com/mingyoung))
+
+## [4.0.0-alpha.1](https://github.com/overtrue/wechat/tree/4.0.0-alpha.1) (2017-08-14)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.13...4.0.0-alpha.1)
+
+**Closed issues:**
+
+- 对doctrine/cache依赖的版本锁定 [\#867](https://github.com/overtrue/wechat/issues/867)
+
+## [3.3.13](https://github.com/overtrue/wechat/tree/3.3.13) (2017-08-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.12...3.3.13)
+
+**Closed issues:**
+
+- 文档中网页授权实例写的不明确 [\#850](https://github.com/overtrue/wechat/issues/850)
+- \[意见\]作者能否提供getTokenFromServer方法扩展从外部第三方获取access\_token [\#837](https://github.com/overtrue/wechat/issues/837)
+- invalid credential, access\_token is invalid or not latest [\#808](https://github.com/overtrue/wechat/issues/808)
+- \[4.0\] 重构卡券 [\#806](https://github.com/overtrue/wechat/issues/806)
+- \[4.0\] 重构 Broadcasting [\#805](https://github.com/overtrue/wechat/issues/805)
+- \[4.0\] 变更日志 [\#746](https://github.com/overtrue/wechat/issues/746)
+
+**Merged pull requests:**
+
+- Fixed open-platform authorizer server token. [\#866](https://github.com/overtrue/wechat/pull/866) ([mingyoung](https://github.com/mingyoung))
+- payment\ClientTest 优化 [\#865](https://github.com/overtrue/wechat/pull/865) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#864](https://github.com/overtrue/wechat/pull/864) ([overtrue](https://github.com/overtrue))
+- 退款通知处理及相关单元测试 [\#863](https://github.com/overtrue/wechat/pull/863) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#862](https://github.com/overtrue/wechat/pull/862) ([overtrue](https://github.com/overtrue))
+- Update dependence version. [\#861](https://github.com/overtrue/wechat/pull/861) ([mingyoung](https://github.com/mingyoung))
+- Add tests. [\#859](https://github.com/overtrue/wechat/pull/859) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#857](https://github.com/overtrue/wechat/pull/857) ([overtrue](https://github.com/overtrue))
+- Payment 单元测试优化 [\#856](https://github.com/overtrue/wechat/pull/856) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#855](https://github.com/overtrue/wechat/pull/855) ([overtrue](https://github.com/overtrue))
+- lists 方法重命名为 list,相关单元测试调整 [\#853](https://github.com/overtrue/wechat/pull/853) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#852](https://github.com/overtrue/wechat/pull/852) ([overtrue](https://github.com/overtrue))
+- Payment 单元测试及部分问题修复 [\#851](https://github.com/overtrue/wechat/pull/851) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#848](https://github.com/overtrue/wechat/pull/848) ([overtrue](https://github.com/overtrue))
+- 调整 Payment\BaseClient 注入的 $app 类型 [\#847](https://github.com/overtrue/wechat/pull/847) ([tianyong90](https://github.com/tianyong90))
+- array\_merge 方法参数类型转换, type hints [\#846](https://github.com/overtrue/wechat/pull/846) ([tianyong90](https://github.com/tianyong90))
+- Fix oauth. [\#845](https://github.com/overtrue/wechat/pull/845) ([mingyoung](https://github.com/mingyoung))
+- Text message. [\#844](https://github.com/overtrue/wechat/pull/844) ([mingyoung](https://github.com/mingyoung))
+- Rename BaseService -\> BasicService. [\#843](https://github.com/overtrue/wechat/pull/843) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#842](https://github.com/overtrue/wechat/pull/842) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#841](https://github.com/overtrue/wechat/pull/841) ([overtrue](https://github.com/overtrue))
+- phpdoc types。 [\#840](https://github.com/overtrue/wechat/pull/840) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#839](https://github.com/overtrue/wechat/pull/839) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#836](https://github.com/overtrue/wechat/pull/836) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#835](https://github.com/overtrue/wechat/pull/835) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#833](https://github.com/overtrue/wechat/pull/833) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#831](https://github.com/overtrue/wechat/pull/831) ([overtrue](https://github.com/overtrue))
+
+## [3.3.12](https://github.com/overtrue/wechat/tree/3.3.12) (2017-08-01)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.11...3.3.12)
+
+**Closed issues:**
+
+- 能否整合微信开放平台在给出一套demo [\#816](https://github.com/overtrue/wechat/issues/816)
+- 请教这个项目的支付部分,尤其是签名和结果回调,是否支持小程序? [\#814](https://github.com/overtrue/wechat/issues/814)
+- 微信意图识别接口返回invalid param [\#804](https://github.com/overtrue/wechat/issues/804)
+- 返回param invalid [\#803](https://github.com/overtrue/wechat/issues/803)
+
+**Merged pull requests:**
+
+- change comment word [\#830](https://github.com/overtrue/wechat/pull/830) ([tianyong90](https://github.com/tianyong90))
+- Fix getTicket. [\#829](https://github.com/overtrue/wechat/pull/829) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#827](https://github.com/overtrue/wechat/pull/827) ([overtrue](https://github.com/overtrue))
+- 修正 HasAttributes Trait 引用错误 [\#825](https://github.com/overtrue/wechat/pull/825) ([tianyong90](https://github.com/tianyong90))
+- Apply fixes from StyleCI [\#824](https://github.com/overtrue/wechat/pull/824) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#822](https://github.com/overtrue/wechat/pull/822) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#820](https://github.com/overtrue/wechat/pull/820) ([mingyoung](https://github.com/mingyoung))
+- Add subscribe message. [\#819](https://github.com/overtrue/wechat/pull/819) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#818](https://github.com/overtrue/wechat/pull/818) ([mingyoung](https://github.com/mingyoung))
+- 微信开放平台帐号管理 [\#817](https://github.com/overtrue/wechat/pull/817) ([XiaoLer](https://github.com/XiaoLer))
+- add method in comment [\#813](https://github.com/overtrue/wechat/pull/813) ([HanSon](https://github.com/HanSon))
+- fixed guzzle version [\#812](https://github.com/overtrue/wechat/pull/812) ([HanSon](https://github.com/HanSon))
+- Apply fixes from StyleCI [\#811](https://github.com/overtrue/wechat/pull/811) ([mingyoung](https://github.com/mingyoung))
+- Downgrade to php 7.0 [\#809](https://github.com/overtrue/wechat/pull/809) ([HanSon](https://github.com/HanSon))
+
+## [3.3.11](https://github.com/overtrue/wechat/tree/3.3.11) (2017-07-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/4.0.0-alpha1...3.3.11)
+
+**Closed issues:**
+
+- 请添加 「退款原因」 参数 [\#802](https://github.com/overtrue/wechat/issues/802)
+
+## [4.0.0-alpha1](https://github.com/overtrue/wechat/tree/4.0.0-alpha1) (2017-07-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.10...4.0.0-alpha1)
+
+**Closed issues:**
+
+- Overtrue\Wechat\Media not found [\#801](https://github.com/overtrue/wechat/issues/801)
+- 在微信的接口配置时Token 无效,可任意输入 [\#800](https://github.com/overtrue/wechat/issues/800)
+
+## [3.3.10](https://github.com/overtrue/wechat/tree/3.3.10) (2017-07-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.9...3.3.10)
+
+**Closed issues:**
+
+- 第三方平台refresh\_token的保存问题 [\#798](https://github.com/overtrue/wechat/issues/798)
+- 网页授权共享session已晚 [\#792](https://github.com/overtrue/wechat/issues/792)
+
+**Merged pull requests:**
+
+- 临时二维码也是支持scene\_str的,这里补充上 [\#797](https://github.com/overtrue/wechat/pull/797) ([lornewang](https://github.com/lornewang))
+- Apply fixes from StyleCI [\#795](https://github.com/overtrue/wechat/pull/795) ([overtrue](https://github.com/overtrue))
+- add card message type [\#794](https://github.com/overtrue/wechat/pull/794) ([IanGely](https://github.com/IanGely))
+- add staff message type wxcard [\#793](https://github.com/overtrue/wechat/pull/793) ([IanGely](https://github.com/IanGely))
+
+## [3.3.9](https://github.com/overtrue/wechat/tree/3.3.9) (2017-07-07)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.8...3.3.9)
+
+**Closed issues:**
+
+- \[4.0\] Http 模块 [\#678](https://github.com/overtrue/wechat/issues/678)
+- \[4.0\] Http 请求类 [\#582](https://github.com/overtrue/wechat/issues/582)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#791](https://github.com/overtrue/wechat/pull/791) ([overtrue](https://github.com/overtrue))
+- Add get user portrait method. [\#790](https://github.com/overtrue/wechat/pull/790) ([getive](https://github.com/getive))
+- \[Feature\] Move directories [\#789](https://github.com/overtrue/wechat/pull/789) ([overtrue](https://github.com/overtrue))
+- \[Feature\] Move traits to kernel. [\#788](https://github.com/overtrue/wechat/pull/788) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#787](https://github.com/overtrue/wechat/pull/787) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#786](https://github.com/overtrue/wechat/pull/786) ([overtrue](https://github.com/overtrue))
+
+## [3.3.8](https://github.com/overtrue/wechat/tree/3.3.8) (2017-07-07)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.7...3.3.8)
+
+**Closed issues:**
+
+- $temporary-\>getStream\($media\_id\) 与 file\_get\_contents\(\) 有区别??? [\#742](https://github.com/overtrue/wechat/issues/742)
+
+## [3.3.7](https://github.com/overtrue/wechat/tree/3.3.7) (2017-07-06)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.6...3.3.7)
+
+**Closed issues:**
+
+- 多添加一个$option [\#772](https://github.com/overtrue/wechat/issues/772)
+- 消息群发,指定openid群发视频时,微信报错invalid message type hint: \[JUs0Oa0779ge25\] [\#757](https://github.com/overtrue/wechat/issues/757)
+
+## [3.3.6](https://github.com/overtrue/wechat/tree/3.3.6) (2017-07-06)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.5...3.3.6)
+
+**Fixed bugs:**
+
+- 素材管理,如果media\_id不存在会保存网页返回的错误代码 [\#592](https://github.com/overtrue/wechat/issues/592)
+
+**Closed issues:**
+
+- https://easywechat.org网站证书刚过期了,知会作者一声 [\#781](https://github.com/overtrue/wechat/issues/781)
+- access\_token 是否能不内部主动请求微信 [\#778](https://github.com/overtrue/wechat/issues/778)
+- 门店创建API \($poi-\>create\) 建议返回 poi\_id / exception [\#774](https://github.com/overtrue/wechat/issues/774)
+- 扩展门店小程序错误 [\#762](https://github.com/overtrue/wechat/issues/762)
+- \[4.0\] jssdk 抽出独立模块 [\#754](https://github.com/overtrue/wechat/issues/754)
+- \[4.0\] 消息加密解密模块提取到 Kernel [\#753](https://github.com/overtrue/wechat/issues/753)
+- 网页能授权但无法获取用户信息,代码跟官方文档一样。 [\#713](https://github.com/overtrue/wechat/issues/713)
+
+**Merged pull requests:**
+
+- Feature: BaseService. [\#785](https://github.com/overtrue/wechat/pull/785) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#784](https://github.com/overtrue/wechat/pull/784) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#783](https://github.com/overtrue/wechat/pull/783) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.5](https://github.com/overtrue/wechat/tree/3.3.5) (2017-07-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.4...3.3.5)
+
+**Implemented enhancements:**
+
+- 并发下access\_token存在脏写隐患 [\#696](https://github.com/overtrue/wechat/issues/696)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#780](https://github.com/overtrue/wechat/pull/780) ([overtrue](https://github.com/overtrue))
+
+## [3.3.4](https://github.com/overtrue/wechat/tree/3.3.4) (2017-07-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.3...3.3.4)
+
+**Closed issues:**
+
+- 网页授权获取用户信息无法打开授权页面 [\#773](https://github.com/overtrue/wechat/issues/773)
+- Class 'EasyWechat\Foundation\Application' not found [\#769](https://github.com/overtrue/wechat/issues/769)
+- 获取小程序二维码报错 [\#766](https://github.com/overtrue/wechat/issues/766)
+- Call to undefined method EasyWeChat\Server\Guard::setRequest\(\) [\#765](https://github.com/overtrue/wechat/issues/765)
+- 网页授权问题,提示scopes类型错误 [\#764](https://github.com/overtrue/wechat/issues/764)
+- 门店小程序扩展错误问题 [\#763](https://github.com/overtrue/wechat/issues/763)
+- 微信开发者平台,全网发布怎么通过 [\#761](https://github.com/overtrue/wechat/issues/761)
+- 微信网页授权重复请求报code无效 [\#714](https://github.com/overtrue/wechat/issues/714)
+
+**Merged pull requests:**
+
+- 新版客服功能-获取聊天记录 [\#775](https://github.com/overtrue/wechat/pull/775) ([wuwenbao](https://github.com/wuwenbao))
+- Fix mini-program qrcode. [\#768](https://github.com/overtrue/wechat/pull/768) ([mingyoung](https://github.com/mingyoung))
+- Add code comments [\#756](https://github.com/overtrue/wechat/pull/756) ([daxiong123](https://github.com/daxiong123))
+
+## [3.3.3](https://github.com/overtrue/wechat/tree/3.3.3) (2017-06-22)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.2...3.3.3)
+
+**Implemented enhancements:**
+
+- \[4.0\] Trait HasHttpRequests [\#671](https://github.com/overtrue/wechat/issues/671)
+- \[4.0\] 缓存抽象成 trait: InteractsWithCache [\#670](https://github.com/overtrue/wechat/issues/670)
+- \[4.0\] 返回值类型可配置 [\#661](https://github.com/overtrue/wechat/issues/661)
+- \[4.0\] 报错信息可选 [\#596](https://github.com/overtrue/wechat/issues/596)
+- \[4.0\] 简化并完善开发者配置项 [\#584](https://github.com/overtrue/wechat/issues/584)
+
+**Fixed bugs:**
+
+- open\_platform.oauth 过早的获取 access token [\#701](https://github.com/overtrue/wechat/issues/701)
+
+**Closed issues:**
+
+- 微信网页支付配置生成 [\#751](https://github.com/overtrue/wechat/issues/751)
+- configForJSSDKPayment [\#744](https://github.com/overtrue/wechat/issues/744)
+- 发现微信上有管理公众号留言的接口,不知道是不是新出的 [\#721](https://github.com/overtrue/wechat/issues/721)
+- oauth能获取用户信息,再通过access\_token与用户openid去获取信息,部分用户的信息为空 [\#720](https://github.com/overtrue/wechat/issues/720)
+- 接入多个公众号 [\#718](https://github.com/overtrue/wechat/issues/718)
+- guzzle curl error28 - 去哪设置默认timeout ? [\#715](https://github.com/overtrue/wechat/issues/715)
+- 使用$server-\>getMessage\(\);报错 [\#712](https://github.com/overtrue/wechat/issues/712)
+- 怎样从数据库中调取配置 [\#711](https://github.com/overtrue/wechat/issues/711)
+- \[4.0\] 支持企业微信 [\#707](https://github.com/overtrue/wechat/issues/707)
+- defaultColor does not work. [\#703](https://github.com/overtrue/wechat/issues/703)
+- 是否支持H5支付 [\#694](https://github.com/overtrue/wechat/issues/694)
+- 生成AccessToken时,似乎没有调用自定义缓存的delete方法 [\#693](https://github.com/overtrue/wechat/issues/693)
+- \[4.0\] PSR-6 缓存接口 [\#692](https://github.com/overtrue/wechat/issues/692)
+- 微信支付沙盒模式支持配置文件配置 [\#690](https://github.com/overtrue/wechat/issues/690)
+- \[4.0\] 优化服务提供器结构 [\#689](https://github.com/overtrue/wechat/issues/689)
+- 强制项目不要自动获取AccessToken [\#688](https://github.com/overtrue/wechat/issues/688)
+- 小程序解密$encryptedData数据 [\#687](https://github.com/overtrue/wechat/issues/687)
+- 微信坑爹timestamp已经解决不需要configForJSSDKPayment改变timestamp中s大小写 [\#686](https://github.com/overtrue/wechat/issues/686)
+- \[4.0\] 所有 API 改名为 Client. [\#677](https://github.com/overtrue/wechat/issues/677)
+- sandbox\_signkey 过期 [\#675](https://github.com/overtrue/wechat/issues/675)
+- 接口配置失败 [\#672](https://github.com/overtrue/wechat/issues/672)
+- 下载语音文件偶尔报错:ErrorException: is\_readable\(\) expects parameter 1 to be a valid path [\#667](https://github.com/overtrue/wechat/issues/667)
+- 微信支付沙箱地址混乱 [\#665](https://github.com/overtrue/wechat/issues/665)
+- 开放平台自动回复出错,提示“该服务号暂时无法提供服务” [\#654](https://github.com/overtrue/wechat/issues/654)
+- \[4.0\]自定义微信API的区域接入点 [\#636](https://github.com/overtrue/wechat/issues/636)
+- 在命令行使用easywechat如何关闭日志 [\#601](https://github.com/overtrue/wechat/issues/601)
+- \[4.0\] PHP 版本最低要求 7.1 [\#586](https://github.com/overtrue/wechat/issues/586)
+- \[4.0\] 简化微信 API 请求 [\#583](https://github.com/overtrue/wechat/issues/583)
+- \[4.0\] 自定义 endpoint [\#521](https://github.com/overtrue/wechat/issues/521)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#750](https://github.com/overtrue/wechat/pull/750) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#749](https://github.com/overtrue/wechat/pull/749) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#747](https://github.com/overtrue/wechat/pull/747) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#745](https://github.com/overtrue/wechat/pull/745) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#740](https://github.com/overtrue/wechat/pull/740) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#737](https://github.com/overtrue/wechat/pull/737) ([mingyoung](https://github.com/mingyoung))
+- 分模块静态调用 [\#734](https://github.com/overtrue/wechat/pull/734) ([mingyoung](https://github.com/mingyoung))
+- Revert "Apply fixes from StyleCI" [\#731](https://github.com/overtrue/wechat/pull/731) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#730](https://github.com/overtrue/wechat/pull/730) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#729](https://github.com/overtrue/wechat/pull/729) ([overtrue](https://github.com/overtrue))
+- Revert "Apply fixes from StyleCI" [\#728](https://github.com/overtrue/wechat/pull/728) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#727](https://github.com/overtrue/wechat/pull/727) ([overtrue](https://github.com/overtrue))
+- 修复Https 请求判断不准 [\#726](https://github.com/overtrue/wechat/pull/726) ([xutl](https://github.com/xutl))
+- Apply fixes from StyleCI [\#725](https://github.com/overtrue/wechat/pull/725) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#724](https://github.com/overtrue/wechat/pull/724) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#723](https://github.com/overtrue/wechat/pull/723) ([mingyoung](https://github.com/mingyoung))
+- Correction notes [\#722](https://github.com/overtrue/wechat/pull/722) ([PersiLiao](https://github.com/PersiLiao))
+- Apply fixes from StyleCI [\#717](https://github.com/overtrue/wechat/pull/717) ([mingyoung](https://github.com/mingyoung))
+- 新增图文消息留言管理接口 [\#716](https://github.com/overtrue/wechat/pull/716) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#710](https://github.com/overtrue/wechat/pull/710) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#709](https://github.com/overtrue/wechat/pull/709) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#708](https://github.com/overtrue/wechat/pull/708) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#706](https://github.com/overtrue/wechat/pull/706) ([overtrue](https://github.com/overtrue))
+- 命令行下不打印日志 [\#705](https://github.com/overtrue/wechat/pull/705) ([mingyoung](https://github.com/mingyoung))
+- add defaultColor [\#704](https://github.com/overtrue/wechat/pull/704) ([damonto](https://github.com/damonto))
+- Fix [\#702](https://github.com/overtrue/wechat/pull/702) ([mingyoung](https://github.com/mingyoung))
+- Add api. [\#700](https://github.com/overtrue/wechat/pull/700) ([mingyoung](https://github.com/mingyoung))
+- Rename method. [\#699](https://github.com/overtrue/wechat/pull/699) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#698](https://github.com/overtrue/wechat/pull/698) ([mingyoung](https://github.com/mingyoung))
+- 修正素材管理中的返回值文档注释,正确的类型应该是集合,而不是字符串。 [\#695](https://github.com/overtrue/wechat/pull/695) ([starlight36](https://github.com/starlight36))
+- Payment sandbox config. [\#691](https://github.com/overtrue/wechat/pull/691) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#684](https://github.com/overtrue/wechat/pull/684) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#683](https://github.com/overtrue/wechat/pull/683) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#682](https://github.com/overtrue/wechat/pull/682) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#681](https://github.com/overtrue/wechat/pull/681) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#680](https://github.com/overtrue/wechat/pull/680) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#679](https://github.com/overtrue/wechat/pull/679) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#676](https://github.com/overtrue/wechat/pull/676) ([mingyoung](https://github.com/mingyoung))
+- checks via composer. [\#673](https://github.com/overtrue/wechat/pull/673) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#668](https://github.com/overtrue/wechat/pull/668) ([overtrue](https://github.com/overtrue))
+- Correct payment sandbox endpoint and add a method to get sandbox sign key [\#666](https://github.com/overtrue/wechat/pull/666) ([skyred](https://github.com/skyred))
+
+## [3.3.2](https://github.com/overtrue/wechat/tree/3.3.2) (2017-04-27)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.1...3.3.2)
+
+**Implemented enhancements:**
+
+- \[4.0\] Open Platform 模块 [\#587](https://github.com/overtrue/wechat/issues/587)
+- \[4.0\] 微信支付 sandbox模式 [\#507](https://github.com/overtrue/wechat/issues/507)
+
+**Closed issues:**
+
+- \[4.0\] staff 模块改名为 customer service [\#585](https://github.com/overtrue/wechat/issues/585)
+
+**Merged pull requests:**
+
+- Module rename. [\#664](https://github.com/overtrue/wechat/pull/664) ([mingyoung](https://github.com/mingyoung))
+- Merge branch master into branch develop. [\#663](https://github.com/overtrue/wechat/pull/663) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#662](https://github.com/overtrue/wechat/pull/662) ([mingyoung](https://github.com/mingyoung))
+- Fix payment tools API [\#660](https://github.com/overtrue/wechat/pull/660) ([mingyoung](https://github.com/mingyoung))
+- Avoid ambiguity [\#659](https://github.com/overtrue/wechat/pull/659) ([mingyoung](https://github.com/mingyoung))
+- Support Payment Sandbox mode [\#658](https://github.com/overtrue/wechat/pull/658) ([skyred](https://github.com/skyred))
+- Apply fixes from StyleCI [\#656](https://github.com/overtrue/wechat/pull/656) ([overtrue](https://github.com/overtrue))
+- Mini program datacube. [\#655](https://github.com/overtrue/wechat/pull/655) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.1](https://github.com/overtrue/wechat/tree/3.3.1) (2017-04-16)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.3.0...3.3.1)
+
+**Closed issues:**
+
+- 微信第三方平台缓存位置,是否可以在配置文件中自定义 [\#648](https://github.com/overtrue/wechat/issues/648)
+- 微信开放平台authorizer token缓存问题 [\#644](https://github.com/overtrue/wechat/issues/644)
+- 微信开放平台发起网页授权bug [\#638](https://github.com/overtrue/wechat/issues/638)
+- 微信公众号不能回复接收到的消息,日志无报错 [\#637](https://github.com/overtrue/wechat/issues/637)
+- \[4.0\]黑名单管理 [\#538](https://github.com/overtrue/wechat/issues/538)
+
+**Merged pull requests:**
+
+- optimizes [\#652](https://github.com/overtrue/wechat/pull/652) ([mingyoung](https://github.com/mingyoung))
+
+## [3.3.0](https://github.com/overtrue/wechat/tree/3.3.0) (2017-04-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.7...3.3.0)
+
+**Closed issues:**
+
+- 微信接口获取openid是怎么排序的? [\#650](https://github.com/overtrue/wechat/issues/650)
+- 缺少网页扫码支付接口 [\#647](https://github.com/overtrue/wechat/issues/647)
+- 微信下的单的默认过期时间是多少啊 [\#645](https://github.com/overtrue/wechat/issues/645)
+- 在获取用户信息是出错 [\#643](https://github.com/overtrue/wechat/issues/643)
+- 调用$app =app\('wechat'\);时报错Use of undefined constant CURLOPT\_IPRESOLVE - assumed 'CURLOPT\_IPRESOLVE' [\#633](https://github.com/overtrue/wechat/issues/633)
+- 提示找不到EasyWeChat\Server\Guard::setRequest\(\)方法 [\#626](https://github.com/overtrue/wechat/issues/626)
+- 开放平台接收ComponentVerifyTicket,会出现Undefined index: FromUserName [\#623](https://github.com/overtrue/wechat/issues/623)
+- 美国移动网络获取不到accessToken [\#610](https://github.com/overtrue/wechat/issues/610)
+- 开放平台 APP 微信登录 [\#604](https://github.com/overtrue/wechat/issues/604)
+
+**Merged pull requests:**
+
+- Merge from open-platform branch. [\#651](https://github.com/overtrue/wechat/pull/651) ([mingyoung](https://github.com/mingyoung))
+- Update code for open-platform [\#649](https://github.com/overtrue/wechat/pull/649) ([mingyoung](https://github.com/mingyoung))
+- Code cleanup & refactoring. [\#646](https://github.com/overtrue/wechat/pull/646) ([mingyoung](https://github.com/mingyoung))
+- support cash coupon [\#642](https://github.com/overtrue/wechat/pull/642) ([HanSon](https://github.com/HanSon))
+- ♻️ All tests have been namespaced. [\#641](https://github.com/overtrue/wechat/pull/641) ([mingyoung](https://github.com/mingyoung))
+- tweak code. [\#640](https://github.com/overtrue/wechat/pull/640) ([mingyoung](https://github.com/mingyoung))
+- modify oauth property [\#639](https://github.com/overtrue/wechat/pull/639) ([jekst](https://github.com/jekst))
+- Apply fixes from StyleCI [\#635](https://github.com/overtrue/wechat/pull/635) ([overtrue](https://github.com/overtrue))
+- ✨ Blacklist. [\#634](https://github.com/overtrue/wechat/pull/634) ([mingyoung](https://github.com/mingyoung))
+- 🔨 Refactoring for mini-program. [\#632](https://github.com/overtrue/wechat/pull/632) ([mingyoung](https://github.com/mingyoung))
+
+## [3.2.7](https://github.com/overtrue/wechat/tree/3.2.7) (2017-03-31)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.6...3.2.7)
+
+**Closed issues:**
+
+- 不管哪个公众号,只要填写 这个接口地址,都能配置或应用成功,实际上是不成功的,不到怎么找错。。 [\#611](https://github.com/overtrue/wechat/issues/611)
+
+**Merged pull requests:**
+
+- 修复一个创建卡券时的 bug, 添加获取微信门店类目表的api [\#631](https://github.com/overtrue/wechat/pull/631) ([Hexor](https://github.com/Hexor))
+
+## [3.2.6](https://github.com/overtrue/wechat/tree/3.2.6) (2017-03-31)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.5...3.2.6)
+
+**Closed issues:**
+
+- 我想大量发模板消息,但send每次都等待返回太慢,有啥解决办法吗? [\#630](https://github.com/overtrue/wechat/issues/630)
+- 3.2开放平台缺少authorizer\_token和authorization [\#629](https://github.com/overtrue/wechat/issues/629)
+- 微信开发平台接受消息报Invalid request signature bug [\#625](https://github.com/overtrue/wechat/issues/625)
+- 图文上传thumb\_media\_id 返回 {"errcode":40007,"errmsg":"invalid media\_id hint: \[\]"} [\#622](https://github.com/overtrue/wechat/issues/622)
+- Encryptor基类hack导致小程序的sessionKey base64\_decode失败 [\#614](https://github.com/overtrue/wechat/issues/614)
+- 是否有 2.1 升级到最新版的方案? [\#609](https://github.com/overtrue/wechat/issues/609)
+- laravel5.3 安装 "overtrue/wechat:~3.1 失败 [\#607](https://github.com/overtrue/wechat/issues/607)
+- overtrue/wechat和phpdoc包依赖冲突。 [\#605](https://github.com/overtrue/wechat/issues/605)
+- \[bug\]2个问题 [\#597](https://github.com/overtrue/wechat/issues/597)
+- 微信第三方平台开发是否只做了一部分? [\#594](https://github.com/overtrue/wechat/issues/594)
+- \[4.0\] ServiceProvider 移动到各自模块里 [\#588](https://github.com/overtrue/wechat/issues/588)
+- Cannot use EasyWeChat\OpenPlatform\Traits\VerifyTicket as VerifyTicket because the name is already in use [\#579](https://github.com/overtrue/wechat/issues/579)
+- 授权state值怎么设置 [\#573](https://github.com/overtrue/wechat/issues/573)
+- mini\_app get jscode problem, report appid & secret value is null [\#569](https://github.com/overtrue/wechat/issues/569)
+- 小程序生成二维码问题 [\#568](https://github.com/overtrue/wechat/issues/568)
+
+**Merged pull requests:**
+
+- Update OpenPlatform AppId [\#624](https://github.com/overtrue/wechat/pull/624) ([jeftom](https://github.com/jeftom))
+- Apply fixes from StyleCI [\#621](https://github.com/overtrue/wechat/pull/621) ([overtrue](https://github.com/overtrue))
+- Apply fixes from StyleCI [\#618](https://github.com/overtrue/wechat/pull/618) ([overtrue](https://github.com/overtrue))
+- Compatible with php5.5 [\#617](https://github.com/overtrue/wechat/pull/617) ([mingyoung](https://github.com/mingyoung))
+- Make the testcase works. [\#616](https://github.com/overtrue/wechat/pull/616) ([mingyoung](https://github.com/mingyoung))
+- Fix mini-program decryptor [\#615](https://github.com/overtrue/wechat/pull/615) ([mingyoung](https://github.com/mingyoung))
+- Missing message handling [\#613](https://github.com/overtrue/wechat/pull/613) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#612](https://github.com/overtrue/wechat/pull/612) ([overtrue](https://github.com/overtrue))
+- 添加卡券创建二维码接口 [\#608](https://github.com/overtrue/wechat/pull/608) ([forecho](https://github.com/forecho))
+- 开放平台大幅重构并且添加测试 [\#606](https://github.com/overtrue/wechat/pull/606) ([tsunamilx](https://github.com/tsunamilx))
+- Update MessageBuilder.php [\#603](https://github.com/overtrue/wechat/pull/603) ([U2Fsd](https://github.com/U2Fsd))
+- 生成 js添加到卡包接口 增加fixed\_begintimestamp、outer\_str字段 [\#602](https://github.com/overtrue/wechat/pull/602) ([gychg](https://github.com/gychg))
+- tests for speed [\#600](https://github.com/overtrue/wechat/pull/600) ([mingyoung](https://github.com/mingyoung))
+- Update test files [\#599](https://github.com/overtrue/wechat/pull/599) ([mingyoung](https://github.com/mingyoung))
+- 允许自定义ticket缓存key [\#598](https://github.com/overtrue/wechat/pull/598) ([XiaoLer](https://github.com/XiaoLer))
+- delete top color [\#595](https://github.com/overtrue/wechat/pull/595) ([HanSon](https://github.com/HanSon))
+- Add payment scan notify handler [\#593](https://github.com/overtrue/wechat/pull/593) ([acgrid](https://github.com/acgrid))
+- Apply fixes from StyleCI [\#591](https://github.com/overtrue/wechat/pull/591) ([overtrue](https://github.com/overtrue))
+- Upgrade packages version to 4.0 [\#590](https://github.com/overtrue/wechat/pull/590) ([reatang](https://github.com/reatang))
+- Move providers to module dir. \#588 [\#589](https://github.com/overtrue/wechat/pull/589) ([overtrue](https://github.com/overtrue))
+- 把OpenPlatform中的组件依赖解耦 [\#581](https://github.com/overtrue/wechat/pull/581) ([reatang](https://github.com/reatang))
+
+## [3.2.5](https://github.com/overtrue/wechat/tree/3.2.5) (2017-02-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.4...3.2.5)
+
+**Merged pull requests:**
+
+- fix naming [\#580](https://github.com/overtrue/wechat/pull/580) ([mingyoung](https://github.com/mingyoung))
+- Allow client code configure its own GuzzleHTTP handler [\#578](https://github.com/overtrue/wechat/pull/578) ([acgrid](https://github.com/acgrid))
+
+## [3.2.4](https://github.com/overtrue/wechat/tree/3.2.4) (2017-01-24)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.3...3.2.4)
+
+**Closed issues:**
+
+- 如何在其他框架下使用$app-\>payment-\>handleNotify [\#574](https://github.com/overtrue/wechat/issues/574)
+- 前后端分离单页下获取的config,认证失败 [\#565](https://github.com/overtrue/wechat/issues/565)
+- 支付签名错误 [\#563](https://github.com/overtrue/wechat/issues/563)
+
+**Merged pull requests:**
+
+- Update Authorizer.php [\#577](https://github.com/overtrue/wechat/pull/577) ([ww380459000](https://github.com/ww380459000))
+- 补全通用卡接口 [\#575](https://github.com/overtrue/wechat/pull/575) ([XiaoLer](https://github.com/XiaoLer))
+- require ext-SimpleXML [\#572](https://github.com/overtrue/wechat/pull/572) ([garveen](https://github.com/garveen))
+- fix README Contribution link [\#571](https://github.com/overtrue/wechat/pull/571) ([zhwei](https://github.com/zhwei))
+- Add user data decryption. [\#570](https://github.com/overtrue/wechat/pull/570) ([mingyoung](https://github.com/mingyoung))
+- change request parameter [\#567](https://github.com/overtrue/wechat/pull/567) ([cloudsthere](https://github.com/cloudsthere))
+- 完善小程序代码 [\#566](https://github.com/overtrue/wechat/pull/566) ([mingyoung](https://github.com/mingyoung))
+- 添加小程序支持 [\#564](https://github.com/overtrue/wechat/pull/564) ([mingyoung](https://github.com/mingyoung))
+
+## [3.2.3](https://github.com/overtrue/wechat/tree/3.2.3) (2017-01-04)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.2...3.2.3)
+
+**Closed issues:**
+
+- 文档里的自定义菜单中,group\_id是否为tag\_id的误写? [\#561](https://github.com/overtrue/wechat/issues/561)
+- Open Platform有简明的使用文档吗?3ks [\#560](https://github.com/overtrue/wechat/issues/560)
+- 刷新access\_token有效期,未发现有相关的封装 [\#540](https://github.com/overtrue/wechat/issues/540)
+
+**Merged pull requests:**
+
+- Update Card.php [\#562](https://github.com/overtrue/wechat/pull/562) ([XiaoLer](https://github.com/XiaoLer))
+- Apply fixes from StyleCI [\#559](https://github.com/overtrue/wechat/pull/559) ([overtrue](https://github.com/overtrue))
+- Update API.php [\#558](https://github.com/overtrue/wechat/pull/558) ([drogjh](https://github.com/drogjh))
+- optimized code [\#557](https://github.com/overtrue/wechat/pull/557) ([mingyoung](https://github.com/mingyoung))
+
+## [3.2.2](https://github.com/overtrue/wechat/tree/3.2.2) (2016-12-27)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.1...3.2.2)
+
+**Closed issues:**
+
+- How to get authorize url? [\#555](https://github.com/overtrue/wechat/issues/555)
+
+**Merged pull requests:**
+
+- fixed downloadBill method result [\#556](https://github.com/overtrue/wechat/pull/556) ([hidehalo](https://github.com/hidehalo))
+- add config:log.permission for monolog [\#554](https://github.com/overtrue/wechat/pull/554) ([woshizoufeng](https://github.com/woshizoufeng))
+- Improve open platform support. [\#553](https://github.com/overtrue/wechat/pull/553) ([mingyoung](https://github.com/mingyoung))
+- Improve. [\#552](https://github.com/overtrue/wechat/pull/552) ([mingyoung](https://github.com/mingyoung))
+- add $forceRefresh param to js-\>ticket\(\) method [\#551](https://github.com/overtrue/wechat/pull/551) ([leo108](https://github.com/leo108))
+
+## [3.2.1](https://github.com/overtrue/wechat/tree/3.2.1) (2016-12-20)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.2.0...3.2.1)
+
+**Merged pull requests:**
+
+- 增加小程序用jscode获取用户信息的接口 [\#550](https://github.com/overtrue/wechat/pull/550) ([soone](https://github.com/soone))
+
+## [3.2.0](https://github.com/overtrue/wechat/tree/3.2.0) (2016-12-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.9...3.2.0)
+
+**Closed issues:**
+
+- 喵喵喵 [\#545](https://github.com/overtrue/wechat/issues/545)
+- HttpException with uploadArticle API [\#544](https://github.com/overtrue/wechat/issues/544)
+- 是否有接入小程序的计划 [\#543](https://github.com/overtrue/wechat/issues/543)
+- "Call to undefined method Overtrue\Socialite\Providers\WeChat Provider::driver\(\) [\#536](https://github.com/overtrue/wechat/issues/536)
+- 服务端Server模块回复音乐消息出错 [\#533](https://github.com/overtrue/wechat/issues/533)
+- 用户授权出现The key "access\_token" could not be empty [\#527](https://github.com/overtrue/wechat/issues/527)
+
+**Merged pull requests:**
+
+- Apply fixes from StyleCI [\#549](https://github.com/overtrue/wechat/pull/549) ([overtrue](https://github.com/overtrue))
+- 添加摇一摇周边模块 [\#548](https://github.com/overtrue/wechat/pull/548) ([allen05ren](https://github.com/allen05ren))
+- Make some compatible. [\#542](https://github.com/overtrue/wechat/pull/542) ([mingyoung](https://github.com/mingyoung))
+- Apply fixes from StyleCI [\#541](https://github.com/overtrue/wechat/pull/541) ([overtrue](https://github.com/overtrue))
+- 改变了http 中 json 方法的接口, 从而支持 添加 添加 query参数 [\#539](https://github.com/overtrue/wechat/pull/539) ([shoaly](https://github.com/shoaly))
+- 提交 [\#537](https://github.com/overtrue/wechat/pull/537) ([shoaly](https://github.com/shoaly))
+- Apply fixes from StyleCI [\#535](https://github.com/overtrue/wechat/pull/535) ([overtrue](https://github.com/overtrue))
+
+## [3.1.9](https://github.com/overtrue/wechat/tree/3.1.9) (2016-12-01)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.8...3.1.9)
+
+**Closed issues:**
+
+- 还是不懂怎么获取unionid [\#531](https://github.com/overtrue/wechat/issues/531)
+- Scope 参数错误或没有 Scope 权限 [\#528](https://github.com/overtrue/wechat/issues/528)
+- $\_SERVER\['SERVER\_ADDR'\] 在mac php7中获取不到 [\#520](https://github.com/overtrue/wechat/issues/520)
+- 能否永久素材其他类型封装个download方法,跟临时一样 [\#505](https://github.com/overtrue/wechat/issues/505)
+- V3.1 JSSDK使用疑惑 [\#503](https://github.com/overtrue/wechat/issues/503)
+- 如何加入QQ群 [\#501](https://github.com/overtrue/wechat/issues/501)
+- 能否在下一个版本把企业的相关接口整合集成进去 [\#496](https://github.com/overtrue/wechat/issues/496)
+- 既然使用了monolog,那么在Application::initializeLogger只使用了文件流的特定形式来记录日志是否合理? [\#494](https://github.com/overtrue/wechat/issues/494)
+- configForShareAddress [\#482](https://github.com/overtrue/wechat/issues/482)
+- 更新微信文章的时候MatialEasyWeChat\Material,如果设置了show\_pic\_cover和content\_source\_url不会生效 [\#470](https://github.com/overtrue/wechat/issues/470)
+- 请问 SDK 是否支持授权接入的公众号接口调用? [\#438](https://github.com/overtrue/wechat/issues/438)
+- 通过unionid发送信息。 [\#411](https://github.com/overtrue/wechat/issues/411)
+- 【新增】设备管理 [\#77](https://github.com/overtrue/wechat/issues/77)
+
+**Merged pull requests:**
+
+- Add support wechat open platform. [\#532](https://github.com/overtrue/wechat/pull/532) ([mingyoung](https://github.com/mingyoung))
+- Applied fixes from StyleCI [\#530](https://github.com/overtrue/wechat/pull/530) ([overtrue](https://github.com/overtrue))
+- 新增硬件设备api [\#529](https://github.com/overtrue/wechat/pull/529) ([soone](https://github.com/soone))
+
+## [3.1.8](https://github.com/overtrue/wechat/tree/3.1.8) (2016-11-23)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.7...3.1.8)
+
+**Closed issues:**
+
+- SCAN 事件会出现无法提供服务 [\#525](https://github.com/overtrue/wechat/issues/525)
+
+## [3.1.7](https://github.com/overtrue/wechat/tree/3.1.7) (2016-10-26)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.6...3.1.7)
+
+**Closed issues:**
+
+- preg\_replace unicode 的兼容问题 [\#515](https://github.com/overtrue/wechat/issues/515)
+
+**Merged pull requests:**
+
+- support psr-http-message-bridge 1.0 [\#524](https://github.com/overtrue/wechat/pull/524) ([wppd](https://github.com/wppd))
+- Applied fixes from StyleCI [\#523](https://github.com/overtrue/wechat/pull/523) ([overtrue](https://github.com/overtrue))
+- for \#520 [\#522](https://github.com/overtrue/wechat/pull/522) ([jinchun](https://github.com/jinchun))
+
+## [3.1.6](https://github.com/overtrue/wechat/tree/3.1.6) (2016-10-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.5...3.1.6)
+
+**Closed issues:**
+
+- PHP Fatal error: Uncaught HttpException [\#517](https://github.com/overtrue/wechat/issues/517)
+- 微信支付回调出错 [\#514](https://github.com/overtrue/wechat/issues/514)
+
+**Merged pull requests:**
+
+- Fix xml preg replace [\#519](https://github.com/overtrue/wechat/pull/519) ([springjk](https://github.com/springjk))
+- fix the DOC [\#518](https://github.com/overtrue/wechat/pull/518) ([ac1982](https://github.com/ac1982))
+
+## [3.1.5](https://github.com/overtrue/wechat/tree/3.1.5) (2016-10-13)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.4...3.1.5)
+
+**Closed issues:**
+
+- wechat 在 larave l5.3 使用 passport 包下无法安装 [\#513](https://github.com/overtrue/wechat/issues/513)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#512](https://github.com/overtrue/wechat/pull/512) ([overtrue](https://github.com/overtrue))
+
+## [3.1.4](https://github.com/overtrue/wechat/tree/3.1.4) (2016-10-12)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.39...3.1.4)
+
+**Closed issues:**
+
+- 微信卡券特殊票券创建之后为什么无法更新卡券信息一致提示code非法。 [\#511](https://github.com/overtrue/wechat/issues/511)
+- 请添加 「退款方式」 参数 [\#509](https://github.com/overtrue/wechat/issues/509)
+- 2.1.40命名空间巨变引发的重大问题\(疑似提错版本了\) [\#508](https://github.com/overtrue/wechat/issues/508)
+- 卡券核销、查询建议 [\#506](https://github.com/overtrue/wechat/issues/506)
+- 支付重复回调问题 [\#504](https://github.com/overtrue/wechat/issues/504)
+
+**Merged pull requests:**
+
+- Changed method doc to the right accepted param type [\#510](https://github.com/overtrue/wechat/pull/510) ([marianoasselborn](https://github.com/marianoasselborn))
+- 增加判断是否有人工客服帐号,避免出现无账号时候,头像为默认头像的情况 [\#502](https://github.com/overtrue/wechat/pull/502) ([hello2t](https://github.com/hello2t))
+- Applied fixes from StyleCI [\#500](https://github.com/overtrue/wechat/pull/500) ([overtrue](https://github.com/overtrue))
+- 为initializeLogger日志初始话函数添加判断分支 [\#499](https://github.com/overtrue/wechat/pull/499) ([403studio](https://github.com/403studio))
+
+## [2.1.39](https://github.com/overtrue/wechat/tree/2.1.39) (2016-09-05)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.41...2.1.39)
+
+## [2.1.41](https://github.com/overtrue/wechat/tree/2.1.41) (2016-09-05)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.3...2.1.41)
+
+**Closed issues:**
+
+- 调用接口次数超过最大限制问题 [\#493](https://github.com/overtrue/wechat/issues/493)
+- 微信退款证书报错 Unable to set private key file [\#492](https://github.com/overtrue/wechat/issues/492)
+- 微信支付存在问题 [\#489](https://github.com/overtrue/wechat/issues/489)
+- 预支付下单 response body 为空 [\#488](https://github.com/overtrue/wechat/issues/488)
+- https check issue [\#486](https://github.com/overtrue/wechat/issues/486)
+
+**Merged pull requests:**
+
+- update composer.json [\#498](https://github.com/overtrue/wechat/pull/498) ([ac1982](https://github.com/ac1982))
+- use openssl instead of mcrypt [\#497](https://github.com/overtrue/wechat/pull/497) ([ac1982](https://github.com/ac1982))
+- 修复 with 方法带数据的问题 [\#491](https://github.com/overtrue/wechat/pull/491) ([XiaoLer](https://github.com/XiaoLer))
+
+## [3.1.3](https://github.com/overtrue/wechat/tree/3.1.3) (2016-08-08)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.2...3.1.3)
+
+**Closed issues:**
+
+- Laravel中写的最简单的例子在phpunit出错。 [\#485](https://github.com/overtrue/wechat/issues/485)
+- 微信的消息回复的FromUserName和ToUserName是不是对调了 [\#484](https://github.com/overtrue/wechat/issues/484)
+- 微信红包不能发给别的公众号的用户吗 [\#483](https://github.com/overtrue/wechat/issues/483)
+- 用户授权登录问题 [\#481](https://github.com/overtrue/wechat/issues/481)
+- cURL error 56: SSLRead\(\) return error -9806 [\#473](https://github.com/overtrue/wechat/issues/473)
+- 会员卡开卡字段文档有错误 [\#471](https://github.com/overtrue/wechat/issues/471)
+- Getting more done in GitHub with ZenHub [\#439](https://github.com/overtrue/wechat/issues/439)
+- 微信支付下单错误 [\#376](https://github.com/overtrue/wechat/issues/376)
+
+**Merged pull requests:**
+
+- update the File class to recognize pdf file. [\#480](https://github.com/overtrue/wechat/pull/480) ([ac1982](https://github.com/ac1982))
+- update testActivateUserForm [\#478](https://github.com/overtrue/wechat/pull/478) ([wangniuniu](https://github.com/wangniuniu))
+- Scrutinizer Auto-Fixes [\#477](https://github.com/overtrue/wechat/pull/477) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Applied fixes from StyleCI [\#476](https://github.com/overtrue/wechat/pull/476) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#475](https://github.com/overtrue/wechat/pull/475) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 开放自定义prefix和缓存键值方法 [\#474](https://github.com/overtrue/wechat/pull/474) ([XiaoLer](https://github.com/XiaoLer))
+- Applied fixes from StyleCI [\#469](https://github.com/overtrue/wechat/pull/469) ([overtrue](https://github.com/overtrue))
+- modify stats [\#468](https://github.com/overtrue/wechat/pull/468) ([wangniuniu](https://github.com/wangniuniu))
+
+## [3.1.2](https://github.com/overtrue/wechat/tree/3.1.2) (2016-07-21)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.38...3.1.2)
+
+**Closed issues:**
+
+- 素材管理中,上传图文下的上传图片,关于返回内容的差异 [\#466](https://github.com/overtrue/wechat/issues/466)
+- spbill\_create\_ip参数设置 [\#461](https://github.com/overtrue/wechat/issues/461)
+
+**Merged pull requests:**
+
+- 更新获取标签下粉丝列表方法 [\#467](https://github.com/overtrue/wechat/pull/467) ([dingdayu](https://github.com/dingdayu))
+- Applied fixes from StyleCI [\#465](https://github.com/overtrue/wechat/pull/465) ([overtrue](https://github.com/overtrue))
+- card module. [\#464](https://github.com/overtrue/wechat/pull/464) ([wangniuniu](https://github.com/wangniuniu))
+- Applied fixes from StyleCI [\#463](https://github.com/overtrue/wechat/pull/463) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#462](https://github.com/overtrue/wechat/pull/462) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+
+## [2.1.38](https://github.com/overtrue/wechat/tree/2.1.38) (2016-07-16)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.1...2.1.38)
+
+**Closed issues:**
+
+- 请问卡券管理功能整合上日程表了吗 [\#454](https://github.com/overtrue/wechat/issues/454)
+
+**Merged pull requests:**
+
+- Typo. [\#460](https://github.com/overtrue/wechat/pull/460) ([tianyong90](https://github.com/tianyong90))
+- Applied fixes from StyleCI [\#459](https://github.com/overtrue/wechat/pull/459) ([overtrue](https://github.com/overtrue))
+- add voice recognition [\#458](https://github.com/overtrue/wechat/pull/458) ([leniy](https://github.com/leniy))
+- Applied fixes from StyleCI [\#457](https://github.com/overtrue/wechat/pull/457) ([overtrue](https://github.com/overtrue))
+- Update API.php [\#456](https://github.com/overtrue/wechat/pull/456) ([marvin8212](https://github.com/marvin8212))
+- Update XML.php [\#455](https://github.com/overtrue/wechat/pull/455) ([canon4ever](https://github.com/canon4ever))
+
+## [3.1.1](https://github.com/overtrue/wechat/tree/3.1.1) (2016-07-12)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.1.0...3.1.1)
+
+**Closed issues:**
+
+- 拿到code=CODE&state=STATE之后怎么拿到openid? [\#452](https://github.com/overtrue/wechat/issues/452)
+- 安装出错 [\#450](https://github.com/overtrue/wechat/issues/450)
+- 自定义菜单接口\(新版\)出错 [\#448](https://github.com/overtrue/wechat/issues/448)
+- h5上没法打开微信app授权界面 [\#447](https://github.com/overtrue/wechat/issues/447)
+- 重构卡券 [\#76](https://github.com/overtrue/wechat/issues/76)
+
+**Merged pull requests:**
+
+- typos. [\#453](https://github.com/overtrue/wechat/pull/453) ([tianye](https://github.com/tianye))
+- edit readme.md [\#451](https://github.com/overtrue/wechat/pull/451) ([tianyong90](https://github.com/tianyong90))
+- Add cache driver config. [\#449](https://github.com/overtrue/wechat/pull/449) ([dingdayu](https://github.com/dingdayu))
+
+## [3.1.0](https://github.com/overtrue/wechat/tree/3.1.0) (2016-06-28)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.21...3.1.0)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#446](https://github.com/overtrue/wechat/pull/446) ([overtrue](https://github.com/overtrue))
+- New Staff API. [\#445](https://github.com/overtrue/wechat/pull/445) ([overtrue](https://github.com/overtrue))
+- 2.1 [\#444](https://github.com/overtrue/wechat/pull/444) ([dongnanyanhai](https://github.com/dongnanyanhai))
+- Fix path. [\#443](https://github.com/overtrue/wechat/pull/443) ([overtrue](https://github.com/overtrue))
+
+## [3.0.21](https://github.com/overtrue/wechat/tree/3.0.21) (2016-06-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.0.1...3.0.21)
+
+**Closed issues:**
+
+- scan出现公众号暂时无法服务的消息 [\#436](https://github.com/overtrue/wechat/issues/436)
+- scan出现公众号暂时无法服务的消息 [\#435](https://github.com/overtrue/wechat/issues/435)
+- 用户标签接口无法使用 [\#433](https://github.com/overtrue/wechat/issues/433)
+- WeChatProvider下的getAuthUrl个人觉得应该暴露出来 [\#432](https://github.com/overtrue/wechat/issues/432)
+- 支持二维码扫描进入公众号推送的SCAN事件 [\#431](https://github.com/overtrue/wechat/issues/431)
+- \[3.0\] EasyWeChat\Support\XML::parse方法会将空节点解析为空数组,而不是空字符串 [\#426](https://github.com/overtrue/wechat/issues/426)
+- 下载二维码, $qrcode-\>download\($ticket,$paths\); 目录参数不可加入 中文 [\#420](https://github.com/overtrue/wechat/issues/420)
+- \[help want\]Is hard to change default configuration of GuzzleHttp [\#415](https://github.com/overtrue/wechat/issues/415)
+- PHP7.0 curl\_setopt 设置问题 [\#413](https://github.com/overtrue/wechat/issues/413)
+- 无法通知微信支付完成 [\#412](https://github.com/overtrue/wechat/issues/412)
+- 如何获取用户的unionid? [\#407](https://github.com/overtrue/wechat/issues/407)
+- 是否支持多框架 [\#406](https://github.com/overtrue/wechat/issues/406)
+- fuckTheWeChatInvalidJSON [\#405](https://github.com/overtrue/wechat/issues/405)
+- Class 'GuzzleHttp\Middleware' not found [\#404](https://github.com/overtrue/wechat/issues/404)
+- 支付统一下单接口签名错误 [\#402](https://github.com/overtrue/wechat/issues/402)
+- payment里没有configForJSSDKPayment方法 [\#401](https://github.com/overtrue/wechat/issues/401)
+- 查询支付的地址多了一个空格,导致查询失败,去掉最后的那个空格后就好了 [\#393](https://github.com/overtrue/wechat/issues/393)
+- 网页授权过不了 [\#392](https://github.com/overtrue/wechat/issues/392)
+- 微信AccessToken被动更新可能会有并发更新的情况出现 [\#390](https://github.com/overtrue/wechat/issues/390)
+- 临时素材下载,文件名和扩展名之间会有2个\[.\] [\#389](https://github.com/overtrue/wechat/issues/389)
+- 有一个地方变量名对不上 [\#380](https://github.com/overtrue/wechat/issues/380)
+- 自定义缓存 [\#379](https://github.com/overtrue/wechat/issues/379)
+- https://easywechat.org/ 底部 “开始使用” url拼错 [\#378](https://github.com/overtrue/wechat/issues/378)
+- 在server.php里面调用yii的model,一直报错 [\#375](https://github.com/overtrue/wechat/issues/375)
+- overture/wechat 2.1.36\(客服消息转发错误\) [\#374](https://github.com/overtrue/wechat/issues/374)
+- 建议支持开发模式下禁用验证 [\#373](https://github.com/overtrue/wechat/issues/373)
+- https://easywechat.org/ 导航 首页 about:blank [\#370](https://github.com/overtrue/wechat/issues/370)
+- laravel 下session问题 [\#369](https://github.com/overtrue/wechat/issues/369)
+- 关于Access——toekn [\#368](https://github.com/overtrue/wechat/issues/368)
+- 返回支付页面时报错:"access\_token" could not be empty [\#367](https://github.com/overtrue/wechat/issues/367)
+- xampp下js-\>config报错 [\#366](https://github.com/overtrue/wechat/issues/366)
+- 官方文档有误 [\#360](https://github.com/overtrue/wechat/issues/360)
+- \[BUG\] 微信收货地址无法成功 [\#359](https://github.com/overtrue/wechat/issues/359)
+- 无法获取 $message-\>ScanCodeInfo-\>ScanType 对象 [\#358](https://github.com/overtrue/wechat/issues/358)
+- \[Bugs\] 项目文档首页跳转问题 [\#357](https://github.com/overtrue/wechat/issues/357)
+- Business和UnifiedOrder没有定义 [\#356](https://github.com/overtrue/wechat/issues/356)
+- 你的网站访问不了。。。。https://easywechat.org/ [\#352](https://github.com/overtrue/wechat/issues/352)
+- 连续多次执行微信支付退款报错 [\#348](https://github.com/overtrue/wechat/issues/348)
+- 客服操作 都是 -1 错误 [\#344](https://github.com/overtrue/wechat/issues/344)
+- 请使用openssl 而不是不安全的mcrypt来加密 [\#342](https://github.com/overtrue/wechat/issues/342)
+- 文本类型的通知消息 [\#341](https://github.com/overtrue/wechat/issues/341)
+- 服务器配置https 并且 通过阿里云 https cdn之后, 会出现 https 判断语句失效 [\#338](https://github.com/overtrue/wechat/issues/338)
+- 作者请问者个sdk支持企业号吗? [\#336](https://github.com/overtrue/wechat/issues/336)
+- laravel 5.1引入包报错 [\#331](https://github.com/overtrue/wechat/issues/331)
+- 申请退款有问题 [\#328](https://github.com/overtrue/wechat/issues/328)
+- 订单相关接口bug [\#327](https://github.com/overtrue/wechat/issues/327)
+- 临时素材接口无法使用 [\#319](https://github.com/overtrue/wechat/issues/319)
+- 使用sendNormal\(\),sendGroup\(\)发送红包时,报Undefined index: HTTP\_CLIENT\_IP [\#316](https://github.com/overtrue/wechat/issues/316)
+- v3中微信卡券功能缺失? [\#307](https://github.com/overtrue/wechat/issues/307)
+- 测试 [\#305](https://github.com/overtrue/wechat/issues/305)
+- \[3.0\] 永久素材上传视频无法上传问题 [\#304](https://github.com/overtrue/wechat/issues/304)
+- Cannot destroy active lambda function [\#296](https://github.com/overtrue/wechat/issues/296)
+- 微信支付-》企业付款也可以增加个类上去,跟企业红包类似 [\#232](https://github.com/overtrue/wechat/issues/232)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#442](https://github.com/overtrue/wechat/pull/442) ([overtrue](https://github.com/overtrue))
+- NGINX HTTPS无法签名 [\#441](https://github.com/overtrue/wechat/pull/441) ([ares333](https://github.com/ares333))
+- Develop [\#440](https://github.com/overtrue/wechat/pull/440) ([overtrue](https://github.com/overtrue))
+- Develop [\#437](https://github.com/overtrue/wechat/pull/437) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#434](https://github.com/overtrue/wechat/pull/434) ([overtrue](https://github.com/overtrue))
+- 修改错误提示信息,方便跟踪错误 [\#430](https://github.com/overtrue/wechat/pull/430) ([zerozh](https://github.com/zerozh))
+- Develop [\#429](https://github.com/overtrue/wechat/pull/429) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#428](https://github.com/overtrue/wechat/pull/428) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#427](https://github.com/overtrue/wechat/pull/427) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#425](https://github.com/overtrue/wechat/pull/425) ([overtrue](https://github.com/overtrue))
+- update annotation [\#424](https://github.com/overtrue/wechat/pull/424) ([lilocon](https://github.com/lilocon))
+- Develop [\#421](https://github.com/overtrue/wechat/pull/421) ([overtrue](https://github.com/overtrue))
+- Set default timeout. [\#419](https://github.com/overtrue/wechat/pull/419) ([overtrue](https://github.com/overtrue))
+- Develop [\#418](https://github.com/overtrue/wechat/pull/418) ([overtrue](https://github.com/overtrue))
+- Develop [\#416](https://github.com/overtrue/wechat/pull/416) ([overtrue](https://github.com/overtrue))
+- better implementation for prepare oauth callback url [\#414](https://github.com/overtrue/wechat/pull/414) ([lichunqiang](https://github.com/lichunqiang))
+- Develop [\#410](https://github.com/overtrue/wechat/pull/410) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#409](https://github.com/overtrue/wechat/pull/409) ([overtrue](https://github.com/overtrue))
+- 增加微信支付服务商支持 [\#408](https://github.com/overtrue/wechat/pull/408) ([takatost](https://github.com/takatost))
+- Develop [\#403](https://github.com/overtrue/wechat/pull/403) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#400](https://github.com/overtrue/wechat/pull/400) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#399](https://github.com/overtrue/wechat/pull/399) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Develop [\#398](https://github.com/overtrue/wechat/pull/398) ([overtrue](https://github.com/overtrue))
+- Develop [\#397](https://github.com/overtrue/wechat/pull/397) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#396](https://github.com/overtrue/wechat/pull/396) ([overtrue](https://github.com/overtrue))
+- Typo & Improve code. [\#395](https://github.com/overtrue/wechat/pull/395) ([jinchun](https://github.com/jinchun))
+- Develop [\#394](https://github.com/overtrue/wechat/pull/394) ([overtrue](https://github.com/overtrue))
+- Bugfix close \#389 [\#391](https://github.com/overtrue/wechat/pull/391) ([overtrue](https://github.com/overtrue))
+- Update NoticeNoticeTest.php [\#388](https://github.com/overtrue/wechat/pull/388) ([xiabeifeng](https://github.com/xiabeifeng))
+- Update Notice.php [\#387](https://github.com/overtrue/wechat/pull/387) ([xiabeifeng](https://github.com/xiabeifeng))
+- Tests for \#384 [\#386](https://github.com/overtrue/wechat/pull/386) ([xiabeifeng](https://github.com/xiabeifeng))
+- Improve Notice API. [\#384](https://github.com/overtrue/wechat/pull/384) ([xiabeifeng](https://github.com/xiabeifeng))
+- 对应根 版本依赖 [\#382](https://github.com/overtrue/wechat/pull/382) ([parkshinhye](https://github.com/parkshinhye))
+- Develop [\#381](https://github.com/overtrue/wechat/pull/381) ([overtrue](https://github.com/overtrue))
+- Develop [\#377](https://github.com/overtrue/wechat/pull/377) ([overtrue](https://github.com/overtrue))
+- Fix test for \#371 [\#372](https://github.com/overtrue/wechat/pull/372) ([overtrue](https://github.com/overtrue))
+- 刷卡支付不需要notify\_url参数 [\#371](https://github.com/overtrue/wechat/pull/371) ([lilocon](https://github.com/lilocon))
+- Applied fixes from StyleCI [\#365](https://github.com/overtrue/wechat/pull/365) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#364](https://github.com/overtrue/wechat/pull/364) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#363](https://github.com/overtrue/wechat/pull/363) ([overtrue](https://github.com/overtrue))
+- Update composer.json [\#361](https://github.com/overtrue/wechat/pull/361) ([jaychan](https://github.com/jaychan))
+- Applied fixes from StyleCI [\#355](https://github.com/overtrue/wechat/pull/355) ([overtrue](https://github.com/overtrue))
+- \[ci skip\]fix document typo [\#354](https://github.com/overtrue/wechat/pull/354) ([lichunqiang](https://github.com/lichunqiang))
+- 自定义Logger [\#353](https://github.com/overtrue/wechat/pull/353) ([lilocon](https://github.com/lilocon))
+- Update Refund.php [\#351](https://github.com/overtrue/wechat/pull/351) ([jaring](https://github.com/jaring))
+- Applied fixes from StyleCI [\#350](https://github.com/overtrue/wechat/pull/350) ([overtrue](https://github.com/overtrue))
+- OpenSSL bugfix. [\#349](https://github.com/overtrue/wechat/pull/349) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#347](https://github.com/overtrue/wechat/pull/347) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#346](https://github.com/overtrue/wechat/pull/346) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#345](https://github.com/overtrue/wechat/pull/345) ([overtrue](https://github.com/overtrue))
+- 添加代码提示 [\#343](https://github.com/overtrue/wechat/pull/343) ([lilocon](https://github.com/lilocon))
+- Applied fixes from StyleCI [\#340](https://github.com/overtrue/wechat/pull/340) ([overtrue](https://github.com/overtrue))
+- Fix bug: Payment::downloadBill\(\) response error. [\#339](https://github.com/overtrue/wechat/pull/339) ([overtrue](https://github.com/overtrue))
+- change get\_client\_ip to get\_server\_ip [\#335](https://github.com/overtrue/wechat/pull/335) ([tianyong90](https://github.com/tianyong90))
+- Payment SSL. [\#334](https://github.com/overtrue/wechat/pull/334) ([overtrue](https://github.com/overtrue))
+- Add a helper to get correct client ip address. fixed \#316 [\#333](https://github.com/overtrue/wechat/pull/333) ([tianyong90](https://github.com/tianyong90))
+- Dependency Bugfix. overtrue/laravel-wechat\#24 [\#332](https://github.com/overtrue/wechat/pull/332) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#330](https://github.com/overtrue/wechat/pull/330) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#329](https://github.com/overtrue/wechat/pull/329) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#326](https://github.com/overtrue/wechat/pull/326) ([overtrue](https://github.com/overtrue))
+- Add order default notify\_url. [\#325](https://github.com/overtrue/wechat/pull/325) ([foreverglory](https://github.com/foreverglory))
+- Revert "Applied fixes from StyleCI" [\#323](https://github.com/overtrue/wechat/pull/323) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#322](https://github.com/overtrue/wechat/pull/322) ([overtrue](https://github.com/overtrue))
+- Develop [\#321](https://github.com/overtrue/wechat/pull/321) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#320](https://github.com/overtrue/wechat/pull/320) ([overtrue](https://github.com/overtrue))
+- 模板消息添加【 获取模板列表】和【 删除模板】接口 [\#318](https://github.com/overtrue/wechat/pull/318) ([forecho](https://github.com/forecho))
+- Applied fixes from StyleCI [\#314](https://github.com/overtrue/wechat/pull/314) ([overtrue](https://github.com/overtrue))
+- fix Temporary upload bug [\#313](https://github.com/overtrue/wechat/pull/313) ([mani95lisa](https://github.com/mani95lisa))
+- Applied fixes from StyleCI [\#312](https://github.com/overtrue/wechat/pull/312) ([overtrue](https://github.com/overtrue))
+- MerchantPay Class [\#311](https://github.com/overtrue/wechat/pull/311) ([ac1982](https://github.com/ac1982))
+- Applied fixes from StyleCI [\#309](https://github.com/overtrue/wechat/pull/309) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#308](https://github.com/overtrue/wechat/pull/308) ([overtrue](https://github.com/overtrue))
+- 删除裂变红包接口中的ip参数 [\#306](https://github.com/overtrue/wechat/pull/306) ([xjchengo](https://github.com/xjchengo))
+- fix code style and some spelling mistakes [\#303](https://github.com/overtrue/wechat/pull/303) ([jinchun](https://github.com/jinchun))
+- Merge Develop [\#302](https://github.com/overtrue/wechat/pull/302) ([overtrue](https://github.com/overtrue))
+- Add method for app payment [\#301](https://github.com/overtrue/wechat/pull/301) ([lichunqiang](https://github.com/lichunqiang))
+- Removed the return syntax [\#300](https://github.com/overtrue/wechat/pull/300) ([lichunqiang](https://github.com/lichunqiang))
+- add return tag [\#299](https://github.com/overtrue/wechat/pull/299) ([lichunqiang](https://github.com/lichunqiang))
+- Merge Develop [\#298](https://github.com/overtrue/wechat/pull/298) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#297](https://github.com/overtrue/wechat/pull/297) ([overtrue](https://github.com/overtrue))
+- \[ci skip\]Update .gitattributes [\#295](https://github.com/overtrue/wechat/pull/295) ([lichunqiang](https://github.com/lichunqiang))
+- Merge Develop [\#294](https://github.com/overtrue/wechat/pull/294) ([overtrue](https://github.com/overtrue))
+
+## [3.0.1](https://github.com/overtrue/wechat/tree/3.0.1) (2016-02-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/3.0...3.0.1)
+
+**Closed issues:**
+
+- composer 安装 3.0版本,报错如下: [\#291](https://github.com/overtrue/wechat/issues/291)
+- \[3.0\] 下载永久素材时,微信返回的Content-Type不正确,导致出错。 [\#290](https://github.com/overtrue/wechat/issues/290)
+- 挖个坑,自己跳 [\#147](https://github.com/overtrue/wechat/issues/147)
+
+**Merged pull requests:**
+
+- Applied fixes from StyleCI [\#293](https://github.com/overtrue/wechat/pull/293) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#292](https://github.com/overtrue/wechat/pull/292) ([overtrue](https://github.com/overtrue))
+
+## [3.0](https://github.com/overtrue/wechat/tree/3.0) (2016-02-17)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.1.0...3.0)
+
+**Implemented enhancements:**
+
+- MIME json 格式检查优化 [\#49](https://github.com/overtrue/wechat/issues/49)
+- 获取 refresh\_token,access\_token [\#43](https://github.com/overtrue/wechat/issues/43)
+- 关于API\_TOKEN\_REFRESH [\#20](https://github.com/overtrue/wechat/issues/20)
+
+**Closed issues:**
+
+- \[3.0\] 无法获取用户分组信息 [\#285](https://github.com/overtrue/wechat/issues/285)
+- 新的laravel 5.2 不能兼容了 [\#284](https://github.com/overtrue/wechat/issues/284)
+- \[3.0\]Message/Article类的$properties内的source\_url没有正常转换为content\_source\_url. [\#281](https://github.com/overtrue/wechat/issues/281)
+- 3.0删除个性菜单失败 [\#280](https://github.com/overtrue/wechat/issues/280)
+- 也许你该给一个代码贡献规范 [\#277](https://github.com/overtrue/wechat/issues/277)
+- 3.0网页授权时scope为snsapi\_base得不到openid [\#276](https://github.com/overtrue/wechat/issues/276)
+- wechat3.0中 有2个地方的js调用参数不一样,超哥没有提供 [\#272](https://github.com/overtrue/wechat/issues/272)
+- 我想知道2.X和3.0有什么大的区别! [\#270](https://github.com/overtrue/wechat/issues/270)
+- 2.1: Link 消息类型没有实现 [\#269](https://github.com/overtrue/wechat/issues/269)
+- 关于模板消息换行的问题 [\#266](https://github.com/overtrue/wechat/issues/266)
+- easywechat Invalid request [\#265](https://github.com/overtrue/wechat/issues/265)
+- 40029不合法的oauth\_code [\#264](https://github.com/overtrue/wechat/issues/264)
+- 下载素材的一个小问题 [\#263](https://github.com/overtrue/wechat/issues/263)
+- \[2.1\] 微信自定义菜单结构变更导致`Menu::get\(\)` 无法读取个性化菜单 [\#262](https://github.com/overtrue/wechat/issues/262)
+- payment中是不是不包含H5和JS的生成配置文件的方法了? [\#261](https://github.com/overtrue/wechat/issues/261)
+- payment下prepare方法bug [\#260](https://github.com/overtrue/wechat/issues/260)
+- UserServiceProvider中似乎忘记注册user.group了 [\#256](https://github.com/overtrue/wechat/issues/256)
+- 2.1.X版媒体下载没有扩展名 [\#252](https://github.com/overtrue/wechat/issues/252)
+- 为什么所有的子模块在自己的库都是develop分支 [\#247](https://github.com/overtrue/wechat/issues/247)
+- 网页授权使用跳转的bug [\#246](https://github.com/overtrue/wechat/issues/246)
+- typo of variable [\#245](https://github.com/overtrue/wechat/issues/245)
+- The implementation class of ServerServiceProvider missing an important [\#244](https://github.com/overtrue/wechat/issues/244)
+- \[3.0\]\[payment\] 两个可能的bug [\#235](https://github.com/overtrue/wechat/issues/235)
+- 发送多图文 [\#233](https://github.com/overtrue/wechat/issues/233)
+- 自定义菜单返回应该把个性化自定义菜单也一起返回 [\#231](https://github.com/overtrue/wechat/issues/231)
+- 发送模板消息 CRUL 错误 [\#223](https://github.com/overtrue/wechat/issues/223)
+- 客服接口暂时测到有3个bug,麻烦修复 [\#222](https://github.com/overtrue/wechat/issues/222)
+- JSSDK access\_token missing [\#211](https://github.com/overtrue/wechat/issues/211)
+- Js.php/ticket [\#210](https://github.com/overtrue/wechat/issues/210)
+- 微信支付里有一个收货地址共享 ,超哥你这里没有,可以加一下不? [\#204](https://github.com/overtrue/wechat/issues/204)
+- 小问题 [\#203](https://github.com/overtrue/wechat/issues/203)
+- 网页授权 跳转 [\#202](https://github.com/overtrue/wechat/issues/202)
+- access token 重复添加的问题 [\#201](https://github.com/overtrue/wechat/issues/201)
+- authorize snsapi\_base 下可以获取unionid [\#198](https://github.com/overtrue/wechat/issues/198)
+- 网页授权 [\#189](https://github.com/overtrue/wechat/issues/189)
+- 一点建议 [\#188](https://github.com/overtrue/wechat/issues/188)
+- 接口更新-新增临时素材接口变动 [\#186](https://github.com/overtrue/wechat/issues/186)
+- 接入多个公众号不用id [\#185](https://github.com/overtrue/wechat/issues/185)
+- \[Insight\] Files should not be executable [\#184](https://github.com/overtrue/wechat/issues/184)
+- 建议不要写死Http [\#183](https://github.com/overtrue/wechat/issues/183)
+- laravel4.2安装不成功 [\#182](https://github.com/overtrue/wechat/issues/182)
+- 是否支持laravel4.2 [\#181](https://github.com/overtrue/wechat/issues/181)
+- 微信出个性化菜单了,希望支持 [\#180](https://github.com/overtrue/wechat/issues/180)
+- 3.0 composer依赖Symfony2.7。能不能支持Symfony3.0? [\#179](https://github.com/overtrue/wechat/issues/179)
+- 发送链接类消息错误 [\#175](https://github.com/overtrue/wechat/issues/175)
+- Throw Exception的时候 Intel server status 设置为200是不是好一些 [\#174](https://github.com/overtrue/wechat/issues/174)
+- 生成临时二维码时,返回EventKey不是传递的值 [\#173](https://github.com/overtrue/wechat/issues/173)
+- 关于素材获取的一个建议 [\#172](https://github.com/overtrue/wechat/issues/172)
+- 能否增加微信APP支付相关方法 [\#171](https://github.com/overtrue/wechat/issues/171)
+- 微信回调URL回调不到 [\#170](https://github.com/overtrue/wechat/issues/170)
+- 素材管理添加永久素材返回JSON/XML内容错误 [\#169](https://github.com/overtrue/wechat/issues/169)
+- \[消息的使用\] 中 \[上传素材文件\] 的文档示例貌似有误 [\#168](https://github.com/overtrue/wechat/issues/168)
+- 素材管理里的download方法不是很符合sdk一站式的解决. [\#165](https://github.com/overtrue/wechat/issues/165)
+- \[Wechat\]不合法的oauth\_code' in /src/Wechat/Http.php:124 [\#164](https://github.com/overtrue/wechat/issues/164)
+- AccessToken Expired Error Code [\#163](https://github.com/overtrue/wechat/issues/163)
+- 素材管理接口出错 [\#162](https://github.com/overtrue/wechat/issues/162)
+- 两处代码php5.4才能运行 [\#158](https://github.com/overtrue/wechat/issues/158)
+- extension is null when calling `download video` in wechat.media [\#157](https://github.com/overtrue/wechat/issues/157)
+- Payment/UnifiedOrder does not support serialize or create by array [\#155](https://github.com/overtrue/wechat/issues/155)
+- 没有找到"微信支付-\>查询订单"相关功能 [\#150](https://github.com/overtrue/wechat/issues/150)
+- 请教,Cache::setter中your\_custom\_set\_cache怎么使用 [\#149](https://github.com/overtrue/wechat/issues/149)
+- 发生异常时, 希望能把发送和接收的原始数据记录下来. [\#148](https://github.com/overtrue/wechat/issues/148)
+- 发送红包,证书错误 [\#144](https://github.com/overtrue/wechat/issues/144)
+- 发视频消息总返回 -1 [\#143](https://github.com/overtrue/wechat/issues/143)
+- 关于PHP版本 [\#141](https://github.com/overtrue/wechat/issues/141)
+- Server消息回复必须以事件方式吗? [\#140](https://github.com/overtrue/wechat/issues/140)
+- 微信支付相关文档细化 [\#138](https://github.com/overtrue/wechat/issues/138)
+- 好奇地问个问题,这项目的测试用例放在哪? [\#135](https://github.com/overtrue/wechat/issues/135)
+- 试了两次,真的不会用 [\#134](https://github.com/overtrue/wechat/issues/134)
+- 不知道这算不算是个BUG [\#133](https://github.com/overtrue/wechat/issues/133)
+- 微信小店 [\#130](https://github.com/overtrue/wechat/issues/130)
+- 多次遇到 accesstoken 无效的问题 [\#129](https://github.com/overtrue/wechat/issues/129)
+- MCH\_KEY 微信支付 [\#128](https://github.com/overtrue/wechat/issues/128)
+- 使用flightphp框架,验证URL的时候,在Apache下接入成功,在Nginx接入失败 [\#126](https://github.com/overtrue/wechat/issues/126)
+- 好东西!可惜没有我需要的微信红包 [\#125](https://github.com/overtrue/wechat/issues/125)
+- Cache存储部件可定制 [\#120](https://github.com/overtrue/wechat/issues/120)
+- 关于Bag [\#119](https://github.com/overtrue/wechat/issues/119)
+- 将代码部署到负载均衡上如何管理access token [\#118](https://github.com/overtrue/wechat/issues/118)
+- 消息接受和回复时,如果不对消息做回复,该如何做? [\#117](https://github.com/overtrue/wechat/issues/117)
+- 请教一个问题 [\#116](https://github.com/overtrue/wechat/issues/116)
+- 关于 Cache [\#115](https://github.com/overtrue/wechat/issues/115)
+- 如何才能获取普通的access\_token [\#113](https://github.com/overtrue/wechat/issues/113)
+- $HTTP\_RAW\_POST\_DATA DEPRECATED [\#111](https://github.com/overtrue/wechat/issues/111)
+- App支付缺少错误码 [\#109](https://github.com/overtrue/wechat/issues/109)
+- 当用户信息有 " 字符时系统出错 \(用户与用户组管理接口\) [\#107](https://github.com/overtrue/wechat/issues/107)
+- 提示错误 [\#106](https://github.com/overtrue/wechat/issues/106)
+- 使用企业号的时候 接入失败啊,在验证url的时候 [\#104](https://github.com/overtrue/wechat/issues/104)
+- 支付签名错误 [\#101](https://github.com/overtrue/wechat/issues/101)
+- 微信支付.$payment-\>getConfig\(\)调用时候\[Wechat\]系统繁忙,此时请开发者稍候再试. [\#96](https://github.com/overtrue/wechat/issues/96)
+- wechat/src/Wechat/Payment/UnifiedOrder.php 小问题 [\#94](https://github.com/overtrue/wechat/issues/94)
+- 请教laravel中如何在微信支付中 catch UnifiedOrder 抛出的异常? [\#93](https://github.com/overtrue/wechat/issues/93)
+- 是否可以增加一个第三方接口融合功能 [\#91](https://github.com/overtrue/wechat/issues/91)
+- 订单查询 [\#90](https://github.com/overtrue/wechat/issues/90)
+- 如何不下载图片,通过mediaId获取图片存储的URL [\#89](https://github.com/overtrue/wechat/issues/89)
+- 'Undefined index: HTTP\_HOST' [\#88](https://github.com/overtrue/wechat/issues/88)
+- Undefined index: HTTP\_HOST [\#87](https://github.com/overtrue/wechat/issues/87)
+- 不能上传gif格式的图片素材 [\#84](https://github.com/overtrue/wechat/issues/84)
+- OAuth重构 [\#74](https://github.com/overtrue/wechat/issues/74)
+- \[3.0\] Tasks [\#50](https://github.com/overtrue/wechat/issues/50)
+- appId 和 appSecret不要作为各个类的构造参数 [\#114](https://github.com/overtrue/wechat/issues/114)
+- 增加debug相关的选项 [\#112](https://github.com/overtrue/wechat/issues/112)
+- 好像没有获取自动回复数据接口 [\#108](https://github.com/overtrue/wechat/issues/108)
+- js端查看微信卡券接口 chooseCard [\#79](https://github.com/overtrue/wechat/issues/79)
+- 【新增】支付 [\#78](https://github.com/overtrue/wechat/issues/78)
+- 模板消息重构 [\#75](https://github.com/overtrue/wechat/issues/75)
+- 素材下载自动识别MIME生成后缀 [\#54](https://github.com/overtrue/wechat/issues/54)
+- \[建议\] 深度结合微信多图文与素材管理 [\#46](https://github.com/overtrue/wechat/issues/46)
+- 群发功能 [\#18](https://github.com/overtrue/wechat/issues/18)
+
+**Merged pull requests:**
+
+- 3.0 [\#289](https://github.com/overtrue/wechat/pull/289) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#288](https://github.com/overtrue/wechat/pull/288) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#287](https://github.com/overtrue/wechat/pull/287) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#286](https://github.com/overtrue/wechat/pull/286) ([overtrue](https://github.com/overtrue))
+- Fix bug in batchGet method. [\#283](https://github.com/overtrue/wechat/pull/283) ([tianyong90](https://github.com/tianyong90))
+- Typo. [\#279](https://github.com/overtrue/wechat/pull/279) ([overtrue](https://github.com/overtrue))
+- Add contribution guide. resolves \#277 [\#278](https://github.com/overtrue/wechat/pull/278) ([overtrue](https://github.com/overtrue))
+- Develop [\#274](https://github.com/overtrue/wechat/pull/274) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#273](https://github.com/overtrue/wechat/pull/273) ([overtrue](https://github.com/overtrue))
+- Develop [\#271](https://github.com/overtrue/wechat/pull/271) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#268](https://github.com/overtrue/wechat/pull/268) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#267](https://github.com/overtrue/wechat/pull/267) ([overtrue](https://github.com/overtrue))
+- Update QRCode.php [\#258](https://github.com/overtrue/wechat/pull/258) ([webshiyue](https://github.com/webshiyue))
+- Add tests for LuckyMoney. [\#255](https://github.com/overtrue/wechat/pull/255) ([tianyong90](https://github.com/tianyong90))
+- CS. [\#254](https://github.com/overtrue/wechat/pull/254) ([overtrue](https://github.com/overtrue))
+- Scrutinizer Auto-Fixes [\#253](https://github.com/overtrue/wechat/pull/253) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Applied fixes from StyleCI [\#251](https://github.com/overtrue/wechat/pull/251) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#250](https://github.com/overtrue/wechat/pull/250) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#249](https://github.com/overtrue/wechat/pull/249) ([overtrue](https://github.com/overtrue))
+- Merge Develop [\#248](https://github.com/overtrue/wechat/pull/248) ([overtrue](https://github.com/overtrue))
+- Merge from Develop [\#243](https://github.com/overtrue/wechat/pull/243) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#242](https://github.com/overtrue/wechat/pull/242) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#241](https://github.com/overtrue/wechat/pull/241) ([overtrue](https://github.com/overtrue))
+- Add Luckymoney. [\#240](https://github.com/overtrue/wechat/pull/240) ([tianyong90](https://github.com/tianyong90))
+- Applied fixes from StyleCI [\#237](https://github.com/overtrue/wechat/pull/237) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#234](https://github.com/overtrue/wechat/pull/234) ([overtrue](https://github.com/overtrue))
+- Multiple News Items Support [\#230](https://github.com/overtrue/wechat/pull/230) ([fanglinks](https://github.com/fanglinks))
+- Applied fixes from StyleCI [\#221](https://github.com/overtrue/wechat/pull/221) ([overtrue](https://github.com/overtrue))
+- \[3.0\]\[Bugfix\]发送图文消息缺少type [\#217](https://github.com/overtrue/wechat/pull/217) ([sunbiao0526](https://github.com/sunbiao0526))
+- fix Js.php 获取自定义cache对象 [\#215](https://github.com/overtrue/wechat/pull/215) ([sunbiao0526](https://github.com/sunbiao0526))
+- Applied fixes from StyleCI [\#197](https://github.com/overtrue/wechat/pull/197) ([overtrue](https://github.com/overtrue))
+- Add alias [\#196](https://github.com/overtrue/wechat/pull/196) ([ruchengtang](https://github.com/ruchengtang))
+- Applied fixes from StyleCI [\#195](https://github.com/overtrue/wechat/pull/195) ([overtrue](https://github.com/overtrue))
+- Applied fixes from StyleCI [\#194](https://github.com/overtrue/wechat/pull/194) ([overtrue](https://github.com/overtrue))
+- Add Broadcast. [\#193](https://github.com/overtrue/wechat/pull/193) ([ruchengtang](https://github.com/ruchengtang))
+- 微信红包类优化 [\#190](https://github.com/overtrue/wechat/pull/190) ([tianyong90](https://github.com/tianyong90))
+- Update ServerServiceProvider.php [\#187](https://github.com/overtrue/wechat/pull/187) ([ghost](https://github.com/ghost))
+- Update README\_EN.md [\#178](https://github.com/overtrue/wechat/pull/178) ([spekulatius](https://github.com/spekulatius))
+- 添加群发消息文档 [\#177](https://github.com/overtrue/wechat/pull/177) ([ruchengtang](https://github.com/ruchengtang))
+- 群发消息 [\#176](https://github.com/overtrue/wechat/pull/176) ([ruchengtang](https://github.com/ruchengtang))
+- Master [\#167](https://github.com/overtrue/wechat/pull/167) ([xiaohome](https://github.com/xiaohome))
+- 微信小店 [\#166](https://github.com/overtrue/wechat/pull/166) ([xiaohome](https://github.com/xiaohome))
+- 红包类更新 [\#161](https://github.com/overtrue/wechat/pull/161) ([overtrue](https://github.com/overtrue))
+- 加入摇一摇红包类,红包类提升至Overtrue命名空间 [\#160](https://github.com/overtrue/wechat/pull/160) ([tianyong90](https://github.com/tianyong90))
+- 2.1 [\#159](https://github.com/overtrue/wechat/pull/159) ([overtrue](https://github.com/overtrue))
+- Update QRCode.php [\#156](https://github.com/overtrue/wechat/pull/156) ([ruchengtang](https://github.com/ruchengtang))
+- 修复使用!=,来判断0 != null 的时候的一个bug [\#154](https://github.com/overtrue/wechat/pull/154) ([Liv1020](https://github.com/Liv1020))
+- 调整多客服类删除客服方法 [\#151](https://github.com/overtrue/wechat/pull/151) ([tianyong90](https://github.com/tianyong90))
+- 修复个bug [\#146](https://github.com/overtrue/wechat/pull/146) ([xiaohome](https://github.com/xiaohome))
+- Update README.md [\#142](https://github.com/overtrue/wechat/pull/142) ([parkshinhye](https://github.com/parkshinhye))
+- Fix code style to PSR-2 [\#139](https://github.com/overtrue/wechat/pull/139) ([tianyong90](https://github.com/tianyong90))
+- 加入红包工具类,支持现金和裂变红包的发送及查询 [\#137](https://github.com/overtrue/wechat/pull/137) ([tianyong90](https://github.com/tianyong90))
+- 卡券类批量获取卡券ID方法支持仅获取指定状态卡券 [\#132](https://github.com/overtrue/wechat/pull/132) ([tianyong90](https://github.com/tianyong90))
+- 添加客服 卡券回复!!! [\#124](https://github.com/overtrue/wechat/pull/124) ([parkshinhye](https://github.com/parkshinhye))
+- 调整退款类中一处异常抛出逻辑并修正单词拼写错误 [\#122](https://github.com/overtrue/wechat/pull/122) ([tianyong90](https://github.com/tianyong90))
+- 加入创建卡券货架接口 [\#121](https://github.com/overtrue/wechat/pull/121) ([tianyong90](https://github.com/tianyong90))
+- 增加退款类 [\#105](https://github.com/overtrue/wechat/pull/105) ([jaring](https://github.com/jaring))
+- 增加获取用户已领取卡券方法 [\#103](https://github.com/overtrue/wechat/pull/103) ([tenstone](https://github.com/tenstone))
+- Scrutinizer Auto-Fixes [\#100](https://github.com/overtrue/wechat/pull/100) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- 修正二维码类中生成卡券二维码方法 [\#99](https://github.com/overtrue/wechat/pull/99) ([tianyong90](https://github.com/tianyong90))
+- 卡券接口加入添加测试白名单方法 [\#98](https://github.com/overtrue/wechat/pull/98) ([tianyong90](https://github.com/tianyong90))
+- 依样画葫芦写了一个查询订单,更改了UnifiedOrder中Http初始化 [\#95](https://github.com/overtrue/wechat/pull/95) ([jaring](https://github.com/jaring))
+- accessToken根据appId变化 [\#92](https://github.com/overtrue/wechat/pull/92) ([keepeye](https://github.com/keepeye))
+- Fix payment sign bug. [\#82](https://github.com/overtrue/wechat/pull/82) ([0i](https://github.com/0i))
+- \[wiki\] wechat payment [\#81](https://github.com/overtrue/wechat/pull/81) ([0i](https://github.com/0i))
+
+## [2.1.0](https://github.com/overtrue/wechat/tree/2.1.0) (2015-08-18)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.35...2.1.0)
+
+**Merged pull requests:**
+
+- Wechat Payment [\#80](https://github.com/overtrue/wechat/pull/80) ([0i](https://github.com/0i))
+
+## [2.0.35](https://github.com/overtrue/wechat/tree/2.0.35) (2015-08-11)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.1...2.0.35)
+
+**Implemented enhancements:**
+
+- Overtrue\Wechat\Http识别JSON的问题 [\#47](https://github.com/overtrue/wechat/issues/47)
+
+**Fixed bugs:**
+
+- 模板消息简单格式无效 [\#34](https://github.com/overtrue/wechat/issues/34)
+
+**Closed issues:**
+
+- $data是数组,title输出不了内容 [\#73](https://github.com/overtrue/wechat/issues/73)
+- 回调是如何传递外部参数的? [\#72](https://github.com/overtrue/wechat/issues/72)
+- 【建议】可以添加微信js的功能吗? [\#71](https://github.com/overtrue/wechat/issues/71)
+- Message::make\('link'\) 无效 [\#70](https://github.com/overtrue/wechat/issues/70)
+- 监听消息 返回Bad Request [\#65](https://github.com/overtrue/wechat/issues/65)
+- 微信素材管理小改版,求跟上~ [\#64](https://github.com/overtrue/wechat/issues/64)
+- 在新浪SAE平台上的部署问题 [\#63](https://github.com/overtrue/wechat/issues/63)
+- $xmlInput = file\_get\_contents\('php://input'\);貌似在某些版本的PHP有问题还是怎的 [\#57](https://github.com/overtrue/wechat/issues/57)
+- 卡券的 attachExtension 方法 [\#56](https://github.com/overtrue/wechat/issues/56)
+- 网页授权$auth-\>authorize\(\) 后还需要保存access\_token吗? [\#53](https://github.com/overtrue/wechat/issues/53)
+- php 5.6版本下出现错误(5.6以下版本正常) [\#51](https://github.com/overtrue/wechat/issues/51)
+- 消息发送后服务器无法正确返回响应 [\#48](https://github.com/overtrue/wechat/issues/48)
+- token验证失败 [\#45](https://github.com/overtrue/wechat/issues/45)
+- 微信关注自动回复问题 [\#44](https://github.com/overtrue/wechat/issues/44)
+- js sdk config 建议增加 beta 字段 [\#35](https://github.com/overtrue/wechat/issues/35)
+- 关于Util\HTTP::encode\(\)中的urlencode\(\)/urldecode\(\)成组操作的疑问 [\#31](https://github.com/overtrue/wechat/issues/31)
+- Media::updateNews\(\) 方法与微信API不一致 [\#29](https://github.com/overtrue/wechat/issues/29)
+- 希望能有一个ThinkPHP的使用示例 [\#28](https://github.com/overtrue/wechat/issues/28)
+- 事件消息 [\#22](https://github.com/overtrue/wechat/issues/22)
+- 模板消息notice [\#21](https://github.com/overtrue/wechat/issues/21)
+- 关于获取(接收)用户发送消息 [\#19](https://github.com/overtrue/wechat/issues/19)
+- 微信公众号绑定的一点问题,请教。 [\#16](https://github.com/overtrue/wechat/issues/16)
+- 获取素材列表错误 [\#15](https://github.com/overtrue/wechat/issues/15)
+
+**Merged pull requests:**
+
+- Scrutinizer Auto-Fixes [\#69](https://github.com/overtrue/wechat/pull/69) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Scrutinizer Auto-Fixes [\#68](https://github.com/overtrue/wechat/pull/68) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Scrutinizer Auto-Fixes [\#67](https://github.com/overtrue/wechat/pull/67) ([scrutinizer-auto-fixer](https://github.com/scrutinizer-auto-fixer))
+- Fixed StyleCI config [\#66](https://github.com/overtrue/wechat/pull/66) ([GrahamCampbell](https://github.com/GrahamCampbell))
+- 洁癖爆发了。。。 [\#62](https://github.com/overtrue/wechat/pull/62) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- fix: js getUrl use Url::current\(\) [\#61](https://github.com/overtrue/wechat/pull/61) ([wdjwxh](https://github.com/wdjwxh))
+- bug-fix: add x-forwarded-host for Url::current [\#60](https://github.com/overtrue/wechat/pull/60) ([wdjwxh](https://github.com/wdjwxh))
+- Fix request method for User::batchGet\(\), should be POST with JSON. [\#59](https://github.com/overtrue/wechat/pull/59) ([acgrid](https://github.com/acgrid))
+- optimize some code [\#58](https://github.com/overtrue/wechat/pull/58) ([tabalt](https://github.com/tabalt))
+- 增加使用media id发送图文消息的功能 [\#52](https://github.com/overtrue/wechat/pull/52) ([zengohm](https://github.com/zengohm))
+- fix Staff::delete, let it works [\#42](https://github.com/overtrue/wechat/pull/42) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- 支持自定义菜单类型:下发消息media\_id、跳转图文消息view\_limited [\#40](https://github.com/overtrue/wechat/pull/40) ([acgrid](https://github.com/acgrid))
+- docline comments & fix AccessToken parameter typos [\#39](https://github.com/overtrue/wechat/pull/39) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- Merge from master [\#38](https://github.com/overtrue/wechat/pull/38) ([overtrue](https://github.com/overtrue))
+- 客服接口Bugfix [\#37](https://github.com/overtrue/wechat/pull/37) ([overtrue](https://github.com/overtrue))
+- fix Staff and AccessToken typos [\#36](https://github.com/overtrue/wechat/pull/36) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- Update QRCode.php [\#33](https://github.com/overtrue/wechat/pull/33) ([refear99](https://github.com/refear99))
+- English Readme [\#32](https://github.com/overtrue/wechat/pull/32) ([hareluya](https://github.com/hareluya))
+- 更新图文消息方法Media::updateNews\(\)与微信API不一致 [\#30](https://github.com/overtrue/wechat/pull/30) ([acgrid](https://github.com/acgrid))
+- 代码之美在于不断修正 :\) [\#27](https://github.com/overtrue/wechat/pull/27) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- the json\_encode $depth parameter was added@5.5.0 [\#26](https://github.com/overtrue/wechat/pull/26) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- fix \#4 for PHP5.3 [\#25](https://github.com/overtrue/wechat/pull/25) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- fix \#4 for PHP5.3 [\#23](https://github.com/overtrue/wechat/pull/23) ([TheNorthMemory](https://github.com/TheNorthMemory))
+- Update QRCode.php [\#17](https://github.com/overtrue/wechat/pull/17) ([gundanx10](https://github.com/gundanx10))
+
+## [2.0.1](https://github.com/overtrue/wechat/tree/2.0.1) (2015-05-08)
+[Full Changelog](https://github.com/overtrue/wechat/compare/2.0.0...2.0.1)
+
+**Closed issues:**
+
+- 2.0版本使用问题 [\#14](https://github.com/overtrue/wechat/issues/14)
+
+## [2.0.0](https://github.com/overtrue/wechat/tree/2.0.0) (2015-05-07)
+[Full Changelog](https://github.com/overtrue/wechat/compare/1.0.1...2.0.0)
+
+**Closed issues:**
+
+- 素材管理 -- 部分图片下载失败 [\#13](https://github.com/overtrue/wechat/issues/13)
+- 素材管理 -- 图片下载失败 [\#12](https://github.com/overtrue/wechat/issues/12)
+- 请问这样判断Mcrypt到底准不准? [\#11](https://github.com/overtrue/wechat/issues/11)
+- 好奇怪啊,开发者中心的服务器配置已经提交并验证成功了,可是message不起作用 [\#10](https://github.com/overtrue/wechat/issues/10)
+- 网页授权一刷新页面就出现40029 不合法的oauth\_code [\#8](https://github.com/overtrue/wechat/issues/8)
+- mcrypt\_module\_open error [\#7](https://github.com/overtrue/wechat/issues/7)
+- composer update 之后报错 [\#6](https://github.com/overtrue/wechat/issues/6)
+- 今天开始,授权时候一直报40029,invalid code的错误 [\#5](https://github.com/overtrue/wechat/issues/5)
+- Using $this when not in object context [\#4](https://github.com/overtrue/wechat/issues/4)
+- 监听事件时不区分 $target(监听所有event和message) [\#3](https://github.com/overtrue/wechat/issues/3)
+- Does this support Oauth already? [\#1](https://github.com/overtrue/wechat/issues/1)
+
+**Merged pull requests:**
+
+- Fix wiki url error [\#9](https://github.com/overtrue/wechat/pull/9) ([sinoon](https://github.com/sinoon))
+- Update Bag.php [\#2](https://github.com/overtrue/wechat/pull/2) ([zerozh](https://github.com/zerozh))
+
+## [1.0.1](https://github.com/overtrue/wechat/tree/1.0.1) (2015-03-19)
+[Full Changelog](https://github.com/overtrue/wechat/compare/1.0...1.0.1)
+
+## [1.0](https://github.com/overtrue/wechat/tree/1.0) (2015-03-13)
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
\ No newline at end of file
diff --git a/vendor/overtrue/wechat/CONTRIBUTING.md b/vendor/overtrue/wechat/CONTRIBUTING.md
new file mode 100644
index 0000000..134204f
--- /dev/null
+++ b/vendor/overtrue/wechat/CONTRIBUTING.md
@@ -0,0 +1,67 @@
+# Contribute
+
+## Introduction
+
+First, thank you for considering contributing to wechat! It's people like you that make the open source community such a great community! 😊
+
+We welcome any type of contribution, not only code. You can help with
+- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
+- **Marketing**: writing blog posts, howto's, printing stickers, ...
+- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
+- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
+- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat).
+
+## Your First Contribution
+
+Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
+
+## Submitting code
+
+Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
+
+## Code review process
+
+The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
+It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
+
+## Financial contributions
+
+We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat).
+Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
+
+## Questions
+
+If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
+You can also reach us at hello@wechat.opencollective.com.
+
+## Credits
+
+### Contributors
+
+Thank you to all the people who have already contributed to wechat!
+
+
+
+### Backers
+
+Thank you to all our backers! [[Become a backer](https://opencollective.com/wechat#backer)]
+
+
+
+
+### Sponsors
+
+Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/wechat#sponsor))
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/vendor/overtrue/wechat/LICENSE b/vendor/overtrue/wechat/LICENSE
index 18e1664..f80ba02 100644
--- a/vendor/overtrue/wechat/LICENSE
+++ b/vendor/overtrue/wechat/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 overtrue
+Copyright (c) overtrue
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/overtrue/wechat/README.md b/vendor/overtrue/wechat/README.md
index 189f034..655787a 100644
--- a/vendor/overtrue/wechat/README.md
+++ b/vendor/overtrue/wechat/README.md
@@ -1,64 +1,37 @@
-
-
-
-
+
-📦 It is probably the best SDK in the world for developing Wechat App.
+📦 一个 PHP 微信开发 SDK。
-
-
-
-
-
-
-
-
-
-
+[](https://github.com/w7corp/easywechat/actions)
+[](https://github.com/w7corp/easywechat/actions)
+[](https://packagist.org/packages/w7corp/easywechat)
+[](https://packagist.org/packages/w7corp/easywechat)
+[](https://packagist.org/packages/w7corp/easywechat)
+[](https://packagist.org/packages/w7corp/easywechat)
+[](https://huntr.dev)
-
+> 📣 **公告**
+>
+> 为了更好的推进项目发展,保障项目更新迭代速度,EasyWeChat 正式并入微擎旗下,加上微擎团队的助力,将会为大家提供更强大更稳固更多元化的开源项目。
+>
+> - 微擎与 EasyWeChat 结合,基于微擎技术资源方面的优势,将积极发展 EasyWeChat 的开源社区,将为 EasyWeChat 开源项目注入巨大活力。
+> - EasyWeChat 原作者 overtrue 将继续担任开源项目的核心开发者,继续参与项目的发展规划,共同打造更强大的开源生态社区。
+> - 项目从 6.0 版本开始将修改包名为 `w7corp/easywechat`,5.x 及以下版本不受影响。
-
- Special thanks to the generous sponsorship by:
-
-
-
-
-
-
-
-
-
+> 🚨 注意:请 PR 时往 5.x 提交,感谢您的贡献!
-
-
-
-
-关注我的公众号我们一起聊聊代码怎么样?
-
-## Feature
-
- - 命名不那么乱七八糟;
- - 隐藏开发者不需要关注的细节;
- - 方法使用更优雅,不必再去研究那些奇怪的的方法名或者类名是做啥用的;
- - 自定义缓存方式;
- - 符合 [PSR](https://github.com/php-fig/fig-standards) 标准,你可以各种方便的与你的框架集成;
- - 高度抽象的消息类,免去各种拼json与xml的痛苦;
- - 详细 Debug 日志,一切交互都一目了然;
## Requirement
-1. PHP >= 5.5.9
-2. **[composer](https://getcomposer.org/)**
+1. PHP >= 7.4
+2. **[Composer](https://getcomposer.org/)**
3. openssl 拓展
4. fileinfo 拓展(素材管理模块需要用到)
-> SDK 对所使用的框架并无特别要求
-
## Installation
```shell
-composer require "overtrue/wechat:~3.1" -vvv
+$ composer require "overtrue/wechat:^5.0" -vvv
```
## Usage
@@ -68,12 +41,11 @@ composer require "overtrue/wechat:~3.1" -vvv
```php
true,
- 'app_id' => 'wx3cf0f39249eb0e60',
- 'secret' => 'f1c242f4f28f735d4687abb469072a29',
+ 'app_id' => 'wx3cf0f39249eb0exxx',
+ 'secret' => 'f1c242f4f28f735d4687abb469072xxx',
'token' => 'easywechat',
'log' => [
'level' => 'debug',
@@ -82,13 +54,13 @@ $options = [
// ...
];
-$app = new Application($options);
+$app = Factory::officialAccount($options);
$server = $app->server;
$user = $app->user;
-$server->setMessageHandler(function($message) use ($user) {
- $fromUser = $user->get($message->FromUserName);
+$server->push(function($message) use ($user) {
+ $fromUser = $user->get($message['FromUserName']);
return "{$fromUser->nickname} 您好!欢迎关注 overtrue!";
});
@@ -96,25 +68,32 @@ $server->setMessageHandler(function($message) use ($user) {
$server->serve()->send();
```
-更多请参考[http://easywechat.org/](http://easywechat.org/)。
+更多请参考 [https://www.easywechat.com/](https://www.easywechat.com/)。
## Documentation
-- Homepage: http://easywechat.org
-- Forum: https://forum.easywechat.org
-- 微信公众平台文档: https://mp.weixin.qq.com/wiki
-- WeChat Official Documentation: http://admin.wechat.com/wiki
-
-> 强烈建议看懂微信文档后再来使用本 SDK。
+[官网](https://www.easywechat.com) · [教程](https://www.aliyundrive.com/s/6CwgtkiBqFV) · [讨论](https://github.com/w7corp/easywechat/discussions) · [微信公众平台](https://mp.weixin.qq.com/wiki) · [WeChat Official](http://admin.wechat.com/wiki)
## Integration
[Laravel 5 拓展包: overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat)
-## Contribution
+## Contributors
+
+This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+
+
+
+## PHP 扩展包开发
+
+> 想知道如何从零开始构建 PHP 扩展包?
+>
+> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
-[Contribution Guide](.github/CONTRIBUTING.md)
## License
MIT
+
+
+[](https://app.fossa.io/projects/git%2Bgithub.com%2Fovertrue%2Fwechat?ref=badge_large)
diff --git a/vendor/overtrue/wechat/SECURITY.md b/vendor/overtrue/wechat/SECURITY.md
new file mode 100644
index 0000000..c03d2cf
--- /dev/null
+++ b/vendor/overtrue/wechat/SECURITY.md
@@ -0,0 +1,21 @@
+# 更新策略
+
+从最初的稳定版本开始,EasyWeChat 的每个发行分支都会得到两年的全面支持。在这期间,被报告的错误和安全问题会被修复并发布。
+在这两年的积极支持期之后,每个分支再被支持一年,只针对关键安全问题。在此期间的发布是根据需要进行的:根据报告的数量,可能有多个版本发布,也可能没有。
+
+## 当前支持版本情况
+
+| 版本 | 发布日期 | 积极维护更新 | 安全性修复 |
+| ------- | ---------------- | -------------------- | ------------------------- |
+| 6.x | 2022.02.02 | 2024.02.02 | 2025.02.02 |
+| 5.x | 2020.07.27 | 2022.07.27 | 2023.07.27 |
+| 4.x | 2017.12.12 | 2019.12.12 | 2020.12.12 |
+| 3.x | 2016.02.19 | 2018.02.19 | 2019.02.19 |
+| 2.x | 2015.05.06 | 2017.05.06 | 2018.05.06 |
+| 1.x | 2015.02.13 | 2017.02.13 | 2017.02.13 |
+
+
+## 报告安全问题
+
+
+如果您发现 EasyWeChat 有安全漏洞,请发送电子邮件至 anzhengchao@gmail.com。所有的安全漏洞都会得到及时的解决。
diff --git a/vendor/overtrue/wechat/composer.json b/vendor/overtrue/wechat/composer.json
index ea4abd1..bc7b976 100644
--- a/vendor/overtrue/wechat/composer.json
+++ b/vendor/overtrue/wechat/composer.json
@@ -1,46 +1,89 @@
{
- "name": "overtrue/wechat",
- "description": "微信SDK",
- "keywords": [
- "wechat",
- "weixin",
- "weixin-sdk",
- "sdk"
- ],
- "license": "MIT",
- "authors": [
- {
- "name": "overtrue",
- "email": "anzhengchao@gmail.com"
- }
- ],
- "require": {
- "php":">=5.5.0",
- "ext-openssl": "*",
- "pimple/pimple": "~3.0",
- "monolog/monolog": "^1.17",
- "overtrue/socialite": "^1.0.25",
- "doctrine/cache": "1.4.*",
- "guzzlehttp/guzzle": "~6.2",
- "symfony/http-foundation": "~2.6|~2.7|~2.8|~3.0",
- "symfony/psr-http-message-bridge": "~0.3|^1.0"
- },
- "require-dev": {
- "phpunit/phpunit": "~4.0",
- "overtrue/phplint": "dev-master",
- "mockery/mockery": "^0.9.9"
- },
- "autoload": {
- "psr-4": {
- "EasyWeChat\\": "src/"
- },
- "files": [
- "src/Payment/helpers.php"
- ]
- },
- "autoload-dev": {
- "psr-4": {
- "EasyWeChat\\Tests\\": "tests/"
- }
+ "name": "overtrue/wechat",
+ "description": "微信SDK",
+ "keywords": [
+ "easywechat",
+ "wechat",
+ "weixin",
+ "weixin-sdk",
+ "sdk"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "overtrue",
+ "email": "anzhengchao@gmail.com"
}
+ ],
+ "require": {
+ "php": ">=7.4",
+ "ext-fileinfo": "*",
+ "ext-openssl": "*",
+ "ext-simplexml": "*",
+ "easywechat-composer/easywechat-composer": "^1.1",
+ "guzzlehttp/guzzle": "^6.2 || ^7.0",
+ "monolog/monolog": "^1.22 || ^2.0",
+ "overtrue/socialite": "^3.2 || ^4.0",
+ "pimple/pimple": "^3.0",
+ "psr/simple-cache": "^1.0||^2.0||^3.0",
+ "symfony/cache": "^3.3 || ^4.3 || ^5.0 || ^6.0",
+ "symfony/event-dispatcher": "^4.3 || ^5.0 || ^6.0",
+ "symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
+ "symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0",
+ "ext-libxml": "*"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.5.0",
+ "brainmaestro/composer-git-hooks": "^2.7",
+ "mikey179/vfsstream": "^1.6",
+ "mockery/mockery": "^1.2.3",
+ "phpstan/phpstan": "^0.12.0",
+ "phpunit/phpunit": "^9.3",
+ "dms/phpunit-arraysubset-asserts": "^0.2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "EasyWeChat\\": "src/"
+ },
+ "files": [
+ "src/Kernel/Support/Helpers.php",
+ "src/Kernel/Helpers.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "EasyWeChat\\Tests\\": "tests/"
+ }
+ },
+ "extra": {
+ "hooks": {
+ "pre-commit": [
+ "composer test",
+ "composer fix-style"
+ ],
+ "pre-push": [
+ "composer test",
+ "composer fix-style"
+ ]
+ }
+ },
+ "scripts": {
+ "post-update-cmd": [
+ "cghooks update"
+ ],
+ "post-merge": "composer install",
+ "post-install-cmd": [
+ "cghooks add --ignore-lock",
+ "cghooks update"
+ ],
+ "phpstan": "vendor/bin/phpstan analyse",
+ "check-style": "vendor/bin/php-cs-fixer fix --using-cache=no --diff --config=.php-cs-fixer.dist.php --dry-run --ansi",
+ "fix-style": "vendor/bin/php-cs-fixer fix --using-cache=no --config=.php-cs-fixer.dist.php --ansi",
+ "test": "vendor/bin/phpunit --colors=always --testdox"
+ },
+ "config": {
+ "allow-plugins": {
+ "easywechat-composer/easywechat-composer": true
+ }
+ }
}
diff --git a/vendor/overtrue/wechat/src/BasicService/Application.php b/vendor/overtrue/wechat/src/BasicService/Application.php
new file mode 100644
index 0000000..e4b1e8a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Application.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService;
+
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Application.
+ *
+ * @author overtrue
+ *
+ * @property \EasyWeChat\BasicService\Jssdk\Client $jssdk
+ * @property \EasyWeChat\BasicService\Media\Client $media
+ * @property \EasyWeChat\BasicService\QrCode\Client $qrcode
+ * @property \EasyWeChat\BasicService\Url\Client $url
+ * @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security
+ */
+class Application extends ServiceContainer
+{
+ /**
+ * @var array
+ */
+ protected $providers = [
+ Jssdk\ServiceProvider::class,
+ QrCode\ServiceProvider::class,
+ Media\ServiceProvider::class,
+ Url\ServiceProvider::class,
+ ContentSecurity\ServiceProvider::class,
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php
new file mode 100644
index 0000000..bad4236
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/Client.php
@@ -0,0 +1,116 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\ContentSecurity;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+ /**
+ * Text content security check.
+ *
+ * @param string $text
+ * @param array $extra
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function checkText(string $text, array $extra = [])
+ {
+ $params = array_merge(['content' => $text], $extra);
+
+ return $this->httpPostJson('wxa/msg_sec_check', $params);
+ }
+
+ /**
+ * Image security check.
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function checkImage(string $path)
+ {
+ return $this->httpUpload('wxa/img_sec_check', ['media' => $path]);
+ }
+
+ /**
+ * Media security check.
+ *
+ * @param string $mediaUrl
+ * @param int $mediaType
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function checkMediaAsync(string $mediaUrl, int $mediaType)
+ {
+ /*
+ * 1:音频;2:图片
+ */
+ $mediaTypes = [1, 2];
+
+ if (!in_array($mediaType, $mediaTypes, true)) {
+ throw new InvalidArgumentException('media type must be 1 or 2');
+ }
+
+ $params = [
+ 'media_url' => $mediaUrl,
+ 'media_type' => $mediaType,
+ ];
+
+ return $this->httpPostJson('wxa/media_check_async', $params);
+ }
+
+ /**
+ * Image security check async.
+ *
+ * @param string $mediaUrl
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function checkImageAsync(string $mediaUrl)
+ {
+ return $this->checkMediaAsync($mediaUrl, 2);
+ }
+
+ /**
+ * Audio security check async.
+ *
+ * @param string $mediaUrl
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function checkAudioAsync(string $mediaUrl)
+ {
+ return $this->checkMediaAsync($mediaUrl, 1);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php
new file mode 100644
index 0000000..3645de9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/ContentSecurity/ServiceProvider.php
@@ -0,0 +1,31 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\ContentSecurity;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['content_security'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php
new file mode 100644
index 0000000..0c0a2fa
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/Client.php
@@ -0,0 +1,223 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Jssdk;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ use InteractsWithCache;
+
+ /**
+ * @var string
+ */
+ protected $ticketEndpoint = 'cgi-bin/ticket/getticket';
+
+ /**
+ * Current URI.
+ *
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * Get config json for jsapi.
+ *
+ * @param array $jsApiList
+ * @param bool $debug
+ * @param bool $beta
+ * @param bool $json
+ * @param array $openTagList
+ * @param string|null $url
+ *
+ * @return array|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function buildConfig(array $jsApiList, bool $debug = false, bool $beta = false, bool $json = true, array $openTagList = [], string $url = null)
+ {
+ $config = array_merge(compact('debug', 'beta', 'jsApiList', 'openTagList'), $this->configSignature($url));
+
+ return $json ? json_encode($config) : $config;
+ }
+
+ /**
+ * Return jsapi config as a PHP array.
+ *
+ * @param array $apis
+ * @param bool $debug
+ * @param bool $beta
+ * @param array $openTagList
+ * @param string|null $url
+ *
+ * @return array|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getConfigArray(array $apis, bool $debug = false, bool $beta = false, array $openTagList = [], string $url = null)
+ {
+ return $this->buildConfig($apis, $debug, $beta, false, $openTagList, $url);
+ }
+
+ /**
+ * Get js ticket.
+ *
+ * @param bool $refresh
+ * @param string $type
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function getTicket(bool $refresh = false, string $type = 'jsapi'): array
+ {
+ $cacheKey = sprintf('easywechat.basic_service.jssdk.ticket.%s.%s', $type, $this->getAppId());
+
+ if (!$refresh && $this->getCache()->has($cacheKey)) {
+ return $this->getCache()->get($cacheKey);
+ }
+
+ /** @var array $result */
+ $result = $this->castResponseToType(
+ $this->requestRaw($this->ticketEndpoint, 'GET', ['query' => ['type' => $type]]),
+ 'array'
+ );
+
+ $this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500);
+
+ if (!$this->getCache()->has($cacheKey)) {
+ throw new RuntimeException('Failed to cache jssdk ticket.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * Build signature.
+ *
+ * @param string|null $url
+ * @param string|null $nonce
+ * @param null $timestamp
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ protected function configSignature(string $url = null, string $nonce = null, $timestamp = null): array
+ {
+ $url = $url ?: $this->getUrl();
+ $nonce = $nonce ?: Support\Str::quickRandom(10);
+ $timestamp = $timestamp ?: time();
+
+ return [
+ 'appId' => $this->getAppId(),
+ 'nonceStr' => $nonce,
+ 'timestamp' => $timestamp,
+ 'url' => $url,
+ 'signature' => $this->getTicketSignature($this->getTicket()['ticket'], $nonce, $timestamp, $url),
+ ];
+ }
+
+ /**
+ * Sign the params.
+ *
+ * @param string $ticket
+ * @param string $nonce
+ * @param int $timestamp
+ * @param string $url
+ *
+ * @return string
+ */
+ public function getTicketSignature($ticket, $nonce, $timestamp, $url): string
+ {
+ return sha1(sprintf('jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s', $ticket, $nonce, $timestamp, $url));
+ }
+
+ /**
+ * @return string
+ */
+ public function dictionaryOrderSignature()
+ {
+ $params = func_get_args();
+
+ sort($params, SORT_STRING);
+
+ return sha1(implode('', $params));
+ }
+
+ /**
+ * Set current url.
+ *
+ * @param string $url
+ *
+ * @return $this
+ */
+ public function setUrl(string $url)
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * Get current url.
+ *
+ * @return string
+ */
+ public function getUrl(): string
+ {
+ if ($this->url) {
+ return $this->url;
+ }
+
+ return Support\current_url();
+ }
+
+ /**
+ * @return string
+ */
+ protected function getAppId()
+ {
+ return $this->app['config']->get('app_id');
+ }
+
+ /**
+ * @return string
+ */
+ protected function getAgentId()
+ {
+ return $this->app['config']->get('agent_id');
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php
new file mode 100644
index 0000000..5581a1e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Jssdk/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Jssdk;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['jssdk'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Media/Client.php b/vendor/overtrue/wechat/src/BasicService/Media/Client.php
new file mode 100644
index 0000000..73345d8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Media/Client.php
@@ -0,0 +1,207 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Media;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Http\StreamResponse;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Allow media type.
+ *
+ * @var array
+ */
+ protected $allowTypes = ['image', 'voice', 'video', 'thumb'];
+
+ /**
+ * Upload image.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadImage($path)
+ {
+ return $this->upload('image', $path);
+ }
+
+ /**
+ * Upload video.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadVideo($path)
+ {
+ return $this->upload('video', $path);
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadVoice($path)
+ {
+ return $this->upload('voice', $path);
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadThumb($path)
+ {
+ return $this->upload('thumb', $path);
+ }
+
+ /**
+ * Upload temporary material.
+ *
+ * @param string $type
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function upload(string $type, string $path)
+ {
+ if (!file_exists($path) || !is_readable($path)) {
+ throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path));
+ }
+
+ if (!in_array($type, $this->allowTypes, true)) {
+ throw new InvalidArgumentException(sprintf("Unsupported media type: '%s'", $type));
+ }
+
+ return $this->httpUpload('cgi-bin/media/upload', ['media' => $path], ['type' => $type]);
+ }
+
+ /**
+ * @param string $path
+ * @param string $title
+ * @param string $description
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadVideoForBroadcasting(string $path, string $title, string $description)
+ {
+ $response = $this->uploadVideo($path);
+ /** @var array $arrayResponse */
+ $arrayResponse = $this->detectAndCastResponseToType($response, 'array');
+
+ if (!empty($arrayResponse['media_id'])) {
+ return $this->createVideoForBroadcasting($arrayResponse['media_id'], $title, $description);
+ }
+
+ return $response;
+ }
+
+ /**
+ * @param string $mediaId
+ * @param string $title
+ * @param string $description
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createVideoForBroadcasting(string $mediaId, string $title, string $description)
+ {
+ return $this->httpPostJson('cgi-bin/media/uploadvideo', [
+ 'media_id' => $mediaId,
+ 'title' => $title,
+ 'description' => $description,
+ ]);
+ }
+
+ /**
+ * Fetch item from WeChat server.
+ *
+ * @param string $mediaId
+ *
+ * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $mediaId)
+ {
+ $response = $this->requestRaw('cgi-bin/media/get', 'GET', [
+ 'query' => [
+ 'media_id' => $mediaId,
+ ],
+ ]);
+
+ if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+ return StreamResponse::buildFromPsrResponse($response);
+ }
+
+ return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+ }
+
+ /**
+ * @param string $mediaId
+ *
+ * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getJssdkMedia(string $mediaId)
+ {
+ $response = $this->requestRaw('cgi-bin/media/get/jssdk', 'GET', [
+ 'query' => [
+ 'media_id' => $mediaId,
+ ],
+ ]);
+
+ if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+ return StreamResponse::buildFromPsrResponse($response);
+ }
+
+ return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php
new file mode 100644
index 0000000..45de142
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Media/ServiceProvider.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Media;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['media'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php
new file mode 100644
index 0000000..92caeeb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/QrCode/Client.php
@@ -0,0 +1,115 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\QrCode;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ public const DAY = 86400;
+ public const SCENE_MAX_VALUE = 100000;
+ public const SCENE_QR_CARD = 'QR_CARD';
+ public const SCENE_QR_TEMPORARY = 'QR_SCENE';
+ public const SCENE_QR_TEMPORARY_STR = 'QR_STR_SCENE';
+ public const SCENE_QR_FOREVER = 'QR_LIMIT_SCENE';
+ public const SCENE_QR_FOREVER_STR = 'QR_LIMIT_STR_SCENE';
+
+ /**
+ * Create forever QR code.
+ *
+ * @param string|int $sceneValue
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function forever($sceneValue)
+ {
+ if (is_int($sceneValue) && $sceneValue > 0 && $sceneValue < self::SCENE_MAX_VALUE) {
+ $type = self::SCENE_QR_FOREVER;
+ $sceneKey = 'scene_id';
+ } else {
+ $type = self::SCENE_QR_FOREVER_STR;
+ $sceneKey = 'scene_str';
+ }
+ $scene = [$sceneKey => $sceneValue];
+
+ return $this->create($type, $scene, false);
+ }
+
+ /**
+ * Create temporary QR code.
+ *
+ * @param string|int $sceneValue
+ * @param int|null $expireSeconds
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function temporary($sceneValue, $expireSeconds = null)
+ {
+ if (is_int($sceneValue) && $sceneValue > 0) {
+ $type = self::SCENE_QR_TEMPORARY;
+ $sceneKey = 'scene_id';
+ } else {
+ $type = self::SCENE_QR_TEMPORARY_STR;
+ $sceneKey = 'scene_str';
+ }
+ $scene = [$sceneKey => $sceneValue];
+
+ return $this->create($type, $scene, true, $expireSeconds);
+ }
+
+ /**
+ * Return url for ticket.
+ * Detail: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542 .
+ *
+ * @param string $ticket
+ *
+ * @return string
+ */
+ public function url($ticket)
+ {
+ return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', urlencode($ticket));
+ }
+
+ /**
+ * Create a QrCode.
+ *
+ * @param string $actionName
+ * @param array $actionInfo
+ * @param bool $temporary
+ * @param int $expireSeconds
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function create($actionName, $actionInfo, $temporary = true, $expireSeconds = null)
+ {
+ null !== $expireSeconds || $expireSeconds = 7 * self::DAY;
+
+ $params = [
+ 'action_name' => $actionName,
+ 'action_info' => ['scene' => $actionInfo],
+ ];
+
+ if ($temporary) {
+ $params['expire_seconds'] = min($expireSeconds, 30 * self::DAY);
+ }
+
+ return $this->httpPostJson('cgi-bin/qrcode/create', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php
new file mode 100644
index 0000000..1638c66
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/QrCode/ServiceProvider.php
@@ -0,0 +1,31 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\QrCode;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['qrcode'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Url/Client.php b/vendor/overtrue/wechat/src/BasicService/Url/Client.php
new file mode 100644
index 0000000..0363b52
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Url/Client.php
@@ -0,0 +1,42 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Url;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Shorten the url.
+ *
+ * @param string $url
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function shorten(string $url)
+ {
+ $params = [
+ 'action' => 'long2short',
+ 'long_url' => $url,
+ ];
+
+ return $this->httpPostJson('cgi-bin/shorturl', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php
new file mode 100644
index 0000000..6740bb8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/BasicService/Url/ServiceProvider.php
@@ -0,0 +1,31 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\BasicService\Url;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['url'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Factory.php b/vendor/overtrue/wechat/src/Factory.php
new file mode 100644
index 0000000..295b4ff
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Factory.php
@@ -0,0 +1,54 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat;
+
+/**
+ * Class Factory.
+ *
+ * @method static \EasyWeChat\Payment\Application payment(array $config)
+ * @method static \EasyWeChat\MiniProgram\Application miniProgram(array $config)
+ * @method static \EasyWeChat\OpenPlatform\Application openPlatform(array $config)
+ * @method static \EasyWeChat\OfficialAccount\Application officialAccount(array $config)
+ * @method static \EasyWeChat\BasicService\Application basicService(array $config)
+ * @method static \EasyWeChat\Work\Application work(array $config)
+ * @method static \EasyWeChat\OpenWork\Application openWork(array $config)
+ * @method static \EasyWeChat\MicroMerchant\Application microMerchant(array $config)
+ */
+class Factory
+{
+ /**
+ * @param string $name
+ * @param array $config
+ *
+ * @return \EasyWeChat\Kernel\ServiceContainer
+ */
+ public static function make($name, array $config)
+ {
+ $namespace = Kernel\Support\Str::studly($name);
+ $application = "\\EasyWeChat\\{$namespace}\\Application";
+
+ return new $application($config);
+ }
+
+ /**
+ * Dynamically pass methods to the application.
+ *
+ * @param string $name
+ * @param array $arguments
+ *
+ * @return mixed
+ */
+ public static function __callStatic($name, $arguments)
+ {
+ return self::make($name, ...$arguments);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/AccessToken.php b/vendor/overtrue/wechat/src/Kernel/AccessToken.php
new file mode 100644
index 0000000..bbf84ec
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/AccessToken.php
@@ -0,0 +1,284 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\AccessTokenInterface;
+use EasyWeChat\Kernel\Exceptions\HttpException;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Traits\HasHttpRequests;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class AccessToken.
+ *
+ * @author overtrue
+ */
+abstract class AccessToken implements AccessTokenInterface
+{
+ use HasHttpRequests;
+ use InteractsWithCache;
+
+ /**
+ * @var \EasyWeChat\Kernel\ServiceContainer
+ */
+ protected $app;
+
+ /**
+ * @var string
+ */
+ protected $requestMethod = 'GET';
+
+ /**
+ * @var string
+ */
+ protected $endpointToGetToken;
+
+ /**
+ * @var string
+ */
+ protected $queryName;
+
+ /**
+ * @var array
+ */
+ protected $token;
+
+ /**
+ * @var string
+ */
+ protected $tokenKey = 'access_token';
+
+ /**
+ * @var string
+ */
+ protected $cachePrefix = 'easywechat.kernel.access_token.';
+
+ /**
+ * AccessToken constructor.
+ *
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+ }
+
+ public function getLastToken(): array
+ {
+ return $this->token;
+ }
+
+ /**
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function getRefreshedToken(): array
+ {
+ return $this->getToken(true);
+ }
+
+ /**
+ * @param bool $refresh
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function getToken(bool $refresh = false): array
+ {
+ $cacheKey = $this->getCacheKey();
+ $cache = $this->getCache();
+
+ if (!$refresh && $cache->has($cacheKey) && $result = $cache->get($cacheKey)) {
+ return $result;
+ }
+
+ /** @var array $token */
+ $token = $this->requestToken($this->getCredentials(), true);
+
+ $this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200);
+
+ $this->token = $token;
+
+ $this->app->events->dispatch(new Events\AccessTokenRefreshed($this));
+
+ return $token;
+ }
+
+ /**
+ * @param string $token
+ * @param int $lifetime
+ *
+ * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface
+ {
+ $this->getCache()->set($this->getCacheKey(), [
+ $this->tokenKey => $token,
+ 'expires_in' => $lifetime,
+ ], $lifetime);
+
+ if (!$this->getCache()->has($this->getCacheKey())) {
+ throw new RuntimeException('Failed to cache access token.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function refresh(): AccessTokenInterface
+ {
+ $this->getToken(true);
+
+ return $this;
+ }
+
+ /**
+ * @param array $credentials
+ * @param bool $toArray
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function requestToken(array $credentials, $toArray = false)
+ {
+ $response = $this->sendRequest($credentials);
+ $result = json_decode($response->getBody()->getContents(), true);
+ $formatted = $this->castResponseToType($response, $this->app['config']->get('response_type'));
+
+ if (empty($result[$this->tokenKey])) {
+ throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted);
+ }
+
+ return $toArray ? $result : $formatted;
+ }
+
+ /**
+ * @param \Psr\Http\Message\RequestInterface $request
+ * @param array $requestOptions
+ *
+ * @return \Psr\Http\Message\RequestInterface
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface
+ {
+ parse_str($request->getUri()->getQuery(), $query);
+
+ $query = http_build_query(array_merge($this->getQuery(), $query));
+
+ return $request->withUri($request->getUri()->withQuery($query));
+ }
+
+ /**
+ * Send http request.
+ *
+ * @param array $credentials
+ *
+ * @return ResponseInterface
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function sendRequest(array $credentials): ResponseInterface
+ {
+ $options = [
+ ('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials,
+ ];
+
+ return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options);
+ }
+
+ /**
+ * @return string
+ */
+ protected function getCacheKey()
+ {
+ return $this->cachePrefix.md5(json_encode($this->getCredentials()));
+ }
+
+ /**
+ * The request query will be used to add to the request.
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\HttpException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ protected function getQuery(): array
+ {
+ return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]];
+ }
+
+ /**
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function getEndpoint(): string
+ {
+ if (empty($this->endpointToGetToken)) {
+ throw new InvalidArgumentException('No endpoint for access token request.');
+ }
+
+ return $this->endpointToGetToken;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTokenKey()
+ {
+ return $this->tokenKey;
+ }
+
+ /**
+ * Credential for get token.
+ *
+ * @return array
+ */
+ abstract protected function getCredentials(): array;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Kernel/BaseClient.php
new file mode 100644
index 0000000..ed02f0d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/BaseClient.php
@@ -0,0 +1,286 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\AccessTokenInterface;
+use EasyWeChat\Kernel\Http\Response;
+use EasyWeChat\Kernel\Traits\HasHttpRequests;
+use GuzzleHttp\MessageFormatter;
+use GuzzleHttp\Middleware;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * Class BaseClient.
+ *
+ * @author overtrue
+ */
+class BaseClient
+{
+ use HasHttpRequests {
+ request as performRequest;
+ }
+
+ /**
+ * @var \EasyWeChat\Kernel\ServiceContainer
+ */
+ protected $app;
+ /**
+ * @var \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null
+ */
+ protected $accessToken = null;
+ /**
+ * @var string
+ */
+ protected $baseUri;
+
+ /**
+ * BaseClient constructor.
+ *
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null $accessToken
+ */
+ public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null)
+ {
+ $this->app = $app;
+ $this->accessToken = $accessToken ?? $this->app['access_token'];
+ }
+
+ /**
+ * GET request.
+ *
+ * @param string $url
+ * @param array $query
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpGet(string $url, array $query = [])
+ {
+ return $this->request($url, 'GET', ['query' => $query]);
+ }
+
+ /**
+ * POST request.
+ *
+ * @param string $url
+ * @param array $data
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpPost(string $url, array $data = [])
+ {
+ return $this->request($url, 'POST', ['form_params' => $data]);
+ }
+
+ /**
+ * JSON request.
+ *
+ * @param string $url
+ * @param array $data
+ * @param array $query
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpPostJson(string $url, array $data = [], array $query = [])
+ {
+ return $this->request($url, 'POST', ['query' => $query, 'json' => $data]);
+ }
+
+ /**
+ * Upload file.
+ *
+ * @param string $url
+ * @param array $files
+ * @param array $form
+ * @param array $query
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpUpload(string $url, array $files = [], array $form = [], array $query = [])
+ {
+ $multipart = [];
+ $headers = [];
+
+ if (isset($form['filename'])) {
+ $headers = [
+ 'Content-Disposition' => 'form-data; name="media"; filename="'.$form['filename'].'"'
+ ];
+ }
+
+ foreach ($files as $name => $path) {
+ $multipart[] = [
+ 'name' => $name,
+ 'contents' => fopen($path, 'r'),
+ 'headers' => $headers
+ ];
+ }
+
+ foreach ($form as $name => $contents) {
+ $multipart[] = compact('name', 'contents');
+ }
+
+ return $this->request(
+ $url,
+ 'POST',
+ ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]
+ );
+ }
+
+ /**
+ * @return AccessTokenInterface
+ */
+ public function getAccessToken(): AccessTokenInterface
+ {
+ return $this->accessToken;
+ }
+
+ /**
+ * @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface $accessToken
+ *
+ * @return $this
+ */
+ public function setAccessToken(AccessTokenInterface $accessToken)
+ {
+ $this->accessToken = $accessToken;
+
+ return $this;
+ }
+
+ /**
+ * @param string $url
+ * @param string $method
+ * @param array $options
+ * @param bool $returnRaw
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false)
+ {
+ if (empty($this->middlewares)) {
+ $this->registerHttpMiddlewares();
+ }
+
+ $response = $this->performRequest($url, $method, $options);
+
+ $this->app->events->dispatch(new Events\HttpResponseCreated($response));
+
+ return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+ }
+
+ /**
+ * @param string $url
+ * @param string $method
+ * @param array $options
+ *
+ * @return \EasyWeChat\Kernel\Http\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function requestRaw(string $url, string $method = 'GET', array $options = [])
+ {
+ return Response::buildFromPsrResponse($this->request($url, $method, $options, true));
+ }
+
+ /**
+ * Register Guzzle middlewares.
+ */
+ protected function registerHttpMiddlewares()
+ {
+ // retry
+ $this->pushMiddleware($this->retryMiddleware(), 'retry');
+ // access token
+ $this->pushMiddleware($this->accessTokenMiddleware(), 'access_token');
+ // log
+ $this->pushMiddleware($this->logMiddleware(), 'log');
+ }
+
+ /**
+ * Attache access token to request query.
+ *
+ * @return \Closure
+ */
+ protected function accessTokenMiddleware()
+ {
+ return function (callable $handler) {
+ return function (RequestInterface $request, array $options) use ($handler) {
+ if ($this->accessToken) {
+ $request = $this->accessToken->applyToRequest($request, $options);
+ }
+
+ return $handler($request, $options);
+ };
+ };
+ }
+
+ /**
+ * Log the request.
+ *
+ * @return \Closure
+ */
+ protected function logMiddleware()
+ {
+ $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
+
+ return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG);
+ }
+
+ /**
+ * Return retry middleware.
+ *
+ * @return \Closure
+ */
+ protected function retryMiddleware()
+ {
+ return Middleware::retry(
+ function (
+ $retries,
+ RequestInterface $request,
+ ResponseInterface $response = null
+ ) {
+ // Limit the number of retries to 2
+ if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) {
+ // Retry on server errors
+ $response = json_decode($body, true);
+
+ if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) {
+ $this->accessToken->refresh();
+ $this->app['logger']->debug('Retrying with refreshed access token.');
+
+ return true;
+ }
+ }
+
+ return false;
+ },
+ function () {
+ return abs($this->app->config->get('http.retry_delay', 500));
+ }
+ );
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php
new file mode 100644
index 0000000..b798f32
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Clauses/Clause.php
@@ -0,0 +1,75 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Clauses;
+
+/**
+ * Class Clause.
+ *
+ * @author mingyoung
+ */
+class Clause
+{
+ /**
+ * @var array
+ */
+ protected $clauses = [
+ 'where' => [],
+ ];
+
+ /**
+ * @param mixed ...$args
+ *
+ * @return $this
+ */
+ public function where(...$args)
+ {
+ array_push($this->clauses['where'], $args);
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $payload
+ *
+ * @return bool
+ */
+ public function intercepted($payload)
+ {
+ return (bool) $this->interceptWhereClause($payload);
+ }
+
+ /**
+ * @param mixed $payload
+ *
+ * @return bool
+ */
+ protected function interceptWhereClause($payload)
+ {
+ foreach ($this->clauses['where'] as $item) {
+ list($key, $value) = $item;
+
+ if (!isset($payload[$key])) {
+ continue;
+ }
+
+ if (is_array($value) && !in_array($payload[$key], $value)) {
+ return true;
+ }
+
+ if (!is_array($value) && $payload[$key] !== $value) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Config.php b/vendor/overtrue/wechat/src/Kernel/Config.php
new file mode 100644
index 0000000..081f6fd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Config.php
@@ -0,0 +1,23 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Support\Collection;
+
+/**
+ * Class Config.
+ *
+ * @author overtrue
+ */
+class Config extends Collection
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php
new file mode 100644
index 0000000..56f4a4d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/AccessTokenInterface.php
@@ -0,0 +1,40 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Interface AuthorizerAccessToken.
+ *
+ * @author overtrue
+ */
+interface AccessTokenInterface
+{
+ /**
+ * @return array
+ */
+ public function getToken(): array;
+
+ /**
+ * @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface
+ */
+ public function refresh(): self;
+
+ /**
+ * @param \Psr\Http\Message\RequestInterface $request
+ * @param array $requestOptions
+ *
+ * @return \Psr\Http\Message\RequestInterface
+ */
+ public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php
new file mode 100644
index 0000000..d947f8f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/Arrayable.php
@@ -0,0 +1,29 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+use ArrayAccess;
+
+/**
+ * Interface Arrayable.
+ *
+ * @author overtrue
+ */
+interface Arrayable extends ArrayAccess
+{
+ /**
+ * Get the instance as an array.
+ *
+ * @return array
+ */
+ public function toArray();
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php
new file mode 100644
index 0000000..c9d116c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/EventHandlerInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+/**
+ * Interface EventHandlerInterface.
+ *
+ * @author mingyoung
+ */
+interface EventHandlerInterface
+{
+ /**
+ * @param mixed $payload
+ */
+ public function handle($payload = null);
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php
new file mode 100644
index 0000000..63768cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MediaInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+/**
+ * Interface MediaInterface.
+ *
+ * @author overtrue
+ */
+interface MediaInterface extends MessageInterface
+{
+ /**
+ * @return string
+ */
+ public function getMediaId(): string;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php
new file mode 100644
index 0000000..29ddb57
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Contracts/MessageInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Contracts;
+
+/**
+ * Interface MessageInterface.
+ *
+ * @author overtrue
+ */
+interface MessageInterface
+{
+ /**
+ * @return string
+ */
+ public function getType(): string;
+
+ /**
+ * @return array
+ */
+ public function transformForJsonRequest(): array;
+
+ /**
+ * @return string
+ */
+ public function transformToXml(): string;
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php
new file mode 100644
index 0000000..e698bbf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Decorators/FinallyResult.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Decorators;
+
+/**
+ * Class FinallyResult.
+ *
+ * @author overtrue
+ */
+class FinallyResult
+{
+ /**
+ * @var mixed
+ */
+ public $content;
+
+ /**
+ * FinallyResult constructor.
+ *
+ * @param mixed $content
+ */
+ public function __construct($content)
+ {
+ $this->content = $content;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php
new file mode 100644
index 0000000..cf1042d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Decorators/TerminateResult.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Decorators;
+
+/**
+ * Class TerminateResult.
+ *
+ * @author overtrue
+ */
+class TerminateResult
+{
+ /**
+ * @var mixed
+ */
+ public $content;
+
+ /**
+ * FinallyResult constructor.
+ *
+ * @param mixed $content
+ */
+ public function __construct($content)
+ {
+ $this->content = $content;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Encryptor.php b/vendor/overtrue/wechat/src/Kernel/Encryptor.php
new file mode 100644
index 0000000..a154809
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Encryptor.php
@@ -0,0 +1,219 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support\AES;
+use EasyWeChat\Kernel\Support\XML;
+use Throwable;
+use function EasyWeChat\Kernel\Support\str_random;
+
+/**
+ * Class Encryptor.
+ *
+ * @author overtrue
+ */
+class Encryptor
+{
+ public const ERROR_INVALID_SIGNATURE = -40001; // Signature verification failed
+ public const ERROR_PARSE_XML = -40002; // Parse XML failed
+ public const ERROR_CALC_SIGNATURE = -40003; // Calculating the signature failed
+ public const ERROR_INVALID_AES_KEY = -40004; // Invalid AESKey
+ public const ERROR_INVALID_APP_ID = -40005; // Check AppID failed
+ public const ERROR_ENCRYPT_AES = -40006; // AES EncryptionInterface failed
+ public const ERROR_DECRYPT_AES = -40007; // AES decryption failed
+ public const ERROR_INVALID_XML = -40008; // Invalid XML
+ public const ERROR_BASE64_ENCODE = -40009; // Base64 encoding failed
+ public const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed
+ public const ERROR_XML_BUILD = -40011; // XML build failed
+ public const ILLEGAL_BUFFER = -41003; // Illegal buffer
+
+ /**
+ * App id.
+ *
+ * @var string
+ */
+ protected $appId;
+
+ /**
+ * App token.
+ *
+ * @var string
+ */
+ protected $token;
+
+ /**
+ * @var string
+ */
+ protected $aesKey;
+
+ /**
+ * Block size.
+ *
+ * @var int
+ */
+ protected $blockSize = 32;
+
+ /**
+ * Constructor.
+ *
+ * @param string $appId
+ * @param string|null $token
+ * @param string|null $aesKey
+ */
+ public function __construct(string $appId, string $token = null, string $aesKey = null)
+ {
+ $this->appId = $appId;
+ $this->token = $token;
+ $this->aesKey = base64_decode($aesKey.'=', true);
+ }
+
+ /**
+ * Get the app token.
+ *
+ * @return string
+ */
+ public function getToken(): string
+ {
+ return $this->token;
+ }
+
+ /**
+ * Encrypt the message and return XML.
+ *
+ * @param string $xml
+ * @param string $nonce
+ * @param int $timestamp
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function encrypt($xml, $nonce = null, $timestamp = null): string
+ {
+ try {
+ $xml = $this->pkcs7Pad(str_random(16).pack('N', strlen($xml)).$xml.$this->appId, $this->blockSize);
+
+ $encrypted = base64_encode(AES::encrypt(
+ $xml,
+ $this->aesKey,
+ substr($this->aesKey, 0, 16),
+ OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
+ ));
+ // @codeCoverageIgnoreStart
+ } catch (Throwable $e) {
+ throw new RuntimeException($e->getMessage(), self::ERROR_ENCRYPT_AES);
+ }
+ // @codeCoverageIgnoreEnd
+
+ !is_null($nonce) || $nonce = substr($this->appId, 0, 10);
+ !is_null($timestamp) || $timestamp = time();
+
+ $response = [
+ 'Encrypt' => $encrypted,
+ 'MsgSignature' => $this->signature($this->token, $timestamp, $nonce, $encrypted),
+ 'TimeStamp' => $timestamp,
+ 'Nonce' => $nonce,
+ ];
+
+ //生成响应xml
+ return XML::build($response);
+ }
+
+ /**
+ * Decrypt message.
+ *
+ * @param string $content
+ * @param string $msgSignature
+ * @param string $nonce
+ * @param string $timestamp
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function decrypt($content, $msgSignature, $nonce, $timestamp): string
+ {
+ $signature = $this->signature($this->token, $timestamp, $nonce, $content);
+
+ if ($signature !== $msgSignature) {
+ throw new RuntimeException('Invalid Signature.', self::ERROR_INVALID_SIGNATURE);
+ }
+
+ $decrypted = AES::decrypt(
+ base64_decode($content, true),
+ $this->aesKey,
+ substr($this->aesKey, 0, 16),
+ OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING
+ );
+ $result = $this->pkcs7Unpad($decrypted);
+ $content = substr($result, 16, strlen($result));
+ $contentLen = unpack('N', substr($content, 0, 4))[1];
+
+ if (trim(substr($content, $contentLen + 4)) !== $this->appId) {
+ throw new RuntimeException('Invalid appId.', self::ERROR_INVALID_APP_ID);
+ }
+
+ return substr($content, 4, $contentLen);
+ }
+
+ /**
+ * Get SHA1.
+ *
+ * @return string
+ */
+ public function signature(): string
+ {
+ $array = func_get_args();
+ sort($array, SORT_STRING);
+
+ return sha1(implode($array));
+ }
+
+ /**
+ * PKCS#7 pad.
+ *
+ * @param string $text
+ * @param int $blockSize
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function pkcs7Pad(string $text, int $blockSize): string
+ {
+ if ($blockSize > 256) {
+ throw new RuntimeException('$blockSize may not be more than 256');
+ }
+ $padding = $blockSize - (strlen($text) % $blockSize);
+ $pattern = chr($padding);
+
+ return $text.str_repeat($pattern, $padding);
+ }
+
+ /**
+ * PKCS#7 unpad.
+ *
+ * @param string $text
+ *
+ * @return string
+ */
+ public function pkcs7Unpad(string $text): string
+ {
+ $pad = ord(substr($text, -1));
+ if ($pad < 1 || $pad > $this->blockSize) {
+ $pad = 0;
+ }
+
+ return substr($text, 0, (strlen($text) - $pad));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php
new file mode 100644
index 0000000..a829901
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/AccessTokenRefreshed.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use EasyWeChat\Kernel\AccessToken;
+
+/**
+ * Class AccessTokenRefreshed.
+ *
+ * @author mingyoung
+ */
+class AccessTokenRefreshed
+{
+ /**
+ * @var \EasyWeChat\Kernel\AccessToken
+ */
+ public $accessToken;
+
+ /**
+ * @param \EasyWeChat\Kernel\AccessToken $accessToken
+ */
+ public function __construct(AccessToken $accessToken)
+ {
+ $this->accessToken = $accessToken;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php
new file mode 100644
index 0000000..89d67fe
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/ApplicationInitialized.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class ApplicationInitialized.
+ *
+ * @author mingyoung
+ */
+class ApplicationInitialized
+{
+ /**
+ * @var \EasyWeChat\Kernel\ServiceContainer
+ */
+ public $app;
+
+ /**
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php
new file mode 100644
index 0000000..2257260
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/HttpResponseCreated.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class HttpResponseCreated.
+ *
+ * @author mingyoung
+ */
+class HttpResponseCreated
+{
+ /**
+ * @var \Psr\Http\Message\ResponseInterface
+ */
+ public $response;
+
+ /**
+ * @param \Psr\Http\Message\ResponseInterface $response
+ */
+ public function __construct(ResponseInterface $response)
+ {
+ $this->response = $response;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php
new file mode 100644
index 0000000..3ab9925
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Events/ServerGuardResponseCreated.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Events;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Class ServerGuardResponseCreated.
+ *
+ * @author mingyoung
+ */
+class ServerGuardResponseCreated
+{
+ /**
+ * @var \Symfony\Component\HttpFoundation\Response
+ */
+ public $response;
+
+ /**
+ * @param \Symfony\Component\HttpFoundation\Response $response
+ */
+ public function __construct(Response $response)
+ {
+ $this->response = $response;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php
new file mode 100644
index 0000000..a0b4f91
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/BadRequestException.php
@@ -0,0 +1,21 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class BadRequestException.
+ *
+ * @author overtrue
+ */
+class BadRequestException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php
new file mode 100644
index 0000000..f29acca
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/DecryptException.php
@@ -0,0 +1,16 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+class DecryptException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php
new file mode 100644
index 0000000..9eba298
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/Exception.php
@@ -0,0 +1,23 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+use Exception as BaseException;
+
+/**
+ * Class Exception.
+ *
+ * @author overtrue
+ */
+class Exception extends BaseException
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php
new file mode 100644
index 0000000..8ab130d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/HttpException.php
@@ -0,0 +1,52 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class HttpException.
+ *
+ * @author overtrue
+ */
+class HttpException extends Exception
+{
+ /**
+ * @var \Psr\Http\Message\ResponseInterface|null
+ */
+ public $response;
+
+ /**
+ * @var \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string|null
+ */
+ public $formattedResponse;
+
+ /**
+ * HttpException constructor.
+ *
+ * @param string $message
+ * @param \Psr\Http\Message\ResponseInterface|null $response
+ * @param null $formattedResponse
+ * @param int|null $code
+ */
+ public function __construct($message, ResponseInterface $response = null, $formattedResponse = null, $code = null)
+ {
+ parent::__construct($message, $code);
+
+ $this->response = $response;
+ $this->formattedResponse = $formattedResponse;
+
+ if ($response) {
+ $response->getBody()->rewind();
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php
new file mode 100644
index 0000000..386a144
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class InvalidArgumentException.
+ *
+ * @author overtrue
+ */
+class InvalidArgumentException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php
new file mode 100644
index 0000000..1e4ae2a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/InvalidConfigException.php
@@ -0,0 +1,21 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class InvalidConfigException.
+ *
+ * @author overtrue
+ */
+class InvalidConfigException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php
new file mode 100644
index 0000000..25f2f5b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/RuntimeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class RuntimeException.
+ *
+ * @author overtrue
+ */
+class RuntimeException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php
new file mode 100644
index 0000000..78ea85c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Exceptions/UnboundServiceException.php
@@ -0,0 +1,21 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Exceptions;
+
+/**
+ * Class InvalidConfigException.
+ *
+ * @author overtrue
+ */
+class UnboundServiceException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Helpers.php
new file mode 100644
index 0000000..3ea48a4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Helpers.php
@@ -0,0 +1,57 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support\Arr;
+use EasyWeChat\Kernel\Support\Collection;
+
+function data_get($data, $key, $default = null)
+{
+ switch (true) {
+ case is_array($data):
+ return Arr::get($data, $key, $default);
+ case $data instanceof Collection:
+ return $data->get($key, $default);
+ case $data instanceof Arrayable:
+ return Arr::get($data->toArray(), $key, $default);
+ case $data instanceof \ArrayIterator:
+ return $data->getArrayCopy()[$key] ?? $default;
+ case $data instanceof \ArrayAccess:
+ return $data[$key] ?? $default;
+ case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator:
+ return $data->getIterator()->getArrayCopy()[$key] ?? $default;
+ case is_object($data):
+ return $data->{$key} ?? $default;
+ default:
+ throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key));
+ }
+}
+
+function data_to_array($data)
+{
+ switch (true) {
+ case is_array($data):
+ return $data;
+ case $data instanceof Collection:
+ return $data->all();
+ case $data instanceof Arrayable:
+ return $data->toArray();
+ case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator:
+ return $data->getIterator()->getArrayCopy();
+ case $data instanceof \ArrayIterator:
+ return $data->getArrayCopy();
+ default:
+ throw new RuntimeException(sprintf('Can\'t transform data to array'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Http/Response.php b/vendor/overtrue/wechat/src/Kernel/Http/Response.php
new file mode 100644
index 0000000..401857a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Http/Response.php
@@ -0,0 +1,121 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Http;
+
+use EasyWeChat\Kernel\Support\Collection;
+use EasyWeChat\Kernel\Support\XML;
+use GuzzleHttp\Psr7\Response as GuzzleResponse;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class Response.
+ *
+ * @author overtrue
+ */
+class Response extends GuzzleResponse
+{
+ /**
+ * @return string
+ */
+ public function getBodyContents()
+ {
+ $this->getBody()->rewind();
+ $contents = $this->getBody()->getContents();
+ $this->getBody()->rewind();
+
+ return $contents;
+ }
+
+ /**
+ * @param \Psr\Http\Message\ResponseInterface $response
+ *
+ * @return \EasyWeChat\Kernel\Http\Response
+ */
+ public static function buildFromPsrResponse(ResponseInterface $response)
+ {
+ return new static(
+ $response->getStatusCode(),
+ $response->getHeaders(),
+ $response->getBody(),
+ $response->getProtocolVersion(),
+ $response->getReasonPhrase()
+ );
+ }
+
+ /**
+ * Build to json.
+ *
+ * @return string
+ */
+ public function toJson()
+ {
+ return json_encode($this->toArray());
+ }
+
+ /**
+ * Build to array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ $content = $this->removeControlCharacters($this->getBodyContents());
+
+ if (false !== stripos($this->getHeaderLine('Content-Type'), 'xml') || 0 === stripos($content, 'toArray());
+ }
+
+ /**
+ * @return object
+ */
+ public function toObject()
+ {
+ return json_decode($this->toJson());
+ }
+
+ /**
+ * @return bool|string
+ */
+ public function __toString()
+ {
+ return $this->getBodyContents();
+ }
+
+ /**
+ * @param string $content
+ *
+ * @return string
+ */
+ protected function removeControlCharacters(string $content)
+ {
+ return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', \mb_convert_encoding($content, 'UTF-8', 'UTF-8'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php
new file mode 100644
index 0000000..104e8c3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Http/StreamResponse.php
@@ -0,0 +1,86 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Http;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support\File;
+
+/**
+ * Class StreamResponse.
+ *
+ * @author overtrue
+ */
+class StreamResponse extends Response
+{
+ /**
+ * @param string $directory
+ * @param string $filename
+ * @param bool $appendSuffix
+ *
+ * @return bool|int
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function save(string $directory, string $filename = '', bool $appendSuffix = true)
+ {
+ $this->getBody()->rewind();
+
+ $directory = rtrim($directory, '/');
+
+ if (!is_dir($directory)) {
+ mkdir($directory, 0755, true); // @codeCoverageIgnore
+ }
+
+ if (!is_writable($directory)) {
+ throw new InvalidArgumentException(sprintf("'%s' is not writable.", $directory));
+ }
+
+ $contents = $this->getBody()->getContents();
+
+ if (empty($contents) || '{' === $contents[0]) {
+ throw new RuntimeException('Invalid media response content.');
+ }
+
+ if (empty($filename)) {
+ if (preg_match('/filename="(?.*?)"/', $this->getHeaderLine('Content-Disposition'), $match)) {
+ $filename = $match['filename'];
+ } else {
+ $filename = md5($contents);
+ }
+ }
+
+ if ($appendSuffix && empty(pathinfo($filename, PATHINFO_EXTENSION))) {
+ $filename .= File::getStreamExt($contents);
+ }
+
+ file_put_contents($directory.'/'.$filename, $contents);
+
+ return $filename;
+ }
+
+ /**
+ * @param string $directory
+ * @param string $filename
+ * @param bool $appendSuffix
+ *
+ * @return bool|int
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function saveAs(string $directory, string $filename, bool $appendSuffix = true)
+ {
+ return $this->save($directory, $filename, $appendSuffix);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php
new file mode 100644
index 0000000..870b46f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Log/LogManager.php
@@ -0,0 +1,594 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Log;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use InvalidArgumentException;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Handler\ErrorLogHandler;
+use Monolog\Handler\FormattableHandlerInterface;
+use Monolog\Handler\HandlerInterface;
+use Monolog\Handler\RotatingFileHandler;
+use Monolog\Handler\SlackWebhookHandler;
+use Monolog\Handler\StreamHandler;
+use Monolog\Handler\SyslogHandler;
+use Monolog\Handler\WhatFailureGroupHandler;
+use Monolog\Logger as Monolog;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class LogManager.
+ *
+ * @author overtrue
+ */
+class LogManager implements LoggerInterface
+{
+ /**
+ * @var \EasyWeChat\Kernel\ServiceContainer
+ */
+ protected $app;
+
+ /**
+ * The array of resolved channels.
+ *
+ * @var array
+ */
+ protected $channels = [];
+
+ /**
+ * The registered custom driver creators.
+ *
+ * @var array
+ */
+ protected $customCreators = [];
+
+ /**
+ * The Log levels.
+ *
+ * @var array
+ */
+ protected $levels = [
+ 'debug' => Monolog::DEBUG,
+ 'info' => Monolog::INFO,
+ 'notice' => Monolog::NOTICE,
+ 'warning' => Monolog::WARNING,
+ 'error' => Monolog::ERROR,
+ 'critical' => Monolog::CRITICAL,
+ 'alert' => Monolog::ALERT,
+ 'emergency' => Monolog::EMERGENCY,
+ ];
+
+ /**
+ * LogManager constructor.
+ *
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * Create a new, on-demand aggregate logger instance.
+ *
+ * @param array $channels
+ * @param string|null $channel
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws \Exception
+ */
+ public function stack(array $channels, $channel = null)
+ {
+ return $this->createStackDriver(compact('channels', 'channel'));
+ }
+
+ /**
+ * Get a log channel instance.
+ *
+ * @param string|null $channel
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function channel($channel = null)
+ {
+ return $this->driver($channel);
+ }
+
+ /**
+ * Get a log driver instance.
+ *
+ * @param string|null $driver
+ *
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function driver($driver = null)
+ {
+ return $this->get($driver ?? $this->getDefaultDriver());
+ }
+
+ /**
+ * Attempt to get the log from the local cache.
+ *
+ * @param string $name
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws \Exception
+ */
+ protected function get($name)
+ {
+ try {
+ return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name));
+ } catch (\Throwable $e) {
+ $logger = $this->createEmergencyLogger();
+
+ $logger->emergency('Unable to create configured logger. Using emergency logger.', [
+ 'exception' => $e,
+ ]);
+
+ return $logger;
+ }
+ }
+
+ /**
+ * Resolve the given log instance by name.
+ *
+ * @param string $name
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function resolve($name)
+ {
+ $config = $this->app['config']->get(\sprintf('log.channels.%s', $name));
+
+ if (is_null($config)) {
+ throw new InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name));
+ }
+
+ if (isset($this->customCreators[$config['driver']])) {
+ return $this->callCustomCreator($config);
+ }
+
+ $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
+
+ if (method_exists($this, $driverMethod)) {
+ return $this->{$driverMethod}($config);
+ }
+
+ throw new InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver']));
+ }
+
+ /**
+ * Create an emergency log handler to avoid white screens of death.
+ *
+ * @return \Monolog\Logger
+ *
+ * @throws \Exception
+ */
+ protected function createEmergencyLogger()
+ {
+ return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler(
+ \sys_get_temp_dir().'/easywechat/easywechat.log',
+ $this->level(['level' => 'debug'])
+ )]));
+ }
+
+ /**
+ * Call a custom driver creator.
+ *
+ * @param array $config
+ *
+ * @return mixed
+ */
+ protected function callCustomCreator(array $config)
+ {
+ return $this->customCreators[$config['driver']]($this->app, $config);
+ }
+
+ /**
+ * Create an aggregate log driver instance.
+ *
+ * @param array $config
+ *
+ * @return \Monolog\Logger
+ *
+ * @throws \Exception
+ */
+ protected function createStackDriver(array $config)
+ {
+ $handlers = [];
+
+ foreach ($config['channels'] ?? [] as $channel) {
+ $handlers = \array_merge($handlers, $this->channel($channel)->getHandlers());
+ }
+
+ if ($config['ignore_exceptions'] ?? false) {
+ $handlers = [new WhatFailureGroupHandler($handlers)];
+ }
+
+ return new Monolog($this->parseChannel($config), $handlers);
+ }
+
+ /**
+ * Create an instance of the single file log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ *
+ * @throws \Exception
+ */
+ protected function createSingleDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new StreamHandler(
+ $config['path'],
+ $this->level($config),
+ $config['bubble'] ?? true,
+ $config['permission'] ?? null,
+ $config['locking'] ?? false
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the daily file log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ */
+ protected function createDailyDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new RotatingFileHandler(
+ $config['path'],
+ $config['days'] ?? 7,
+ $this->level($config),
+ $config['bubble'] ?? true,
+ $config['permission'] ?? null,
+ $config['locking'] ?? false
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the Slack log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ * @throws \Monolog\Handler\MissingExtensionException
+ */
+ protected function createSlackDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new SlackWebhookHandler(
+ $config['url'],
+ $config['channel'] ?? null,
+ $config['username'] ?? 'EasyWeChat',
+ $config['attachment'] ?? true,
+ $config['emoji'] ?? ':boom:',
+ $config['short'] ?? false,
+ $config['context'] ?? true,
+ $this->level($config),
+ $config['bubble'] ?? true,
+ $config['exclude_fields'] ?? []
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the syslog log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ */
+ protected function createSyslogDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(new SyslogHandler(
+ 'EasyWeChat',
+ $config['facility'] ?? LOG_USER,
+ $this->level($config)
+ ), $config),
+ ]);
+ }
+
+ /**
+ * Create an instance of the "error log" log driver.
+ *
+ * @param array $config
+ *
+ * @return \Psr\Log\LoggerInterface
+ */
+ protected function createErrorlogDriver(array $config)
+ {
+ return new Monolog($this->parseChannel($config), [
+ $this->prepareHandler(
+ new ErrorLogHandler(
+ $config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM,
+ $this->level($config)
+ )
+ ),
+ ]);
+ }
+
+ /**
+ * Prepare the handlers for usage by Monolog.
+ *
+ * @param array $handlers
+ *
+ * @return array
+ */
+ protected function prepareHandlers(array $handlers): array
+ {
+ foreach ($handlers as $key => $handler) {
+ $handlers[$key] = $this->prepareHandler($handler);
+ }
+
+ return $handlers;
+ }
+
+ /**
+ * Prepare the handler for usage by Monolog.
+ *
+ * @param \Monolog\Handler\HandlerInterface $handler
+ * @param array $config
+ *
+ * @return \Monolog\Handler\HandlerInterface
+ */
+ protected function prepareHandler(HandlerInterface $handler, array $config = [])
+ {
+ if (!isset($config['formatter'])) {
+ if ($handler instanceof FormattableHandlerInterface) {
+ $handler->setFormatter($this->formatter());
+ }
+ }
+
+ return $handler;
+ }
+
+ /**
+ * Get a Monolog formatter instance.
+ *
+ * @return \Monolog\Formatter\FormatterInterface
+ */
+ protected function formatter()
+ {
+ $formatter = new LineFormatter(null, null, true, true);
+ $formatter->includeStacktraces();
+
+ return $formatter;
+ }
+
+ /**
+ * Extract the log channel from the given configuration.
+ *
+ * @param array $config
+ *
+ * @return string
+ */
+ protected function parseChannel(array $config): string
+ {
+ return $config['name'] ?? 'EasyWeChat';
+ }
+
+ /**
+ * Parse the string level into a Monolog constant.
+ *
+ * @param array $config
+ *
+ * @return int
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function level(array $config): int
+ {
+ $level = $config['level'] ?? 'debug';
+
+ if (isset($this->levels[$level])) {
+ return $this->levels[$level];
+ }
+
+ throw new InvalidArgumentException('Invalid log level.');
+ }
+
+ /**
+ * Get the default log driver name.
+ *
+ * @return string
+ */
+ public function getDefaultDriver(): ?string
+ {
+ return $this->app['config']['log.default'];
+ }
+
+ /**
+ * Set the default log driver name.
+ *
+ * @param string $name
+ */
+ public function setDefaultDriver($name)
+ {
+ $this->app['config']['log.default'] = $name;
+ }
+
+ /**
+ * Register a custom driver creator Closure.
+ *
+ * @param string $driver
+ * @param \Closure $callback
+ *
+ * @return $this
+ */
+ public function extend(string $driver, \Closure $callback): LogManager
+ {
+ $this->customCreators[$driver] = $callback->bindTo($this, $this);
+
+ return $this;
+ }
+
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function emergency($message, array $context = []): void
+ {
+ $this->driver()->emergency($message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return void
+ *
+ * @throws \Exception
+ */
+ public function alert($message, array $context = []): void
+ {
+ $this->driver()->alert($message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \Exception
+ */
+ public function critical($message, array $context = []): void
+ {
+ $this->driver()->critical($message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \Exception
+ */
+ public function error($message, array $context = []): void
+ {
+ $this->driver()->error($message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \Exception
+ */
+ public function warning($message, array $context = []): void
+ {
+ $this->driver()->warning($message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \Exception
+ */
+ public function notice($message, array $context = []): void
+ {
+ $this->driver()->notice($message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \Exception
+ */
+ public function info($message, array $context = []): void
+ {
+ $this->driver()->info($message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \Exception
+ */
+ public function debug($message, array $context = []): void
+ {
+ $this->driver()->debug($message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ *
+ * @throws \Exception
+ */
+ public function log($level, $message, array $context = []): void
+ {
+ $this->driver()->log($level, $message, $context);
+ }
+
+ /**
+ * Dynamically call the default driver instance.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @throws \Exception
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->driver()->$method(...$parameters);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Article.php b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php
new file mode 100644
index 0000000..4792a1f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Article.php
@@ -0,0 +1,58 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Article.
+ */
+class Article extends Message
+{
+ /**
+ * @var string
+ */
+ protected $type = 'mpnews';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'thumb_media_id',
+ 'author',
+ 'title',
+ 'content',
+ 'digest',
+ 'source_url',
+ 'show_cover',
+ ];
+
+ /**
+ * Aliases of attribute.
+ *
+ * @var array
+ */
+ protected $jsonAliases = [
+ 'content_source_url' => 'source_url',
+ 'show_cover_pic' => 'show_cover',
+ ];
+
+ /**
+ * @var array
+ */
+ protected $required = [
+ 'thumb_media_id',
+ 'title',
+ 'content',
+ 'show_cover',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Card.php b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php
new file mode 100644
index 0000000..0e21231
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Card.php
@@ -0,0 +1,52 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Card.php.
+ *
+ * @author overtrue
+ * @copyright 2015 overtrue
+ *
+ * @see https://github.com/overtrue
+ * @see http://overtrue.me
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Card.
+ */
+class Card extends Message
+{
+ /**
+ * Message type.
+ *
+ * @var string
+ */
+ protected $type = 'wxcard';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = ['card_id'];
+
+ /**
+ * Media constructor.
+ *
+ * @param string $cardId
+ */
+ public function __construct(string $cardId)
+ {
+ parent::__construct(['card_id' => $cardId]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php
new file mode 100644
index 0000000..ae57910
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceEvent.php
@@ -0,0 +1,40 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class DeviceEvent.
+ *
+ * @property string $media_id
+ */
+class DeviceEvent extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'device_event';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'device_type',
+ 'device_id',
+ 'content',
+ 'session_id',
+ 'open_id',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php
new file mode 100644
index 0000000..87e627a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/DeviceText.php
@@ -0,0 +1,50 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class DeviceText.
+ *
+ * @property string $content
+ */
+class DeviceText extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'device_text';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'device_type',
+ 'device_id',
+ 'content',
+ 'session_id',
+ 'open_id',
+ ];
+
+ public function toXmlArray()
+ {
+ return [
+ 'DeviceType' => $this->get('device_type'),
+ 'DeviceID' => $this->get('device_id'),
+ 'SessionID' => $this->get('session_id'),
+ 'Content' => base64_encode($this->get('content')),
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/File.php b/vendor/overtrue/wechat/src/Kernel/Messages/File.php
new file mode 100644
index 0000000..a14c42f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/File.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Image.
+ *
+ * @property string $media_id
+ */
+class File extends Media
+{
+ /**
+ * @var string
+ */
+ protected $type = 'file';
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Image.php b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php
new file mode 100644
index 0000000..982033b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Image.php
@@ -0,0 +1,27 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Image.
+ *
+ * @property string $media_id
+ */
+class Image extends Media
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'image';
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/InteractiveTaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/InteractiveTaskCard.php
new file mode 100644
index 0000000..e0f7d13
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/InteractiveTaskCard.php
@@ -0,0 +1,49 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class InteractiveTaskCard.
+ *
+ * @description 企业微信 interactive_taskcard 任务卡片消息类型
+ *
+ * @author xyj2156
+ * @date 2021年5月25日 15:21:03
+ *
+ * @property string $title
+ * @property string $description
+ * @property string $url
+ * @property string $task_id
+ * @property array $btn
+ */
+class InteractiveTaskCard extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'interactive_taskcard';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'title',
+ 'description',
+ 'url',
+ 'task_id',
+ 'btn',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Link.php b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php
new file mode 100644
index 0000000..9c126b5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Link.php
@@ -0,0 +1,36 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Link.
+ */
+class Link extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'link';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'title',
+ 'description',
+ 'url',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Location.php b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php
new file mode 100644
index 0000000..f10351b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Location.php
@@ -0,0 +1,38 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Location.
+ */
+class Location extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'location';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'latitude',
+ 'longitude',
+ 'scale',
+ 'label',
+ 'precision',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Media.php b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php
new file mode 100644
index 0000000..d8706fe
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Media.php
@@ -0,0 +1,70 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+use EasyWeChat\Kernel\Contracts\MediaInterface;
+use EasyWeChat\Kernel\Support\Str;
+
+/**
+ * Class Media.
+ */
+class Media extends Message implements MediaInterface
+{
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = ['media_id'];
+
+ /**
+ * @var array
+ */
+ protected $required = [
+ 'media_id',
+ ];
+
+ /**
+ * MaterialClient constructor.
+ *
+ * @param string $mediaId
+ * @param string $type
+ * @param array $attributes
+ */
+ public function __construct(string $mediaId, $type = null, array $attributes = [])
+ {
+ parent::__construct(array_merge(['media_id' => $mediaId], $attributes));
+
+ !empty($type) && $this->setType($type);
+ }
+
+ /**
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function getMediaId(): string
+ {
+ $this->checkRequiredAttributes();
+
+ return $this->get('media_id');
+ }
+
+ public function toXmlArray()
+ {
+ return [
+ Str::studly($this->getType()) => [
+ 'MediaId' => $this->get('media_id'),
+ ],
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Message.php b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php
new file mode 100644
index 0000000..3135cb1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Message.php
@@ -0,0 +1,210 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Support\XML;
+use EasyWeChat\Kernel\Traits\HasAttributes;
+
+/**
+ * Class Messages.
+ */
+abstract class Message implements MessageInterface
+{
+ use HasAttributes;
+
+ public const TEXT = 2;
+ public const IMAGE = 4;
+ public const VOICE = 8;
+ public const VIDEO = 16;
+ public const SHORT_VIDEO = 32;
+ public const LOCATION = 64;
+ public const LINK = 128;
+ public const DEVICE_EVENT = 256;
+ public const DEVICE_TEXT = 512;
+ public const FILE = 1024;
+ public const TEXT_CARD = 2048;
+ public const TRANSFER = 4096;
+ public const EVENT = 1048576;
+ public const MINIPROGRAM_PAGE = 2097152;
+ public const MINIPROGRAM_NOTICE = 4194304;
+ public const ALL = self::TEXT | self::IMAGE | self::VOICE | self::VIDEO | self::SHORT_VIDEO | self::LOCATION | self::LINK
+ | self::DEVICE_EVENT | self::DEVICE_TEXT | self::FILE | self::TEXT_CARD | self::TRANSFER | self::EVENT
+ | self::MINIPROGRAM_PAGE | self::MINIPROGRAM_NOTICE;
+
+ /**
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * @var int
+ */
+ protected $id;
+
+ /**
+ * @var string
+ */
+ protected $to;
+
+ /**
+ * @var string
+ */
+ protected $from;
+
+ /**
+ * @var array
+ */
+ protected $properties = [];
+
+ /**
+ * @var array
+ */
+ protected $jsonAliases = [];
+
+ /**
+ * Message constructor.
+ *
+ * @param array $attributes
+ */
+ public function __construct(array $attributes = [])
+ {
+ $this->setAttributes($attributes);
+ }
+
+ /**
+ * Return type name message.
+ *
+ * @return string
+ */
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * @param string $type
+ */
+ public function setType(string $type)
+ {
+ $this->type = $type;
+ }
+
+ /**
+ * Magic getter.
+ *
+ * @param string $property
+ *
+ * @return mixed
+ */
+ public function __get($property)
+ {
+ if (property_exists($this, $property)) {
+ return $this->$property;
+ }
+
+ return $this->getAttribute($property);
+ }
+
+ /**
+ * Magic setter.
+ *
+ * @param string $property
+ * @param mixed $value
+ *
+ * @return Message
+ */
+ public function __set($property, $value)
+ {
+ if (property_exists($this, $property)) {
+ $this->$property = $value;
+ } else {
+ $this->setAttribute($property, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $appends
+ *
+ * @return array
+ */
+ public function transformForJsonRequestWithoutType(array $appends = [])
+ {
+ return $this->transformForJsonRequest($appends, false);
+ }
+
+ /**
+ * @param array $appends
+ * @param bool $withType
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function transformForJsonRequest(array $appends = [], $withType = true): array
+ {
+ if (!$withType) {
+ return $this->propertiesToArray([], $this->jsonAliases);
+ }
+ $messageType = $this->getType();
+ $data = array_merge(['msgtype' => $messageType], $appends);
+
+ $data[$messageType] = array_merge($data[$messageType] ?? [], $this->propertiesToArray([], $this->jsonAliases));
+
+ return $data;
+ }
+
+ /**
+ * @param array $appends
+ * @param bool $returnAsArray
+ *
+ * @return string
+ */
+ public function transformToXml(array $appends = [], bool $returnAsArray = false): string
+ {
+ $data = array_merge(['MsgType' => $this->getType()], $this->toXmlArray(), $appends);
+
+ return $returnAsArray ? $data : XML::build($data);
+ }
+
+ /**
+ * @param array $data
+ * @param array $aliases
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function propertiesToArray(array $data, array $aliases = []): array
+ {
+ $this->checkRequiredAttributes();
+
+ foreach ($this->attributes as $property => $value) {
+ if (is_null($value) && !$this->isRequired($property)) {
+ continue;
+ }
+ $alias = array_search($property, $aliases, true);
+
+ $data[$alias ?: $property] = $this->get($property);
+ }
+
+ return $data;
+ }
+
+ public function toXmlArray()
+ {
+ throw new RuntimeException(sprintf('Class "%s" cannot support transform to XML message.', __CLASS__));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php
new file mode 100644
index 0000000..e4973b1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/MiniProgramPage.php
@@ -0,0 +1,31 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class MiniProgramPage.
+ */
+class MiniProgramPage extends Message
+{
+ protected $type = 'miniprogrampage';
+
+ protected $properties = [
+ 'title',
+ 'appid',
+ 'pagepath',
+ 'thumb_media_id',
+ ];
+
+ protected $required = [
+ 'thumb_media_id', 'appid', 'pagepath',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/MiniprogramNotice.php b/vendor/overtrue/wechat/src/Kernel/Messages/MiniprogramNotice.php
new file mode 100644
index 0000000..c18b259
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/MiniprogramNotice.php
@@ -0,0 +1,13 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Music.
+ *
+ * @property string $url
+ * @property string $hq_url
+ * @property string $title
+ * @property string $description
+ * @property string $thumb_media_id
+ * @property string $format
+ */
+class Music extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'music';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'title',
+ 'description',
+ 'url',
+ 'hq_url',
+ 'thumb_media_id',
+ 'format',
+ ];
+
+ /**
+ * Aliases of attribute.
+ *
+ * @var array
+ */
+ protected $jsonAliases = [
+ 'musicurl' => 'url',
+ 'hqmusicurl' => 'hq_url',
+ ];
+
+ public function toXmlArray()
+ {
+ $music = [
+ 'Music' => [
+ 'Title' => $this->get('title'),
+ 'Description' => $this->get('description'),
+ 'MusicUrl' => $this->get('url'),
+ 'HQMusicUrl' => $this->get('hq_url'),
+ ],
+ ];
+ if ($thumbMediaId = $this->get('thumb_media_id')) {
+ $music['Music']['ThumbMediaId'] = $thumbMediaId;
+ }
+
+ return $music;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/News.php b/vendor/overtrue/wechat/src/Kernel/Messages/News.php
new file mode 100644
index 0000000..2ad46e8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/News.php
@@ -0,0 +1,73 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class News.
+ *
+ * @author overtrue
+ */
+class News extends Message
+{
+ /**
+ * @var string
+ */
+ protected $type = 'news';
+
+ /**
+ * @var array
+ */
+ protected $properties = [
+ 'items',
+ ];
+
+ /**
+ * News constructor.
+ *
+ * @param array $items
+ */
+ public function __construct(array $items = [])
+ {
+ parent::__construct(compact('items'));
+ }
+
+ /**
+ * @param array $data
+ * @param array $aliases
+ *
+ * @return array
+ */
+ public function propertiesToArray(array $data, array $aliases = []): array
+ {
+ return ['articles' => array_map(function ($item) {
+ if ($item instanceof NewsItem) {
+ return $item->toJsonArray();
+ }
+ }, $this->get('items'))];
+ }
+
+ public function toXmlArray()
+ {
+ $items = [];
+
+ foreach ($this->get('items') as $item) {
+ if ($item instanceof NewsItem) {
+ $items[] = $item->toXmlArray();
+ }
+ }
+
+ return [
+ 'ArticleCount' => count($items),
+ 'Articles' => $items,
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php
new file mode 100644
index 0000000..50bf336
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/NewsItem.php
@@ -0,0 +1,57 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class NewsItem.
+ */
+class NewsItem extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'news';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'title',
+ 'description',
+ 'url',
+ 'image',
+ ];
+
+ public function toJsonArray()
+ {
+ return [
+ 'title' => $this->get('title'),
+ 'description' => $this->get('description'),
+ 'url' => $this->get('url'),
+ 'picurl' => $this->get('image'),
+ ];
+ }
+
+ public function toXmlArray()
+ {
+ return [
+ 'Title' => $this->get('title'),
+ 'Description' => $this->get('description'),
+ 'Url' => $this->get('url'),
+ 'PicUrl' => $this->get('image'),
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php
new file mode 100644
index 0000000..53f2a78
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Raw.php
@@ -0,0 +1,56 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Raw.
+ */
+class Raw extends Message
+{
+ /**
+ * @var string
+ */
+ protected $type = 'raw';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = ['content'];
+
+ /**
+ * Constructor.
+ *
+ * @param string $content
+ */
+ public function __construct(string $content)
+ {
+ parent::__construct(['content' => strval($content)]);
+ }
+
+ /**
+ * @param array $appends
+ * @param bool $withType
+ *
+ * @return array
+ */
+ public function transformForJsonRequest(array $appends = [], $withType = true): array
+ {
+ return json_decode($this->content, true) ?? [];
+ }
+
+ public function __toString()
+ {
+ return $this->get('content') ?? '';
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/ReplyInteractiveTaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/ReplyInteractiveTaskCard.php
new file mode 100644
index 0000000..f917935
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/ReplyInteractiveTaskCard.php
@@ -0,0 +1,60 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class ReplyInteractiveTaskCard
+ *
+ * @property array{'replace_name':string} $properties
+ *
+ * @description 专门为回复 InteractiveTaskCard 类型任务卡片消息而创建的类型
+ * @author xyj2156
+ *
+ * @package App\Extend\EnterpriseApplication\BusinessWX\Message
+ */
+class ReplyInteractiveTaskCard extends Message
+{
+ /**
+ * Message Type
+ *
+ * @var string
+ */
+ protected $type = 'update_taskcard';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'replace_name',
+ ];
+
+ /**
+ * ReplyInteractiveTaskCard constructor.
+ *
+ * @param string $replace_name
+ */
+ public function __construct(string $replace_name = '')
+ {
+ parent::__construct(compact('replace_name'));
+ }
+
+ public function toXmlArray()
+ {
+ return [
+ 'TaskCard' => [
+ 'ReplaceName' => $this->get('replace_name'),
+ ],
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php
new file mode 100644
index 0000000..1dc1db9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/ShortVideo.php
@@ -0,0 +1,30 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class ShortVideo.
+ *
+ * @property string $title
+ * @property string $media_id
+ * @property string $description
+ * @property string $thumb_media_id
+ */
+class ShortVideo extends Video
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'shortvideo';
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php
new file mode 100644
index 0000000..d02c411
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/TaskCard.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class TaskCard.
+ *
+ * @property string $title
+ * @property string $description
+ * @property string $url
+ * @property string $task_id
+ * @property array $btn
+ */
+class TaskCard extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'taskcard';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'title',
+ 'description',
+ 'url',
+ 'task_id',
+ 'btn',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Text.php b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php
new file mode 100644
index 0000000..050e54a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Text.php
@@ -0,0 +1,54 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Text.
+ *
+ * @property string $content
+ */
+class Text extends Message
+{
+ /**
+ * Message type.
+ *
+ * @var string
+ */
+ protected $type = 'text';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = ['content'];
+
+ /**
+ * Text constructor.
+ *
+ * @param string $content
+ */
+ public function __construct(string $content = '')
+ {
+ parent::__construct(compact('content'));
+ }
+
+ /**
+ * @return array
+ */
+ public function toXmlArray()
+ {
+ return [
+ 'Content' => $this->get('content'),
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php
new file mode 100644
index 0000000..edfb7c5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/TextCard.php
@@ -0,0 +1,40 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Text.
+ *
+ * @property string $title
+ * @property string $description
+ * @property string $url
+ */
+class TextCard extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'textcard';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'title',
+ 'description',
+ 'url',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php
new file mode 100644
index 0000000..ff27d1a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Transfer.php
@@ -0,0 +1,56 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Transfer.
+ *
+ * @property string $to
+ * @property string $account
+ */
+class Transfer extends Message
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'transfer_customer_service';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'account',
+ ];
+
+ /**
+ * Transfer constructor.
+ *
+ * @param string|null $account
+ */
+ public function __construct(string $account = null)
+ {
+ parent::__construct(compact('account'));
+ }
+
+ public function toXmlArray()
+ {
+ return empty($this->get('account')) ? [] : [
+ 'TransInfo' => [
+ 'KfAccount' => $this->get('account'),
+ ],
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Video.php b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php
new file mode 100644
index 0000000..90f72a0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Video.php
@@ -0,0 +1,65 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Video.
+ *
+ * @property string $video
+ * @property string $title
+ * @property string $media_id
+ * @property string $description
+ * @property string $thumb_media_id
+ */
+class Video extends Media
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'video';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'title',
+ 'description',
+ 'media_id',
+ 'thumb_media_id',
+ ];
+
+ /**
+ * Video constructor.
+ *
+ * @param string $mediaId
+ * @param array $attributes
+ */
+ public function __construct(string $mediaId, array $attributes = [])
+ {
+ parent::__construct($mediaId, 'video', $attributes);
+ }
+
+ public function toXmlArray()
+ {
+ return [
+ 'Video' => [
+ 'MediaId' => $this->get('media_id'),
+ 'Title' => $this->get('title'),
+ 'Description' => $this->get('description'),
+ ],
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php
new file mode 100644
index 0000000..ff723ea
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Messages/Voice.php
@@ -0,0 +1,37 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Messages;
+
+/**
+ * Class Voice.
+ *
+ * @property string $media_id
+ */
+class Voice extends Media
+{
+ /**
+ * Messages type.
+ *
+ * @var string
+ */
+ protected $type = 'voice';
+
+ /**
+ * Properties.
+ *
+ * @var array
+ */
+ protected $properties = [
+ 'media_id',
+ 'recognition',
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php
new file mode 100644
index 0000000..24ff919
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/ConfigServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use EasyWeChat\Kernel\Config;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ConfigServiceProvider.
+ *
+ * @author overtrue
+ */
+class ConfigServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ !isset($pimple['config']) && $pimple['config'] = function ($app) {
+ return new Config($app->getConfig());
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php
new file mode 100644
index 0000000..97b2b99
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/EventDispatcherServiceProvider.php
@@ -0,0 +1,47 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+
+/**
+ * Class EventDispatcherServiceProvider.
+ *
+ * @author mingyoung
+ */
+class EventDispatcherServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ !isset($pimple['events']) && $pimple['events'] = function ($app) {
+ $dispatcher = new EventDispatcher();
+
+ foreach ($app->config->get('events.listen', []) as $event => $listeners) {
+ foreach ($listeners as $listener) {
+ $dispatcher->addListener($event, $listener);
+ }
+ }
+
+ return $dispatcher;
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php
new file mode 100644
index 0000000..1d8badd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/ExtensionServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use EasyWeChatComposer\Extension;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ExtensionServiceProvider.
+ *
+ * @author overtrue
+ */
+class ExtensionServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ !isset($pimple['extension']) && $pimple['extension'] = function ($app) {
+ return new Extension($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php
new file mode 100644
index 0000000..e21edfa
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/HttpClientServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use GuzzleHttp\Client;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class HttpClientServiceProvider.
+ *
+ * @author overtrue
+ */
+class HttpClientServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ !isset($pimple['http_client']) && $pimple['http_client'] = function ($app) {
+ return new Client($app['config']->get('http', []));
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php
new file mode 100644
index 0000000..88058bb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/LogServiceProvider.php
@@ -0,0 +1,47 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use EasyWeChat\Kernel\Log\LogManager;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class LoggingServiceProvider.
+ *
+ * @author overtrue
+ */
+class LogServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ !isset($pimple['log']) && $pimple['log'] = function ($app) {
+ $config = $app['config']->get('log');
+
+ if (!empty($config)) {
+ $app->rebind('config', $app['config']->merge($config));
+ }
+
+ return new LogManager($app);
+ };
+
+ !isset($pimple['logger']) && $pimple['logger'] = $pimple['log'];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php
new file mode 100644
index 0000000..d38522e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Providers/RequestServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Providers;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class RequestServiceProvider.
+ *
+ * @author overtrue
+ */
+class RequestServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param Container $pimple A container instance
+ */
+ public function register(Container $pimple)
+ {
+ !isset($pimple['request']) && $pimple['request'] = function () {
+ return Request::createFromGlobals();
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/ServerGuard.php b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php
new file mode 100644
index 0000000..066b220
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/ServerGuard.php
@@ -0,0 +1,375 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Exceptions\BadRequestException;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Messages\Message;
+use EasyWeChat\Kernel\Messages\News;
+use EasyWeChat\Kernel\Messages\NewsItem;
+use EasyWeChat\Kernel\Messages\Raw as RawMessage;
+use EasyWeChat\Kernel\Messages\Text;
+use EasyWeChat\Kernel\Support\XML;
+use EasyWeChat\Kernel\Traits\Observable;
+use EasyWeChat\Kernel\Traits\ResponseCastable;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Class ServerGuard.
+ *
+ * 1. url 里的 signature 只是将 token+nonce+timestamp 得到的签名,只是用于验证当前请求的,在公众号环境下一直有
+ * 2. 企业号消息发送时是没有的,因为固定为完全模式,所以 url 里不会存在 signature, 只有 msg_signature 用于解密消息的
+ *
+ * @author overtrue
+ */
+class ServerGuard
+{
+ use Observable;
+ use ResponseCastable;
+
+ /**
+ * @var bool
+ */
+ protected $alwaysValidate = false;
+
+ /**
+ * Empty string.
+ */
+ public const SUCCESS_EMPTY_RESPONSE = 'success';
+
+ /**
+ * @var array
+ */
+ public const MESSAGE_TYPE_MAPPING = [
+ 'text' => Message::TEXT,
+ 'image' => Message::IMAGE,
+ 'voice' => Message::VOICE,
+ 'video' => Message::VIDEO,
+ 'shortvideo' => Message::SHORT_VIDEO,
+ 'location' => Message::LOCATION,
+ 'link' => Message::LINK,
+ 'device_event' => Message::DEVICE_EVENT,
+ 'device_text' => Message::DEVICE_TEXT,
+ 'event' => Message::EVENT,
+ 'file' => Message::FILE,
+ 'miniprogrampage' => Message::MINIPROGRAM_PAGE,
+ ];
+
+ /**
+ * @var \EasyWeChat\Kernel\ServiceContainer
+ */
+ protected $app;
+
+ /**
+ * Constructor.
+ *
+ * @codeCoverageIgnore
+ *
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+
+ foreach ($this->app->extension->observers() as $observer) {
+ call_user_func_array([$this, 'push'], $observer);
+ }
+ }
+
+ /**
+ * Handle and return response.
+ *
+ * @return Response
+ *
+ * @throws BadRequestException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function serve(): Response
+ {
+ $this->app['logger']->debug('Request received:', [
+ 'method' => $this->app['request']->getMethod(),
+ 'uri' => $this->app['request']->getUri(),
+ 'content-type' => $this->app['request']->getContentType(),
+ 'content' => $this->app['request']->getContent(),
+ ]);
+
+ $response = $this->validate()->resolve();
+
+ $this->app['logger']->debug('Server response created:', ['content' => $response->getContent()]);
+
+ return $response;
+ }
+
+ /**
+ * @return $this
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+ */
+ public function validate()
+ {
+ if (!$this->alwaysValidate && !$this->isSafeMode()) {
+ return $this;
+ }
+
+ if ($this->app['request']->get('signature') !== $this->signature([
+ $this->getToken(),
+ $this->app['request']->get('timestamp'),
+ $this->app['request']->get('nonce'),
+ ])) {
+ throw new BadRequestException('Invalid request signature.', 400);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Force validate request.
+ *
+ * @return $this
+ */
+ public function forceValidate()
+ {
+ $this->alwaysValidate = true;
+
+ return $this;
+ }
+
+ /**
+ * Get request message.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|string
+ *
+ * @throws BadRequestException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getMessage()
+ {
+ $message = $this->parseMessage($this->app['request']->getContent(false));
+
+ if (!is_array($message) || empty($message)) {
+ throw new BadRequestException('No message received.');
+ }
+
+ if ($this->isSafeMode() && !empty($message['Encrypt'])) {
+ $message = $this->decryptMessage($message);
+
+ // Handle JSON format.
+ $dataSet = json_decode($message, true);
+
+ if ($dataSet && (JSON_ERROR_NONE === json_last_error())) {
+ return $dataSet;
+ }
+
+ $message = XML::parse($message);
+ }
+
+ return $this->detectAndCastResponseToType($message, $this->app->config->get('response_type'));
+ }
+
+ /**
+ * Resolve server request and return the response.
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ protected function resolve(): Response
+ {
+ $result = $this->handleRequest();
+
+ if ($this->shouldReturnRawResponse()) {
+ $response = new Response($result['response']);
+ } else {
+ $response = new Response(
+ $this->buildResponse($result['to'], $result['from'], $result['response']),
+ 200,
+ ['Content-Type' => 'application/xml']
+ );
+ }
+
+ $this->app->events->dispatch(new Events\ServerGuardResponseCreated($response));
+
+ return $response;
+ }
+
+ /**
+ * @return string|null
+ */
+ protected function getToken()
+ {
+ return $this->app['config']['token'];
+ }
+
+ /**
+ * @param string $to
+ * @param string $from
+ * @param \EasyWeChat\Kernel\Contracts\MessageInterface|string|int $message
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function buildResponse(string $to, string $from, $message)
+ {
+ if (empty($message) || self::SUCCESS_EMPTY_RESPONSE === $message) {
+ return self::SUCCESS_EMPTY_RESPONSE;
+ }
+
+ if ($message instanceof RawMessage) {
+ return $message->get('content', self::SUCCESS_EMPTY_RESPONSE);
+ }
+
+ if (is_string($message) || is_numeric($message)) {
+ $message = new Text((string) $message);
+ }
+
+ if (is_array($message) && reset($message) instanceof NewsItem) {
+ $message = new News($message);
+ }
+
+ if (!($message instanceof Message)) {
+ throw new InvalidArgumentException(sprintf('Invalid Messages type "%s".', gettype($message)));
+ }
+
+ return $this->buildReply($to, $from, $message);
+ }
+
+ /**
+ * Handle request.
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ protected function handleRequest(): array
+ {
+ $castedMessage = $this->getMessage();
+
+ $messageArray = $this->detectAndCastResponseToType($castedMessage, 'array');
+
+ $response = $this->dispatch(self::MESSAGE_TYPE_MAPPING[$messageArray['MsgType'] ?? $messageArray['msg_type'] ?? 'text'], $castedMessage);
+
+ return [
+ 'to' => $messageArray['FromUserName'] ?? '',
+ 'from' => $messageArray['ToUserName'] ?? '',
+ 'response' => $response,
+ ];
+ }
+
+ /**
+ * Build reply XML.
+ *
+ * @param string $to
+ * @param string $from
+ * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+ *
+ * @return string
+ */
+ protected function buildReply(string $to, string $from, MessageInterface $message): string
+ {
+ $prepends = [
+ 'ToUserName' => $to,
+ 'FromUserName' => $from,
+ 'CreateTime' => time(),
+ 'MsgType' => $message->getType(),
+ ];
+
+ $response = $message->transformToXml($prepends);
+
+ if ($this->isSafeMode()) {
+ $this->app['logger']->debug('Messages safe mode is enabled.');
+ $response = $this->app['encryptor']->encrypt($response);
+ }
+
+ return $response;
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return string
+ */
+ protected function signature(array $params)
+ {
+ sort($params, SORT_STRING);
+
+ return sha1(implode($params));
+ }
+
+ /**
+ * Parse message array from raw php input.
+ *
+ * @param string $content
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+ */
+ protected function parseMessage($content)
+ {
+ try {
+ if (0 === stripos($content, '<')) {
+ $content = XML::parse($content);
+ } else {
+ // Handle JSON format.
+ $dataSet = json_decode($content, true);
+ if ($dataSet && (JSON_ERROR_NONE === json_last_error())) {
+ $content = $dataSet;
+ }
+ }
+
+ return (array) $content;
+ } catch (\Exception $e) {
+ throw new BadRequestException(sprintf('Invalid message content:(%s) %s', $e->getCode(), $e->getMessage()), $e->getCode());
+ }
+ }
+
+ /**
+ * Check the request message safe mode.
+ *
+ * @return bool
+ */
+ protected function isSafeMode(): bool
+ {
+ return $this->app['request']->get('signature') && 'aes' === $this->app['request']->get('encrypt_type');
+ }
+
+ /**
+ * @return bool
+ */
+ protected function shouldReturnRawResponse(): bool
+ {
+ return false;
+ }
+
+ /**
+ * @param array $message
+ *
+ * @return mixed
+ */
+ protected function decryptMessage(array $message)
+ {
+ return $message = $this->app['encryptor']->decrypt(
+ $message['Encrypt'],
+ $this->app['request']->get('msg_signature'),
+ $this->app['request']->get('nonce'),
+ $this->app['request']->get('timestamp')
+ );
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php
new file mode 100644
index 0000000..be1e0a1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/ServiceContainer.php
@@ -0,0 +1,167 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel;
+
+use EasyWeChat\Kernel\Providers\ConfigServiceProvider;
+use EasyWeChat\Kernel\Providers\EventDispatcherServiceProvider;
+use EasyWeChat\Kernel\Providers\ExtensionServiceProvider;
+use EasyWeChat\Kernel\Providers\HttpClientServiceProvider;
+use EasyWeChat\Kernel\Providers\LogServiceProvider;
+use EasyWeChat\Kernel\Providers\RequestServiceProvider;
+use EasyWeChatComposer\Traits\WithAggregator;
+use Pimple\Container;
+
+/**
+ * Class ServiceContainer.
+ *
+ * @author overtrue
+ *
+ * @property \EasyWeChat\Kernel\Config $config
+ * @property \Symfony\Component\HttpFoundation\Request $request
+ * @property \GuzzleHttp\Client $http_client
+ * @property \Monolog\Logger $logger
+ * @property \Symfony\Component\EventDispatcher\EventDispatcher $events
+ */
+class ServiceContainer extends Container
+{
+ use WithAggregator;
+
+ /**
+ * @var string
+ */
+ protected $id;
+
+ /**
+ * @var array
+ */
+ protected $providers = [];
+
+ /**
+ * @var array
+ */
+ protected $defaultConfig = [];
+
+ /**
+ * @var array
+ */
+ protected $userConfig = [];
+
+ /**
+ * Constructor.
+ *
+ * @param array $config
+ * @param array $prepends
+ * @param string|null $id
+ */
+ public function __construct(array $config = [], array $prepends = [], string $id = null)
+ {
+ $this->userConfig = $config;
+
+ parent::__construct($prepends);
+
+ $this->registerProviders($this->getProviders());
+
+ $this->id = $id;
+
+ $this->aggregate();
+
+ $this->events->dispatch(new Events\ApplicationInitialized($this));
+ }
+
+ /**
+ * @return string
+ */
+ public function getId()
+ {
+ return $this->id ?? $this->id = md5(json_encode($this->userConfig));
+ }
+
+ /**
+ * @return array
+ */
+ public function getConfig()
+ {
+ $base = [
+ // http://docs.guzzlephp.org/en/stable/request-options.html
+ 'http' => [
+ 'timeout' => 30.0,
+ 'base_uri' => 'https://api.weixin.qq.com/',
+ ],
+ ];
+
+ return array_replace_recursive($base, $this->defaultConfig, $this->userConfig);
+ }
+
+ /**
+ * Return all providers.
+ *
+ * @return array
+ */
+ public function getProviders()
+ {
+ return array_merge([
+ ConfigServiceProvider::class,
+ LogServiceProvider::class,
+ RequestServiceProvider::class,
+ HttpClientServiceProvider::class,
+ ExtensionServiceProvider::class,
+ EventDispatcherServiceProvider::class,
+ ], $this->providers);
+ }
+
+ /**
+ * @param string $id
+ * @param mixed $value
+ */
+ public function rebind($id, $value)
+ {
+ $this->offsetUnset($id);
+ $this->offsetSet($id, $value);
+ }
+
+ /**
+ * Magic get access.
+ *
+ * @param string $id
+ *
+ * @return mixed
+ */
+ public function __get($id)
+ {
+ if ($this->shouldDelegate($id)) {
+ return $this->delegateTo($id);
+ }
+
+ return $this->offsetGet($id);
+ }
+
+ /**
+ * Magic set access.
+ *
+ * @param string $id
+ * @param mixed $value
+ */
+ public function __set($id, $value)
+ {
+ $this->offsetSet($id, $value);
+ }
+
+ /**
+ * @param array $providers
+ */
+ public function registerProviders(array $providers)
+ {
+ foreach ($providers as $provider) {
+ parent::register(new $provider());
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/AES.php b/vendor/overtrue/wechat/src/Kernel/Support/AES.php
new file mode 100644
index 0000000..73b1b24
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/AES.php
@@ -0,0 +1,85 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+/**
+ * Class AES.
+ *
+ * @author overtrue
+ */
+class AES
+{
+ /**
+ * @param string $text
+ * @param string $key
+ * @param string $iv
+ * @param int $option
+ *
+ * @return string
+ */
+ public static function encrypt(string $text, string $key, string $iv, int $option = OPENSSL_RAW_DATA): string
+ {
+ self::validateKey($key);
+ self::validateIv($iv);
+
+ return openssl_encrypt($text, self::getMode($key), $key, $option, $iv);
+ }
+
+ /**
+ * @param string $cipherText
+ * @param string $key
+ * @param string $iv
+ * @param int $option
+ * @param string|null $method
+ *
+ * @return string
+ */
+ public static function decrypt(string $cipherText, string $key, string $iv, int $option = OPENSSL_RAW_DATA, $method = null): string
+ {
+ self::validateKey($key);
+ self::validateIv($iv);
+
+ return openssl_decrypt($cipherText, $method ?: self::getMode($key), $key, $option, $iv);
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return string
+ */
+ public static function getMode($key)
+ {
+ return 'aes-'.(8 * strlen($key)).'-cbc';
+ }
+
+ /**
+ * @param string $key
+ */
+ public static function validateKey(string $key)
+ {
+ if (!in_array(strlen($key), [16, 24, 32], true)) {
+ throw new \InvalidArgumentException(sprintf('Key length must be 16, 24, or 32 bytes; got key len (%s).', strlen($key)));
+ }
+ }
+
+ /**
+ * @param string $iv
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function validateIv(string $iv)
+ {
+ if (!empty($iv) && 16 !== strlen($iv)) {
+ throw new \InvalidArgumentException('IV length must be 16 bytes.');
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Arr.php b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php
new file mode 100644
index 0000000..c251da2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Arr.php
@@ -0,0 +1,466 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+/**
+ * Array helper from Illuminate\Support\Arr.
+ */
+class Arr
+{
+ /**
+ * Add an element to an array using "dot" notation if it doesn't exist.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return array
+ */
+ public static function add(array $array, $key, $value)
+ {
+ if (is_null(static::get($array, $key))) {
+ static::set($array, $key, $value);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Cross join the given arrays, returning all possible permutations.
+ *
+ * @param array ...$arrays
+ *
+ * @return array
+ */
+ public static function crossJoin(...$arrays)
+ {
+ $results = [[]];
+
+ foreach ($arrays as $index => $array) {
+ $append = [];
+
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+
+ $append[] = $product;
+ }
+ }
+
+ $results = $append;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public static function divide(array $array)
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ *
+ * @param array $array
+ * @param string $prepend
+ *
+ * @return array
+ */
+ public static function dot(array $array, $prepend = '')
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ if (is_array($value) && !empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
+ } else {
+ $results[$prepend.$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of items.
+ *
+ * @param array $array
+ * @param array|string $keys
+ *
+ * @return array
+ */
+ public static function except(array $array, $keys)
+ {
+ static::forget($array, $keys);
+
+ return $array;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param array $array
+ * @param string|int $key
+ *
+ * @return bool
+ */
+ public static function exists(array $array, $key)
+ {
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function first(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return $default;
+ }
+
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+
+ foreach ($array as $key => $value) {
+ if (call_user_func($callback, $value, $key)) {
+ return $value;
+ }
+ }
+
+ return $default;
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function last(array $array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ return empty($array) ? $default : end($array);
+ }
+
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ *
+ * @param array $array
+ * @param int $depth
+ *
+ * @return array
+ */
+ public static function flatten(array $array, $depth = \PHP_INT_MAX)
+ {
+ return array_reduce($array, function ($result, $item) use ($depth) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+
+ if (!is_array($item)) {
+ return array_merge($result, [$item]);
+ } elseif (1 === $depth) {
+ return array_merge($result, array_values($item));
+ }
+
+ return array_merge($result, static::flatten($item, $depth - 1));
+ }, []);
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array $array
+ * @param array|string $keys
+ */
+ public static function forget(array &$array, $keys)
+ {
+ $original = &$array;
+
+ $keys = (array) $keys;
+
+ if (0 === count($keys)) {
+ return;
+ }
+
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+
+ continue;
+ }
+
+ $parts = explode('.', $key);
+
+ // clean up before each pass
+ $array = &$original;
+
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function get(array $array, $key, $default = null)
+ {
+ if (is_null($key)) {
+ return $array;
+ }
+
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return $default;
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param array $array
+ * @param string|array $keys
+ *
+ * @return bool
+ */
+ public static function has(array $array, $keys)
+ {
+ if (is_null($keys)) {
+ return false;
+ }
+
+ $keys = (array) $keys;
+
+ if (empty($array)) {
+ return false;
+ }
+
+ if ($keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+
+ if (static::exists($array, $key)) {
+ continue;
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ *
+ * @param array $array
+ *
+ * @return bool
+ */
+ public static function isAssoc(array $array)
+ {
+ $keys = array_keys($array);
+
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array $array
+ * @param array|string $keys
+ *
+ * @return array
+ */
+ public static function only(array $array, $keys)
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @param array $array
+ * @param mixed $value
+ * @param mixed $key
+ *
+ * @return array
+ */
+ public static function prepend(array $array, $value, $key = null)
+ {
+ if (is_null($key)) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public static function pull(array &$array, $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * Get a 1 value from an array.
+ *
+ * @param array $array
+ * @param int|null $amount
+ *
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random(array $array, int $amount = null)
+ {
+ if (is_null($amount)) {
+ return $array[array_rand($array)];
+ }
+
+ $keys = array_rand($array, $amount);
+
+ $results = [];
+
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ *
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param array $array
+ * @param string $key
+ * @param mixed $value
+ *
+ * @return array
+ */
+ public static function set(array &$array, string $key, $value)
+ {
+ $keys = explode('.', $key);
+
+ while (count($keys) > 1) {
+ $key = array_shift($keys);
+
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (!isset($array[$key]) || !is_array($array[$key])) {
+ $array[$key] = [];
+ }
+
+ $array = &$array[$key];
+ }
+
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ /**
+ * Filter the array using the given callback.
+ *
+ * @param array $array
+ * @param callable $callback
+ *
+ * @return array
+ */
+ public static function where(array $array, callable $callback)
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * If the given value is not an array, wrap it in one.
+ *
+ * @param mixed $value
+ *
+ * @return array
+ */
+ public static function wrap($value)
+ {
+ return !is_array($value) ? [$value] : $value;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php
new file mode 100644
index 0000000..72e0b04
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/ArrayAccessible.php
@@ -0,0 +1,66 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use ArrayAccess;
+use ArrayIterator;
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use IteratorAggregate;
+
+/**
+ * Class ArrayAccessible.
+ *
+ * @author overtrue
+ */
+class ArrayAccessible implements ArrayAccess, IteratorAggregate, Arrayable
+{
+ private $array;
+
+ public function __construct(array $array = [])
+ {
+ $this->array = $array;
+ }
+
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->array);
+ }
+
+ public function offsetGet($offset)
+ {
+ return $this->array[$offset];
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if (null === $offset) {
+ $this->array[] = $value;
+ } else {
+ $this->array[$offset] = $value;
+ }
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->array[$offset]);
+ }
+
+ public function getIterator()
+ {
+ return new ArrayIterator($this->array);
+ }
+
+ public function toArray()
+ {
+ return $this->array;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Collection.php b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php
new file mode 100644
index 0000000..d1b77a9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Collection.php
@@ -0,0 +1,440 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use ArrayAccess;
+use ArrayIterator;
+use Countable;
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use IteratorAggregate;
+use JsonSerializable;
+use Serializable;
+
+/**
+ * Class Collection.
+ */
+class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable, Arrayable
+{
+ /**
+ * The collection data.
+ *
+ * @var array
+ */
+ protected $items = [];
+
+ /**
+ * set data.
+ *
+ * @param array $items
+ */
+ public function __construct(array $items = [])
+ {
+ foreach ($items as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+
+ /**
+ * Return all items.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return $this->items;
+ }
+
+ /**
+ * Return specific items.
+ *
+ * @param array $keys
+ *
+ * @return \EasyWeChat\Kernel\Support\Collection
+ */
+ public function only(array $keys)
+ {
+ $return = [];
+
+ foreach ($keys as $key) {
+ $value = $this->get($key);
+
+ if (!is_null($value)) {
+ $return[$key] = $value;
+ }
+ }
+
+ return new static($return);
+ }
+
+ /**
+ * Get all items except for those with the specified keys.
+ *
+ * @param mixed $keys
+ *
+ * @return static
+ */
+ public function except($keys)
+ {
+ $keys = is_array($keys) ? $keys : func_get_args();
+
+ return new static(Arr::except($this->items, $keys));
+ }
+
+ /**
+ * Merge data.
+ *
+ * @param Collection|array $items
+ *
+ * @return \EasyWeChat\Kernel\Support\Collection
+ */
+ public function merge($items)
+ {
+ $clone = new static($this->all());
+
+ foreach ($items as $key => $value) {
+ $clone->set($key, $value);
+ }
+
+ return $clone;
+ }
+
+ /**
+ * To determine Whether the specified element exists.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ return !is_null(Arr::get($this->items, $key));
+ }
+
+ /**
+ * Retrieve the first item.
+ *
+ * @return mixed
+ */
+ public function first()
+ {
+ return reset($this->items);
+ }
+
+ /**
+ * Retrieve the last item.
+ *
+ * @return bool
+ */
+ public function last()
+ {
+ $end = end($this->items);
+
+ reset($this->items);
+
+ return $end;
+ }
+
+ /**
+ * add the item value.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function add($key, $value)
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Set the item value.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function set($key, $value)
+ {
+ Arr::set($this->items, $key, $value);
+ }
+
+ /**
+ * Retrieve item from Collection.
+ *
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ return Arr::get($this->items, $key, $default);
+ }
+
+ /**
+ * Remove item form Collection.
+ *
+ * @param string $key
+ */
+ public function forget($key)
+ {
+ Arr::forget($this->items, $key);
+ }
+
+ /**
+ * Build to array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->all();
+ }
+
+ /**
+ * Build to json.
+ *
+ * @param int $option
+ *
+ * @return string
+ */
+ public function toJson($option = JSON_UNESCAPED_UNICODE)
+ {
+ return json_encode($this->all(), $option);
+ }
+
+ /**
+ * To string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toJson();
+ }
+
+ /**
+ * (PHP 5 >= 5.4.0)
+ * Specify data which should be serialized to JSON.
+ *
+ * @see http://php.net/manual/en/jsonserializable.jsonserialize.php
+ *
+ * @return mixed data which can be serialized by json_encode ,
+ * which is a value of any type other than a resource
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return $this->items;
+ }
+
+ public function __serialize(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * String representation of object.
+ *
+ * @see http://php.net/manual/en/serializable.serialize.php
+ *
+ * @return string the string representation of the object or null
+ */
+ public function serialize()
+ {
+ return serialize($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Retrieve an external iterator.
+ *
+ * @see http://php.net/manual/en/iteratoraggregate.getiterator.php
+ *
+ * @return \ArrayIterator An instance of an object implementing Iterator or
+ * Traversable
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Count elements of an object.
+ *
+ * @see http://php.net/manual/en/countable.count.php
+ *
+ * @return int the custom count as an integer.
+ *
+ *
+ * The return value is cast to an integer
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ public function __unserialize(array $data): void
+ {
+ $this->items = $data;
+ }
+
+ /**
+ * (PHP 5 >= 5.1.0)
+ * Constructs the object.
+ *
+ * @see http://php.net/manual/en/serializable.unserialize.php
+ *
+ * @param string $serialized
+ * The string representation of the object.
+ *
+ *
+ * @return mixed|void
+ */
+ public function unserialize($serialized)
+ {
+ return $this->items = unserialize($serialized);
+ }
+
+ /**
+ * Get a data by key.
+ *
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Assigns a value to the specified data.
+ *
+ * @param string $key
+ * @param mixed $value
+ */
+ public function __set($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Whether or not an data exists by key.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return $this->has($key);
+ }
+
+ /**
+ * Unset an data by key.
+ *
+ * @param string $key
+ */
+ public function __unset($key)
+ {
+ $this->forget($key);
+ }
+
+ /**
+ * var_export.
+ *
+ * @param array $properties
+ *
+ * @return array
+ */
+ public static function __set_state(array $properties)
+ {
+ return (new static($properties))->all();
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Whether a offset exists.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetexists.php
+ *
+ * @param mixed $offset
+ * An offset to check for.
+ *
+ *
+ * @return bool true on success or false on failure.
+ * The return value will be casted to boolean if non-boolean was returned
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return $this->has($offset);
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to unset.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetunset.php
+ *
+ * @param mixed $offset
+ * The offset to unset.
+ *
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ if ($this->offsetExists($offset)) {
+ $this->forget($offset);
+ }
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to retrieve.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetget.php
+ *
+ * @param mixed $offset
+ * The offset to retrieve.
+ *
+ *
+ * @return mixed Can return all value types
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->offsetExists($offset) ? $this->get($offset) : null;
+ }
+
+ /**
+ * (PHP 5 >= 5.0.0)
+ * Offset to set.
+ *
+ * @see http://php.net/manual/en/arrayaccess.offsetset.php
+ *
+ * @param mixed $offset
+ * The offset to assign the value to.
+ *
+ * @param mixed $value
+ * The value to set.
+ *
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ $this->set($offset, $value);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/File.php b/vendor/overtrue/wechat/src/Kernel/Support/File.php
new file mode 100644
index 0000000..b51b41d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/File.php
@@ -0,0 +1,135 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use finfo;
+
+/**
+ * Class File.
+ */
+class File
+{
+ /**
+ * MIME mapping.
+ *
+ * @var array
+ */
+ protected static $extensionMap = [
+ 'audio/wav' => '.wav',
+ 'audio/x-ms-wma' => '.wma',
+ 'video/x-ms-wmv' => '.wmv',
+ 'video/mp4' => '.mp4',
+ 'audio/mpeg' => '.mp3',
+ 'audio/amr' => '.amr',
+ 'application/vnd.rn-realmedia' => '.rm',
+ 'audio/mid' => '.mid',
+ 'image/bmp' => '.bmp',
+ 'image/gif' => '.gif',
+ 'image/png' => '.png',
+ 'image/tiff' => '.tiff',
+ 'image/jpeg' => '.jpg',
+ 'application/pdf' => '.pdf',
+
+ // 列举更多的文件 mime, 企业号是支持的,公众平台这边之后万一也更新了呢
+ 'application/msword' => '.doc',
+
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => '.docx',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => '.dotx',
+ 'application/vnd.ms-word.document.macroEnabled.12' => '.docm',
+ 'application/vnd.ms-word.template.macroEnabled.12' => '.dotm',
+
+ 'application/vnd.ms-excel' => '.xls',
+
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => '.xltx',
+ 'application/vnd.ms-excel.sheet.macroEnabled.12' => '.xlsm',
+ 'application/vnd.ms-excel.template.macroEnabled.12' => '.xltm',
+ 'application/vnd.ms-excel.addin.macroEnabled.12' => '.xlam',
+ 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => '.xlsb',
+
+ 'application/vnd.ms-powerpoint' => '.ppt',
+
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => '.pptx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template' => '.potx',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => '.ppsx',
+ 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => '.ppam',
+ ];
+
+ /**
+ * File header signatures.
+ *
+ * @var array
+ */
+ protected static $signatures = [
+ 'ffd8ff' => '.jpg',
+ '424d' => '.bmp',
+ '47494638' => '.gif',
+ '2f55736572732f6f7665' => '.png',
+ '89504e47' => '.png',
+ '494433' => '.mp3',
+ 'fffb' => '.mp3',
+ 'fff3' => '.mp3',
+ '3026b2758e66cf11' => '.wma',
+ '52494646' => '.wav',
+ '57415645' => '.wav',
+ '41564920' => '.avi',
+ '000001ba' => '.mpg',
+ '000001b3' => '.mpg',
+ '2321414d52' => '.amr',
+ '25504446' => '.pdf',
+ ];
+
+ /**
+ * Return steam extension.
+ *
+ * @param string $stream
+ *
+ * @return string|false
+ */
+ public static function getStreamExt($stream)
+ {
+ $ext = self::getExtBySignature($stream);
+
+ try {
+ if (empty($ext) && is_readable($stream)) {
+ $stream = file_get_contents($stream);
+ }
+ } catch (\Exception $e) {
+ }
+
+ $fileInfo = new finfo(FILEINFO_MIME);
+
+ $mime = strstr($fileInfo->buffer($stream), ';', true);
+
+ return isset(self::$extensionMap[$mime]) ? self::$extensionMap[$mime] : $ext;
+ }
+
+ /**
+ * Get file extension by file header signature.
+ *
+ * @param string $stream
+ *
+ * @return string
+ */
+ public static function getExtBySignature($stream)
+ {
+ $prefix = strval(bin2hex(mb_strcut($stream, 0, 10)));
+
+ foreach (self::$signatures as $signature => $extension) {
+ if (0 === strpos($prefix, strval($signature))) {
+ return $extension;
+ }
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php
new file mode 100644
index 0000000..08d4092
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Helpers.php
@@ -0,0 +1,131 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+/*
+ * helpers.
+ *
+ * @author overtrue
+ */
+
+/**
+ * Generate a signature.
+ *
+ * @param array $attributes
+ * @param string $key
+ * @param string $encryptMethod
+ *
+ * @return string
+ */
+function generate_sign(array $attributes, $key, $encryptMethod = 'md5')
+{
+ ksort($attributes);
+
+ $attributes['key'] = $key;
+
+ return strtoupper(call_user_func_array($encryptMethod, [urldecode(http_build_query($attributes))]));
+}
+
+/**
+ * @param string $signType
+ * @param string $secretKey
+ *
+ * @return \Closure|string
+ */
+function get_encrypt_method(string $signType, string $secretKey = '')
+{
+ if ('HMAC-SHA256' === $signType) {
+ return function ($str) use ($secretKey) {
+ return hash_hmac('sha256', $str, $secretKey);
+ };
+ }
+
+ return 'md5';
+}
+
+/**
+ * Get client ip.
+ *
+ * @return string
+ */
+function get_client_ip()
+{
+ if (!empty($_SERVER['REMOTE_ADDR'])) {
+ $ip = $_SERVER['REMOTE_ADDR'];
+ } else {
+ // for php-cli(phpunit etc.)
+ $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname());
+ }
+
+ return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1';
+}
+
+/**
+ * Get current server ip.
+ *
+ * @return string
+ */
+function get_server_ip()
+{
+ if (!empty($_SERVER['SERVER_ADDR'])) {
+ $ip = $_SERVER['SERVER_ADDR'];
+ } elseif (!empty($_SERVER['SERVER_NAME'])) {
+ $ip = gethostbyname($_SERVER['SERVER_NAME']);
+ } else {
+ // for php-cli(phpunit etc.)
+ $ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname());
+ }
+
+ return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1';
+}
+
+/**
+ * Return current url.
+ *
+ * @return string
+ */
+function current_url()
+{
+ $protocol = 'http://';
+
+ if ((!empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'http') === 'https') {
+ $protocol = 'https://';
+ }
+
+ return $protocol.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
+}
+
+/**
+ * Return random string.
+ *
+ * @param string $length
+ *
+ * @return string
+ */
+function str_random($length)
+{
+ return Str::random($length);
+}
+
+/**
+ * @param string $content
+ * @param string $publicKey
+ *
+ * @return string
+ */
+function rsa_public_encrypt($content, $publicKey)
+{
+ $encrypted = '';
+ openssl_public_encrypt($content, $encrypted, openssl_pkey_get_public($publicKey), OPENSSL_PKCS1_OAEP_PADDING);
+
+ return base64_encode($encrypted);
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/Str.php b/vendor/overtrue/wechat/src/Kernel/Support/Str.php
new file mode 100644
index 0000000..3a51968
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/Str.php
@@ -0,0 +1,193 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+
+/**
+ * Class Str.
+ */
+class Str
+{
+ /**
+ * The cache of snake-cased words.
+ *
+ * @var array
+ */
+ protected static $snakeCache = [];
+
+ /**
+ * The cache of camel-cased words.
+ *
+ * @var array
+ */
+ protected static $camelCache = [];
+
+ /**
+ * The cache of studly-cased words.
+ *
+ * @var array
+ */
+ protected static $studlyCache = [];
+
+ /**
+ * Convert a value to camel case.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function camel($value)
+ {
+ if (isset(static::$camelCache[$value])) {
+ return static::$camelCache[$value];
+ }
+
+ return static::$camelCache[$value] = lcfirst(static::studly($value));
+ }
+
+ /**
+ * Generate a more truly "random" alpha-numeric string.
+ *
+ * @param int $length
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public static function random($length = 16)
+ {
+ $string = '';
+
+ while (($len = strlen($string)) < $length) {
+ $size = $length - $len;
+
+ $bytes = static::randomBytes($size);
+
+ $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Generate a more truly "random" bytes.
+ *
+ * @param int $length
+ *
+ * @return string
+ *
+ * @throws RuntimeException
+ *
+ * @codeCoverageIgnore
+ *
+ * @throws \Exception
+ */
+ public static function randomBytes($length = 16)
+ {
+ if (function_exists('random_bytes')) {
+ $bytes = random_bytes($length);
+ } elseif (function_exists('openssl_random_pseudo_bytes')) {
+ $bytes = openssl_random_pseudo_bytes($length, $strong);
+ if (false === $bytes || false === $strong) {
+ throw new RuntimeException('Unable to generate random string.');
+ }
+ } else {
+ throw new RuntimeException('OpenSSL extension is required for PHP 5 users.');
+ }
+
+ return $bytes;
+ }
+
+ /**
+ * Generate a "random" alpha-numeric string.
+ *
+ * Should not be considered sufficient for cryptography, etc.
+ *
+ * @param int $length
+ *
+ * @return string
+ */
+ public static function quickRandom($length = 16)
+ {
+ $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ return substr(str_shuffle(str_repeat($pool, $length)), 0, $length);
+ }
+
+ /**
+ * Convert the given string to upper-case.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function upper($value)
+ {
+ return mb_strtoupper($value);
+ }
+
+ /**
+ * Convert the given string to title case.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function title($value)
+ {
+ return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
+ }
+
+ /**
+ * Convert a string to snake case.
+ *
+ * @param string $value
+ * @param string $delimiter
+ *
+ * @return string
+ */
+ public static function snake($value, $delimiter = '_')
+ {
+ $key = $value.$delimiter;
+
+ if (isset(static::$snakeCache[$key])) {
+ return static::$snakeCache[$key];
+ }
+
+ if (!ctype_lower($value)) {
+ $value = strtolower(preg_replace('/(.)(?=[A-Z])/', '$1'.$delimiter, $value));
+ }
+
+ return static::$snakeCache[$key] = trim($value, '_');
+ }
+
+ /**
+ * Convert a value to studly caps case.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function studly($value)
+ {
+ $key = $value;
+
+ if (isset(static::$studlyCache[$key])) {
+ return static::$studlyCache[$key];
+ }
+
+ $value = ucwords(str_replace(['-', '_'], ' ', $value));
+
+ return static::$studlyCache[$key] = str_replace(' ', '', $value);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Support/XML.php b/vendor/overtrue/wechat/src/Kernel/Support/XML.php
new file mode 100644
index 0000000..1d22bc5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Support/XML.php
@@ -0,0 +1,167 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Support;
+
+use SimpleXMLElement;
+
+/**
+ * Class XML.
+ */
+class XML
+{
+ /**
+ * XML to array.
+ *
+ * @param string $xml XML string
+ *
+ * @return array
+ */
+ public static function parse($xml)
+ {
+ $backup = PHP_MAJOR_VERSION < 8 ? libxml_disable_entity_loader(true) : null;
+
+ $result = self::normalize(simplexml_load_string(self::sanitize($xml), 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS));
+
+ PHP_MAJOR_VERSION < 8 && libxml_disable_entity_loader($backup);
+
+ return $result;
+ }
+
+ /**
+ * XML encode.
+ *
+ * @param mixed $data
+ * @param string $root
+ * @param string $item
+ * @param string $attr
+ * @param string $id
+ *
+ * @return string
+ */
+ public static function build(
+ $data,
+ $root = 'xml',
+ $item = 'item',
+ $attr = '',
+ $id = 'id'
+ ) {
+ if (is_array($attr)) {
+ $_attr = [];
+
+ foreach ($attr as $key => $value) {
+ $_attr[] = "{$key}=\"{$value}\"";
+ }
+
+ $attr = implode(' ', $_attr);
+ }
+
+ $attr = trim($attr);
+ $attr = empty($attr) ? '' : " {$attr}";
+ $xml = "<{$root}{$attr}>";
+ $xml .= self::data2Xml($data, $item, $id);
+ $xml .= "{$root}>";
+
+ return $xml;
+ }
+
+ /**
+ * Build CDATA.
+ *
+ * @param string $string
+ *
+ * @return string
+ */
+ public static function cdata($string)
+ {
+ return sprintf('', $string);
+ }
+
+ /**
+ * Object to array.
+ *
+ *
+ * @param SimpleXMLElement $obj
+ *
+ * @return array
+ */
+ protected static function normalize($obj)
+ {
+ $result = null;
+
+ if (is_object($obj)) {
+ $obj = (array) $obj;
+ }
+
+ if (is_array($obj)) {
+ foreach ($obj as $key => $value) {
+ $res = self::normalize($value);
+ if (('@attributes' === $key) && ($key)) {
+ $result = $res; // @codeCoverageIgnore
+ } else {
+ $result[$key] = $res;
+ }
+ }
+ } else {
+ $result = $obj;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Array to XML.
+ *
+ * @param array $data
+ * @param string $item
+ * @param string $id
+ *
+ * @return string
+ */
+ protected static function data2Xml($data, $item = 'item', $id = 'id')
+ {
+ $xml = $attr = '';
+
+ foreach ($data as $key => $val) {
+ if (is_numeric($key)) {
+ $id && $attr = " {$id}=\"{$key}\"";
+ $key = $item;
+ }
+
+ $xml .= "<{$key}{$attr}>";
+
+ if ((is_array($val) || is_object($val))) {
+ $xml .= self::data2Xml((array) $val, $item, $id);
+ } else {
+ $xml .= is_numeric($val) ? $val : self::cdata($val);
+ }
+
+ $xml .= "{$key}>";
+ }
+
+ return $xml;
+ }
+
+ /**
+ * Delete invalid characters in XML.
+ *
+ * @see https://www.w3.org/TR/2008/REC-xml-20081126/#charsets - XML charset range
+ * @see http://php.net/manual/en/regexp.reference.escape.php - escape in UTF-8 mode
+ *
+ * @param string $xml
+ *
+ * @return string
+ */
+ public static function sanitize($xml)
+ {
+ return preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $xml);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php
new file mode 100644
index 0000000..238d9e2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasAttributes.php
@@ -0,0 +1,251 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Support\Arr;
+use EasyWeChat\Kernel\Support\Str;
+
+/**
+ * Trait Attributes.
+ */
+trait HasAttributes
+{
+ /**
+ * @var array
+ */
+ protected $attributes = [];
+
+ /**
+ * @var bool
+ */
+ protected $snakeable = true;
+
+ /**
+ * Set Attributes.
+ *
+ * @param array $attributes
+ *
+ * @return $this
+ */
+ public function setAttributes(array $attributes = [])
+ {
+ $this->attributes = $attributes;
+
+ return $this;
+ }
+
+ /**
+ * Set attribute.
+ *
+ * @param string $attribute
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setAttribute($attribute, $value)
+ {
+ Arr::set($this->attributes, $attribute, $value);
+
+ return $this;
+ }
+
+ /**
+ * Get attribute.
+ *
+ * @param string $attribute
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ return Arr::get($this->attributes, $attribute, $default);
+ }
+
+ /**
+ * @param string $attribute
+ *
+ * @return bool
+ */
+ public function isRequired($attribute)
+ {
+ return in_array($attribute, $this->getRequired(), true);
+ }
+
+ /**
+ * @return array|mixed
+ */
+ public function getRequired()
+ {
+ return property_exists($this, 'required') ? $this->required : [];
+ }
+
+ /**
+ * Set attribute.
+ *
+ * @param string $attribute
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function with($attribute, $value)
+ {
+ $this->snakeable && $attribute = Str::snake($attribute);
+
+ $this->setAttribute($attribute, $value);
+
+ return $this;
+ }
+
+ /**
+ * Override parent set() method.
+ *
+ * @param string $attribute
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function set($attribute, $value)
+ {
+ $this->setAttribute($attribute, $value);
+
+ return $this;
+ }
+
+ /**
+ * Override parent get() method.
+ *
+ * @param string $attribute
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get($attribute, $default = null)
+ {
+ return $this->getAttribute($attribute, $default);
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function has(string $key)
+ {
+ return Arr::has($this->attributes, $key);
+ }
+
+ /**
+ * @param array $attributes
+ *
+ * @return $this
+ */
+ public function merge(array $attributes)
+ {
+ $this->attributes = array_merge($this->attributes, $attributes);
+
+ return $this;
+ }
+
+ /**
+ * @param array|string $keys
+ *
+ * @return array
+ */
+ public function only($keys)
+ {
+ return Arr::only($this->attributes, $keys);
+ }
+
+ /**
+ * Return all items.
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function all()
+ {
+ $this->checkRequiredAttributes();
+
+ return $this->attributes;
+ }
+
+ /**
+ * Magic call.
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return $this
+ */
+ public function __call($method, $args)
+ {
+ if (0 === stripos($method, 'with')) {
+ return $this->with(substr($method, 4), array_shift($args));
+ }
+
+ throw new \BadMethodCallException(sprintf('Method "%s" does not exists.', $method));
+ }
+
+ /**
+ * Magic get.
+ *
+ * @param string $property
+ *
+ * @return mixed
+ */
+ public function __get($property)
+ {
+ return $this->get($property);
+ }
+
+ /**
+ * Magic set.
+ *
+ * @param string $property
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function __set($property, $value)
+ {
+ return $this->with($property, $value);
+ }
+
+ /**
+ * Whether or not an data exists by key.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function __isset($key)
+ {
+ return isset($this->attributes[$key]);
+ }
+
+ /**
+ * Check required attributes.
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function checkRequiredAttributes()
+ {
+ foreach ($this->getRequired() as $attribute) {
+ if (is_null($this->get($attribute))) {
+ throw new InvalidArgumentException(sprintf('"%s" cannot be empty.', $attribute));
+ }
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php
new file mode 100644
index 0000000..9bf0a02
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/HasHttpRequests.php
@@ -0,0 +1,231 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\HandlerStack;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Trait HasHttpRequests.
+ *
+ * @author overtrue
+ */
+trait HasHttpRequests
+{
+ use ResponseCastable;
+
+ /**
+ * @var \GuzzleHttp\ClientInterface
+ */
+ protected $httpClient;
+
+ /**
+ * @var array
+ */
+ protected $middlewares = [];
+
+ /**
+ * @var \GuzzleHttp\HandlerStack
+ */
+ protected $handlerStack;
+
+ /**
+ * @var array
+ */
+ protected static $defaults = [
+ 'curl' => [
+ CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
+ ],
+ ];
+
+ /**
+ * Set guzzle default settings.
+ *
+ * @param array $defaults
+ */
+ public static function setDefaultOptions($defaults = [])
+ {
+ self::$defaults = $defaults;
+ }
+
+ /**
+ * Return current guzzle default settings.
+ *
+ * @return array
+ */
+ public static function getDefaultOptions(): array
+ {
+ return self::$defaults;
+ }
+
+ /**
+ * Set GuzzleHttp\Client.
+ *
+ * @param \GuzzleHttp\ClientInterface $httpClient
+ *
+ * @return $this
+ */
+ public function setHttpClient(ClientInterface $httpClient)
+ {
+ $this->httpClient = $httpClient;
+
+ return $this;
+ }
+
+ /**
+ * Return GuzzleHttp\ClientInterface instance.
+ *
+ * @return ClientInterface
+ */
+ public function getHttpClient(): ClientInterface
+ {
+ if (!($this->httpClient instanceof ClientInterface)) {
+ if (property_exists($this, 'app') && $this->app['http_client']) {
+ $this->httpClient = $this->app['http_client'];
+ } else {
+ $this->httpClient = new Client(['handler' => HandlerStack::create($this->getGuzzleHandler())]);
+ }
+ }
+
+ return $this->httpClient;
+ }
+
+ /**
+ * Add a middleware.
+ *
+ * @param callable $middleware
+ * @param string $name
+ *
+ * @return $this
+ */
+ public function pushMiddleware(callable $middleware, string $name = null)
+ {
+ if (!is_null($name)) {
+ $this->middlewares[$name] = $middleware;
+ } else {
+ array_push($this->middlewares, $middleware);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return all middlewares.
+ *
+ * @return array
+ */
+ public function getMiddlewares(): array
+ {
+ return $this->middlewares;
+ }
+
+ /**
+ * Make a request.
+ *
+ * @param string $url
+ * @param string $method
+ * @param array $options
+ *
+ * @return \Psr\Http\Message\ResponseInterface
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function request($url, $method = 'GET', $options = []): ResponseInterface
+ {
+ $method = strtoupper($method);
+
+ $options = array_merge(self::$defaults, $options, ['handler' => $this->getHandlerStack()]);
+
+ $options = $this->fixJsonIssue($options);
+
+ if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) {
+ $options['base_uri'] = $this->baseUri;
+ }
+
+ $response = $this->getHttpClient()->request($method, $url, $options);
+ $response->getBody()->rewind();
+
+ return $response;
+ }
+
+ /**
+ * @param \GuzzleHttp\HandlerStack $handlerStack
+ *
+ * @return $this
+ */
+ public function setHandlerStack(HandlerStack $handlerStack)
+ {
+ $this->handlerStack = $handlerStack;
+
+ return $this;
+ }
+
+ /**
+ * Build a handler stack.
+ *
+ * @return \GuzzleHttp\HandlerStack
+ */
+ public function getHandlerStack(): HandlerStack
+ {
+ if ($this->handlerStack) {
+ return $this->handlerStack;
+ }
+
+ $this->handlerStack = HandlerStack::create($this->getGuzzleHandler());
+
+ foreach ($this->middlewares as $name => $middleware) {
+ $this->handlerStack->push($middleware, $name);
+ }
+
+ return $this->handlerStack;
+ }
+
+ /**
+ * @param array $options
+ *
+ * @return array
+ */
+ protected function fixJsonIssue(array $options): array
+ {
+ if (isset($options['json']) && is_array($options['json'])) {
+ $options['headers'] = array_merge($options['headers'] ?? [], ['Content-Type' => 'application/json']);
+
+ if (empty($options['json'])) {
+ $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_FORCE_OBJECT);
+ } else {
+ $options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_UNESCAPED_UNICODE);
+ }
+
+ unset($options['json']);
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get guzzle handler.
+ *
+ * @return callable
+ */
+ protected function getGuzzleHandler()
+ {
+ if (property_exists($this, 'app') && isset($this->app['guzzle_handler'])) {
+ return is_string($handler = $this->app->raw('guzzle_handler'))
+ ? new $handler()
+ : $handler;
+ }
+
+ return \GuzzleHttp\choose_handler();
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php
new file mode 100644
index 0000000..6ee9e25
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/InteractsWithCache.php
@@ -0,0 +1,105 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\SimpleCache\CacheInterface as SimpleCacheInterface;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
+use Symfony\Component\Cache\Psr16Cache;
+use Symfony\Component\Cache\Simple\FilesystemCache;
+
+/**
+ * Trait InteractsWithCache.
+ *
+ * @author overtrue
+ */
+trait InteractsWithCache
+{
+ /**
+ * @var \Psr\SimpleCache\CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * Get cache instance.
+ *
+ * @return \Psr\SimpleCache\CacheInterface
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function getCache()
+ {
+ if ($this->cache) {
+ return $this->cache;
+ }
+
+ if (property_exists($this, 'app') && $this->app instanceof ServiceContainer && isset($this->app['cache'])) {
+ $this->setCache($this->app['cache']);
+
+ // Fix PHPStan error
+ assert($this->cache instanceof \Psr\SimpleCache\CacheInterface);
+
+ return $this->cache;
+ }
+
+ return $this->cache = $this->createDefaultCache();
+ }
+
+ /**
+ * Set cache instance.
+ *
+ * @param \Psr\SimpleCache\CacheInterface|\Psr\Cache\CacheItemPoolInterface $cache
+ *
+ * @return $this
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function setCache($cache)
+ {
+ if (empty(\array_intersect([SimpleCacheInterface::class, CacheItemPoolInterface::class], \class_implements($cache)))) {
+ throw new InvalidArgumentException(\sprintf('The cache instance must implements %s or %s interface.', SimpleCacheInterface::class, CacheItemPoolInterface::class));
+ }
+
+ if ($cache instanceof CacheItemPoolInterface) {
+ if (!$this->isSymfony43OrHigher()) {
+ throw new InvalidArgumentException(sprintf('The cache instance must implements %s', SimpleCacheInterface::class));
+ }
+ $cache = new Psr16Cache($cache);
+ }
+
+ $this->cache = $cache;
+
+ return $this;
+ }
+
+ /**
+ * @return \Psr\SimpleCache\CacheInterface
+ */
+ protected function createDefaultCache()
+ {
+ if ($this->isSymfony43OrHigher()) {
+ return new Psr16Cache(new FilesystemAdapter('easywechat', 1500));
+ }
+
+ return new FilesystemCache();
+ }
+
+ /**
+ * @return bool
+ */
+ protected function isSymfony43OrHigher(): bool
+ {
+ return \class_exists('Symfony\Component\Cache\Psr16Cache');
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php
new file mode 100644
index 0000000..ba46fd9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/Observable.php
@@ -0,0 +1,285 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Clauses\Clause;
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Decorators\FinallyResult;
+use EasyWeChat\Kernel\Decorators\TerminateResult;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Trait Observable.
+ *
+ * @author overtrue
+ */
+trait Observable
+{
+ /**
+ * @var array
+ */
+ protected $handlers = [];
+
+ /**
+ * @var array
+ */
+ protected $clauses = [];
+
+ /**
+ * @param \Closure|EventHandlerInterface|callable|string $handler
+ * @param \Closure|EventHandlerInterface|callable|string $condition
+ *
+ * @return \EasyWeChat\Kernel\Clauses\Clause
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \ReflectionException
+ */
+ public function push($handler, $condition = '*')
+ {
+ list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition);
+
+ if (!isset($this->handlers[$condition])) {
+ $this->handlers[$condition] = [];
+ }
+
+ array_push($this->handlers[$condition], $handler);
+
+ return $this->newClause($handler);
+ }
+
+ /**
+ * @param array $handlers
+ *
+ * @return $this
+ */
+ public function setHandlers(array $handlers = [])
+ {
+ $this->handlers = $handlers;
+
+ return $this;
+ }
+
+ /**
+ * @param \Closure|EventHandlerInterface|string $handler
+ * @param \Closure|EventHandlerInterface|string $condition
+ *
+ * @return \EasyWeChat\Kernel\Clauses\Clause
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \ReflectionException
+ */
+ public function unshift($handler, $condition = '*')
+ {
+ list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition);
+
+ if (!isset($this->handlers[$condition])) {
+ $this->handlers[$condition] = [];
+ }
+
+ array_unshift($this->handlers[$condition], $handler);
+
+ return $this->newClause($handler);
+ }
+
+ /**
+ * @param string $condition
+ * @param \Closure|EventHandlerInterface|string $handler
+ *
+ * @return \EasyWeChat\Kernel\Clauses\Clause
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \ReflectionException
+ */
+ public function observe($condition, $handler)
+ {
+ return $this->push($handler, $condition);
+ }
+
+ /**
+ * @param string $condition
+ * @param \Closure|EventHandlerInterface|string $handler
+ *
+ * @return \EasyWeChat\Kernel\Clauses\Clause
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \ReflectionException
+ */
+ public function on($condition, $handler)
+ {
+ return $this->push($handler, $condition);
+ }
+
+ /**
+ * @param string|int $event
+ * @param mixed ...$payload
+ *
+ * @return mixed|null
+ */
+ public function dispatch($event, $payload)
+ {
+ return $this->notify($event, $payload);
+ }
+
+ /**
+ * @param string|int $event
+ * @param mixed ...$payload
+ *
+ * @return mixed|null
+ */
+ public function notify($event, $payload)
+ {
+ $result = null;
+
+ foreach ($this->handlers as $condition => $handlers) {
+ if ('*' === $condition || ($condition & $event) === $event) {
+ foreach ($handlers as $handler) {
+ if ($clause = $this->clauses[$this->getHandlerHash($handler)] ?? null) {
+ if ($clause->intercepted($payload)) {
+ continue;
+ }
+ }
+
+ $response = $this->callHandler($handler, $payload);
+
+ switch (true) {
+ case $response instanceof TerminateResult:
+ return $response->content;
+ case true === $response:
+ continue 2;
+ case false === $response:
+ break 3;
+ case !empty($response) && !($result instanceof FinallyResult):
+ $result = $response;
+ }
+ }
+ }
+ }
+
+ return $result instanceof FinallyResult ? $result->content : $result;
+ }
+
+ /**
+ * @return array
+ */
+ public function getHandlers()
+ {
+ return $this->handlers;
+ }
+
+ /**
+ * @param mixed $handler
+ *
+ * @return \EasyWeChat\Kernel\Clauses\Clause
+ */
+ protected function newClause($handler): Clause
+ {
+ return $this->clauses[$this->getHandlerHash($handler)] = new Clause();
+ }
+
+ /**
+ * @param mixed $handler
+ *
+ * @return string
+ */
+ protected function getHandlerHash($handler)
+ {
+ if (is_string($handler)) {
+ return $handler;
+ }
+
+ if (is_array($handler)) {
+ return is_string($handler[0])
+ ? $handler[0].'::'.$handler[1]
+ : get_class($handler[0]).$handler[1];
+ }
+
+ return spl_object_hash($handler);
+ }
+
+ /**
+ * @param callable $handler
+ * @param mixed $payload
+ *
+ * @return mixed
+ */
+ protected function callHandler(callable $handler, $payload)
+ {
+ try {
+ return call_user_func_array($handler, [$payload]);
+ } catch (\Exception $e) {
+ if (property_exists($this, 'app') && $this->app instanceof ServiceContainer) {
+ $this->app['logger']->error($e->getCode().': '.$e->getMessage(), [
+ 'code' => $e->getCode(),
+ 'message' => $e->getMessage(),
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ ]);
+ }
+ }
+ }
+
+ /**
+ * @param mixed $handler
+ *
+ * @return \Closure
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \ReflectionException
+ */
+ protected function makeClosure($handler)
+ {
+ if (is_callable($handler)) {
+ return $handler;
+ }
+
+ if (is_string($handler) && '*' !== $handler) {
+ if (!class_exists($handler)) {
+ throw new InvalidArgumentException(sprintf('Class "%s" not exists.', $handler));
+ }
+
+ if (!in_array(EventHandlerInterface::class, (new \ReflectionClass($handler))->getInterfaceNames(), true)) {
+ throw new InvalidArgumentException(sprintf('Class "%s" not an instance of "%s".', $handler, EventHandlerInterface::class));
+ }
+
+ return function ($payload) use ($handler) {
+ return (new $handler($this->app ?? null))->handle($payload);
+ };
+ }
+
+ if ($handler instanceof EventHandlerInterface) {
+ return function () use ($handler) {
+ return $handler->handle(...func_get_args());
+ };
+ }
+
+ throw new InvalidArgumentException('No valid handler is found in arguments.');
+ }
+
+ /**
+ * @param mixed $handler
+ * @param mixed $condition
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \ReflectionException
+ */
+ protected function resolveHandlerAndCondition($handler, $condition): array
+ {
+ if (is_int($handler) || (is_string($handler) && !class_exists($handler))) {
+ list($handler, $condition) = [$condition, $handler];
+ }
+
+ return [$this->makeClosure($handler), $condition];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php
new file mode 100644
index 0000000..5492bd4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Kernel/Traits/ResponseCastable.php
@@ -0,0 +1,93 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Kernel\Traits;
+
+use EasyWeChat\Kernel\Contracts\Arrayable;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
+use EasyWeChat\Kernel\Http\Response;
+use EasyWeChat\Kernel\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Trait ResponseCastable.
+ *
+ * @author overtrue
+ */
+trait ResponseCastable
+{
+ /**
+ * @param \Psr\Http\Message\ResponseInterface $response
+ * @param string|null $type
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ protected function castResponseToType(ResponseInterface $response, $type = null)
+ {
+ $response = Response::buildFromPsrResponse($response);
+ $response->getBody()->rewind();
+
+ switch ($type ?? 'array') {
+ case 'collection':
+ return $response->toCollection();
+ case 'array':
+ return $response->toArray();
+ case 'object':
+ return $response->toObject();
+ case 'raw':
+ return $response;
+ default:
+ if (!is_subclass_of($type, Arrayable::class)) {
+ throw new InvalidConfigException(sprintf('Config key "response_type" classname must be an instanceof %s', Arrayable::class));
+ }
+
+ return new $type($response);
+ }
+ }
+
+ /**
+ * @param mixed $response
+ * @param string|null $type
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ protected function detectAndCastResponseToType($response, $type = null)
+ {
+ switch (true) {
+ case $response instanceof ResponseInterface:
+ $response = Response::buildFromPsrResponse($response);
+
+ break;
+ case $response instanceof Arrayable:
+ $response = new Response(200, [], json_encode($response->toArray()));
+
+ break;
+ case ($response instanceof Collection) || is_array($response) || is_object($response):
+ $response = new Response(200, [], json_encode($response));
+
+ break;
+ case is_scalar($response):
+ $response = new Response(200, [], (string) $response);
+
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Unsupported response type "%s"', gettype($response)));
+ }
+
+ return $this->castResponseToType($response, $type);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Application.php b/vendor/overtrue/wechat/src/MicroMerchant/Application.php
new file mode 100644
index 0000000..cd455fb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Application.php
@@ -0,0 +1,173 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException;
+
+/**
+ * Class Application.
+ *
+ * @author liuml
+ *
+ * @property \EasyWeChat\MicroMerchant\Certficates\Client $certficates
+ * @property \EasyWeChat\MicroMerchant\Material\Client $material
+ * @property \EasyWeChat\MicroMerchant\MerchantConfig\Client $merchantConfig
+ * @property \EasyWeChat\MicroMerchant\Withdraw\Client $withdraw
+ * @property \EasyWeChat\MicroMerchant\Media\Client $media
+ *
+ * @method mixed submitApplication(array $params)
+ * @method mixed getStatus(string $applymentId, string $businessCode = '')
+ * @method mixed upgrade(array $params)
+ * @method mixed getUpgradeStatus(string $subMchId = '')
+ */
+class Application extends ServiceContainer
+{
+ /**
+ * @var array
+ */
+ protected $providers = [
+ // Base services
+ Base\ServiceProvider::class,
+ Certficates\ServiceProvider::class,
+ MerchantConfig\ServiceProvider::class,
+ Material\ServiceProvider::class,
+ Withdraw\ServiceProvider::class,
+ Media\ServiceProvider::class,
+ ];
+
+ /**
+ * @var array
+ */
+ protected $defaultConfig = [
+ 'http' => [
+ 'base_uri' => 'https://api.mch.weixin.qq.com/',
+ ],
+ 'log' => [
+ 'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod
+ 'channels' => [
+ // 测试环境
+ 'dev' => [
+ 'driver' => 'single',
+ 'path' => '/tmp/easywechat.log',
+ 'level' => 'debug',
+ ],
+ // 生产环境
+ 'prod' => [
+ 'driver' => 'daily',
+ 'path' => '/tmp/easywechat.log',
+ 'level' => 'info',
+ ],
+ ],
+ ],
+ ];
+
+ /**
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function getKey()
+ {
+ $key = $this['config']->key;
+
+ if (empty($key)) {
+ throw new InvalidArgumentException('config key connot be empty.');
+ }
+
+ if (32 !== strlen($key)) {
+ throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key));
+ }
+
+ return $key;
+ }
+
+ /**
+ * set sub-mch-id and appid.
+ *
+ * @param string $subMchId Identification Number of Small and Micro Businessmen Reported by Service Providers
+ * @param string $appId Public Account ID of Service Provider
+ *
+ * @return $this
+ */
+ public function setSubMchId(string $subMchId, string $appId = '')
+ {
+ $this['config']->set('sub_mch_id', $subMchId);
+ if ($appId) {
+ $this['config']->set('appid', $appId);
+ }
+
+ return $this;
+ }
+
+ /**
+ * setCertificate.
+ *
+ * @param string $certificate
+ * @param string $serialNo
+ *
+ * @return $this
+ */
+ public function setCertificate(string $certificate, string $serialNo)
+ {
+ $this['config']->set('certificate', $certificate);
+ $this['config']->set('serial_no', $serialNo);
+
+ return $this;
+ }
+
+ /**
+ * Returning true indicates that the verification is successful,
+ * returning false indicates that the signature field does not exist or is empty,
+ * and if the signature verification is wrong, the InvalidSignException will be thrown directly.
+ *
+ * @param array $data
+ *
+ * @return bool
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+ */
+ public function verifySignature(array $data)
+ {
+ if (!isset($data['sign']) || empty($data['sign'])) {
+ return false;
+ }
+
+ $sign = $data['sign'];
+ unset($data['sign']);
+
+ $signType = strlen($sign) > 32 ? 'HMAC-SHA256' : 'MD5';
+ $secretKey = $this->getKey();
+
+ $encryptMethod = Support\get_encrypt_method($signType, $secretKey);
+
+ if (Support\generate_sign($data, $secretKey, $encryptMethod) === $sign) {
+ return true;
+ }
+
+ throw new InvalidSignException('return value signature verification error');
+ }
+
+ /**
+ * @param string $name
+ * @param array $arguments
+ *
+ * @return mixed
+ */
+ public function __call($name, $arguments)
+ {
+ return call_user_func_array([$this['base'], $name], $arguments);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php
new file mode 100644
index 0000000..ba92756
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/Client.php
@@ -0,0 +1,126 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Base;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author liuml
+ * @DateTime 2019-05-30 14:19
+ */
+class Client extends BaseClient
+{
+ /**
+ * apply to settle in to become a small micro merchant.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function submitApplication(array $params)
+ {
+ $params = $this->processParams(array_merge($params, [
+ 'version' => '3.0',
+ 'cert_sn' => '',
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ]));
+
+ return $this->safeRequest('applyment/micro/submit', $params);
+ }
+
+ /**
+ * query application status.
+ *
+ * @param string $applymentId
+ * @param string $businessCode
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getStatus(string $applymentId, string $businessCode = '')
+ {
+ if (!empty($applymentId)) {
+ $params = [
+ 'applyment_id' => $applymentId,
+ ];
+ } else {
+ $params = [
+ 'business_code' => $businessCode,
+ ];
+ }
+
+ $params = array_merge($params, [
+ 'version' => '1.0',
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ]);
+
+ return $this->safeRequest('applyment/micro/getstate', $params);
+ }
+
+ /**
+ * merchant upgrade api.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function upgrade(array $params)
+ {
+ $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id;
+ $params = $this->processParams(array_merge($params, [
+ 'version' => '1.0',
+ 'cert_sn' => '',
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ]));
+
+ return $this->safeRequest('applyment/micro/submitupgrade', $params);
+ }
+
+ /**
+ * get upgrade status.
+ *
+ * @param string $subMchId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getUpgradeStatus(string $subMchId = '')
+ {
+ return $this->safeRequest('applyment/micro/getupgradestate', [
+ 'version' => '1.0',
+ 'sign_type' => 'HMAC-SHA256',
+ 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+ 'nonce_str' => uniqid('micro'),
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php
new file mode 100644
index 0000000..db2f056
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['base'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php
new file mode 100644
index 0000000..62a6cf5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/Client.php
@@ -0,0 +1,93 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Certficates;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException;
+
+/**
+ * Class Client.
+ *
+ * @author liuml
+ * @DateTime 2019-05-30 14:19
+ */
+class Client extends BaseClient
+{
+ /**
+ * get certficates.
+ *
+ * @param bool $returnRaw
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(bool $returnRaw = false)
+ {
+ $params = [
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ];
+
+ if (true === $returnRaw) {
+ return $this->requestRaw('risk/getcertficates', $params);
+ }
+ /** @var array $response */
+ $response = $this->requestArray('risk/getcertficates', $params);
+
+ if ('SUCCESS' !== $response['return_code']) {
+ throw new InvalidArgumentException(sprintf('Failed to get certificate. return_code_msg: "%s" .', $response['return_code'].'('.$response['return_msg'].')'));
+ }
+ if ('SUCCESS' !== $response['result_code']) {
+ throw new InvalidArgumentException(sprintf('Failed to get certificate. result_err_code_desc: "%s" .', $response['result_code'].'('.$response['err_code'].'['.$response['err_code_desc'].'])'));
+ }
+ $certificates = \GuzzleHttp\json_decode($response['certificates'], true)['data'][0];
+ $ciphertext = $this->decrypt($certificates['encrypt_certificate']);
+ unset($certificates['encrypt_certificate']);
+ $certificates['certificates'] = $ciphertext;
+
+ return $certificates;
+ }
+
+ /**
+ * decrypt ciphertext.
+ *
+ * @param array $encryptCertificate
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException
+ */
+ public function decrypt(array $encryptCertificate)
+ {
+ if (false === extension_loaded('sodium')) {
+ throw new InvalidExtensionException('sodium extension is not installed,Reference link https://php.net/manual/zh/book.sodium.php');
+ }
+
+ if (false === sodium_crypto_aead_aes256gcm_is_available()) {
+ throw new InvalidExtensionException('aes256gcm is not currently supported');
+ }
+
+ // sodium_crypto_aead_aes256gcm_decrypt function needs to open libsodium extension.
+ // https://www.php.net/manual/zh/function.sodium-crypto-aead-aes256gcm-decrypt.php
+ return sodium_crypto_aead_aes256gcm_decrypt(
+ base64_decode($encryptCertificate['ciphertext'], true),
+ $encryptCertificate['associated_data'],
+ $encryptCertificate['nonce'],
+ $this->app['config']->apiv3_key
+ );
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php
new file mode 100644
index 0000000..2e88b7e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Certficates/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Certficates;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['certficates'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php
new file mode 100644
index 0000000..6f49542
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/BaseClient.php
@@ -0,0 +1,256 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\MicroMerchant\Application;
+use EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException;
+use EasyWeChat\Payment\Kernel\BaseClient as PaymentBaseClient;
+
+/**
+ * Class BaseClient.
+ *
+ * @author liuml
+ * @DateTime 2019-07-10 12:06
+ */
+class BaseClient extends PaymentBaseClient
+{
+ /**
+ * @var string
+ */
+ protected $certificates;
+
+ /**
+ * BaseClient constructor.
+ *
+ * @param \EasyWeChat\MicroMerchant\Application $app
+ */
+ public function __construct(Application $app)
+ {
+ $this->app = $app;
+
+ $this->setHttpClient($this->app['http_client']);
+ }
+
+ /**
+ * Extra request params.
+ *
+ * @return array
+ */
+ protected function prepends()
+ {
+ return [];
+ }
+
+ /**
+ * httpUpload.
+ *
+ * @param string $url
+ * @param array $files
+ * @param array $form
+ * @param array $query
+ * @param bool $returnResponse
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function httpUpload(string $url, array $files = [], array $form = [], array $query = [], $returnResponse = false)
+ {
+ $multipart = [];
+
+ foreach ($files as $name => $path) {
+ $multipart[] = [
+ 'name' => $name,
+ 'contents' => fopen($path, 'r'),
+ ];
+ }
+
+ $base = [
+ 'mch_id' => $this->app['config']['mch_id'],
+ ];
+
+ $form = array_merge($base, $form);
+
+ $form['sign'] = $this->getSign($form);
+
+ foreach ($form as $name => $contents) {
+ $multipart[] = compact('name', 'contents');
+ }
+
+ $options = [
+ 'query' => $query,
+ 'multipart' => $multipart,
+ 'connect_timeout' => 30,
+ 'timeout' => 30,
+ 'read_timeout' => 30,
+ 'cert' => $this->app['config']->get('cert_path'),
+ 'ssl_key' => $this->app['config']->get('key_path'),
+ ];
+
+ $this->pushMiddleware($this->logMiddleware(), 'log');
+
+ $response = $this->performRequest($url, 'POST', $options);
+
+ $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+ // auto verify signature
+ if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) {
+ $this->app->verifySignature($this->castResponseToType($response, 'array'));
+ } else {
+ $this->app->verifySignature($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * request.
+ *
+ * @param string $endpoint
+ * @param array $params
+ * @param string $method
+ * @param array $options
+ * @param bool $returnResponse
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false)
+ {
+ $base = [
+ 'mch_id' => $this->app['config']['mch_id'],
+ ];
+
+ $params = array_merge($base, $this->prepends(), $params);
+ $params['sign'] = $this->getSign($params);
+ $options = array_merge([
+ 'body' => Support\XML::build($params),
+ ], $options);
+
+ $this->pushMiddleware($this->logMiddleware(), 'log');
+ $response = $this->performRequest($endpoint, $method, $options);
+ $result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+ // auto verify signature
+ if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) {
+ $this->app->verifySignature($this->castResponseToType($response, 'array'));
+ } else {
+ $this->app->verifySignature($result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * processing parameters contain fields that require sensitive information encryption.
+ *
+ * @param array $params
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+ */
+ protected function processParams(array $params)
+ {
+ $serial_no = $this->app['config']->get('serial_no');
+ if (null === $serial_no) {
+ throw new InvalidArgumentException('config serial_no connot be empty.');
+ }
+
+ $params['cert_sn'] = $serial_no;
+ $sensitive_fields = $this->getSensitiveFieldsName();
+ foreach ($params as $k => $v) {
+ if (in_array($k, $sensitive_fields, true)) {
+ $params[$k] = $this->encryptSensitiveInformation($v);
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * To id card, mobile phone number and other fields sensitive information encryption.
+ *
+ * @param string $string
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+ */
+ protected function encryptSensitiveInformation(string $string)
+ {
+ $certificates = $this->app['config']->get('certificate');
+ if (null === $certificates) {
+ throw new InvalidArgumentException('config certificate connot be empty.');
+ }
+
+ $encrypted = '';
+ $publicKeyResource = openssl_get_publickey($certificates);
+ $f = openssl_public_encrypt($string, $encrypted, $publicKeyResource, OPENSSL_NO_PADDING);
+ openssl_free_key($publicKeyResource);
+ if ($f) {
+ return base64_encode($encrypted);
+ }
+
+ throw new EncryptException('Encryption of sensitive information failed');
+ }
+
+ /**
+ * get sensitive fields name.
+ *
+ * @return array
+ */
+ protected function getSensitiveFieldsName()
+ {
+ return [
+ 'id_card_name',
+ 'id_card_number',
+ 'account_name',
+ 'account_number',
+ 'contact',
+ 'contact_phone',
+ 'contact_email',
+ 'legal_person',
+ 'mobile_phone',
+ 'email',
+ ];
+ }
+
+ /**
+ * getSign.
+ *
+ * @param array $params
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function getSign(array $params)
+ {
+ $params = array_filter($params);
+
+ $key = $this->app->getKey();
+
+ $encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $key);
+
+ return Support\generate_sign($params, $key, $encryptMethod);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php
new file mode 100644
index 0000000..dd874ae
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/EncryptException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+/**
+ * Class EncryptException.
+ *
+ * @author liuml
+ */
+class EncryptException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php
new file mode 100644
index 0000000..41e21cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidExtensionException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+/**
+ * Class InvalidExtensionException.
+ *
+ * @author liuml
+ */
+class InvalidExtensionException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php
new file mode 100644
index 0000000..d09d92b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Kernel/Exceptions/InvalidSignException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+/**
+ * Class InvalidSignException.
+ *
+ * @author liuml
+ */
+class InvalidSignException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php
new file mode 100644
index 0000000..ee1038a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/Client.php
@@ -0,0 +1,73 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Material;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author liuml
+ * @DateTime 2019-05-30 14:19
+ */
+class Client extends BaseClient
+{
+ /**
+ * update settlement card.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setSettlementCard(array $params)
+ {
+ $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id;
+ $params = $this->processParams(array_merge($params, [
+ 'version' => '1.0',
+ 'cert_sn' => '',
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ]));
+
+ return $this->safeRequest('applyment/micro/modifyarchives', $params);
+ }
+
+ /**
+ * update contact info.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateContact(array $params)
+ {
+ $params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id;
+ $params = $this->processParams(array_merge($params, [
+ 'version' => '1.0',
+ 'cert_sn' => '',
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ]));
+
+ return $this->safeRequest('applyment/micro/modifycontactinfo', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php
new file mode 100644
index 0000000..dec5af7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Material/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Material;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['material'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php
new file mode 100644
index 0000000..fde755b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/Client.php
@@ -0,0 +1,49 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Media;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author liuml
+ * @DateTime 2019-06-10 14:50
+ */
+class Client extends BaseClient
+{
+ /**
+ * Upload material.
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+ */
+ public function upload(string $path)
+ {
+ if (!file_exists($path) || !is_readable($path)) {
+ throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path));
+ }
+
+ $form = [
+ 'media_hash' => strtolower(md5_file($path)),
+ 'sign_type' => 'HMAC-SHA256',
+ ];
+
+ return $this->httpUpload('secapi/mch/uploadmedia', ['media' => $path], $form);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php
new file mode 100644
index 0000000..9d46a84
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Media/ServiceProvider.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Media;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['media'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php
new file mode 100644
index 0000000..c8573af
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/Client.php
@@ -0,0 +1,134 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\MerchantConfig;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author liuml
+ * @DateTime 2019-05-30 14:19
+ */
+class Client extends BaseClient
+{
+ /**
+ * Service providers configure recommendation functions for small and micro businesses.
+ *
+ * @param string $subAppId
+ * @param string $subscribeAppId
+ * @param string $receiptAppId
+ * @param string $subMchId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setFollowConfig(string $subAppId, string $subscribeAppId, string $receiptAppId = '', string $subMchId = '')
+ {
+ $params = [
+ 'sub_appid' => $subAppId,
+ 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+ ];
+
+ if (!empty($subscribeAppId)) {
+ $params['subscribe_appid'] = $subscribeAppId;
+ } else {
+ $params['receipt_appid'] = $receiptAppId;
+ }
+
+ return $this->safeRequest('secapi/mkt/addrecommendconf', array_merge($params, [
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ]));
+ }
+
+ /**
+ * Configure the new payment directory.
+ *
+ * @param string $jsapiPath
+ * @param string $appId
+ * @param string $subMchId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function addPath(string $jsapiPath, string $appId = '', string $subMchId = '')
+ {
+ return $this->addConfig([
+ 'appid' => $appId ?: $this->app['config']->appid,
+ 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+ 'jsapi_path' => $jsapiPath,
+ ]);
+ }
+
+ /**
+ * bind appid.
+ *
+ * @param string $subAppId
+ * @param string $appId
+ * @param string $subMchId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function bindAppId(string $subAppId, string $appId = '', string $subMchId = '')
+ {
+ return $this->addConfig([
+ 'appid' => $appId ?: $this->app['config']->appid,
+ 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+ 'sub_appid' => $subAppId,
+ ]);
+ }
+
+ /**
+ * add sub dev config.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ private function addConfig(array $params)
+ {
+ return $this->safeRequest('secapi/mch/addsubdevconfig', $params);
+ }
+
+ /**
+ * query Sub Dev Config.
+ *
+ * @param string $subMchId
+ * @param string $appId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getConfig(string $subMchId = '', string $appId = '')
+ {
+ return $this->safeRequest('secapi/mch/querysubdevconfig', [
+ 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+ 'appid' => $appId ?: $this->app['config']->appid,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php
new file mode 100644
index 0000000..5b78710
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/MerchantConfig/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\MerchantConfig;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['merchantConfig'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php
new file mode 100644
index 0000000..c96c363
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/Client.php
@@ -0,0 +1,67 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Withdraw;
+
+use EasyWeChat\MicroMerchant\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author liuml
+ * @DateTime 2019-05-30 14:19
+ */
+class Client extends BaseClient
+{
+ /**
+ * Query withdrawal status.
+ *
+ * @param string $date
+ * @param string $subMchId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function queryWithdrawalStatus($date, $subMchId = '')
+ {
+ return $this->safeRequest('fund/queryautowithdrawbydate', [
+ 'date' => $date,
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+ ]);
+ }
+
+ /**
+ * Re-initiation of withdrawal.
+ *
+ * @param string $date
+ * @param string $subMchId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function requestWithdraw($date, $subMchId = '')
+ {
+ return $this->safeRequest('fund/reautowithdrawbydate', [
+ 'date' => $date,
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ 'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php
new file mode 100644
index 0000000..b9c0141
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MicroMerchant/Withdraw/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MicroMerchant\Withdraw;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['withdraw'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php
new file mode 100644
index 0000000..16f273d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/Client.php
@@ -0,0 +1,85 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\ActivityMessage;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+class Client extends BaseClient
+{
+ /**
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function createActivityId()
+ {
+ return $this->httpGet('cgi-bin/message/wxopen/activityid/create');
+ }
+
+ /**
+ * @param string $activityId
+ * @param int $state
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateMessage(string $activityId, int $state = 0, array $params = [])
+ {
+ if (!in_array($state, [0, 1], true)) {
+ throw new InvalidArgumentException('"state" should be "0" or "1".');
+ }
+
+ $params = $this->formatParameters($params);
+
+ $params = [
+ 'activity_id' => $activityId,
+ 'target_state' => $state,
+ 'template_info' => ['parameter_list' => $params],
+ ];
+
+ return $this->httpPostJson('cgi-bin/message/wxopen/updatablemsg/send', $params);
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function formatParameters(array $params)
+ {
+ $formatted = [];
+
+ foreach ($params as $name => $value) {
+ if (!in_array($name, ['member_count', 'room_limit', 'path', 'version_type'], true)) {
+ continue;
+ }
+
+ if ('version_type' === $name && !in_array($value, ['develop', 'trial', 'release'], true)) {
+ throw new InvalidArgumentException('Invalid value of attribute "version_type".');
+ }
+
+ $formatted[] = [
+ 'name' => $name,
+ 'value' => strval($value),
+ ];
+ }
+
+ return $formatted;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php
new file mode 100644
index 0000000..fa7d3cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/ActivityMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\ActivityMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['activity_message'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php
new file mode 100644
index 0000000..82d2af2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/Client.php
@@ -0,0 +1,92 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\AppCode;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Http\StreamResponse;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get AppCode.
+ *
+ * @param string $path
+ * @param array $optional
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ */
+ public function get(string $path, array $optional = [])
+ {
+ $params = array_merge([
+ 'path' => $path,
+ ], $optional);
+
+ return $this->getStream('wxa/getwxacode', $params);
+ }
+
+ /**
+ * Get AppCode unlimit.
+ *
+ * @param string $scene
+ * @param array $optional
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ */
+ public function getUnlimit(string $scene, array $optional = [])
+ {
+ $params = array_merge([
+ 'scene' => $scene,
+ ], $optional);
+
+ return $this->getStream('wxa/getwxacodeunlimit', $params);
+ }
+
+ /**
+ * Create QrCode.
+ *
+ * @param string $path
+ * @param int|null $width
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ */
+ public function getQrCode(string $path, int $width = null)
+ {
+ return $this->getStream('cgi-bin/wxaapp/createwxaqrcode', compact('path', 'width'));
+ }
+
+ /**
+ * Get stream.
+ *
+ * @param string $endpoint
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function getStream(string $endpoint, array $params)
+ {
+ $response = $this->requestRaw($endpoint, 'POST', ['json' => $params]);
+
+ if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+ return StreamResponse::buildFromPsrResponse($response);
+ }
+
+ return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php
new file mode 100644
index 0000000..fefdc22
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/AppCode/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\AppCode;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['app_code'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Application.php b/vendor/overtrue/wechat/src/MiniProgram/Application.php
new file mode 100644
index 0000000..d012c2b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Application.php
@@ -0,0 +1,122 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram;
+
+use EasyWeChat\BasicService;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung
+ *
+ * @property \EasyWeChat\MiniProgram\Auth\AccessToken $access_token
+ * @property \EasyWeChat\MiniProgram\DataCube\Client $data_cube
+ * @property \EasyWeChat\MiniProgram\AppCode\Client $app_code
+ * @property \EasyWeChat\MiniProgram\Auth\Client $auth
+ * @property \EasyWeChat\OfficialAccount\Server\Guard $server
+ * @property \EasyWeChat\MiniProgram\Encryptor $encryptor
+ * @property \EasyWeChat\MiniProgram\TemplateMessage\Client $template_message
+ * @property \EasyWeChat\OfficialAccount\CustomerService\Client $customer_service
+ * @property \EasyWeChat\MiniProgram\Plugin\Client $plugin
+ * @property \EasyWeChat\MiniProgram\Plugin\DevClient $plugin_dev
+ * @property \EasyWeChat\MiniProgram\UniformMessage\Client $uniform_message
+ * @property \EasyWeChat\MiniProgram\ActivityMessage\Client $activity_message
+ * @property \EasyWeChat\MiniProgram\Express\Client $express
+ * @property \EasyWeChat\MiniProgram\NearbyPoi\Client $nearby_poi
+ * @property \EasyWeChat\MiniProgram\OCR\Client $ocr
+ * @property \EasyWeChat\MiniProgram\Soter\Client $soter
+ * @property \EasyWeChat\BasicService\Media\Client $media
+ * @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security
+ * @property \EasyWeChat\MiniProgram\Mall\ForwardsMall $mall
+ * @property \EasyWeChat\MiniProgram\SubscribeMessage\Client $subscribe_message
+ * @property \EasyWeChat\MiniProgram\RealtimeLog\Client $realtime_log
+ * @property \EasyWeChat\MiniProgram\RiskControl\Client $risk_control
+ * @property \EasyWeChat\MiniProgram\Search\Client $search
+ * @property \EasyWeChat\MiniProgram\Live\Client $live
+ * @property \EasyWeChat\MiniProgram\Broadcast\Client $broadcast
+ * @property \EasyWeChat\MiniProgram\UrlScheme\Client $url_scheme
+ * @property \EasyWeChat\MiniProgram\Union\Client $union
+ * @property \EasyWeChat\MiniProgram\Shop\Register\Client $shop_register
+ * @property \EasyWeChat\MiniProgram\Shop\Basic\Client $shop_basic
+ * @property \EasyWeChat\MiniProgram\Shop\Account\Client $shop_account
+ * @property \EasyWeChat\MiniProgram\Shop\Spu\Client $shop_spu
+ * @property \EasyWeChat\MiniProgram\Shop\Order\Client $shop_order
+ * @property \EasyWeChat\MiniProgram\Shop\Delivery\Client $shop_delivery
+ * @property \EasyWeChat\MiniProgram\Shop\Aftersale\Client $shop_aftersale
+ * @property \EasyWeChat\MiniProgram\Business\Client $business
+ * @property \EasyWeChat\MiniProgram\UrlLink\Client $url_link
+ * @property \EasyWeChat\MiniProgram\QrCode\Client $qr_code
+ * @property \EasyWeChat\MiniProgram\PhoneNumber\Client $phone_number
+ * @property \EasyWeChat\MiniProgram\ShortLink\Client $short_link
+ */
+class Application extends ServiceContainer
+{
+ /**
+ * @var array
+ */
+ protected $providers = [
+ Auth\ServiceProvider::class,
+ DataCube\ServiceProvider::class,
+ AppCode\ServiceProvider::class,
+ Server\ServiceProvider::class,
+ TemplateMessage\ServiceProvider::class,
+ CustomerService\ServiceProvider::class,
+ UniformMessage\ServiceProvider::class,
+ ActivityMessage\ServiceProvider::class,
+ OpenData\ServiceProvider::class,
+ Plugin\ServiceProvider::class,
+ QrCode\ServiceProvider::class,
+ Base\ServiceProvider::class,
+ Express\ServiceProvider::class,
+ NearbyPoi\ServiceProvider::class,
+ OCR\ServiceProvider::class,
+ Soter\ServiceProvider::class,
+ Mall\ServiceProvider::class,
+ SubscribeMessage\ServiceProvider::class,
+ RealtimeLog\ServiceProvider::class,
+ RiskControl\ServiceProvider::class,
+ Search\ServiceProvider::class,
+ Live\ServiceProvider::class,
+ Broadcast\ServiceProvider::class,
+ UrlScheme\ServiceProvider::class,
+ UrlLink\ServiceProvider::class,
+ Union\ServiceProvider::class,
+ PhoneNumber\ServiceProvider::class,
+ ShortLink\ServiceProvider::class,
+ // Base services
+ BasicService\Media\ServiceProvider::class,
+ BasicService\ContentSecurity\ServiceProvider::class,
+
+ Shop\Register\ServiceProvider::class,
+ Shop\Basic\ServiceProvider::class,
+ Shop\Account\ServiceProvider::class,
+ Shop\Spu\ServiceProvider::class,
+ Shop\Order\ServiceProvider::class,
+ Shop\Delivery\ServiceProvider::class,
+ Shop\Aftersale\ServiceProvider::class,
+ Business\ServiceProvider::class,
+ ];
+
+ /**
+ * Handle dynamic calls.
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ return $this->base->$method(...$args);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php
new file mode 100644
index 0000000..af08921
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/AccessToken.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * Class AccessToken.
+ *
+ * @author mingyoung
+ */
+class AccessToken extends BaseAccessToken
+{
+ /**
+ * @var string
+ */
+ protected $endpointToGetToken = 'cgi-bin/token';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getCredentials(): array
+ {
+ return [
+ 'grant_type' => 'client_credential',
+ 'appid' => $this->app['config']['app_id'],
+ 'secret' => $this->app['config']['secret'],
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php
new file mode 100644
index 0000000..90d2c88
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/Client.php
@@ -0,0 +1,43 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Auth;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Auth.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get session info by code.
+ *
+ * @param string $code
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function session(string $code)
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ 'secret' => $this->app['config']['secret'],
+ 'js_code' => $code,
+ 'grant_type' => 'authorization_code',
+ ];
+
+ return $this->httpGet('sns/jscode2session', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php
new file mode 100644
index 0000000..fcb687b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Auth/ServiceProvider.php
@@ -0,0 +1,32 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['access_token']) && $app['access_token'] = function ($app) {
+ return new AccessToken($app);
+ };
+
+ !isset($app['auth']) && $app['auth'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php
new file mode 100644
index 0000000..84d6b81
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Base/Client.php
@@ -0,0 +1,38 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Base;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get paid unionid.
+ *
+ * @param string $openid
+ * @param array $options
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPaidUnionid($openid, $options = [])
+ {
+ return $this->httpGet('wxa/getpaidunionid', compact('openid') + $options);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php
new file mode 100644
index 0000000..d9aa41b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['base'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Broadcast/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/Client.php
new file mode 100644
index 0000000..d9fd6ca
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/Client.php
@@ -0,0 +1,539 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Broadcast;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Abbotton
+ */
+class Client extends BaseClient
+{
+ /**
+ * Add broadcast goods.
+ *
+ * @param array $goodsInfo
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(array $goodsInfo)
+ {
+ $params = [
+ 'goodsInfo' => $goodsInfo,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/add', $params);
+ }
+
+ /**
+ * Reset audit.
+ *
+ * @param int $auditId
+ * @param int $goodsId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function resetAudit(int $auditId, int $goodsId)
+ {
+ $params = [
+ 'auditId' => $auditId,
+ 'goodsId' => $goodsId,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/resetaudit', $params);
+ }
+
+ /**
+ * Resubmit audit goods.
+ *
+ * @param int $goodsId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function resubmitAudit(int $goodsId)
+ {
+ $params = [
+ 'goodsId' => $goodsId,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/audit', $params);
+ }
+
+ /**
+ * Delete broadcast goods.
+ *
+ * @param int $goodsId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(int $goodsId)
+ {
+ $params = [
+ 'goodsId' => $goodsId,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/delete', $params);
+ }
+
+ /**
+ * Update goods info.
+ *
+ * @param array $goodsInfo
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(array $goodsInfo)
+ {
+ $params = [
+ 'goodsInfo' => $goodsInfo,
+ ];
+
+ return $this->httpPostJson('wxaapi/broadcast/goods/update', $params);
+ }
+
+ /**
+ * Get goods information and review status.
+ *
+ * @param array $goodsIdArray
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getGoodsWarehouse(array $goodsIdArray)
+ {
+ $params = [
+ 'goods_ids' => $goodsIdArray,
+ ];
+
+ return $this->httpPostJson('wxa/business/getgoodswarehouse', $params);
+ }
+
+ /**
+ * Get goods list based on status
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getApproved(array $params)
+ {
+ return $this->httpGet('wxaapi/broadcast/goods/getapproved', $params);
+ }
+
+ /**
+ * Add goods to the designated live room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addGoods(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/addgoods', $params);
+ }
+
+ /**
+ * Get Room List.
+ *
+ * @param int $start
+ * @param int $limit
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @author onekb <1@1kb.ren>
+ */
+ public function getRooms(int $start = 0, int $limit = 10)
+ {
+ $params = [
+ 'start' => $start,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('wxa/business/getliveinfo', $params);
+ }
+
+ /**
+ * Get Playback List.
+ *
+ * @param int $roomId
+ * @param int $start
+ * @param int $limit
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @author onekb <1@1kb.ren>
+ */
+ public function getPlaybacks(int $roomId, int $start = 0, int $limit = 10)
+ {
+ $params = [
+ 'action' => 'get_replay',
+ 'room_id' => $roomId,
+ 'start' => $start,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('wxa/business/getliveinfo', $params);
+ }
+
+ /**
+ * Create a live room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createLiveRoom(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/create', $params);
+ }
+
+ /**
+ * Delete a live room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function deleteLiveRoom(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/deleteroom', $params);
+ }
+
+ /**
+ * Update a live room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function updateLiveRoom(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/editroom', $params);
+ }
+
+ /**
+ * Gets the live room push stream url.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getPushUrl(array $params)
+ {
+ return $this->httpGet('wxaapi/broadcast/room/getpushurl', $params);
+ }
+
+ /**
+ * Gets the live room share qrcode.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getShareQrcode(array $params)
+ {
+ return $this->httpGet('wxaapi/broadcast/room/getsharedcode', $params);
+ }
+
+ /**
+ * Add a live room assistant.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function addAssistant(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/addassistant', $params);
+ }
+
+ /**
+ * Update a live room assistant.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function updateAssistant(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/modifyassistant', $params);
+ }
+
+ /**
+ * Delete a live room assistant.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function deleteAssistant(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/removeassistant', $params);
+ }
+
+ /**
+ * Gets the assistant list.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getAssistantList(array $params)
+ {
+ return $this->httpGet('wxaapi/broadcast/room/getassistantlist', $params);
+ }
+
+ /**
+ * Add the sub anchor.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function addSubAnchor(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/addsubanchor', $params);
+ }
+
+ /**
+ * Update the sub anchor.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function updateSubAnchor(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/modifysubanchor', $params);
+ }
+
+ /**
+ * Delete the sub anchor.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function deleteSubAnchor(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/deletesubanchor', $params);
+ }
+
+ /**
+ * Gets the sub anchor info.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getSubAnchor(array $params)
+ {
+ return $this->httpGet('wxaapi/broadcast/room/getsubanchor', $params);
+ }
+
+ /**
+ * Turn official index on/off.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function updateFeedPublic(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/updatefeedpublic', $params);
+ }
+
+ /**
+ * Turn playback status on/off.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function updateReplay(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/updatereplay', $params);
+ }
+
+ /**
+ * Turn customer service status on/off.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function updateKf(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/updatekf', $params);
+ }
+
+ /**
+ * Turn global comments status on/off.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function updateComment(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/room/updatecomment', $params);
+ }
+
+ /**
+ * Add member role.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function addRole(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/role/addrole', $params);
+ }
+
+ /**
+ * Delete member role.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function deleteRole(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/role/deleterole', $params);
+ }
+
+ /**
+ * Gets the role list.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getRoleList(array $params)
+ {
+ return $this->httpGet('wxaapi/broadcast/role/getrolelist', $params);
+ }
+
+ /**
+ * Gets long-term subscribers.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getFollowers(array $params)
+ {
+ return $this->httpPost('wxa/business/get_wxa_followers', $params);
+ }
+
+ /**
+ * Sending live broadcast start event to long-term subscribers.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function pushMessage(array $params)
+ {
+ return $this->httpPost('wxa/business/push_message', $params);
+ }
+
+ /**
+ * Change the status of goods on/off shelves in room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateGoodsInRoom(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/goods/onsale', $params);
+ }
+
+ /**
+ * Delete goods in room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deleteGoodsInRoom(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/goods/deleteInRoom', $params);
+ }
+
+ /**
+ * Push goods in room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function pushGoods(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/goods/push', $params);
+ }
+
+ /**
+ * Change goods sort in room.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function sortGoods(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/goods/sort', $params);
+ }
+
+ /**
+ * Download goods explanation video.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function downloadGoodsExplanationVideo(array $params)
+ {
+ return $this->httpPost('wxaapi/broadcast/goods/getVideo', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Broadcast/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/ServiceProvider.php
new file mode 100644
index 0000000..f33f29f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Broadcast/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Broadcast;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author Abbotton
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['broadcast'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Business/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Business/Client.php
new file mode 100644
index 0000000..b320b70
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Business/Client.php
@@ -0,0 +1,149 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * Business register
+ * @param string $accountName
+ * @param string $nickname
+ * @param string $iconMediaId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function register(string $accountName, string $nickname, string $iconMediaId)
+ {
+ $params = [
+ 'account_name' => $accountName,
+ 'nickname' => $nickname,
+ 'icon_media_id' => $iconMediaId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/business/register', $params);
+ }
+
+ /**
+ * Get business
+ * @param int $businessId
+ * @param string $accountName
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getBusiness(int $businessId = 0, string $accountName = '')
+ {
+ if (empty($businessId) && empty($accountName)) {
+ throw new InvalidArgumentException('Missing parameter.');
+ }
+ if ($businessId) {
+ $params = [
+ 'business_id' => $businessId,
+ ];
+ } else {
+ $params = [
+ 'account_name' => $accountName,
+ ];
+ }
+
+ return $this->httpPostJson('cgi-bin/business/get', $params);
+ }
+
+ /**
+ * Get business list
+ * @param int $offset
+ * @param int $count
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $offset = 0, int $count = 10)
+ {
+ $params = [
+ 'offset' => $offset,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('cgi-bin/business/list', $params);
+ }
+
+ /**
+ * Update business.
+ * @param int $businessId
+ * @param string $nickname
+ * @param string $iconMediaId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $businessId, string $nickname = '', string $iconMediaId = '')
+ {
+ $params = [
+ 'business_id' => $businessId,
+ 'nickname' => $nickname,
+ 'icon_media_id' => $iconMediaId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/business/update', $params);
+ }
+
+ /**
+ * Get message builder.
+ *
+ * @param \EasyWeChat\Kernel\Messages\Message|string $message
+ *
+ * @return \EasyWeChat\MiniProgram\Business\Messenger
+ */
+ public function message($message)
+ {
+ $messageBuilder = new Messenger($this);
+
+ return $messageBuilder->message($message);
+ }
+
+ /**
+ * Send a message.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $message)
+ {
+ return $this->httpPostJson('cgi-bin/message/custom/business/send', $message);
+ }
+
+ /**
+ * Typing status.
+ * @param int $businessId
+ * @param string $toUser openid
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function typing(int $businessId, string $toUser)
+ {
+ $params = [
+ 'business_id' => $businessId,
+ 'touser' => $toUser,
+ 'command' => 'Typing',
+ ];
+
+ return $this->httpPostJson('cgi-bin/business/typing', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Business/Messenger.php b/vendor/overtrue/wechat/src/MiniProgram/Business/Messenger.php
new file mode 100644
index 0000000..fee2e50
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Business/Messenger.php
@@ -0,0 +1,179 @@
+
+ */
+class Messenger
+{
+ /**
+ * Messages to send.
+ *
+ * @var \EasyWeChat\Kernel\Messages\Message;
+ */
+ protected $message;
+
+ /**
+ * Messages target user open id.
+ *
+ * @var string
+ */
+ protected $to;
+
+ /**
+ * Messages sender staff id.
+ *
+ * @var string
+ */
+ protected $account;
+
+ /**
+ * Customer service instance.
+ *
+ * @var \EasyWeChat\MiniProgram\Business\Client
+ */
+ protected $client;
+
+ /**
+ * Messages businessId
+ *
+ * @var int
+ */
+ protected $businessId;
+
+ /**
+ * MessageBuilder constructor.
+ *
+ * @param \EasyWeChat\MiniProgram\Business\Client $client
+ */
+ public function __construct(Client $client)
+ {
+ $this->client = $client;
+ }
+
+ /**
+ * Set message to send.
+ *
+ * @param string|Message $message
+ *
+ * @return Messenger
+ */
+ public function message($message)
+ {
+ if (is_string($message)) {
+ $message = new Text($message);
+ }
+
+ $this->message = $message;
+
+ return $this;
+ }
+
+ /**
+ * Set staff account to send message.
+ *
+ * @return Messenger
+ */
+ public function by(string $account)
+ {
+ $this->account = $account;
+
+ return $this;
+ }
+
+ /**
+ * @return Messenger
+ */
+ public function from(string $account)
+ {
+ return $this->by($account);
+ }
+
+ /**
+ * Set target user open id.
+ *
+ * @param string $openid
+ *
+ * @return Messenger
+ */
+ public function to($openid)
+ {
+ $this->to = $openid;
+
+ return $this;
+ }
+
+ /**
+ * Set target business id.
+ *
+ * @param int $businessId
+ *
+ * @return Messenger
+ */
+ public function business($businessId)
+ {
+ $this->businessId = $businessId;
+
+ return $this;
+ }
+
+ /**
+ * Send the message.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function send()
+ {
+ if (empty($this->message)) {
+ throw new RuntimeException('No message to send.');
+ }
+
+ if ($this->message instanceof RawMessage) {
+ $message = json_decode($this->message->get('content'), true);
+ } else {
+ $prepends = [
+ 'touser' => $this->to,
+ ];
+ if ($this->account) {
+ $prepends['customservice'] = ['kf_account' => $this->account];
+ }
+ if ($this->businessId) {
+ $prepends['businessid'] = $this->businessId;
+ }
+ $message = $this->message->transformForJsonRequest($prepends);
+ }
+
+ return $this->client->send($message);
+ }
+
+ /**
+ * Return property.
+ *
+ * @return mixed
+ */
+ public function __get(string $property)
+ {
+ if (property_exists($this, $property)) {
+ return $this->$property;
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Business/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Business/ServiceProvider.php
new file mode 100644
index 0000000..8a4c5d1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Business/ServiceProvider.php
@@ -0,0 +1,29 @@
+
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['business'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php
new file mode 100644
index 0000000..cfd3039
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/CustomerService/ServiceProvider.php
@@ -0,0 +1,34 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\CustomerService;
+
+use EasyWeChat\OfficialAccount\CustomerService\Client;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['customer_service'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php
new file mode 100644
index 0000000..385ef45
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/Client.php
@@ -0,0 +1,203 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\DataCube;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get summary trend.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function summaryTrend(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappiddailysummarytrend', $from, $to);
+ }
+
+ /**
+ * Get daily visit trend.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function dailyVisitTrend(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappiddailyvisittrend', $from, $to);
+ }
+
+ /**
+ * Get weekly visit trend.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function weeklyVisitTrend(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappidweeklyvisittrend', $from, $to);
+ }
+
+ /**
+ * Get monthly visit trend.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function monthlyVisitTrend(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappidmonthlyvisittrend', $from, $to);
+ }
+
+ /**
+ * Get visit distribution.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function visitDistribution(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappidvisitdistribution', $from, $to);
+ }
+
+ /**
+ * Get daily retain info.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function dailyRetainInfo(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappiddailyretaininfo', $from, $to);
+ }
+
+ /**
+ * Get weekly retain info.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function weeklyRetainInfo(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappidweeklyretaininfo', $from, $to);
+ }
+
+ /**
+ * Get monthly retain info.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function monthlyRetainInfo(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappidmonthlyretaininfo', $from, $to);
+ }
+
+ /**
+ * Get visit page.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function visitPage(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappidvisitpage', $from, $to);
+ }
+
+ /**
+ * Get user portrait.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function userPortrait(string $from, string $to)
+ {
+ return $this->query('datacube/getweanalysisappiduserportrait', $from, $to);
+ }
+
+ /**
+ * get performance data
+ * @param string $from
+ * @param string $to
+ * @param string $module
+ * @param string $networktype
+ * @param string $device_level
+ * @param string $device
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function devicePerformanceData(string $from, string $to, string $module, string $networktype = '-1', string $device_level = '-1', string $device = '-1')
+ {
+ $payload = [
+ 'time' => [
+ 'end_timestamp' => strtotime($to),
+ 'begin_timestamp' => strtotime($from),
+ ],
+ 'module' => $module,
+ 'params' => [
+ ['field' => 'networktype', 'value' => $networktype],
+ ['field' => 'device_level', 'value' => $device_level],
+ ['field' => 'device', 'value' => $device],
+ ]
+ ];
+ return $this->httpPostJson('wxa/business/performance/boot', $payload);
+ }
+
+ /**
+ * Unify query.
+ *
+ * @param string $api
+ * @param string $from
+ * @param string $to
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function query(string $api, string $from, string $to)
+ {
+ $params = [
+ 'begin_date' => $from,
+ 'end_date' => $to,
+ ];
+
+ return $this->httpPostJson($api, $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php
new file mode 100644
index 0000000..258d7c4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/DataCube/ServiceProvider.php
@@ -0,0 +1,28 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\DataCube;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['data_cube'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php
new file mode 100644
index 0000000..fc6ec5f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Encryptor.php
@@ -0,0 +1,52 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram;
+
+use EasyWeChat\Kernel\Encryptor as BaseEncryptor;
+use EasyWeChat\Kernel\Exceptions\DecryptException;
+use EasyWeChat\Kernel\Support\AES;
+
+/**
+ * Class Encryptor.
+ *
+ * @author mingyoung
+ */
+class Encryptor extends BaseEncryptor
+{
+ /**
+ * Decrypt data.
+ *
+ * @param string $sessionKey
+ * @param string $iv
+ * @param string $encrypted
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\DecryptException
+ */
+ public function decryptData(string $sessionKey, string $iv, string $encrypted): array
+ {
+ $decrypted = AES::decrypt(
+ base64_decode($encrypted, false),
+ base64_decode($sessionKey, false),
+ base64_decode($iv, false)
+ );
+
+ $decrypted = json_decode($decrypted, true);
+
+ if (!$decrypted) {
+ throw new DecryptException('The given payload is invalid.');
+ }
+
+ return $decrypted;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php
new file mode 100644
index 0000000..86fd309
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Express/Client.php
@@ -0,0 +1,208 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Express;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author kehuanhuan <1152018701@qq.com>
+ */
+class Client extends BaseClient
+{
+ /**
+ * 绑定、解绑物流账号
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function bind(array $params = [])
+ {
+ if (empty($params['type']) || empty($params['biz_id']) || empty($params['delivery_id'])) {
+ throw new InvalidArgumentException('Missing parameter.');
+ }
+
+ return $this->httpPostJson('cgi-bin/express/business/account/bind', $params);
+ }
+
+ /**
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function listProviders()
+ {
+ return $this->httpGet('cgi-bin/express/business/delivery/getall');
+ }
+
+ /**
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getAllAccount()
+ {
+ return $this->httpGet('cgi-bin/express/business/account/getall');
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createWaybill(array $params = [])
+ {
+ return $this->httpPostJson('cgi-bin/express/business/order/add', $params);
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deleteWaybill(array $params = [])
+ {
+ return $this->httpPostJson('cgi-bin/express/business/order/cancel', $params);
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getWaybill(array $params = [])
+ {
+ return $this->httpPostJson('cgi-bin/express/business/order/get', $params);
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getWaybillTrack(array $params = [])
+ {
+ return $this->httpPostJson('cgi-bin/express/business/path/get', $params);
+ }
+
+ /**
+ * @param string $deliveryId
+ * @param string $bizId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getBalance(string $deliveryId, string $bizId)
+ {
+ return $this->httpPostJson('cgi-bin/express/business/quota/get', [
+ 'delivery_id' => $deliveryId,
+ 'biz_id' => $bizId,
+ ]);
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPrinter()
+ {
+ return $this->httpPostJson('cgi-bin/express/business/printer/getall');
+ }
+
+ /**
+ * @param string $openid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function bindPrinter(string $openid)
+ {
+ return $this->httpPostJson('cgi-bin/express/business/printer/update', [
+ 'update_type' => 'bind',
+ 'openid' => $openid,
+ ]);
+ }
+
+ /**
+ * @param string $openid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unbindPrinter(string $openid)
+ {
+ return $this->httpPostJson('cgi-bin/express/business/printer/update', [
+ 'update_type' => 'unbind',
+ 'openid' => $openid,
+ ]);
+ }
+
+ /**
+ * 创建退货 ID
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function createReturn(array $params = [])
+ {
+ return $this->httpPostJson('cgi-bin/express/delivery/return/add', $params);
+ }
+
+ /**
+ * 查询退货 ID 状态
+ * @param string $returnId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getReturn(string $returnId)
+ {
+ return $this->httpPostJson('cgi-bin/express/delivery/return/get', [
+ 'return_id' => $returnId
+ ]);
+ }
+
+ /**
+ * 解绑退货 ID
+ * @param string $returnId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function unbindReturn(string $returnId)
+ {
+ return $this->httpPostJson('cgi-bin/express/delivery/return/unbind', [
+ 'return_id' => $returnId
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php
new file mode 100644
index 0000000..4794094
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Express/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Express;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author kehuanhuan <1152018701@qq.com>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['express'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Live/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Live/Client.php
new file mode 100644
index 0000000..ecc4324
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Live/Client.php
@@ -0,0 +1,63 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Live;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author onekb <1@1kb.ren>
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get Room List.
+ *
+ * @param int $start
+ * @param int $limit
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @deprecated This method has been merged into `\EasyWeChat\MiniProgram\Broadcast`
+ */
+ public function getRooms(int $start = 0, int $limit = 10)
+ {
+ $params = [
+ 'start' => $start,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('wxa/business/getliveinfo', $params);
+ }
+
+ /**
+ * Get Playback List.
+ *
+ * @param int $roomId
+ * @param int $start
+ * @param int $limit
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @deprecated This method has been merged into `\EasyWeChat\MiniProgram\Broadcast`
+ */
+ public function getPlaybacks(int $roomId, int $start = 0, int $limit = 10)
+ {
+ $params = [
+ 'action' => 'get_replay',
+ 'room_id' => $roomId,
+ 'start' => $start,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('wxa/business/getliveinfo', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Live/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Live/ServiceProvider.php
new file mode 100644
index 0000000..6d6dff6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Live/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Live;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author onekb <1@1kb.ren>
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['live'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php
new file mode 100644
index 0000000..7a07e77
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/CartClient.php
@@ -0,0 +1,88 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class CartClient extends BaseClient
+{
+ /**
+ * 导入收藏.
+ *
+ * @param array $params
+ * @param bool $isTest
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add($params, $isTest = false)
+ {
+ return $this->httpPostJson('mall/addshoppinglist', $params, ['is_test' => (int) $isTest]);
+ }
+
+ /**
+ * 查询用户收藏信息.
+ *
+ * @param array $params
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function query($params)
+ {
+ return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'batchquery']);
+ }
+
+ /**
+ * 查询用户收藏信息.
+ *
+ * @param array $params
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function queryByPage($params)
+ {
+ return $this->httpPostJson('mall/queryshoppinglist', $params, ['type' => 'getbypage']);
+ }
+
+ /**
+ * 删除收藏.
+ *
+ * @param string $openid
+ * @param array $products
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete($openid, array $products = [])
+ {
+ if (empty($products)) {
+ return $this->httpPostJson('mall/deletebizallshoppinglist', ['user_open_id' => $openid]);
+ }
+
+ return $this->httpPostJson('mall/deleteshoppinglist', ['user_open_id' => $openid, 'sku_product_list' => $products]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php
new file mode 100644
index 0000000..f727b17
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ForwardsMall.php
@@ -0,0 +1,48 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung
+ *
+ * @property \EasyWeChat\MiniProgram\Mall\OrderClient $order
+ * @property \EasyWeChat\MiniProgram\Mall\CartClient $cart
+ * @property \EasyWeChat\MiniProgram\Mall\ProductClient $product
+ * @property \EasyWeChat\MiniProgram\Mall\MediaClient $media
+ */
+class ForwardsMall
+{
+ /**
+ * @var \EasyWeChat\Kernel\ServiceContainer
+ */
+ protected $app;
+
+ /**
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ */
+ public function __construct($app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * @param string $property
+ *
+ * @return mixed
+ */
+ public function __get($property)
+ {
+ return $this->app["mall.{$property}"];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php
new file mode 100644
index 0000000..a3827bc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/MediaClient.php
@@ -0,0 +1,37 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class MediaClient extends BaseClient
+{
+ /**
+ * 更新或导入媒体信息.
+ *
+ * @param array $params
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function import($params)
+ {
+ return $this->httpPostJson('mall/importmedia', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php
new file mode 100644
index 0000000..9879695
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/OrderClient.php
@@ -0,0 +1,75 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class OrderClient extends BaseClient
+{
+ /**
+ * 导入订单.
+ *
+ * @param array $params
+ * @param bool $isHistory
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add($params, $isHistory = false)
+ {
+ return $this->httpPostJson('mall/importorder', $params, ['action' => 'add-order', 'is_history' => (int) $isHistory]);
+ }
+
+ /**
+ * 导入订单.
+ *
+ * @param array $params
+ * @param bool $isHistory
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update($params, $isHistory = false)
+ {
+ return $this->httpPostJson('mall/importorder', $params, ['action' => 'update-order', 'is_history' => (int) $isHistory]);
+ }
+
+ /**
+ * 删除订单.
+ *
+ * @param string $openid
+ * @param string $orderId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete($openid, $orderId)
+ {
+ $params = [
+ 'user_open_id' => $openid,
+ 'order_id' => $orderId,
+ ];
+
+ return $this->httpPostJson('mall/deleteorder', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php
new file mode 100644
index 0000000..f7fbfd0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ProductClient.php
@@ -0,0 +1,68 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class ProductClient extends BaseClient
+{
+ /**
+ * 更新或导入物品信息.
+ *
+ * @param array $params
+ * @param bool $isTest
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function import($params, $isTest = false)
+ {
+ return $this->httpPostJson('mall/importproduct', $params, ['is_test' => (int) $isTest]);
+ }
+
+ /**
+ * 查询物品信息.
+ *
+ * @param array $params
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function query($params)
+ {
+ return $this->httpPostJson('mall/queryproduct', $params, ['type' => 'batchquery']);
+ }
+
+ /**
+ * 小程序的物品是否可被搜索.
+ *
+ * @param bool $value
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setSearchable($value)
+ {
+ return $this->httpPostJson('mall/brandmanage', ['can_be_search' => $value], ['action' => 'set_biz_can_be_search']);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php
new file mode 100644
index 0000000..964395b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Mall/ServiceProvider.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Mall;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['mall'] = function ($app) {
+ return new ForwardsMall($app);
+ };
+
+ $app['mall.order'] = function ($app) {
+ return new OrderClient($app);
+ };
+
+ $app['mall.cart'] = function ($app) {
+ return new CartClient($app);
+ };
+
+ $app['mall.product'] = function ($app) {
+ return new ProductClient($app);
+ };
+
+ $app['mall.media'] = function ($app) {
+ return new MediaClient($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php
new file mode 100644
index 0000000..da9ee05
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/Client.php
@@ -0,0 +1,123 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\NearbyPoi;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author joyeekk
+ */
+class Client extends BaseClient
+{
+ /**
+ * Add nearby poi.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add(array $params)
+ {
+ $params = array_merge([
+ 'is_comm_nearby' => '1',
+ 'poi_id' => '',
+ ], $params);
+
+ return $this->httpPostJson('wxa/addnearbypoi', $params);
+ }
+
+ /**
+ * Update nearby poi.
+ *
+ * @param string $poiId
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(string $poiId, array $params)
+ {
+ $params = array_merge([
+ 'is_comm_nearby' => '1',
+ 'poi_id' => $poiId,
+ ], $params);
+
+ return $this->httpPostJson('wxa/addnearbypoi', $params);
+ }
+
+ /**
+ * Delete nearby poi.
+ *
+ * @param string $poiId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $poiId)
+ {
+ return $this->httpPostJson('wxa/delnearbypoi', [
+ 'poi_id' => $poiId,
+ ]);
+ }
+
+ /**
+ * Get nearby poi list.
+ *
+ * @param int $page
+ * @param int $pageRows
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function list(int $page, int $pageRows)
+ {
+ return $this->httpGet('wxa/getnearbypoilist', [
+ 'page' => $page,
+ 'page_rows' => $pageRows,
+ ]);
+ }
+
+ /**
+ * Set nearby poi show status.
+ *
+ * @param string $poiId
+ * @param int $status
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setVisibility(string $poiId, int $status)
+ {
+ if (!in_array($status, [0, 1], true)) {
+ throw new InvalidArgumentException('status should be 0 or 1.');
+ }
+
+ return $this->httpPostJson('wxa/setnearbypoishowstatus', [
+ 'poi_id' => $poiId,
+ 'status' => $status,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php
new file mode 100644
index 0000000..cb15bb3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/NearbyPoi/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\NearbyPoi;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author joyeekk
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['nearby_poi'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php
new file mode 100644
index 0000000..80e0001
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/OCR/ServiceProvider.php
@@ -0,0 +1,34 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\OCR;
+
+use EasyWeChat\OfficialAccount\OCR\Client;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author joyeekk
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['ocr'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php
new file mode 100644
index 0000000..21da580
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/Client.php
@@ -0,0 +1,91 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\OpenData;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+ /**
+ * removeUserStorage.
+ *
+ * @param string $openid
+ * @param string $sessionKey
+ * @param array $key
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function removeUserStorage(string $openid, string $sessionKey, array $key)
+ {
+ $data = ['key' => $key];
+ $query = [
+ 'openid' => $openid,
+ 'sig_method' => 'hmac_sha256',
+ 'signature' => hash_hmac('sha256', json_encode($data), $sessionKey),
+ ];
+
+ return $this->httpPostJson('wxa/remove_user_storage', $data, $query);
+ }
+
+ /**
+ * setUserStorage.
+ *
+ * @param string $openid
+ * @param string $sessionKey
+ * @param array $kvList
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setUserStorage(string $openid, string $sessionKey, array $kvList)
+ {
+ $kvList = $this->formatKVLists($kvList);
+
+ $data = ['kv_list' => $kvList];
+ $query = [
+ 'openid' => $openid,
+ 'sig_method' => 'hmac_sha256',
+ 'signature' => hash_hmac('sha256', json_encode($data), $sessionKey),
+ ];
+
+ return $this->httpPostJson('wxa/set_user_storage', $data, $query);
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return array
+ */
+ protected function formatKVLists(array $params)
+ {
+ $formatted = [];
+
+ foreach ($params as $name => $value) {
+ $formatted[] = [
+ 'key' => $name,
+ 'value' => strval($value),
+ ];
+ }
+
+ return $formatted;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php
new file mode 100644
index 0000000..3ea1287
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/OpenData/ServiceProvider.php
@@ -0,0 +1,28 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\OpenData;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['open_data'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/Client.php b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/Client.php
new file mode 100644
index 0000000..4052cae
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/Client.php
@@ -0,0 +1,47 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\PhoneNumber;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @package EasyWeChat\MiniProgram\PhoneNumber
+ *
+ * @author 读心印
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取用户手机号.
+ *
+ * @see https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/phonenumber/phonenumber.getPhoneNumber.html
+ *
+ * @param string $code
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ *
+ * @author 读心印
+ */
+ public function getUserPhoneNumber(string $code)
+ {
+ $params = [
+ 'code' => $code
+ ];
+
+ return $this->httpPostJson('wxa/business/getuserphonenumber', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/ServiceProvider.php
new file mode 100644
index 0000000..db4e76f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/PhoneNumber/ServiceProvider.php
@@ -0,0 +1,28 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\PhoneNumber;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['phone_number'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php
new file mode 100644
index 0000000..cd885cc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/Client.php
@@ -0,0 +1,67 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Plugin;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * @param string $appId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function apply($appId)
+ {
+ return $this->httpPostJson('wxa/plugin', [
+ 'action' => 'apply',
+ 'plugin_appid' => $appId,
+ ]);
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list()
+ {
+ return $this->httpPostJson('wxa/plugin', [
+ 'action' => 'list',
+ ]);
+ }
+
+ /**
+ * @param string $appId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unbind($appId)
+ {
+ return $this->httpPostJson('wxa/plugin', [
+ 'action' => 'unbind',
+ 'plugin_appid' => $appId,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php
new file mode 100644
index 0000000..3f25edc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/DevClient.php
@@ -0,0 +1,93 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Plugin;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class DevClient.
+ *
+ * @author her-cat
+ */
+class DevClient extends BaseClient
+{
+ /**
+ * Get users.
+ *
+ * @param int $page
+ * @param int $size
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getUsers(int $page = 1, int $size = 10)
+ {
+ return $this->httpPostJson('wxa/devplugin', [
+ 'action' => 'dev_apply_list',
+ 'page' => $page,
+ 'num' => $size,
+ ]);
+ }
+
+ /**
+ * Agree to use plugin.
+ *
+ * @param string $appId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function agree(string $appId)
+ {
+ return $this->httpPostJson('wxa/devplugin', [
+ 'action' => 'dev_agree',
+ 'appid' => $appId,
+ ]);
+ }
+
+ /**
+ * Refuse to use plugin.
+ *
+ * @param string $reason
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function refuse(string $reason)
+ {
+ return $this->httpPostJson('wxa/devplugin', [
+ 'action' => 'dev_refuse',
+ 'reason' => $reason,
+ ]);
+ }
+
+ /**
+ * Delete rejected applications.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete()
+ {
+ return $this->httpPostJson('wxa/devplugin', [
+ 'action' => 'dev_delete',
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php
new file mode 100644
index 0000000..097c77e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Plugin/ServiceProvider.php
@@ -0,0 +1,42 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Plugin;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param \Pimple\Container $app
+ */
+ public function register(Container $app)
+ {
+ $app['plugin'] = function ($app) {
+ return new Client($app);
+ };
+
+ $app['plugin_dev'] = function ($app) {
+ return new DevClient($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/QRCode/Client.php b/vendor/overtrue/wechat/src/MiniProgram/QRCode/Client.php
new file mode 100644
index 0000000..2fb98e5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/QRCode/Client.php
@@ -0,0 +1,126 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\QrCode;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
+use EasyWeChat\Kernel\Support\Collection;
+use GuzzleHttp\Exception\GuzzleException;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * QrCode Client
+ *
+ * 普通链接二维码
+ *
+ * @link https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/qrcode/qrcode.html
+ * @link https://developers.weixin.qq.com/miniprogram/introduction/qrcode.html
+ *
+ * @author dysodeng
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取已设置的二维码规则
+ *
+ * @return array|Collection|object|ResponseInterface|string
+ *
+ * @throws InvalidConfigException
+ * @throws GuzzleException
+ */
+ public function list()
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpget');
+ }
+
+ /**
+ * 获取校验文件名称及内容
+ *
+ * @return array|Collection|object|ResponseInterface|string
+ *
+ * @throws GuzzleException
+ * @throws InvalidConfigException
+ */
+ public function getVerifyFile()
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpdownload');
+ }
+
+ /**
+ * 增加或修改二维码规则
+ *
+ * @param array $params
+ *
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws GuzzleException
+ * @throws InvalidConfigException
+ */
+ public function set(array $params)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpadd', $params);
+ }
+
+ /**
+ * 发布已设置的二维码规则
+ *
+ * @param string $prefix
+ *
+ * @return array|Collection|object|ResponseInterface|string
+ *
+ * @throws GuzzleException
+ * @throws InvalidConfigException
+ */
+ public function publish(string $prefix)
+ {
+ $params = [
+ 'prefix' => $prefix
+ ];
+ return $this->httpPostJson('cgi-bin/wxopen/qrcodejumppublish', $params);
+ }
+
+ /**
+ * 删除已设置的二维码规则
+ *
+ * @param string $prefix
+ *
+ * @return array|Collection|object|ResponseInterface|string
+ *
+ * @throws GuzzleException
+ * @throws InvalidConfigException
+ */
+ public function delete(string $prefix)
+ {
+ $params = [
+ 'prefix' => $prefix
+ ];
+ return $this->httpPostJson('cgi-bin/wxopen/qrcodejumpdelete', $params);
+ }
+
+ /**
+ * 将二维码长链接转成短链接
+ *
+ * @param string $long_url
+ *
+ * @return array|Collection|object|ResponseInterface|string
+ *
+ * @throws GuzzleException
+ * @throws InvalidConfigException
+ */
+ public function shortUrl(string $long_url)
+ {
+ $params = [
+ 'long_url' => $long_url,
+ 'action' => 'long2short'
+ ];
+ return $this->httpPostJson('cgi-bin/shorturl', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/QRCode/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/QRCode/ServiceProvider.php
new file mode 100644
index 0000000..ee2a0aa
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/QRCode/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\QrCode;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * QrCode ServiceProvider.
+ *
+ * @author dysodeng
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $pimple)
+ {
+ $pimple['qr_code'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/Client.php b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/Client.php
new file mode 100644
index 0000000..9363619
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/Client.php
@@ -0,0 +1,46 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\RealtimeLog;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat
+ */
+class Client extends BaseClient
+{
+ /**
+ * Real time log query.
+ *
+ * @param string $date
+ * @param int $beginTime
+ * @param int $endTime
+ * @param array $options
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function search(string $date, int $beginTime, int $endTime, array $options = [])
+ {
+ $params = [
+ 'date' => $date,
+ 'begintime' => $beginTime,
+ 'endtime' => $endTime,
+ ];
+
+ return $this->httpGet('wxaapi/userlog/userlog_search', $params + $options);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/ServiceProvider.php
new file mode 100644
index 0000000..9a28141
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/RealtimeLog/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\RealtimeLog;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function register(Container $app)
+ {
+ $app['realtime_log'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/RiskControl/Client.php b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/Client.php
new file mode 100644
index 0000000..a6c00c0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/Client.php
@@ -0,0 +1,32 @@
+httpPostJson('wxa/getuserriskrank', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/RiskControl/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/ServiceProvider.php
new file mode 100644
index 0000000..94d2758
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/RiskControl/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Search;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat
+ */
+class Client extends BaseClient
+{
+ /**
+ * Submit applet page URL and parameter information.
+ *
+ * @param array $pages
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function submitPage(array $pages)
+ {
+ return $this->httpPostJson('wxa/search/wxaapi_submitpages', compact('pages'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Search/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Search/ServiceProvider.php
new file mode 100644
index 0000000..85cb569
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Search/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Search;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['search'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php
new file mode 100644
index 0000000..0586bb9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Server/ServiceProvider.php
@@ -0,0 +1,42 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Server;
+
+use EasyWeChat\MiniProgram\Encryptor;
+use EasyWeChat\OfficialAccount\Server\Guard;
+use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['encryptor']) && $app['encryptor'] = function ($app) {
+ return new Encryptor(
+ $app['config']['app_id'],
+ $app['config']['token'],
+ $app['config']['aes_key']
+ );
+ };
+
+ !isset($app['server']) && $app['server'] = function ($app) {
+ $guard = new Guard($app);
+ $guard->push(new EchoStrHandler($app));
+
+ return $guard;
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/Client.php
new file mode 100644
index 0000000..1988e7e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/Client.php
@@ -0,0 +1,67 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取商家类目列表
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getCategoryList()
+ {
+ return $this->httpPostJson('shop/account/get_category_list');
+ }
+
+ /**
+ * 获取商家品牌列表
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getBrandList()
+ {
+ return $this->httpPostJson('shop/account/get_brand_list');
+ }
+
+ /**
+ * 更新商家信息
+ *
+ * @param string $path 小程序path
+ * @param string $phone 客服联系方式
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateInfo(string $path = '', string $phone = '')
+ {
+ return $this->httpPostJson('shop/account/update_info', [
+ 'service_agent_path' => $path,
+ 'service_agent_phone' => $phone,
+ ]);
+ }
+
+ /**
+ * 获取商家信息
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getInfo()
+ {
+ return $this->httpPostJson('shop/account/get_info');
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/ServiceProvider.php
new file mode 100644
index 0000000..42e8bf8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Account/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function register(Container $app)
+ {
+ $app['shop_account'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/Client.php
new file mode 100644
index 0000000..ebb83d1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/Client.php
@@ -0,0 +1,54 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * 创建售后
+ *
+ * @param array $aftersale
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add(array $aftersale)
+ {
+ return $this->httpPostJson('shop/ecaftersale/add', $aftersale);
+ }
+
+ /**
+ * 获取订单下售后单
+ *
+ * @param array $order 订单数据
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(array $order)
+ {
+ return $this->httpPostJson('shop/ecaftersale/get', $order);
+ }
+
+ /**
+ * 更新售后
+ *
+ * @param array $order 订单数据
+ * @param array $aftersale 售后数据
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(array $order, array $aftersale)
+ {
+ return $this->httpPostJson('shop/ecaftersale/update', array_merge($order, $aftersale));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/ServiceProvider.php
new file mode 100644
index 0000000..91d9ab7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Aftersale/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function register(Container $app)
+ {
+ $app['shop_aftersale'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/Client.php
new file mode 100644
index 0000000..b2cf95f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/Client.php
@@ -0,0 +1,110 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取商品类目
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getCat()
+ {
+ return $this->httpPostJson('shop/cat/get');
+ }
+
+ /**
+ * @param string $imageFilePath 图片文件路径
+ * @param int $respType 返回类型
+ * @param int $uploadType 上传类型,本地图片或者网络图片
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function imgUpload(string $imageFilePath, int $respType = 1, int $uploadType = 0)
+ {
+ if ($uploadType == 0) {
+ return $this->httpUpload('shop/img/upload', [
+ 'media' => $imageFilePath,
+ ], [
+ 'resp_type' => $respType,
+ ]);
+ } else {
+ return $this->httpPostJson('shop/img/upload', [], [
+ 'img_url' => $imageFilePath,
+ 'resp_type' => $respType,
+ 'upload_type' => $uploadType
+ ]);
+ }
+ }
+
+ /**
+ * 品牌审核
+ *
+ * @param array $brand 品牌信息
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function auditBrand(array $brand)
+ {
+ return $this->httpPostJson('shop/audit/audit_brand', [
+ 'audit_req' => $brand
+ ]);
+ }
+
+ /**
+ * 类目审核
+ *
+ * @param array $category 类目资质
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function auditCategory(array $category)
+ {
+ return $this->httpPostJson('shop/audit/audit_category', [
+ 'audit_req' => $category
+ ]);
+ }
+
+ /**
+ * 获取审核结果
+ *
+ * @param string $auditId 提交审核时返回的id
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function auditResult(string $auditId)
+ {
+ return $this->httpPostJson('shop/audit/result', [
+ 'audit_id' => $auditId
+ ]);
+ }
+
+ /**
+ * 获取小程序资质
+ *
+ * @param int $reqType
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getMiniAppCertificate(int $reqType = 2)
+ {
+ return $this->httpPostJson('shop/audit/get_miniapp_certificate', [
+ 'req_type' => $reqType
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/ServiceProvider.php
new file mode 100644
index 0000000..a8ed616
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Basic/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function register(Container $app)
+ {
+ $app['shop_basic'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/Client.php
new file mode 100644
index 0000000..c924034
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/Client.php
@@ -0,0 +1,52 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取快递公司列表
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getCompanyList()
+ {
+ return $this->httpPostJson('shop/delivery/get_company_list');
+ }
+
+ /**
+ * 订单发货
+ *
+ * @param array $order
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $order)
+ {
+ return $this->httpPostJson('shop/delivery/send', $order);
+ }
+
+ /**
+ * 订单确认收货
+ *
+ * @param array $order
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function recieve(array $order)
+ {
+ return $this->httpPostJson('shop/delivery/recieve', $order);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/ServiceProvider.php
new file mode 100644
index 0000000..eb8ce16
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Delivery/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function register(Container $app)
+ {
+ $app['shop_delivery'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/Client.php
new file mode 100644
index 0000000..81a4cac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/Client.php
@@ -0,0 +1,157 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * 检查场景值是否在支付校验范围内
+ *
+ * @param int $scene 场景值
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function sceneCheck(int $scene)
+ {
+ return $this->httpPostJson('shop/scene/check', ['scene' => $scene]);
+ }
+
+ /**
+ * 生成订单
+ *
+ * @param array $order
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(array $order)
+ {
+ return $this->httpPostJson('shop/order/add', $order);
+ }
+
+ /**
+ * 生成订单
+ *
+ * @param array $order
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add(array $order)
+ {
+ return $this->httpPostJson('shop/order/add', $order);
+ }
+
+ /**
+ * 获取订单详情
+ *
+ * @param string $openid 用户的openid
+ * @param array $orderId 微信侧订单id (订单id二选一)
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $openid, array $orderId)
+ {
+ return $this->httpPostJson('shop/order/get', array_merge($orderId, ['openid' => $openid]));
+ }
+
+ /**
+ * 关闭订单
+ *
+ * @param array $params 请求参数
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function close(array $params)
+ {
+ return $this->httpPostJson('shop/order/close', $params);
+ }
+
+ /**
+ * 获取订单列表
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getList(array $data)
+ {
+ return $this->httpPostJson('shop/order/get_list', $data);
+ }
+
+ /**
+ * 同步订单支付结果
+ *
+ * @param array $pay
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function pay(array $pay)
+ {
+ return $this->httpPostJson('shop/order/pay', $pay);
+ }
+
+ /**
+ * 同步订单支付结果
+ *
+ * @param array $pay
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function syncPayState(array $pay)
+ {
+ return $this->httpPostJson('shop/order/pay', $pay);
+ }
+
+ /**
+ * 同步订单支付结果
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPayInfo(array $params)
+ {
+ return $this->httpPostJson('shop/order/getpaymentparams', $params);
+ }
+
+ /**
+ * 获取推广员订单
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getFinderOrders(array $params)
+ {
+ return $this->httpPostJson('shop/order/get_list_by_finder', $params);
+ }
+
+ /**
+ * 获取分享员订单
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getSharerOrders(array $params)
+ {
+ return $this->httpPostJson('shop/order/get_list_by_sharer', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/ServiceProvider.php
new file mode 100644
index 0000000..4b28114
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Order/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function register(Container $app)
+ {
+ $app['shop_order'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/Client.php
new file mode 100644
index 0000000..4667cee
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/Client.php
@@ -0,0 +1,76 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Shop\Register;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat
+ */
+class Client extends BaseClient
+{
+ /**
+ * 接入申请
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function apply()
+ {
+ return $this->httpPostJson('shop/register/apply');
+ }
+
+ /**
+ * 获取接入状态
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function check()
+ {
+ return $this->httpPostJson('shop/register/check');
+ }
+
+ /**
+ * 完成接入任务
+ *
+ * @param int $accessInfoItem
+ * 6:完成spu接口,7:完成订单接口,8:完成物流接口,9:完成售后接口,10:测试完成,11:发版完成
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function finishAccessInfo(int $accessInfoItem)
+ {
+ return $this->httpPostJson('shop/register/finish_access_info', [
+ 'access_info_item' => $accessInfoItem
+ ]);
+ }
+
+ /**
+ * 场景接入申请
+ *
+ * @param int $sceneGroupId 1:视频号、公众号场景
+
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function applyScene(int $sceneGroupId = 1)
+ {
+ return $this->httpPostJson('shop/register/apply_scene', [
+ 'scene_group_id' => $sceneGroupId
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/ServiceProvider.php
new file mode 100644
index 0000000..58fd286
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Register/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Shop\Register;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function register(Container $app)
+ {
+ $app['shop_register'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/Client.php
new file mode 100644
index 0000000..dc06fd1
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/Client.php
@@ -0,0 +1,132 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * 添加商品
+ *
+ * @param array $product 商品信息
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add(array $product)
+ {
+ return $this->httpPostJson('shop/spu/add', $product);
+ }
+
+ /**
+ * 删除商品
+ *
+ * @param array $productId 商品编号信息
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function del(array $productId)
+ {
+ return $this->httpPostJson('shop/spu/del', $productId);
+ }
+
+ /**
+ * 获取商品
+ *
+ * @param array $productId 商品编号信息
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(array $productId)
+ {
+ return $this->httpPostJson('shop/spu/get', $productId);
+ }
+
+ /**
+ * 获取商品列表
+ *
+ * @param array $product 商品信息
+ * @param array $page 分页信息
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getList(array $product, array $page)
+ {
+ return $this->httpPostJson('shop/spu/get_list', array_merge($product, $page));
+ }
+
+ /**
+ * 撤回商品审核
+ *
+ * @param array $productId 商品编号信息 交易组件平台内部商品ID,与out_product_id二选一
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delAudit(array $productId)
+ {
+ return $this->httpPostJson('shop/spu/del_audit', $productId);
+ }
+
+ /**
+ * 更新商品
+ *
+ * @param array $product
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(array $product)
+ {
+ return $this->httpPostJson('shop/spu/update', $product);
+ }
+
+ /**
+ * 该免审更新商品
+ *
+ * @param array $product
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateWithoutAudit(array $product)
+ {
+ return $this->httpPostJson('shop/spu/update_without_audit', $product);
+ }
+
+ /**
+ * 上架商品
+ *
+ * @param array $productId 商品编号数据 交易组件平台内部商品ID,与out_product_id二选一
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function listing(array $productId)
+ {
+ return $this->httpPostJson('shop/spu/listing', $productId);
+ }
+
+ /**
+ * 下架商品
+ *
+ * @param array $productId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delisting(array $productId)
+ {
+ return $this->httpPostJson('shop/spu/delisting', $productId);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/ServiceProvider.php
new file mode 100644
index 0000000..c87344d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Shop/Spu/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ * @package EasyWeChat\MiniProgram\Shop\Spu
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function register(Container $app)
+ {
+ $app['shop_spu'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/ShortLink/Client.php b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/Client.php
new file mode 100644
index 0000000..efce5f7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/Client.php
@@ -0,0 +1,42 @@
+
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取小程序 Short Link
+ *
+ * @param string $pageUrl
+ * @param string $pageTitle
+ * @param bool $isPermanent
+ *
+ * @return array|Collection|object|ResponseInterface|string
+ *
+ * @throws GuzzleException
+ * @throws InvalidConfigException
+ */
+ public function getShortLink(string $pageUrl, string $pageTitle, bool $isPermanent = false)
+ {
+ $params = [
+ 'page_url' => $pageUrl,
+ 'page_title' => $pageTitle,
+ 'is_permanent' => $isPermanent,
+ ];
+
+ return $this->httpPostJson('wxa/genwxashortlink', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/ShortLink/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/ServiceProvider.php
new file mode 100644
index 0000000..79a28ec
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/ShortLink/ServiceProvider.php
@@ -0,0 +1,19 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Soter;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat
+ */
+class Client extends BaseClient
+{
+ /**
+ * @param string $openid
+ * @param string $json
+ * @param string $signature
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function verifySignature(string $openid, string $json, string $signature)
+ {
+ return $this->httpPostJson('cgi-bin/soter/verify_signature', [
+ 'openid' => $openid,
+ 'json_string' => $json,
+ 'json_signature' => $signature,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php
new file mode 100644
index 0000000..c8520db
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Soter/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Soter;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function register(Container $app)
+ {
+ $app['soter'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php
new file mode 100644
index 0000000..1e797ee
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/Client.php
@@ -0,0 +1,209 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\SubscribeMessage;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use ReflectionClass;
+
+/**
+ * Class Client.
+ *
+ * @author hugo
+ */
+class Client extends BaseClient
+{
+ /**
+ * {@inheritdoc}.
+ */
+ protected $message = [
+ 'touser' => '',
+ 'template_id' => '',
+ 'page' => '',
+ 'data' => [],
+ 'miniprogram_state' => 'formal',
+ ];
+
+ /**
+ * {@inheritdoc}.
+ */
+ protected $required = ['touser', 'template_id', 'data'];
+
+ /**
+ * Send a template message.
+ *
+ * @param array $data
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $data = [])
+ {
+ $params = $this->formatMessage($data);
+
+ $this->restoreMessage();
+
+ return $this->httpPostJson('cgi-bin/message/subscribe/send', $params);
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function formatMessage(array $data = [])
+ {
+ $params = array_merge($this->message, $data);
+
+ foreach ($params as $key => $value) {
+ if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) {
+ throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+ }
+
+ $params[$key] = empty($value) ? $this->message[$key] : $value;
+ }
+
+ foreach ($params['data'] as $key => $value) {
+ if (is_array($value)) {
+ if (\array_key_exists('value', $value)) {
+ $params['data'][$key] = ['value' => $value['value']];
+
+ continue;
+ }
+
+ if (count($value) >= 1) {
+ $value = [
+ 'value' => $value[0],
+// 'color' => $value[1],// color unsupported
+ ];
+ }
+ } else {
+ $value = [
+ 'value' => strval($value),
+ ];
+ }
+
+ $params['data'][$key] = $value;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Restore message.
+ */
+ protected function restoreMessage()
+ {
+ $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message'];
+ }
+
+ /**
+ * Combine templates and add them to your personal template library under your account.
+ *
+ * @param string $tid
+ * @param array $kidList
+ * @param string|null $sceneDesc
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addTemplate(string $tid, array $kidList, string $sceneDesc = null)
+ {
+ $sceneDesc = $sceneDesc ?? '';
+ $data = \compact('tid', 'kidList', 'sceneDesc');
+
+ return $this->httpPost('wxaapi/newtmpl/addtemplate', $data);
+ }
+
+ /**
+ * Delete personal template under account.
+ *
+ * @param string $id
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deleteTemplate(string $id)
+ {
+ return $this->httpPost('wxaapi/newtmpl/deltemplate', ['priTmplId' => $id]);
+ }
+
+ /**
+ * Get keyword list under template title.
+ *
+ * @param string $tid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTemplateKeywords(string $tid)
+ {
+ return $this->httpGet('wxaapi/newtmpl/getpubtemplatekeywords', compact('tid'));
+ }
+
+ /**
+ * Get the title of the public template under the category to which the account belongs.
+ *
+ * @param array $ids
+ * @param int $start
+ * @param int $limit
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTemplateTitles(array $ids, int $start = 0, int $limit = 30)
+ {
+ $ids = \implode(',', $ids);
+ $query = \compact('ids', 'start', 'limit');
+
+ return $this->httpGet('wxaapi/newtmpl/getpubtemplatetitles', $query);
+ }
+
+ /**
+ * Get list of personal templates under the current account.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTemplates()
+ {
+ return $this->httpGet('wxaapi/newtmpl/gettemplate');
+ }
+
+ /**
+ * Get the category of the applet account.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getCategory()
+ {
+ return $this->httpGet('wxaapi/newtmpl/getcategory');
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php
new file mode 100644
index 0000000..726b3ed
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/SubscribeMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\SubscribeMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['subscribe_message'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php
new file mode 100644
index 0000000..06dfbcb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/Client.php
@@ -0,0 +1,114 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\TemplateMessage;
+
+use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ public const API_SEND = 'cgi-bin/message/wxopen/template/send';
+
+ /**
+ * {@inheritdoc}.
+ */
+ protected $message = [
+ 'touser' => '',
+ 'template_id' => '',
+ 'page' => '',
+ 'form_id' => '',
+ 'data' => [],
+ 'emphasis_keyword' => '',
+ ];
+
+ /**
+ * {@inheritdoc}.
+ */
+ protected $required = ['touser', 'template_id', 'form_id'];
+
+ /**
+ * @param int $offset
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $offset, int $count)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/template/library/list', compact('offset', 'count'));
+ }
+
+ /**
+ * @param string $id
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $id)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/template/library/get', compact('id'));
+ }
+
+ /**
+ * @param string $id
+ * @param array $keyword
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add(string $id, array $keyword)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/template/add', [
+ 'id' => $id,
+ 'keyword_id_list' => $keyword,
+ ]);
+ }
+
+ /**
+ * @param string $templateId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $templateId)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/template/del', [
+ 'template_id' => $templateId,
+ ]);
+ }
+
+ /**
+ * @param int $offset
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTemplates(int $offset, int $count)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/template/list', compact('offset', 'count'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php
new file mode 100644
index 0000000..776a15e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/TemplateMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\TemplateMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['template_message'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php
new file mode 100644
index 0000000..0cc84fe
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/Client.php
@@ -0,0 +1,146 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\UniformMessage;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\OfficialAccount\TemplateMessage\Client as BaseClient;
+
+class Client extends BaseClient
+{
+ public const API_SEND = 'cgi-bin/message/wxopen/template/uniform_send';
+
+ /**
+ * {@inheritdoc}.
+ *
+ * @var array
+ */
+ protected $message = [
+ 'touser' => '',
+ ];
+
+ /**
+ * Weapp Attributes.
+ *
+ * @var array
+ */
+ protected $weappMessage = [
+ 'template_id' => '',
+ 'page' => '',
+ 'form_id' => '',
+ 'data' => [],
+ 'emphasis_keyword' => '',
+ ];
+
+ /**
+ * Official account attributes.
+ *
+ * @var array
+ */
+ protected $mpMessage = [
+ 'appid' => '',
+ 'template_id' => '',
+ 'url' => '',
+ 'miniprogram' => [],
+ 'data' => [],
+ ];
+
+ /**
+ * Required attributes.
+ *
+ * @var array
+ */
+ protected $required = ['touser', 'template_id', 'form_id', 'miniprogram', 'appid'];
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function formatMessage(array $data = [])
+ {
+ $params = array_merge($this->message, $data);
+
+ if (empty($params['touser'])) {
+ throw new InvalidArgumentException(sprintf('Attribute "touser" can not be empty!'));
+ }
+
+ if (!empty($params['weapp_template_msg'])) {
+ $params['weapp_template_msg'] = $this->formatWeappMessage($params['weapp_template_msg']);
+ }
+
+ if (!empty($params['mp_template_msg'])) {
+ $params['mp_template_msg'] = $this->formatMpMessage($params['mp_template_msg']);
+ }
+
+ return $params;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function formatWeappMessage(array $data = [])
+ {
+ $params = $this->baseFormat($data, $this->weappMessage);
+
+ $params['data'] = $this->formatData($params['data'] ?? []);
+
+ return $params;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function formatMpMessage(array $data = [])
+ {
+ $params = $this->baseFormat($data, $this->mpMessage);
+
+ if (empty($params['miniprogram']['appid'])) {
+ $params['miniprogram']['appid'] = $this->app['config']['app_id'];
+ }
+
+ $params['data'] = $this->formatData($params['data'] ?? []);
+
+ return $params;
+ }
+
+ /**
+ * @param array $data
+ * @param array $default
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function baseFormat($data = [], $default = [])
+ {
+ $params = array_merge($default, $data);
+ foreach ($params as $key => $value) {
+ if (in_array($key, $this->required, true) && empty($value) && empty($default[$key])) {
+ throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+ }
+
+ $params[$key] = empty($value) ? $default[$key] : $value;
+ }
+
+ return $params;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php
new file mode 100644
index 0000000..0e86db3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/UniformMessage/ServiceProvider.php
@@ -0,0 +1,28 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\UniformMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['uniform_message'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Union/Client.php b/vendor/overtrue/wechat/src/MiniProgram/Union/Client.php
new file mode 100644
index 0000000..dd46d80
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Union/Client.php
@@ -0,0 +1,228 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Union;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Abbotton
+ */
+class Client extends BaseClient
+{
+ /**
+ * Add promotion.
+ *
+ * @param string $promotionSourceName
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createPromotion(string $promotionSourceName)
+ {
+ $params = [
+ 'promotionSourceName' => $promotionSourceName,
+ ];
+
+ return $this->httpPostJson('union/promoter/promotion/add', $params);
+ }
+
+ /**
+ * Delete promotion.
+ *
+ * @param string $promotionSourcePid
+ * @param string $promotionSourceName
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deletePromotion(string $promotionSourcePid, string $promotionSourceName)
+ {
+ $params = [
+ 'promotionSourceName' => $promotionSourceName,
+ 'promotionSourcePid' => $promotionSourcePid,
+ ];
+
+ return $this->httpPostJson('union/promoter/promotion/del', $params);
+ }
+
+ /**
+ * Update promotion.
+ *
+ * @param array $previousPromotionInfo
+ * @param array $promotionInfo
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updatePromotion(array $previousPromotionInfo, array $promotionInfo)
+ {
+ $params = [
+ 'previousPromotionInfo' => $previousPromotionInfo,
+ 'promotionInfo' => $promotionInfo,
+ ];
+
+ return $this->httpPostJson('union/promoter/promotion/upd', $params);
+ }
+
+ /**
+ * Get a list of promotion spots.
+ *
+ * @param int $start
+ * @param int $limit
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPromotionSourceList(int $start = 0, int $limit = 20)
+ {
+ $params = [
+ 'start' => $start,
+ 'limit' => $limit
+ ];
+
+ return $this->httpGet('union/promoter/promotion/list', $params);
+ }
+
+ /**
+ * Get the list of affiliate product categories and category IDs.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getProductCategory()
+ {
+ return $this->httpGet('union/promoter/product/category');
+ }
+
+ /**
+ * Get the list and detail of affiliate product.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getProductList(array $params)
+ {
+ return $this->httpGet('union/promoter/product/list', $params);
+ }
+
+ /**
+ * Get product promotion materials
+ *
+ * @param string $pid
+ * @param array $productList
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getProductMaterial(string $pid, array $productList)
+ {
+ $params = [
+ 'pid' => $pid,
+ 'productList' => $productList,
+ ];
+
+ return $this->httpPostJson('union/promoter/product/generate', $params);
+ }
+
+ /**
+ * Query order details based on order ID array.
+ *
+ * @param array $orderIdList
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getOrderInfo(array $orderIdList)
+ {
+ return $this->httpPostJson('union/promoter/order/info', $orderIdList);
+ }
+
+ /**
+ * Query and filter the order list.
+ *
+ * @param int $page
+ * @param string $startTimestamp
+ * @param string $endTimestamp
+ * @param string $commissionStatus
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function searchOrder(int $page = 1, $startTimestamp = '', $endTimestamp = '', $commissionStatus = '')
+ {
+ $params = [
+ 'page' => $page,
+ 'startTimestamp' => $startTimestamp,
+ 'endTimestamp' => $endTimestamp,
+ 'commissionStatus' => $commissionStatus
+ ];
+
+ return $this->httpGet('union/promoter/order/search', $params);
+ }
+
+ /**
+ * Get featured products of union.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getFeaturedProducts(array $params)
+ {
+ return $this->httpGet('union/promoter/product/select', $params);
+ }
+
+ /**
+ * Query the details of the targeted plan.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTargetPlanInfo(array $params)
+ {
+ return $this->httpGet('union/promoter/target/plan_info', $params);
+ }
+
+ /**
+ * Apply to join the targeted plan.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function applyJoinTargetPlan(array $params)
+ {
+ return $this->httpPostJson('union/promoter/target/apply_target', $params);
+ }
+
+ /**
+ * Query the status of the targeted plan apply.
+ *
+ * @param array $params
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTargetPlanStatus(array $params)
+ {
+ return $this->httpGet('union/promoter/target/apply_status', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/Union/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/Union/ServiceProvider.php
new file mode 100644
index 0000000..ba1207a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/Union/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\MiniProgram\Union;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author Abbotton
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['union'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/UrlLink/Client.php b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/Client.php
new file mode 100644
index 0000000..45b189f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/Client.php
@@ -0,0 +1,32 @@
+httpPostJson('wxa/generate_urllink', $param);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/UrlLink/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/ServiceProvider.php
new file mode 100644
index 0000000..3201d36
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/UrlLink/ServiceProvider.php
@@ -0,0 +1,19 @@
+httpPostJson('wxa/generatescheme', $param);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/MiniProgram/UrlScheme/ServiceProvider.php b/vendor/overtrue/wechat/src/MiniProgram/UrlScheme/ServiceProvider.php
new file mode 100644
index 0000000..cb8ad1a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/MiniProgram/UrlScheme/ServiceProvider.php
@@ -0,0 +1,19 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount;
+
+use EasyWeChat\BasicService;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Application.
+ *
+ * @author overtrue
+ *
+ * @property \EasyWeChat\BasicService\Media\Client $media
+ * @property \EasyWeChat\BasicService\Url\Client $url
+ * @property \EasyWeChat\BasicService\QrCode\Client $qrcode
+ * @property \EasyWeChat\BasicService\Jssdk\Client $jssdk
+ * @property \EasyWeChat\OfficialAccount\Auth\AccessToken $access_token
+ * @property \EasyWeChat\OfficialAccount\Server\Guard $server
+ * @property \EasyWeChat\OfficialAccount\User\UserClient $user
+ * @property \EasyWeChat\OfficialAccount\User\TagClient $user_tag
+ * @property \EasyWeChat\OfficialAccount\Menu\Client $menu
+ * @property \EasyWeChat\OfficialAccount\TemplateMessage\Client $template_message
+ * @property \EasyWeChat\OfficialAccount\SubscribeMessage\Client $subscribe_message
+ * @property \EasyWeChat\OfficialAccount\Material\Client $material
+ * @property \EasyWeChat\OfficialAccount\CustomerService\Client $customer_service
+ * @property \EasyWeChat\OfficialAccount\CustomerService\SessionClient $customer_service_session
+ * @property \EasyWeChat\OfficialAccount\Semantic\Client $semantic
+ * @property \EasyWeChat\OfficialAccount\DataCube\Client $data_cube
+ * @property \EasyWeChat\OfficialAccount\AutoReply\Client $auto_reply
+ * @property \EasyWeChat\OfficialAccount\Broadcasting\Client $broadcasting
+ * @property \EasyWeChat\OfficialAccount\Card\Card $card
+ * @property \EasyWeChat\OfficialAccount\Device\Client $device
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\ShakeAround $shake_around
+ * @property \EasyWeChat\OfficialAccount\POI\Client $poi
+ * @property \EasyWeChat\OfficialAccount\Store\Client $store
+ * @property \EasyWeChat\OfficialAccount\Base\Client $base
+ * @property \EasyWeChat\OfficialAccount\Comment\Client $comment
+ * @property \EasyWeChat\OfficialAccount\OCR\Client $ocr
+ * @property \EasyWeChat\OfficialAccount\Goods\Client $goods
+ * @property \Overtrue\Socialite\Providers\WeChat $oauth
+ * @property \EasyWeChat\OfficialAccount\WiFi\Client $wifi
+ * @property \EasyWeChat\OfficialAccount\WiFi\CardClient $wifi_card
+ * @property \EasyWeChat\OfficialAccount\WiFi\DeviceClient $wifi_device
+ * @property \EasyWeChat\OfficialAccount\WiFi\ShopClient $wifi_shop
+ * @property \EasyWeChat\OfficialAccount\Guide\Client $guide
+ * @property \EasyWeChat\OfficialAccount\Draft\Client $draft
+ * @property \EasyWeChat\OfficialAccount\FreePublish\Client $free_publish
+ */
+class Application extends ServiceContainer
+{
+ /**
+ * @var array
+ */
+ protected $providers = [
+ Auth\ServiceProvider::class,
+ Server\ServiceProvider::class,
+ User\ServiceProvider::class,
+ OAuth\ServiceProvider::class,
+ Menu\ServiceProvider::class,
+ TemplateMessage\ServiceProvider::class,
+ SubscribeMessage\ServiceProvider::class,
+ Material\ServiceProvider::class,
+ CustomerService\ServiceProvider::class,
+ Semantic\ServiceProvider::class,
+ DataCube\ServiceProvider::class,
+ POI\ServiceProvider::class,
+ AutoReply\ServiceProvider::class,
+ Broadcasting\ServiceProvider::class,
+ Card\ServiceProvider::class,
+ Device\ServiceProvider::class,
+ ShakeAround\ServiceProvider::class,
+ Store\ServiceProvider::class,
+ Comment\ServiceProvider::class,
+ Base\ServiceProvider::class,
+ OCR\ServiceProvider::class,
+ Goods\ServiceProvider::class,
+ WiFi\ServiceProvider::class,
+ Draft\ServiceProvider::class,
+ FreePublish\ServiceProvider::class,
+ // Base services
+ BasicService\QrCode\ServiceProvider::class,
+ BasicService\Media\ServiceProvider::class,
+ BasicService\Url\ServiceProvider::class,
+ BasicService\Jssdk\ServiceProvider::class,
+ // Append Guide Interface
+ Guide\ServiceProvider::class,
+ ];
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php
new file mode 100644
index 0000000..f010e38
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/AccessToken.php
@@ -0,0 +1,39 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * Class AuthorizerAccessToken.
+ *
+ * @author overtrue
+ */
+class AccessToken extends BaseAccessToken
+{
+ /**
+ * @var string
+ */
+ protected $endpointToGetToken = 'cgi-bin/token';
+
+ /**
+ * @return array
+ */
+ protected function getCredentials(): array
+ {
+ return [
+ 'grant_type' => 'client_credential',
+ 'appid' => $this->app['config']['app_id'],
+ 'secret' => $this->app['config']['secret'],
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php
new file mode 100644
index 0000000..e748730
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Auth/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['access_token']) && $app['access_token'] = function ($app) {
+ return new AccessToken($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php
new file mode 100644
index 0000000..6d2e4d6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/Client.php
@@ -0,0 +1,34 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\AutoReply;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get current auto reply settings.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function current()
+ {
+ return $this->httpGet('cgi-bin/get_current_autoreply_info');
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php
new file mode 100644
index 0000000..4377550
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/AutoReply/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\AutoReply;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['auto_reply'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php
new file mode 100644
index 0000000..e93d025
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/Client.php
@@ -0,0 +1,103 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Base;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get api quota.
+ *
+ * @param string $cgiPath api cgi_path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getQuota(string $cgiPath)
+ {
+ $params = [
+ 'cgi_path' => $cgiPath,
+ ];
+
+ return $this->httpPostJson('cgi-bin/openapi/quota/get', $params);
+ }
+
+ /**
+ * Clear quota.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function clearQuota()
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ ];
+
+ return $this->httpPostJson('cgi-bin/clear_quota', $params);
+ }
+
+ /**
+ * Get wechat callback ip.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getValidIps()
+ {
+ return $this->httpGet('cgi-bin/getcallbackip');
+ }
+
+ /**
+ * Check the callback address network.
+ *
+ * @param string $action
+ * @param string $operator
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function checkCallbackUrl(string $action = 'all', string $operator = 'DEFAULT')
+ {
+ if (!in_array($action, ['dns', 'ping', 'all'], true)) {
+ throw new InvalidArgumentException('The action must be dns, ping, all.');
+ }
+
+ $operator = strtoupper($operator);
+
+ if (!in_array($operator, ['CHINANET', 'UNICOM', 'CAP', 'DEFAULT'], true)) {
+ throw new InvalidArgumentException('The operator must be CHINANET, UNICOM, CAP, DEFAULT.');
+ }
+
+ $params = [
+ 'action' => $action,
+ 'check_operator' => $operator,
+ ];
+
+ return $this->httpPostJson('cgi-bin/callback/check', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php
new file mode 100644
index 0000000..409593d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['base'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php
new file mode 100644
index 0000000..4910a64
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/Client.php
@@ -0,0 +1,383 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Broadcasting;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Messages\Card;
+use EasyWeChat\Kernel\Messages\Image;
+use EasyWeChat\Kernel\Messages\Media;
+use EasyWeChat\Kernel\Messages\Text;
+use EasyWeChat\Kernel\Support\Arr;
+
+/**
+ * Class Client.
+ *
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewTextByName($text, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewNewsByName($mediaId, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVoiceByName($mediaId, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewImageByName($mediaId, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewVideoByName($message, $name);
+ * @method \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string previewCardByName($cardId, $name);
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ public const PREVIEW_BY_OPENID = 'touser';
+ public const PREVIEW_BY_NAME = 'towxname';
+
+ /**
+ * Send a message.
+ *
+ * @param array $message
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $message)
+ {
+ if (empty($message['filter']) && empty($message['touser'])) {
+ throw new RuntimeException('The message reception object is not specified');
+ }
+
+ $api = Arr::get($message, 'touser') ? 'cgi-bin/message/mass/send' : 'cgi-bin/message/mass/sendall';
+
+ return $this->httpPostJson($api, $message);
+ }
+
+ /**
+ * Preview a message.
+ *
+ * @param array $message
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function preview(array $message)
+ {
+ return $this->httpPostJson('cgi-bin/message/mass/preview', $message);
+ }
+
+ /**
+ * Delete a broadcast.
+ *
+ * @param string $msgId
+ * @param int $index
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $msgId, int $index = 0)
+ {
+ $options = [
+ 'msg_id' => $msgId,
+ 'article_idx' => $index,
+ ];
+
+ return $this->httpPostJson('cgi-bin/message/mass/delete', $options);
+ }
+
+ /**
+ * Get a broadcast status.
+ *
+ * @param string $msgId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function status(string $msgId)
+ {
+ $options = [
+ 'msg_id' => $msgId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/message/mass/get', $options);
+ }
+
+ /**
+ * Send a text message.
+ *
+ * @param string $message
+ * @param mixed $reception
+ * @param array $attributes
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function sendText(string $message, $reception = null, array $attributes = [])
+ {
+ return $this->sendMessage(new Text($message), $reception, $attributes);
+ }
+
+ /**
+ * Send a news message.
+ *
+ * @param string $mediaId
+ * @param mixed $reception
+ * @param array $attributes
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function sendNews(string $mediaId, $reception = null, array $attributes = [])
+ {
+ return $this->sendMessage(new Media($mediaId, 'mpnews'), $reception, $attributes);
+ }
+
+ /**
+ * Send a voice message.
+ *
+ * @param string $mediaId
+ * @param mixed $reception
+ * @param array $attributes
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function sendVoice(string $mediaId, $reception = null, array $attributes = [])
+ {
+ return $this->sendMessage(new Media($mediaId, 'voice'), $reception, $attributes);
+ }
+
+ /**
+ * Send a image message.
+ *
+ * @param string $mediaId
+ * @param mixed $reception
+ * @param array $attributes
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function sendImage(string $mediaId, $reception = null, array $attributes = [])
+ {
+ return $this->sendMessage(new Image($mediaId), $reception, $attributes);
+ }
+
+ /**
+ * Send a video message.
+ *
+ * @param string $mediaId
+ * @param mixed $reception
+ * @param array $attributes
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function sendVideo(string $mediaId, $reception = null, array $attributes = [])
+ {
+ return $this->sendMessage(new Media($mediaId, 'mpvideo'), $reception, $attributes);
+ }
+
+ /**
+ * Send a card message.
+ *
+ * @param string $cardId
+ * @param mixed $reception
+ * @param array $attributes
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function sendCard(string $cardId, $reception = null, array $attributes = [])
+ {
+ return $this->sendMessage(new Card($cardId), $reception, $attributes);
+ }
+
+ /**
+ * Preview a text message.
+ *
+ * @param string $message message
+ * @param string $reception
+ * @param string $method
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function previewText(string $message, $reception, $method = self::PREVIEW_BY_OPENID)
+ {
+ return $this->previewMessage(new Text($message), $reception, $method);
+ }
+
+ /**
+ * Preview a news message.
+ *
+ * @param string $mediaId message
+ * @param string $reception
+ * @param string $method
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function previewNews(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+ {
+ return $this->previewMessage(new Media($mediaId, 'mpnews'), $reception, $method);
+ }
+
+ /**
+ * Preview a voice message.
+ *
+ * @param string $mediaId message
+ * @param string $reception
+ * @param string $method
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function previewVoice(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+ {
+ return $this->previewMessage(new Media($mediaId, 'voice'), $reception, $method);
+ }
+
+ /**
+ * Preview a image message.
+ *
+ * @param string $mediaId message
+ * @param string $reception
+ * @param string $method
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function previewImage(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+ {
+ return $this->previewMessage(new Image($mediaId), $reception, $method);
+ }
+
+ /**
+ * Preview a video message.
+ *
+ * @param string $mediaId message
+ * @param string $reception
+ * @param string $method
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function previewVideo(string $mediaId, $reception, $method = self::PREVIEW_BY_OPENID)
+ {
+ return $this->previewMessage(new Media($mediaId, 'mpvideo'), $reception, $method);
+ }
+
+ /**
+ * Preview a card message.
+ *
+ * @param string $cardId message
+ * @param string $reception
+ * @param string $method
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function previewCard(string $cardId, $reception, $method = self::PREVIEW_BY_OPENID)
+ {
+ return $this->previewMessage(new Card($cardId), $reception, $method);
+ }
+
+ /**
+ * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+ * @param string $reception
+ * @param string $method
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function previewMessage(MessageInterface $message, string $reception, $method = self::PREVIEW_BY_OPENID)
+ {
+ $message = (new MessageBuilder())->message($message)->buildForPreview($method, $reception);
+
+ return $this->preview($message);
+ }
+
+ /**
+ * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+ * @param mixed $reception
+ * @param array $attributes
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function sendMessage(MessageInterface $message, $reception = null, $attributes = [])
+ {
+ $message = (new MessageBuilder())->message($message)->with($attributes)->toAll();
+
+ if (\is_int($reception)) {
+ $message->toTag($reception);
+ } elseif (\is_array($reception)) {
+ $message->toUsers($reception);
+ }
+
+ return $this->send($message->build());
+ }
+
+ /**
+ * @codeCoverageIgnore
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ if (strpos($method, 'ByName') > 0) {
+ $method = strstr($method, 'ByName', true);
+
+ if (method_exists($this, $method)) {
+ array_push($args, self::PREVIEW_BY_NAME);
+
+ return $this->$method(...$args);
+ }
+ }
+
+ throw new \BadMethodCallException(sprintf('Method %s not exists.', $method));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php
new file mode 100644
index 0000000..70465b0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/MessageBuilder.php
@@ -0,0 +1,162 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Broadcasting;
+
+use EasyWeChat\Kernel\Contracts\MessageInterface;
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+
+/**
+ * Class MessageBuilder.
+ *
+ * @author overtrue
+ */
+class MessageBuilder
+{
+ /**
+ * @var array
+ */
+ protected $to = [];
+
+ /**
+ * @var \EasyWeChat\Kernel\Contracts\MessageInterface
+ */
+ protected $message;
+
+ /**
+ * @var array
+ */
+ protected $attributes = [];
+
+ /**
+ * Set message.
+ *
+ * @param \EasyWeChat\Kernel\Contracts\MessageInterface $message
+ *
+ * @return $this
+ */
+ public function message(MessageInterface $message)
+ {
+ $this->message = $message;
+
+ return $this;
+ }
+
+ /**
+ * Set target user or group.
+ *
+ * @param array $to
+ *
+ * @return $this
+ */
+ public function to(array $to)
+ {
+ $this->to = $to;
+
+ return $this;
+ }
+
+ /**
+ * @param int $tagId
+ *
+ * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder
+ */
+ public function toTag(int $tagId)
+ {
+ $this->to([
+ 'filter' => [
+ 'is_to_all' => false,
+ 'tag_id' => $tagId,
+ ],
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * @param array $openids
+ *
+ * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder
+ */
+ public function toUsers(array $openids)
+ {
+ $this->to([
+ 'touser' => $openids,
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function toAll()
+ {
+ $this->to([
+ 'filter' => ['is_to_all' => true],
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * @param array $attributes
+ *
+ * @return \EasyWeChat\OfficialAccount\Broadcasting\MessageBuilder
+ */
+ public function with(array $attributes)
+ {
+ $this->attributes = $attributes;
+
+ return $this;
+ }
+
+ /**
+ * Build message.
+ *
+ * @param array $prepends
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function build(array $prepends = []): array
+ {
+ if (empty($this->message)) {
+ throw new RuntimeException('No message content to send.');
+ }
+
+ $content = $this->message->transformForJsonRequest();
+
+ if (empty($prepends)) {
+ $prepends = $this->to;
+ }
+
+ $message = array_merge($prepends, $content, $this->attributes);
+
+ return $message;
+ }
+
+ /**
+ * Build preview message.
+ *
+ * @param string $by
+ * @param string $user
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function buildForPreview(string $by, string $user): array
+ {
+ return $this->build([$by => $user]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php
new file mode 100644
index 0000000..1f956cf
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Broadcasting/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Broadcasting;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['broadcasting'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php
new file mode 100644
index 0000000..b1575ab
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/BoardingPassClient.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class BoardingPassClient.
+ *
+ * @author overtrue
+ */
+class BoardingPassClient extends Client
+{
+ /**
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function checkin(array $params)
+ {
+ return $this->httpPostJson('card/boardingpass/checkin', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php
new file mode 100644
index 0000000..14e817b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Card.php
@@ -0,0 +1,52 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Card.
+ *
+ * @author overtrue
+ *
+ * @property \EasyWeChat\OfficialAccount\Card\CodeClient $code
+ * @property \EasyWeChat\OfficialAccount\Card\MeetingTicketClient $meeting_ticket
+ * @property \EasyWeChat\OfficialAccount\Card\MemberCardClient $member_card
+ * @property \EasyWeChat\OfficialAccount\Card\GeneralCardClient $general_card
+ * @property \EasyWeChat\OfficialAccount\Card\MovieTicketClient $movie_ticket
+ * @property \EasyWeChat\OfficialAccount\Card\CoinClient $coin
+ * @property \EasyWeChat\OfficialAccount\Card\SubMerchantClient $sub_merchant
+ * @property \EasyWeChat\OfficialAccount\Card\BoardingPassClient $boarding_pass
+ * @property \EasyWeChat\OfficialAccount\Card\JssdkClient $jssdk
+ * @property \EasyWeChat\OfficialAccount\Card\GiftCardClient $gift_card
+ * @property \EasyWeChat\OfficialAccount\Card\GiftCardOrderClient $gift_card_order
+ * @property \EasyWeChat\OfficialAccount\Card\GiftCardPageClient $gift_card_page
+ * @property \EasyWeChat\OfficialAccount\Card\InvoiceClient $invoice
+ */
+class Card extends Client
+{
+ /**
+ * @param string $property
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function __get($property)
+ {
+ if (isset($this->app["card.{$property}"])) {
+ return $this->app["card.{$property}"];
+ }
+
+ throw new InvalidArgumentException(sprintf('No card service named "%s".', $property));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php
new file mode 100644
index 0000000..7395d09
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/Client.php
@@ -0,0 +1,428 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ use InteractsWithCache;
+
+ /**
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * Ticket cache key.
+ *
+ * @var string
+ */
+ protected $ticketCacheKey;
+
+ /**
+ * Ticket cache prefix.
+ *
+ * @var string
+ */
+ protected $ticketCachePrefix = 'easywechat.official_account.card.api_ticket.';
+
+ /**
+ * 获取卡券颜色.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function colors()
+ {
+ return $this->httpGet('card/getcolors');
+ }
+
+ /**
+ * 卡券开放类目查询接口.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function categories()
+ {
+ return $this->httpGet('card/getapplyprotocol');
+ }
+
+ /**
+ * 创建卡券.
+ *
+ * @param string $cardType
+ * @param array $attributes
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create($cardType = 'member_card', array $attributes = [])
+ {
+ $params = [
+ 'card' => [
+ 'card_type' => strtoupper($cardType),
+ strtolower($cardType) => $attributes,
+ ],
+ ];
+
+ return $this->httpPostJson('card/create', $params);
+ }
+
+ /**
+ * 查看卡券详情.
+ *
+ * @param string $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get($cardId)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/get', $params);
+ }
+
+ /**
+ * 批量查询卡列表.
+ *
+ * @param int $offset
+ * @param int $count
+ * @param string $statusList
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list($offset = 0, $count = 10, $statusList = 'CARD_STATUS_VERIFY_OK')
+ {
+ $params = [
+ 'offset' => $offset,
+ 'count' => $count,
+ 'status_list' => $statusList,
+ ];
+
+ return $this->httpPostJson('card/batchget', $params);
+ }
+
+ /**
+ * 更改卡券信息接口 and 设置跟随推荐接口.
+ *
+ * @param string $cardId
+ * @param string $type
+ * @param array $attributes
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update($cardId, $type, array $attributes = [])
+ {
+ $card = [];
+ $card['card_id'] = $cardId;
+ $card[strtolower($type)] = $attributes;
+
+ return $this->httpPostJson('card/update', $card);
+ }
+
+ /**
+ * 删除卡券接口.
+ *
+ * @param string $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete($cardId)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/delete', $params);
+ }
+
+ /**
+ * 创建二维码.
+ *
+ * @param array $cards
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createQrCode(array $cards)
+ {
+ return $this->httpPostJson('card/qrcode/create', $cards);
+ }
+
+ /**
+ * ticket 换取二维码图片.
+ *
+ * @param string $ticket
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getQrCode($ticket)
+ {
+ $baseUri = 'https://mp.weixin.qq.com/cgi-bin/showqrcode';
+ $params = [
+ 'ticket' => $ticket,
+ ];
+
+ $response = $this->requestRaw($baseUri, 'GET', $params);
+
+ return [
+ 'status' => $response->getStatusCode(),
+ 'reason' => $response->getReasonPhrase(),
+ 'headers' => $response->getHeaders(),
+ 'body' => strval($response->getBody()),
+ 'url' => $baseUri.'?'.http_build_query($params),
+ ];
+ }
+
+ /**
+ * 通过ticket换取二维码 链接.
+ *
+ * @param string $ticket
+ *
+ * @return string
+ */
+ public function getQrCodeUrl($ticket)
+ {
+ return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', $ticket);
+ }
+
+ /**
+ * 创建货架接口.
+ *
+ * @param string $banner
+ * @param string $pageTitle
+ * @param bool $canShare
+ * @param string $scene [SCENE_NEAR_BY 附近,SCENE_MENU 自定义菜单,SCENE_QRCODE 二维码,SCENE_ARTICLE 公众号文章,
+ * SCENE_H5 h5页面,SCENE_IVR 自动回复,SCENE_CARD_CUSTOM_CELL 卡券自定义cell]
+ * @param array $cardList
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createLandingPage($banner, $pageTitle, $canShare, $scene, $cardList)
+ {
+ $params = [
+ 'banner' => $banner,
+ 'page_title' => $pageTitle,
+ 'can_share' => $canShare,
+ 'scene' => $scene,
+ 'card_list' => $cardList,
+ ];
+
+ return $this->httpPostJson('card/landingpage/create', $params);
+ }
+
+ /**
+ * 图文消息群发卡券.
+ *
+ * @param string $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getHtml($cardId)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/mpnews/gethtml', $params);
+ }
+
+ /**
+ * 设置测试白名单.
+ *
+ * @param array $openids
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setTestWhitelist($openids)
+ {
+ $params = [
+ 'openid' => $openids,
+ ];
+
+ return $this->httpPostJson('card/testwhitelist/set', $params);
+ }
+
+ /**
+ * 设置测试白名单(by username).
+ *
+ * @param array $usernames
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setTestWhitelistByName(array $usernames)
+ {
+ $params = [
+ 'username' => $usernames,
+ ];
+
+ return $this->httpPostJson('card/testwhitelist/set', $params);
+ }
+
+ /**
+ * 获取用户已领取卡券接口.
+ *
+ * @param string $openid
+ * @param string $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getUserCards($openid, $cardId = '')
+ {
+ $params = [
+ 'openid' => $openid,
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/user/getcardlist', $params);
+ }
+
+ /**
+ * 设置微信买单接口.
+ * 设置买单的 card_id 必须已经配置了门店,否则会报错.
+ *
+ * @param string $cardId
+ * @param bool $isOpen
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setPayCell($cardId, $isOpen = true)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ 'is_open' => $isOpen,
+ ];
+
+ return $this->httpPostJson('card/paycell/set', $params);
+ }
+
+ /**
+ * 设置自助核销接口
+ * 设置买单的 card_id 必须已经配置了门店,否则会报错.
+ *
+ * @param string $cardId
+ * @param bool $isOpen
+ * @param bool $verifyCod
+ * @param bool $remarkAmount
+ *
+ * @return mixed
+ */
+ public function setPayConsumeCell($cardId, $isOpen = true, $verifyCod = false, $remarkAmount = false)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ 'is_open' => $isOpen,
+ 'need_verify_cod' => $verifyCod,
+ 'need_remark_amount' => $remarkAmount,
+ ];
+
+ return $this->httpPostJson('card/selfconsumecell/set', $params);
+ }
+
+ /**
+ * 增加库存.
+ *
+ * @param string $cardId
+ * @param int $amount
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function increaseStock($cardId, $amount)
+ {
+ return $this->updateStock($cardId, $amount, 'increase');
+ }
+
+ /**
+ * 减少库存.
+ *
+ * @param string $cardId
+ * @param int $amount
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function reduceStock($cardId, $amount)
+ {
+ return $this->updateStock($cardId, $amount, 'reduce');
+ }
+
+ /**
+ * 修改库存接口.
+ *
+ * @param string $cardId
+ * @param int $amount
+ * @param string $action
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function updateStock($cardId, $amount, $action = 'increase')
+ {
+ $key = 'increase' === $action ? 'increase_stock_value' : 'reduce_stock_value';
+ $params = [
+ 'card_id' => $cardId,
+ $key => abs($amount),
+ ];
+
+ return $this->httpPostJson('card/modifystock', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php
new file mode 100644
index 0000000..dcf96da
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CodeClient.php
@@ -0,0 +1,193 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class CodeClient.
+ *
+ * @author overtrue
+ */
+class CodeClient extends BaseClient
+{
+ /**
+ * 导入code接口.
+ *
+ * @param string $cardId
+ * @param array $codes
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deposit(string $cardId, array $codes)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ 'code' => $codes,
+ ];
+
+ return $this->httpPostJson('card/code/deposit', $params);
+ }
+
+ /**
+ * 查询导入code数目.
+ *
+ * @param string $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getDepositedCount(string $cardId)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/code/getdepositcount', $params);
+ }
+
+ /**
+ * 核查code接口.
+ *
+ * @param string $cardId
+ * @param array $codes
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function check(string $cardId, array $codes)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ 'code' => $codes,
+ ];
+
+ return $this->httpPostJson('card/code/checkcode', $params);
+ }
+
+ /**
+ * 查询 Code 接口.
+ *
+ * @param string $code
+ * @param string $cardId
+ * @param bool $checkConsume
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $code, string $cardId = '', bool $checkConsume = true)
+ {
+ $params = [
+ 'code' => $code,
+ 'check_consume' => $checkConsume,
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/code/get', $params);
+ }
+
+ /**
+ * 更改Code接口.
+ *
+ * @param string $code
+ * @param string $newCode
+ * @param string $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(string $code, string $newCode, string $cardId = '')
+ {
+ $params = [
+ 'code' => $code,
+ 'new_code' => $newCode,
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/code/update', $params);
+ }
+
+ /**
+ * 设置卡券失效.
+ *
+ * @param string $code
+ * @param string $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function disable(string $code, string $cardId = '')
+ {
+ $params = [
+ 'code' => $code,
+ 'card_id' => $cardId,
+ ];
+
+ return $this->httpPostJson('card/code/unavailable', $params);
+ }
+
+ /**
+ * 核销 Code 接口.
+ *
+ * @param string $code
+ * @param string|null $cardId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function consume(string $code, string $cardId = null)
+ {
+ $params = [
+ 'code' => $code,
+ ];
+
+ if (!is_null($cardId)) {
+ $params['card_id'] = $cardId;
+ }
+
+ return $this->httpPostJson('card/code/consume', $params);
+ }
+
+ /**
+ * Code解码接口.
+ *
+ * @param string $encryptedCode
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function decrypt(string $encryptedCode)
+ {
+ $params = [
+ 'encrypt_code' => $encryptedCode,
+ ];
+
+ return $this->httpPostJson('card/code/decrypt', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php
new file mode 100644
index 0000000..b99d841
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/CoinClient.php
@@ -0,0 +1,119 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class CoinClient.
+ *
+ * @author overtrue
+ */
+class CoinClient extends BaseClient
+{
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function activate()
+ {
+ return $this->httpGet('card/pay/activate');
+ }
+
+ /**
+ * @param string $cardId
+ * @param int $quantity
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPrice(string $cardId, int $quantity)
+ {
+ return $this->httpPostJson('card/pay/getpayprice', [
+ 'card_id' => $cardId,
+ 'quantity' => $quantity,
+ ]);
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function summary()
+ {
+ return $this->httpGet('card/pay/getcoinsinfo');
+ }
+
+ /**
+ * @param int $count
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function recharge(int $count)
+ {
+ return $this->httpPostJson('card/pay/recharge', [
+ 'coin_count' => $count,
+ ]);
+ }
+
+ /**
+ * @param string $orderId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function order(string $orderId)
+ {
+ return $this->httpPostJson('card/pay/getorder', ['order_id' => $orderId]);
+ }
+
+ /**
+ * @param array $filters
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function orders(array $filters)
+ {
+ return $this->httpPostJson('card/pay/getorderlist', $filters);
+ }
+
+ /**
+ * @param string $cardId
+ * @param string $orderId
+ * @param int $quantity
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function confirm(string $cardId, string $orderId, int $quantity)
+ {
+ return $this->httpPostJson('card/pay/confirm', [
+ 'card_id' => $cardId,
+ 'order_id' => $orderId,
+ 'quantity' => $quantity,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php
new file mode 100644
index 0000000..1505981
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GeneralCardClient.php
@@ -0,0 +1,71 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class GeneralCardClient.
+ *
+ * @author overtrue
+ */
+class GeneralCardClient extends Client
+{
+ /**
+ * 通用卡接口激活.
+ *
+ * @param array $info
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function activate(array $info = [])
+ {
+ return $this->httpPostJson('card/generalcard/activate', $info);
+ }
+
+ /**
+ * 通用卡撤销激活.
+ *
+ * @param string $cardId
+ * @param string $code
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deactivate(string $cardId, string $code)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ 'code' => $code,
+ ];
+
+ return $this->httpPostJson('card/generalcard/unactivate', $params);
+ }
+
+ /**
+ * 更新会员信息.
+ *
+ * @param array $params
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateUser(array $params = [])
+ {
+ return $this->httpPostJson('card/generalcard/updateuser', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php
new file mode 100644
index 0000000..d8779d0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardClient.php
@@ -0,0 +1,74 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class GiftCardClient.
+ *
+ * @author overtrue
+ */
+class GiftCardClient extends BaseClient
+{
+ /**
+ * 申请微信支付礼品卡权限接口.
+ *
+ * @param string $subMchId
+ *
+ * @return mixed
+ */
+ public function add(string $subMchId)
+ {
+ $params = [
+ 'sub_mch_id' => $subMchId,
+ ];
+
+ return $this->httpPostJson('card/giftcard/pay/whitelist/add', $params);
+ }
+
+ /**
+ * 绑定商户号到礼品卡小程序接口(商户号必须为公众号申请的商户号,否则报错).
+ *
+ * @param string $subMchId
+ * @param string $wxaAppid
+ *
+ * @return mixed
+ */
+ public function bind(string $subMchId, string $wxaAppid)
+ {
+ $params = [
+ 'sub_mch_id' => $subMchId,
+ 'wxa_appid' => $wxaAppid,
+ ];
+
+ return $this->httpPostJson('card/giftcard/pay/submch/bind', $params);
+ }
+
+ /**
+ * 上传小程序代码.
+ *
+ * @param string $wxaAppid
+ * @param string $pageId
+ *
+ * @return mixed
+ */
+ public function set(string $wxaAppid, string $pageId)
+ {
+ $params = [
+ 'wxa_appid' => $wxaAppid,
+ 'page_id' => $pageId,
+ ];
+
+ return $this->httpPostJson('card/giftcard/wxa/set', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php
new file mode 100644
index 0000000..b4e1b15
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardOrderClient.php
@@ -0,0 +1,78 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class GiftCardOrderClient.
+ *
+ * @author overtrue
+ */
+class GiftCardOrderClient extends BaseClient
+{
+ /**
+ * 查询-单个礼品卡订单信息接口.
+ *
+ * @param string $orderId
+ *
+ * @return mixed
+ */
+ public function get(string $orderId)
+ {
+ $params = [
+ 'order_id' => $orderId,
+ ];
+
+ return $this->httpPostJson('card/giftcard/order/get', $params);
+ }
+
+ /**
+ * 查询-批量查询礼品卡订单信息接口.
+ *
+ * @param int $beginTime
+ * @param int $endTime
+ * @param int $offset
+ * @param int $count
+ * @param string $sortType
+ *
+ * @return mixed
+ */
+ public function list(int $beginTime, int $endTime, int $offset = 0, int $count = 10, string $sortType = 'ASC')
+ {
+ $params = [
+ 'begin_time' => $beginTime,
+ 'end_time' => $endTime,
+ 'sort_type' => $sortType,
+ 'offset' => $offset,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('card/giftcard/order/batchget', $params);
+ }
+
+ /**
+ * 退款接口.
+ *
+ * @param string $orderId
+ *
+ * @return mixed
+ */
+ public function refund(string $orderId)
+ {
+ $params = [
+ 'order_id' => $orderId,
+ ];
+
+ return $this->httpPostJson('card/giftcard/order/refund', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php
new file mode 100644
index 0000000..bf36109
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/GiftCardPageClient.php
@@ -0,0 +1,102 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class GiftCardPageClient.
+ *
+ * @author overtrue
+ */
+class GiftCardPageClient extends BaseClient
+{
+ /**
+ * 创建-礼品卡货架接口.
+ *
+ * @param array $attributes
+ *
+ * @return mixed
+ */
+ public function add(array $attributes)
+ {
+ $params = [
+ 'page' => $attributes,
+ ];
+
+ return $this->httpPostJson('card/giftcard/page/add', $params);
+ }
+
+ /**
+ * 查询-礼品卡货架信息接口.
+ *
+ * @param string $pageId
+ *
+ * @return mixed
+ */
+ public function get(string $pageId)
+ {
+ $params = [
+ 'page_id' => $pageId,
+ ];
+
+ return $this->httpPostJson('card/giftcard/page/get', $params);
+ }
+
+ /**
+ * 修改-礼品卡货架信息接口.
+ *
+ * @param string $pageId
+ * @param string $bannerPicUrl
+ * @param array $themeList
+ *
+ * @return mixed
+ */
+ public function update(string $pageId, string $bannerPicUrl, array $themeList)
+ {
+ $params = [
+ 'page' => [
+ 'page_id' => $pageId,
+ 'banner_pic_url' => $bannerPicUrl,
+ 'theme_list' => $themeList,
+ ],
+ ];
+
+ return $this->httpPostJson('card/giftcard/page/update', $params);
+ }
+
+ /**
+ * 查询-礼品卡货架列表接口.
+ *
+ * @return mixed
+ */
+ public function list()
+ {
+ return $this->httpPostJson('card/giftcard/page/batchget');
+ }
+
+ /**
+ * 下架-礼品卡货架接口(下架某一个货架或者全部货架).
+ *
+ * @param string $pageId
+ *
+ * @return mixed
+ */
+ public function setMaintain(string $pageId = '')
+ {
+ $params = ($pageId ? ['page_id' => $pageId] : ['all' => true]) + [
+ 'maintain' => true,
+ ];
+
+ return $this->httpPostJson('card/giftcard/maintain/set', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php
new file mode 100644
index 0000000..ebf1e5f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/InvoiceClient.php
@@ -0,0 +1,191 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class InvoiceClient.
+ *
+ * @author overtrue
+ */
+class InvoiceClient extends BaseClient
+{
+ /**
+ * 设置支付后开票信息接口.
+ *
+ * @param string $mchid
+ * @param string $sPappid
+ *
+ * @return mixed
+ */
+ public function set(string $mchid, string $sPappid)
+ {
+ $params = [
+ 'paymch_info' => [
+ 'mchid' => $mchid,
+ 's_pappid' => $sPappid,
+ ],
+ ];
+
+ return $this->setBizAttr('set_pay_mch', $params);
+ }
+
+ /**
+ * 查询支付后开票信息接口.
+ *
+ * @return mixed
+ */
+ public function get()
+ {
+ return $this->setBizAttr('get_pay_mch');
+ }
+
+ /**
+ * 设置授权页字段信息接口.
+ *
+ * @param array $userData
+ * @param array $bizData
+ *
+ * @return mixed
+ */
+ public function setAuthField(array $userData, array $bizData)
+ {
+ $params = [
+ 'auth_field' => [
+ 'user_field' => $userData,
+ 'biz_field' => $bizData,
+ ],
+ ];
+
+ return $this->setBizAttr('set_auth_field', $params);
+ }
+
+ /**
+ * 查询授权页字段信息接口.
+ *
+ * @return mixed
+ */
+ public function getAuthField()
+ {
+ return $this->setBizAttr('get_auth_field');
+ }
+
+ /**
+ * 查询开票信息.
+ *
+ * @param string $orderId
+ * @param string $appId
+ *
+ * @return mixed
+ */
+ public function getAuthData(string $appId, string $orderId)
+ {
+ $params = [
+ 'order_id' => $orderId,
+ 's_appid' => $appId,
+ ];
+
+ return $this->httpPost('card/invoice/getauthdata', $params);
+ }
+
+ /**
+ * 创建卡券发票模板
+ *
+ * @param string $orderId
+ * @param string $appId
+ *
+ * @return mixed
+ */
+ public function createCard(array $params)
+ {
+ return $this->httpPostJson('/card/invoice/platform/createcard', $params);
+ }
+
+ /**
+ * 电子发票卡券插入用户卡包
+ *
+ * @param string $orderId
+ * @param string $appId
+ *
+ * @return mixed
+ */
+ public function insert(array $params)
+ {
+ return $this->httpPostJson('/card/invoice/insert', $params);
+ }
+
+ /**
+ * 更新发票卡券状态
+ *
+ * @param string $orderId
+ * @param string $appId
+ *
+ * @return mixed
+ */
+ public function updatestatus(array $params)
+ {
+ return $this->httpPostJson('/card/invoice/platform/updatestatus', $params);
+ }
+
+ /**
+ * 设置授权页字段信息接口.
+ *
+ * @param array $userData
+ * @param array $bizData
+ *
+ * @return mixed
+ */
+ public function setContact(array $params)
+ {
+ return $this->setBizAttr('set_contact', $params);
+ }
+
+ /**
+ * 获取授权页链接
+ *
+ * @param string $orderId
+ * @param string $appId
+ *
+ * @return mixed
+ */
+ public function getauthurl(array $params)
+ {
+ return $this->httpPostJson('/card/invoice/getauthurl', $params);
+ }
+
+ /**
+ * 创建卡券发票模板
+ *
+ * @param string $orderId
+ * @param string $appId
+ *
+ * @return mixed
+ */
+ public function setUrl()
+ {
+ return $this->httpPostJson('/card/invoice/seturl', []);
+ }
+
+ /**
+ * @param string $action
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ private function setBizAttr(string $action, array $params = [])
+ {
+ return $this->httpPostJson('card/invoice/setbizattr', $params, ['action' => $action]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php
new file mode 100644
index 0000000..9a6bb51
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/JssdkClient.php
@@ -0,0 +1,85 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\BasicService\Jssdk\Client as Jssdk;
+use EasyWeChat\Kernel\Support\Arr;
+use function EasyWeChat\Kernel\Support\str_random;
+
+/**
+ * Class Jssdk.
+ *
+ * @author overtrue
+ */
+class JssdkClient extends Jssdk
+{
+ /**
+ * @param bool $refresh
+ * @param string $type
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function getTicket(bool $refresh = false, string $type = 'wx_card'): array
+ {
+ return parent::getTicket($refresh, $type);
+ }
+
+ /**
+ * 微信卡券:JSAPI 卡券发放.
+ *
+ * @param array $cards
+ *
+ * @return string
+ */
+ public function assign(array $cards)
+ {
+ return json_encode(array_map(function ($card) {
+ return $this->attachExtension($card['card_id'], $card);
+ }, $cards));
+ }
+
+ /**
+ * 生成 js添加到卡包 需要的 card_list 项.
+ *
+ * @param string $cardId
+ * @param array $extension
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function attachExtension($cardId, array $extension = [])
+ {
+ $timestamp = time();
+ $nonce = str_random(6);
+ $ticket = $this->getTicket()['ticket'];
+
+ $ext = array_merge(['timestamp' => $timestamp, 'nonce_str' => $nonce], Arr::only(
+ $extension,
+ ['code', 'openid', 'outer_id', 'balance', 'fixed_begintimestamp', 'outer_str']
+ ));
+
+ $ext['signature'] = $this->dictionaryOrderSignature($ticket, $timestamp, $cardId, $ext['code'] ?? '', $ext['openid'] ?? '', $nonce);
+
+ return [
+ 'cardId' => $cardId,
+ 'cardExt' => json_encode($ext),
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php
new file mode 100644
index 0000000..892c5ad
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MeetingTicketClient.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class MeetingTicketClient.
+ *
+ * @author overtrue
+ */
+class MeetingTicketClient extends Client
+{
+ /**
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateUser(array $params)
+ {
+ return $this->httpPostJson('card/meetingticket/updateuser', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php
new file mode 100644
index 0000000..41d051c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MemberCardClient.php
@@ -0,0 +1,123 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class MemberCardClient.
+ *
+ * @author overtrue
+ */
+class MemberCardClient extends Client
+{
+ /**
+ * 会员卡接口激活.
+ *
+ * @param array $info
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function activate(array $info = [])
+ {
+ return $this->httpPostJson('card/membercard/activate', $info);
+ }
+
+ /**
+ * 设置开卡字段接口.
+ *
+ * @param string $cardId
+ * @param array $settings
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setActivationForm(string $cardId, array $settings)
+ {
+ $params = array_merge(['card_id' => $cardId], $settings);
+
+ return $this->httpPostJson('card/membercard/activateuserform/set', $params);
+ }
+
+ /**
+ * 拉取会员信息接口.
+ *
+ * @param string $cardId
+ * @param string $code
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getUser(string $cardId, string $code)
+ {
+ $params = [
+ 'card_id' => $cardId,
+ 'code' => $code,
+ ];
+
+ return $this->httpPostJson('card/membercard/userinfo/get', $params);
+ }
+
+ /**
+ * 更新会员信息.
+ *
+ * @param array $params
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateUser(array $params = [])
+ {
+ return $this->httpPostJson('card/membercard/updateuser', $params);
+ }
+
+ /**
+ * 获取用户提交资料.
+ *
+ * @param string $activateTicket
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getActivationForm($activateTicket)
+ {
+ $params = [
+ 'activate_ticket' => $activateTicket,
+ ];
+
+ return $this->httpPostJson('card/membercard/activatetempinfo/get', $params);
+ }
+
+ /**
+ * 获取开卡组件链接接口.
+ *
+ * @param array $params 包含会员卡ID和随机字符串
+ *
+ * @return string 开卡组件链接
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getActivateUrl(array $params = [])
+ {
+ return $this->httpPostJson('card/membercard/activate/geturl', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php
new file mode 100644
index 0000000..e6d9036
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/MovieTicketClient.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+/**
+ * Class MovieTicketClient.
+ *
+ * @author overtrue
+ */
+class MovieTicketClient extends Client
+{
+ /**
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateUser(array $params)
+ {
+ return $this->httpPostJson('card/movieticket/updateuser', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php
new file mode 100644
index 0000000..3807fa3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/ServiceProvider.php
@@ -0,0 +1,89 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['card'] = function ($app) {
+ return new Card($app);
+ };
+
+ $app['card.client'] = function ($app) {
+ return new Client($app);
+ };
+
+ $app['card.coin'] = function ($app) {
+ return new CoinClient($app);
+ };
+
+ $app['card.sub_merchant'] = function ($app) {
+ return new SubMerchantClient($app);
+ };
+
+ $app['card.code'] = function ($app) {
+ return new CodeClient($app);
+ };
+
+ $app['card.movie_ticket'] = function ($app) {
+ return new MovieTicketClient($app);
+ };
+
+ $app['card.member_card'] = function ($app) {
+ return new MemberCardClient($app);
+ };
+
+ $app['card.general_card'] = function ($app) {
+ return new GeneralCardClient($app);
+ };
+
+ $app['card.boarding_pass'] = function ($app) {
+ return new BoardingPassClient($app);
+ };
+
+ $app['card.meeting_ticket'] = function ($app) {
+ return new MeetingTicketClient($app);
+ };
+
+ $app['card.jssdk'] = function ($app) {
+ return new JssdkClient($app);
+ };
+
+ $app['card.gift_card'] = function ($app) {
+ return new GiftCardClient($app);
+ };
+
+ $app['card.gift_card_order'] = function ($app) {
+ return new GiftCardOrderClient($app);
+ };
+
+ $app['card.gift_card_page'] = function ($app) {
+ return new GiftCardPageClient($app);
+ };
+
+ $app['card.invoice'] = function ($app) {
+ return new InvoiceClient($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php
new file mode 100644
index 0000000..77cfad2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Card/SubMerchantClient.php
@@ -0,0 +1,123 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Card;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Support\Arr;
+
+/**
+ * Class SubMerchantClient.
+ *
+ * @author overtrue
+ */
+class SubMerchantClient extends BaseClient
+{
+ /**
+ * 添加子商户.
+ *
+ * @param array $info
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(array $info = [])
+ {
+ $params = [
+ 'info' => Arr::only($info, [
+ 'brand_name',
+ 'logo_url',
+ 'protocol',
+ 'end_time',
+ 'primary_category_id',
+ 'secondary_category_id',
+ 'agreement_media_id',
+ 'operator_media_id',
+ 'app_id',
+ ]),
+ ];
+
+ return $this->httpPostJson('card/submerchant/submit', $params);
+ }
+
+ /**
+ * 更新子商户.
+ *
+ * @param int $merchantId
+ * @param array $info
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $merchantId, array $info = [])
+ {
+ $params = [
+ 'info' => array_merge(
+ ['merchant_id' => $merchantId],
+ Arr::only($info, [
+ 'brand_name',
+ 'logo_url',
+ 'protocol',
+ 'end_time',
+ 'primary_category_id',
+ 'secondary_category_id',
+ 'agreement_media_id',
+ 'operator_media_id',
+ 'app_id',
+ ])
+ ),
+ ];
+
+ return $this->httpPostJson('card/submerchant/update', $params);
+ }
+
+ /**
+ * 获取子商户信息.
+ *
+ * @param int $merchantId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(int $merchantId)
+ {
+ return $this->httpPostJson('card/submerchant/get', ['merchant_id' => $merchantId]);
+ }
+
+ /**
+ * 批量获取子商户信息.
+ *
+ * @param int $beginId
+ * @param int $limit
+ * @param string $status
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $beginId = 0, int $limit = 50, string $status = 'CHECKING')
+ {
+ $params = [
+ 'begin_id' => $beginId,
+ 'limit' => $limit,
+ 'status' => $status,
+ ];
+
+ return $this->httpPostJson('card/submerchant/batchget', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php
new file mode 100644
index 0000000..4f45b74
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/Client.php
@@ -0,0 +1,208 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Comment;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Open article comment.
+ *
+ * @param string $msgId
+ * @param int|null $index
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function open(string $msgId, int $index = null)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/open', $params);
+ }
+
+ /**
+ * Close comment.
+ *
+ * @param string $msgId
+ * @param int|null $index
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function close(string $msgId, int $index = null)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/close', $params);
+ }
+
+ /**
+ * Get article comments.
+ *
+ * @param string $msgId
+ * @param int $index
+ * @param int $begin
+ * @param int $count
+ * @param int $type
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(string $msgId, int $index, int $begin, int $count, int $type = 0)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ 'begin' => $begin,
+ 'count' => $count,
+ 'type' => $type,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/list', $params);
+ }
+
+ /**
+ * Mark elect comment.
+ *
+ * @param string $msgId
+ * @param int $index
+ * @param int $commentId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function markElect(string $msgId, int $index, int $commentId)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ 'user_comment_id' => $commentId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/markelect', $params);
+ }
+
+ /**
+ * Unmark elect comment.
+ *
+ * @param string $msgId
+ * @param int $index
+ * @param int $commentId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unmarkElect(string $msgId, int $index, int $commentId)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ 'user_comment_id' => $commentId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/unmarkelect', $params);
+ }
+
+ /**
+ * Delete comment.
+ *
+ * @param string $msgId
+ * @param int $index
+ * @param int $commentId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $msgId, int $index, int $commentId)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ 'user_comment_id' => $commentId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/delete', $params);
+ }
+
+ /**
+ * Reply to a comment.
+ *
+ * @param string $msgId
+ * @param int $index
+ * @param int $commentId
+ * @param string $content
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function reply(string $msgId, int $index, int $commentId, string $content)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ 'user_comment_id' => $commentId,
+ 'content' => $content,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/reply/add', $params);
+ }
+
+ /**
+ * Delete a reply.
+ *
+ * @param string $msgId
+ * @param int $index
+ * @param int $commentId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deleteReply(string $msgId, int $index, int $commentId)
+ {
+ $params = [
+ 'msg_data_id' => $msgId,
+ 'index' => $index,
+ 'user_comment_id' => $commentId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/comment/reply/delete', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php
new file mode 100644
index 0000000..8d6806c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Comment/ServiceProvider.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Comment;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['comment'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php
new file mode 100644
index 0000000..64e43e5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Client.php
@@ -0,0 +1,230 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * List all staffs.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function list()
+ {
+ return $this->httpGet('cgi-bin/customservice/getkflist');
+ }
+
+ /**
+ * List all online staffs.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function online()
+ {
+ return $this->httpGet('cgi-bin/customservice/getonlinekflist');
+ }
+
+ /**
+ * Create a staff.
+ *
+ * @param string $account
+ * @param string $nickname
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(string $account, string $nickname)
+ {
+ $params = [
+ 'kf_account' => $account,
+ 'nickname' => $nickname,
+ ];
+
+ return $this->httpPostJson('customservice/kfaccount/add', $params);
+ }
+
+ /**
+ * Update a staff.
+ *
+ * @param string $account
+ * @param string $nickname
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(string $account, string $nickname)
+ {
+ $params = [
+ 'kf_account' => $account,
+ 'nickname' => $nickname,
+ ];
+
+ return $this->httpPostJson('customservice/kfaccount/update', $params);
+ }
+
+ /**
+ * Delete a staff.
+ *
+ * @param string $account
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $account)
+ {
+ return $this->httpPostJson('customservice/kfaccount/del', [], ['kf_account' => $account]);
+ }
+
+ /**
+ * Invite a staff.
+ *
+ * @param string $account
+ * @param string $wechatId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function invite(string $account, string $wechatId)
+ {
+ $params = [
+ 'kf_account' => $account,
+ 'invite_wx' => $wechatId,
+ ];
+
+ return $this->httpPostJson('customservice/kfaccount/inviteworker', $params);
+ }
+
+ /**
+ * Set staff avatar.
+ *
+ * @param string $account
+ * @param string $path
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setAvatar(string $account, string $path)
+ {
+ return $this->httpUpload('customservice/kfaccount/uploadheadimg', ['media' => $path], [], ['kf_account' => $account]);
+ }
+
+ /**
+ * Get message builder.
+ *
+ * @param \EasyWeChat\Kernel\Messages\Message|string $message
+ *
+ * @return \EasyWeChat\OfficialAccount\CustomerService\Messenger
+ */
+ public function message($message)
+ {
+ $messageBuilder = new Messenger($this);
+
+ return $messageBuilder->message($message);
+ }
+
+ /**
+ * Send a message.
+ *
+ * @param array $message
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $message)
+ {
+ return $this->httpPostJson('cgi-bin/message/custom/send', $message);
+ }
+
+ /**
+ * Show typing status.
+ *
+ * @param string $openid
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function showTypingStatusToUser(string $openid)
+ {
+ return $this->httpPostJson('cgi-bin/message/custom/typing', [
+ 'touser' => $openid,
+ 'command' => 'Typing',
+ ]);
+ }
+
+ /**
+ * Hide typing status.
+ *
+ * @param string $openid
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function hideTypingStatusToUser(string $openid)
+ {
+ return $this->httpPostJson('cgi-bin/message/custom/typing', [
+ 'touser' => $openid,
+ 'command' => 'CancelTyping',
+ ]);
+ }
+
+ /**
+ * Get messages history.
+ *
+ * @param int $startTime
+ * @param int $endTime
+ * @param int $msgId
+ * @param int $number
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function messages($startTime, $endTime, int $msgId = 1, int $number = 10000)
+ {
+ $params = [
+ 'starttime' => is_numeric($startTime) ? $startTime : strtotime($startTime),
+ 'endtime' => is_numeric($endTime) ? $endTime : strtotime($endTime),
+ 'msgid' => $msgId,
+ 'number' => $number,
+ ];
+
+ return $this->httpPostJson('customservice/msgrecord/getmsglist', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php
new file mode 100644
index 0000000..69bf2f3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/Messenger.php
@@ -0,0 +1,165 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Messages\Message;
+use EasyWeChat\Kernel\Messages\Raw as RawMessage;
+use EasyWeChat\Kernel\Messages\Text;
+
+/**
+ * Class MessageBuilder.
+ *
+ * @author overtrue
+ */
+class Messenger
+{
+ /**
+ * Messages to send.
+ *
+ * @var \EasyWeChat\Kernel\Messages\Message;
+ */
+ protected $message;
+
+ /**
+ * Messages target user open id.
+ *
+ * @var string
+ */
+ protected $to;
+
+ /**
+ * Messages sender staff id.
+ *
+ * @var string
+ */
+ protected $account;
+
+ /**
+ * Customer service instance.
+ *
+ * @var \EasyWeChat\OfficialAccount\CustomerService\Client
+ */
+ protected $client;
+
+ /**
+ * MessageBuilder constructor.
+ *
+ * @param \EasyWeChat\OfficialAccount\CustomerService\Client $client
+ */
+ public function __construct(Client $client)
+ {
+ $this->client = $client;
+ }
+
+ /**
+ * Set message to send.
+ *
+ * @param string|Message $message
+ *
+ * @return Messenger
+ */
+ public function message($message)
+ {
+ if (is_string($message)) {
+ $message = new Text($message);
+ }
+
+ $this->message = $message;
+
+ return $this;
+ }
+
+ /**
+ * Set staff account to send message.
+ *
+ * @param string $account
+ *
+ * @return Messenger
+ */
+ public function by(string $account)
+ {
+ $this->account = $account;
+
+ return $this;
+ }
+
+ /**
+ * @param string $account
+ *
+ * @return Messenger
+ */
+ public function from(string $account)
+ {
+ return $this->by($account);
+ }
+
+ /**
+ * Set target user open id.
+ *
+ * @param string $openid
+ *
+ * @return Messenger
+ */
+ public function to($openid)
+ {
+ $this->to = $openid;
+
+ return $this;
+ }
+
+ /**
+ * Send the message.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function send()
+ {
+ if (empty($this->message)) {
+ throw new RuntimeException('No message to send.');
+ }
+
+ if ($this->message instanceof RawMessage) {
+ $message = json_decode($this->message->get('content'), true);
+ } else {
+ $prepends = [
+ 'touser' => $this->to,
+ ];
+ if ($this->account) {
+ $prepends['customservice'] = ['kf_account' => $this->account];
+ }
+ $message = $this->message->transformForJsonRequest($prepends);
+ }
+
+ return $this->client->send($message);
+ }
+
+ /**
+ * Return property.
+ *
+ * @param string $property
+ *
+ * @return mixed
+ */
+ public function __get(string $property)
+ {
+ if (property_exists($this, $property)) {
+ return $this->$property;
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php
new file mode 100644
index 0000000..a879ce8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/ServiceProvider.php
@@ -0,0 +1,37 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['customer_service'] = function ($app) {
+ return new Client($app);
+ };
+
+ $app['customer_service_session'] = function ($app) {
+ return new SessionClient($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php
new file mode 100644
index 0000000..b92b6db
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/CustomerService/SessionClient.php
@@ -0,0 +1,104 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\CustomerService;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class SessionClient.
+ *
+ * @author overtrue
+ */
+class SessionClient extends BaseClient
+{
+ /**
+ * List all sessions of $account.
+ *
+ * @param string $account
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function list(string $account)
+ {
+ return $this->httpGet('customservice/kfsession/getsessionlist', ['kf_account' => $account]);
+ }
+
+ /**
+ * List all the people waiting.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function waiting()
+ {
+ return $this->httpGet('customservice/kfsession/getwaitcase');
+ }
+
+ /**
+ * Create a session.
+ *
+ * @param string $account
+ * @param string $openid
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(string $account, string $openid)
+ {
+ $params = [
+ 'kf_account' => $account,
+ 'openid' => $openid,
+ ];
+
+ return $this->httpPostJson('customservice/kfsession/create', $params);
+ }
+
+ /**
+ * Close a session.
+ *
+ * @param string $account
+ * @param string $openid
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function close(string $account, string $openid)
+ {
+ $params = [
+ 'kf_account' => $account,
+ 'openid' => $openid,
+ ];
+
+ return $this->httpPostJson('customservice/kfsession/close', $params);
+ }
+
+ /**
+ * Get a session.
+ *
+ * @param string $openid
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function get(string $openid)
+ {
+ return $this->httpGet('customservice/kfsession/getsession', ['openid' => $openid]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php
new file mode 100644
index 0000000..0bf1c21
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/Client.php
@@ -0,0 +1,340 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\DataCube;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取用户增减数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function userSummary(string $from, string $to)
+ {
+ return $this->query('datacube/getusersummary', $from, $to);
+ }
+
+ /**
+ * 获取累计用户数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function userCumulate(string $from, string $to)
+ {
+ return $this->query('datacube/getusercumulate', $from, $to);
+ }
+
+ /**
+ * 获取图文群发每日数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function articleSummary(string $from, string $to)
+ {
+ return $this->query('datacube/getarticlesummary', $from, $to);
+ }
+
+ /**
+ * 获取图文群发总数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function articleTotal(string $from, string $to)
+ {
+ return $this->query('datacube/getarticletotal', $from, $to);
+ }
+
+ /**
+ * 获取图文统计数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function userReadSummary(string $from, string $to)
+ {
+ return $this->query('datacube/getuserread', $from, $to);
+ }
+
+ /**
+ * 获取图文统计分时数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function userReadHourly(string $from, string $to)
+ {
+ return $this->query('datacube/getuserreadhour', $from, $to);
+ }
+
+ /**
+ * 获取图文分享转发数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function userShareSummary(string $from, string $to)
+ {
+ return $this->query('datacube/getusershare', $from, $to);
+ }
+
+ /**
+ * 获取图文分享转发分时数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function userShareHourly(string $from, string $to)
+ {
+ return $this->query('datacube/getusersharehour', $from, $to);
+ }
+
+ /**
+ * 获取消息发送概况数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function upstreamMessageSummary(string $from, string $to)
+ {
+ return $this->query('datacube/getupstreammsg', $from, $to);
+ }
+
+ /**
+ * 获取消息分送分时数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function upstreamMessageHourly(string $from, string $to)
+ {
+ return $this->query('datacube/getupstreammsghour', $from, $to);
+ }
+
+ /**
+ * 获取消息发送周数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function upstreamMessageWeekly(string $from, string $to)
+ {
+ return $this->query('datacube/getupstreammsgweek', $from, $to);
+ }
+
+ /**
+ * 获取消息发送月数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function upstreamMessageMonthly(string $from, string $to)
+ {
+ return $this->query('datacube/getupstreammsgmonth', $from, $to);
+ }
+
+ /**
+ * 获取消息发送分布数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function upstreamMessageDistSummary(string $from, string $to)
+ {
+ return $this->query('datacube/getupstreammsgdist', $from, $to);
+ }
+
+ /**
+ * 获取消息发送分布周数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function upstreamMessageDistWeekly(string $from, string $to)
+ {
+ return $this->query('datacube/getupstreammsgdistweek', $from, $to);
+ }
+
+ /**
+ * 获取消息发送分布月数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function upstreamMessageDistMonthly(string $from, string $to)
+ {
+ return $this->query('datacube/getupstreammsgdistmonth', $from, $to);
+ }
+
+ /**
+ * 获取接口分析数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function interfaceSummary(string $from, string $to)
+ {
+ return $this->query('datacube/getinterfacesummary', $from, $to);
+ }
+
+ /**
+ * 获取接口分析分时数据.
+ *
+ * @param string $from
+ * @param string $to
+ *
+ * @return mixed
+ */
+ public function interfaceSummaryHourly(string $from, string $to)
+ {
+ return $this->query('datacube/getinterfacesummaryhour', $from, $to);
+ }
+
+ /**
+ * 拉取卡券概况数据接口.
+ *
+ * @param string $from
+ * @param string $to
+ * @param int $condSource
+ *
+ * @return mixed
+ */
+ public function cardSummary(string $from, string $to, $condSource = 0)
+ {
+ $ext = [
+ 'cond_source' => intval($condSource),
+ ];
+
+ return $this->query('datacube/getcardbizuininfo', $from, $to, $ext);
+ }
+
+ /**
+ * 获取免费券数据接口.
+ *
+ * @param string $from
+ * @param string $to
+ * @param int $condSource
+ * @param string $cardId
+ *
+ * @return mixed
+ */
+ public function freeCardSummary(string $from, string $to, int $condSource = 0, string $cardId = '')
+ {
+ $ext = [
+ 'cond_source' => intval($condSource),
+ 'card_id' => $cardId,
+ ];
+
+ return $this->query('datacube/getcardcardinfo', $from, $to, $ext);
+ }
+
+ /**
+ * 拉取会员卡数据接口.
+ *
+ * @param string $from
+ * @param string $to
+ * @param int $condSource
+ *
+ * @return mixed
+ */
+ public function memberCardSummary(string $from, string $to, $condSource = 0)
+ {
+ $ext = [
+ 'cond_source' => intval($condSource),
+ ];
+
+ return $this->query('datacube/getcardmembercardinfo', $from, $to, $ext);
+ }
+
+ /**
+ * 拉取单张会员卡数据接口.
+ *
+ * @param string $from
+ * @param string $to
+ * @param string $cardId
+ *
+ * @return mixed
+ */
+ public function memberCardSummaryById(string $from, string $to, string $cardId)
+ {
+ $ext = [
+ 'card_id' => $cardId,
+ ];
+
+ return $this->query('datacube/getcardmembercarddetail', $from, $to, $ext);
+ }
+
+ /**
+ * 查询数据.
+ *
+ * @param string $api
+ * @param string $from
+ * @param string $to
+ * @param array $ext
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function query(string $api, string $from, string $to, array $ext = [])
+ {
+ $params = array_merge([
+ 'begin_date' => $from,
+ 'end_date' => $to,
+ ], $ext);
+
+ return $this->httpPostJson($api, $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php
new file mode 100644
index 0000000..bfec89a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/DataCube/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\DataCube;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['data_cube'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php
new file mode 100644
index 0000000..8dc9d73
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/Client.php
@@ -0,0 +1,251 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Device;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @see http://iot.weixin.qq.com/wiki/new/index.html
+ *
+ * @author soone <66812590@qq.com>
+ */
+class Client extends BaseClient
+{
+ /**
+ * @param string $deviceId
+ * @param string $openid
+ * @param string $content
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function message(string $deviceId, string $openid, string $content)
+ {
+ $params = [
+ 'device_type' => $this->app['config']['device_type'],
+ 'device_id' => $deviceId,
+ 'open_id' => $openid,
+ 'content' => base64_encode($content),
+ ];
+
+ return $this->httpPostJson('device/transmsg', $params);
+ }
+
+ /**
+ * Get device qrcode.
+ *
+ * @param array $deviceIds
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function qrCode(array $deviceIds)
+ {
+ $params = [
+ 'device_num' => count($deviceIds),
+ 'device_id_list' => $deviceIds,
+ ];
+
+ return $this->httpPostJson('device/create_qrcode', $params);
+ }
+
+ /**
+ * @param array $devices
+ * @param string $productId
+ * @param int $opType
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function authorize(array $devices, string $productId, int $opType = 0)
+ {
+ $params = [
+ 'device_num' => count($devices),
+ 'device_list' => $devices,
+ 'op_type' => $opType,
+ 'product_id' => $productId,
+ ];
+
+ return $this->httpPostJson('device/authorize_device', $params);
+ }
+
+ /**
+ * 获取 device id 和二维码
+ *
+ * @param string $productId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function createId(string $productId)
+ {
+ $params = [
+ 'product_id' => $productId,
+ ];
+
+ return $this->httpGet('device/getqrcode', $params);
+ }
+
+ /**
+ * @param string $openid
+ * @param string $deviceId
+ * @param string $ticket
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function bind(string $openid, string $deviceId, string $ticket)
+ {
+ $params = [
+ 'ticket' => $ticket,
+ 'device_id' => $deviceId,
+ 'openid' => $openid,
+ ];
+
+ return $this->httpPostJson('device/bind', $params);
+ }
+
+ /**
+ * @param string $openid
+ * @param string $deviceId
+ * @param string $ticket
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unbind(string $openid, string $deviceId, string $ticket)
+ {
+ $params = [
+ 'ticket' => $ticket,
+ 'device_id' => $deviceId,
+ 'openid' => $openid,
+ ];
+
+ return $this->httpPostJson('device/unbind', $params);
+ }
+
+ /**
+ * @param string $openid
+ * @param string $deviceId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function forceBind(string $openid, string $deviceId)
+ {
+ $params = [
+ 'device_id' => $deviceId,
+ 'openid' => $openid,
+ ];
+
+ return $this->httpPostJson('device/compel_bind', $params);
+ }
+
+ /**
+ * @param string $openid
+ * @param string $deviceId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function forceUnbind(string $openid, string $deviceId)
+ {
+ $params = [
+ 'device_id' => $deviceId,
+ 'openid' => $openid,
+ ];
+
+ return $this->httpPostJson('device/compel_unbind', $params);
+ }
+
+ /**
+ * @param string $deviceId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function status(string $deviceId)
+ {
+ $params = [
+ 'device_id' => $deviceId,
+ ];
+
+ return $this->httpGet('device/get_stat', $params);
+ }
+
+ /**
+ * @param string $ticket
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function verify(string $ticket)
+ {
+ $params = [
+ 'ticket' => $ticket,
+ ];
+
+ return $this->httpPost('device/verify_qrcode', $params);
+ }
+
+ /**
+ * @param string $deviceId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function openid(string $deviceId)
+ {
+ $params = [
+ 'device_type' => $this->app['config']['device_type'],
+ 'device_id' => $deviceId,
+ ];
+
+ return $this->httpGet('device/get_openid', $params);
+ }
+
+ /**
+ * @param string $openid
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function listByOpenid(string $openid)
+ {
+ $params = [
+ 'openid' => $openid,
+ ];
+
+ return $this->httpGet('device/get_bind_device', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php
new file mode 100644
index 0000000..e3dce8e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Device/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Device;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author soone <66812590@qq.com
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['device'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Draft/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Draft/Client.php
new file mode 100644
index 0000000..5c20698
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Draft/Client.php
@@ -0,0 +1,108 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Draft;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Messages\Article;
+
+/**
+ * Class Client.
+ *
+ * @author wangdongzhao
+ */
+class Client extends BaseClient
+{
+ /**
+ * Add new articles to the draft.
+ * @param array $articles
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add(array $articles)
+ {
+ return $this->httpPostJson('cgi-bin/draft/add', $articles);
+ }
+
+ /**
+ * Get article from the draft.
+ * @param string $mediaId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $mediaId)
+ {
+ return $this->httpPostJson('cgi-bin/draft/get', ['media_id' => $mediaId]);
+ }
+
+ /**
+ * Update article
+ * @param string $mediaId
+ * @param int $index
+ * @param mixed $article
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(string $mediaId, int $index, $article)
+ {
+ $params = [
+ 'media_id' => $mediaId,
+ 'index' => $index,
+ 'articles' => $article
+ ];
+ return $this->httpPostJson('cgi-bin/draft/update', $params);
+ }
+
+ /**
+ * Get draft total count
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function count()
+ {
+ return $this->httpPostJson('cgi-bin/draft/count');
+ }
+
+ /**
+ * Batch get articles from the draft.
+ * @param int $offset
+ * @param int $count
+ * @param int $noContent
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function batchGet(int $offset = 0, int $count = 20, int $noContent = 0)
+ {
+ $params = [
+ 'offset' => $offset,
+ 'count' => $count,
+ 'no_content' => $noContent
+ ];
+ return $this->httpPostJson('cgi-bin/draft/batchget', $params);
+ }
+
+ /**
+ * Delete article.
+ * @param string $mediaId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $mediaId)
+ {
+ return $this->httpPostJson('cgi-bin/draft/delete', ['media_id' => $mediaId]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Draft/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Draft/ServiceProvider.php
new file mode 100644
index 0000000..f38b8ea
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Draft/ServiceProvider.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Draft;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author wangdongzhao
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['draft'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/Client.php
new file mode 100644
index 0000000..c885fcd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/Client.php
@@ -0,0 +1,90 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\FreePublish;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Messages\Article;
+
+/**
+ * Class Client.
+ *
+ * @author wangdongzhao
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get publish status.
+ * @param string $publishId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $publishId)
+ {
+ return $this->httpPostJson('cgi-bin/freepublish/get', ['publish_id' => $publishId]);
+ }
+
+ /**
+ * Submit article.
+ * @param string $mediaId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function submit(string $mediaId)
+ {
+ return $this->httpPostJson('cgi-bin/freepublish/submit', ['media_id' => $mediaId]);
+ }
+
+ /**
+ * Get article.
+ * @param string $articleId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getArticle(string $articleId)
+ {
+ return $this->httpPostJson('cgi-bin/freepublish/getarticle', ['article_id' => $articleId]);
+ }
+
+ /**
+ * Batch get articles.
+ * @param int $offset
+ * @param int $count
+ * @param int $noContent
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function batchGet(int $offset = 0, int $count = 20, int $noContent = 0)
+ {
+ $params = [
+ 'offset' => $offset,
+ 'count' => $count,
+ 'no_content' => $noContent
+ ];
+ return $this->httpPostJson('cgi-bin/freepublish/batchget', $params);
+ }
+
+ /**
+ * Delete article
+ * @param string $articleId
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $articleId)
+ {
+ return $this->httpPostJson('cgi-bin/freepublish/delete', ['article_id' => $articleId]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/ServiceProvider.php
new file mode 100644
index 0000000..a4696b5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/FreePublish/ServiceProvider.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\FreePublish;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author wangdongzhao
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['free_publish'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php
new file mode 100644
index 0000000..c720e31
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/Client.php
@@ -0,0 +1,113 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Goods;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat
+ */
+class Client extends BaseClient
+{
+ /**
+ * Add the goods.
+ *
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function add(array $data)
+ {
+ return $this->httpPostJson('scan/product/v2/add', [
+ 'product' => $data,
+ ]);
+ }
+
+ /**
+ * Update the goods.
+ *
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(array $data)
+ {
+ return $this->httpPostJson('scan/product/v2/add', [
+ 'product' => $data,
+ ]);
+ }
+
+ /**
+ * Get add or update goods results.
+ *
+ * @param string $ticket
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function status(string $ticket)
+ {
+ return $this->httpPostJson('scan/product/v2/status', [
+ 'status_ticket' => $ticket,
+ ]);
+ }
+
+ /**
+ * Get goods information.
+ *
+ * @param string $pid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $pid)
+ {
+ return $this->httpPostJson('scan/product/v2/getinfo', [
+ 'product' => [
+ 'pid' => $pid,
+ ],
+ ]);
+ }
+
+ /**
+ * Get a list of goods.
+ *
+ * @param string $context
+ * @param int $page
+ * @param int $size
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(string $context = '', int $page = 1, int $size = 10)
+ {
+ return $this->httpPostJson('scan/product/v2/getinfobypage', [
+ 'page_context' => $context,
+ 'page_num' => $page,
+ 'page_size' => $size,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php
new file mode 100644
index 0000000..38a0902
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Goods/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Goods;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['goods'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Guide/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Guide/Client.php
new file mode 100644
index 0000000..9c771dc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Guide/Client.php
@@ -0,0 +1,991 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Guide;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
+use EasyWeChat\Kernel\Support\Collection;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class Client.
+ *
+ * @author MillsGuo
+ */
+class Client extends BaseClient
+{
+ /**
+ * 添加顾问
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $guideHeadImgUrl
+ * @param string $guideNickname
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createAdviser($guideAccount = '', $guideOpenid = '', $guideHeadImgUrl = '', $guideNickname = '')
+ {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+ if (!empty($guideHeadImgUrl)) {
+ $params['guide_headimgurl'] = $guideHeadImgUrl;
+ }
+ if (!empty($guideNickname)) {
+ $params['guide_nickname'] = $guideNickname;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/addguideacct', $params);
+ }
+
+ /**
+ * 获取顾问信息
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getAdviser($guideAccount = '', $guideOpenid = '')
+ {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/getguideacct', $params);
+ }
+
+ /**
+ * 修改顾问的昵称或头像
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $guideHeadImgUrl
+ * @param string $guideNickname
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function updateAdviser($guideAccount = '', $guideOpenid = '', $guideHeadImgUrl = '', $guideNickname = '')
+ {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+ if (!empty($guideHeadImgUrl)) {
+ $params['guide_headimgurl'] = $guideHeadImgUrl;
+ }
+ if (!empty($guideNickname)) {
+ $params['guide_nickname'] = $guideNickname;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/updateguideacct', $params);
+ }
+
+ /**
+ * 删除顾问
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteAdviser($guideAccount = '', $guideOpenid = '')
+ {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/delguideacct', $params);
+ }
+
+ /**
+ * 获取服务号顾问列表
+ *
+ * @return mixed
+ *
+ * @throws InvalidConfigException
+ */
+ public function getAdvisers($count, $page)
+ {
+ $params = [
+ 'page' => $page,
+ 'num' => $count
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getguideacctlist', $params);
+ }
+
+ /**
+ * 生成顾问二维码
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $qrCodeInfo
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createQrCode($guideAccount = '', $guideOpenid = '', $qrCodeInfo = '')
+ {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+ if (!empty($qrCodeInfo)) {
+ $params['qrcode_info'] = $qrCodeInfo;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/guidecreateqrcode', $params);
+ }
+
+ /**
+ * 获取顾问聊天记录
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $openid
+ * @param int $beginTime
+ * @param int $endTime
+ * @param int $page
+ * @param int $count
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getBuyerChatRecords($guideAccount = '', $guideOpenid = '', $openid = '', $beginTime = 0, $endTime = 0, $page = 1, $count = 100)
+ {
+ $params = [
+ 'page' => $page,
+ 'num' => $count
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+ if (!empty($openid)) {
+ $params['openid'] = $openid;
+ }
+ if (!empty($beginTime)) {
+ $params['begin_time'] = $beginTime;
+ }
+ if (!empty($endTime)) {
+ $params['end_time'] = $endTime;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/getguidebuyerchatrecord', $params);
+ }
+
+ /**
+ * 设置快捷回复与关注自动回复
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param bool $isDelete
+ * @param array $fastReplyListArray
+ * @param array $guideAutoReply
+ * @param array $guideAutoReplyPlus
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function setConfig($guideAccount = '', $guideOpenid = '', $isDelete = false, $fastReplyListArray = array(), $guideAutoReply = array(), $guideAutoReplyPlus = array())
+ {
+ $params = [
+ 'is_delete' => $isDelete
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+ if (!empty($fastReplyListArray)) {
+ $params['guide_fast_reply_list'] = $fastReplyListArray;
+ }
+ if (!empty($guideAutoReply)) {
+ $params['guide_auto_reply'] = $guideAutoReply;
+ }
+ if (!empty($guideAutoReplyPlus)) {
+ $params['guide_auto_reply_plus'] = $guideAutoReplyPlus;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/setguideconfig', $params);
+ }
+
+ /**
+ * 获取快捷回复与关注自动回复
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getConfig($guideAccount = '', $guideOpenid = '')
+ {
+ try {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+ } catch (InvalidConfigException $e) {
+ $params = array();
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/getguideconfig', $params);
+ }
+
+ /**
+ * 设置离线自动回复与敏感词
+ * @param bool $isDelete
+ * @param array $blackKeyword
+ * @param array $guideAutoReply
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function setAdviserConfig(bool $isDelete, array $blackKeyword = [], array $guideAutoReply = [])
+ {
+ $params = [
+ 'is_delete' => $isDelete
+ ];
+ if (!empty($blackKeyword)) {
+ $params['black_keyword'] = $blackKeyword;
+ }
+ if (!empty($guideAutoReply)) {
+ $params['guide_auto_reply'] = $guideAutoReply;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/setguideacctconfig', $params);
+ }
+
+ /**
+ * 获取离线自动回复与敏感词
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getAdviserConfig()
+ {
+ return $this->httpPostJson('cgi-bin/guide/getguideacctconfig', array());
+ }
+
+ /**
+ * 允许微信用户复制小程序页面路径
+ * @param string $wxaAppid 小程序APPID
+ * @param string $wxUsername 微信用户的微信号
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function allowCopyMiniAppPath(string $wxaAppid, string $wxUsername)
+ {
+ $params = [
+ 'wxa_appid' => $wxaAppid,
+ 'wx_username' => $wxUsername
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/pushshowwxapathmenu', $params);
+ }
+
+ /**
+ * 传入微信号或OPENID二选一
+ * @param array $params
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @return array
+ * @throws InvalidConfigException
+ */
+ protected function selectAccountAndOpenid($params, $guideAccount = '', $guideOpenid = '')
+ {
+ if (!is_array($params)) {
+ throw new InvalidConfigException("传入配置参数必须为数组");
+ }
+ if (!empty($guideOpenid)) {
+ $params['guide_openid'] = $guideOpenid;
+ } elseif (!empty($guideAccount)) {
+ $params['guide_account'] = $guideAccount;
+ } else {
+ throw new InvalidConfigException("微信号和OPENID不能同时为空");
+ }
+
+ return $params;
+ }
+
+ /**
+ * 新建顾问分组
+ * @param string $groupName
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createGroup(string $groupName)
+ {
+ $params = [
+ 'group_name' => $groupName
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/newguidegroup', $params);
+ }
+
+ /**
+ * 获取顾问分组列表
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getGuideGroups()
+ {
+ return $this->httpPostJson('cgi-bin/guide/getguidegrouplist', array());
+ }
+
+ /**
+ * 获取指定顾问分组信息
+ * @param int $groupId
+ * @param int $page
+ * @param int $num
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getGroups(int $groupId, int $page, int $num)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ 'page' => $page,
+ 'num' => $num
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getgroupinfo', $params);
+ }
+
+ /**
+ * 分组内添加顾问
+ * @param int $groupId
+ * @param string $guideAccount
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function addGroupGuide(int $groupId, string $guideAccount)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ 'gruide_account' => $guideAccount
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/addguide2guidegroup', $params);
+ }
+
+ /**
+ * 分组内删除顾问
+ * @param int $groupId
+ * @param string $guideAccount
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteGroupGuide(int $groupId, string $guideAccount)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ 'guide_account' => $guideAccount
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/delguide2guidegroup', $params);
+ }
+
+ /**
+ * 获取顾问所在分组
+ * @param string $guideAccount
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getGuideGroup(string $guideAccount)
+ {
+ $params = [
+ 'guide_account' => $guideAccount
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getgroupbyguide', $params);
+ }
+
+ /**
+ * 删除指定顾问分组
+ * @param int $groupId
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteGroup(int $groupId)
+ {
+ $params = [
+ 'group_id' => $groupId
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/delguidegroup', $params);
+ }
+
+ /**
+ * 为顾问分配客户
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param array $buyerList
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createBuyerRelation(string $guideAccount, string $guideOpenid, array $buyerList)
+ {
+ $params = [
+ 'buyer_list' => $buyerList
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/addguidebuyerrelation', $params);
+ }
+
+ /**
+ * 为顾问移除客户
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param array $openidList
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteBuyerRelation(string $guideAccount, string $guideOpenid, array $openidList)
+ {
+ $params = [
+ 'openid_list' => $openidList
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/delguidebuyerrelation', $params);
+ }
+
+ /**
+ * 获取顾问的客户列表
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param int $page
+ * @param int $num
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getBuyerRelations(string $guideAccount, string $guideOpenid, int $page, int $num)
+ {
+ $params = [
+ 'page' => $page,
+ 'num' => $num
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/getguidebuyerrelationlist', $params);
+ }
+
+ /**
+ * 为客户更换顾问
+ * @param string $oldGuideTarget
+ * @param string $newGuideTarget
+ * @param array $openidList
+ * @param bool $useTargetOpenid true使用OPENID,false使用微信号
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function rebindBuyerGuide(string $oldGuideTarget, string $newGuideTarget, array $openidList, bool $useTargetOpenid = true)
+ {
+ $params = [
+ 'openid_list' => $openidList
+ ];
+ if ($useTargetOpenid) {
+ $params['old_guide_openid'] = $oldGuideTarget;
+ $params['new_guide_openid'] = $newGuideTarget;
+ } else {
+ $params['old_guide_account'] = $oldGuideTarget;
+ $params['new_guide_account'] = $newGuideTarget;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/rebindguideacctforbuyer', $params);
+ }
+
+ /**
+ * 修改客户昵称
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $openid
+ * @param string $nickname
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function updateBuyerRelation(string $guideAccount, string $guideOpenid, string $openid, string $nickname)
+ {
+ $params = [
+ 'openid' => $openid,
+ 'buyer_nickname' => $nickname
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/updateguidebuyerrelation', $params);
+ }
+
+ /**
+ * 查询客户所属顾问
+ * @param string $openid
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getBuyerRelation(string $openid)
+ {
+ $params = [
+ 'openid' => $openid
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getguidebuyerrelationbybuyer', $params);
+ }
+
+ /**
+ * 查询指定顾问和客户的关系
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $openid
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getBuyerRelationByGuide(string $guideAccount, string $guideOpenid, string $openid)
+ {
+ $params = [
+ 'openid' => $openid
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/getguidebuyerrelation', $params);
+ }
+
+ /**
+ * 新建可查询的标签类型
+ * @param string $tagName
+ * @param array $tagValues
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function newTagOption(string $tagName, array $tagValues)
+ {
+ $params = [
+ 'tag_name' => $tagName,
+ 'tag_values' => $tagValues
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/newguidetagoption', $params);
+ }
+
+ /**
+ * 删除指定标签类型
+ * @param string $tagName
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteTagOption(string $tagName)
+ {
+ $params = [
+ 'tag_name' => $tagName
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/delguidetagoption', $params);
+ }
+
+ /**
+ * 为标签添加可选值
+ * @param string $tagName
+ * @param array $tagValues
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createTagOption(string $tagName, array $tagValues)
+ {
+ $params = [
+ 'tag_name' => $tagName,
+ 'tag_values' => $tagValues
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/addguidetagoption', $params);
+ }
+
+ /**
+ * 获取标签和可选值
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getTagOption()
+ {
+ return $this->httpPostJson('cgi-bin/guide/getguidetagoption', array());
+ }
+
+ /**
+ * 为客户设置标签
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param array $openidList
+ * @param string $tagValue
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function setBuyersTag(string $guideAccount, string $guideOpenid, array $openidList, string $tagValue)
+ {
+ $params = [
+ 'tag_value' => $tagValue,
+ 'openid_list' => $openidList
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/addguidebuyertag', $params);
+ }
+
+ /**
+ * 查询客户标签
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $openid
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getBuyerTags(string $guideAccount, string $guideOpenid, string $openid)
+ {
+ $params = [
+ 'openid' => $openid
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/getguidebuyertag', $params);
+ }
+
+ /**
+ * 根据标签值筛选粉丝
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param int $pushCount
+ * @param array $tagValues
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getBuyerByTag(string $guideAccount, string $guideOpenid, int $pushCount = 0, array $tagValues = array())
+ {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+ if ($pushCount > 0) {
+ $params['push_count'] = $pushCount;
+ }
+ if (count($tagValues) > 0) {
+ $params['tag_values'] = $tagValues;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/queryguidebuyerbytag', $params);
+ }
+
+ /**
+ * 删除客户标签
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $tagValue
+ * @param array $openidList
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteBuyerTag(string $guideAccount, string $guideOpenid, string $tagValue, array $openidList)
+ {
+ $params = [
+ 'tag_value' => $tagValue
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+ if (count($openidList) > 0) {
+ $params['openid_list'] = $openidList;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/delguidebuyertag', $params);
+ }
+
+ /**
+ * 设置自定义客户信息
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $openid
+ * @param array $displayTagList
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function setBuyerDisplayTags(string $guideAccount, string $guideOpenid, string $openid, array $displayTagList)
+ {
+ $params = [
+ 'openid' => $openid,
+ 'display_tag_list' => $displayTagList
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/addguidebuyerdisplaytag', $params);
+ }
+
+ /**
+ * 获取自定义客户信息
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $openid
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getBuyerDisplayTags(string $guideAccount, string $guideOpenid, string $openid)
+ {
+ $params = [
+ 'openid' => $openid
+ ];
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/getguidebuyerdisplaytag', $params);
+ }
+
+ /**
+ * 添加小程序卡片素材
+ * @param string $mediaId
+ * @param string $title
+ * @param string $path
+ * @param string $appid
+ * @param int $type
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createCardMaterial(string $mediaId, string $title, string $path, string $appid, int $type = 0)
+ {
+ $params = [
+ 'media_id' => $mediaId,
+ 'type' => $type,
+ 'title' => $title,
+ 'path' => $path,
+ 'appid' => $appid
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/setguidecardmaterial', $params);
+ }
+
+ /**
+ * 查询小程序卡片素材
+ * @param int $type
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getCardMaterial(int $type = 0)
+ {
+ $params = [
+ 'type' => $type
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getguidecardmaterial', $params);
+ }
+
+ /**
+ * 删除小程序卡片素材
+ * @param string $title
+ * @param string $path
+ * @param string $appid
+ * @param int $type
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteCardMaterial(string $title, string $path, string $appid, int $type = 0)
+ {
+ $params = [
+ 'type' => $type,
+ 'title' => $title,
+ 'path' => $path,
+ 'appid' => $appid
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/delguidecardmaterial', $params);
+ }
+
+ /**
+ * 添加图片素材
+ * @param string $mediaId
+ * @param int $type
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createImageMaterial(string $mediaId, int $type = 0)
+ {
+ $params = [
+ 'media_id' => $mediaId,
+ 'type' => $type
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/setguideimagematerial', $params);
+ }
+
+ /**
+ * 查询图片素材
+ * @param int $type
+ * @param int $start
+ * @param int $num
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getImageMaterial(int $type, int $start, int $num)
+ {
+ $params = [
+ 'type' => $type,
+ 'start' => $start,
+ 'num' => $num
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getguideimagematerial', $params);
+ }
+
+ /**
+ * 删除图片素材
+ * @param int $type
+ * @param string $picUrl
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteImageMaterial(int $type, string $picUrl)
+ {
+ $params = [
+ 'type' => $type,
+ 'picurl' => $picUrl
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/delguideimagematerial', $params);
+ }
+
+ /**
+ * 添加文字素材
+ * @param int $type
+ * @param string $word
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createWordMaterial(int $type, string $word)
+ {
+ $params = [
+ 'type' => $type,
+ 'word' => $word
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/setguidewordmaterial', $params);
+ }
+
+ /**
+ * 查询文字素材
+ * @param int $type
+ * @param int $start
+ * @param int $num
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getWordMaterial(int $type, int $start, int $num)
+ {
+ $params = [
+ 'type' => $type,
+ 'start' => $start,
+ 'num' => $num
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getguidewordmaterial', $params);
+ }
+
+ /**
+ * 删除文字素材
+ * @param int $type
+ * @param string $word
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function deleteWordMaterial(int $type, string $word)
+ {
+ $params = [
+ 'type' => $type,
+ 'word' => $word
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/delguidewordmaterial', $params);
+ }
+
+ /**
+ * 添加群发任务,为指定顾问添加群发任务
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param string $taskName
+ * @param string $taskRemark
+ * @param int $pushTime
+ * @param array $openidArray
+ * @param array $materialArray
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function createMasSendJob(string $guideAccount, string $guideOpenid, string $taskName, string $taskRemark, int $pushTime, array $openidArray, array $materialArray)
+ {
+ $params = [
+ 'task_name' => $taskName,
+ 'push_time' => $pushTime,
+ 'openid' => $openidArray,
+ 'material' => $materialArray
+ ];
+ if (!empty($taskRemark)) {
+ $params['task_remark'] = $taskRemark;
+ }
+ $params = $this->selectAccountAndOpenid($params, $guideAccount, $guideOpenid);
+
+ return $this->httpPostJson('cgi-bin/guide/addguidemassendjob', $params);
+ }
+
+ /**
+ * 获取群发任务列表
+ * @param string $guideAccount
+ * @param string $guideOpenid
+ * @param array $taskStatus
+ * @param int $offset
+ * @param int $limit
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getMasSendJobs(string $guideAccount, string $guideOpenid, array $taskStatus = [], int $offset = 0, int $limit = 50)
+ {
+ $params = $this->selectAccountAndOpenid(array(), $guideAccount, $guideOpenid);
+ if (!empty($taskStatus)) {
+ $params['task_status'] = $taskStatus;
+ }
+ if ($offset > 0) {
+ $params['offset'] = $offset;
+ }
+ if ($limit != 50) {
+ $params['limit'] = $limit;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/getguidemassendjoblist', $params);
+ }
+
+ /**
+ * 获取指定群发任务信息
+ * @param string $taskId
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function getMasSendJob(string $taskId)
+ {
+ $params = [
+ 'task_id' => $taskId
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/getguidemassendjob', $params);
+ }
+
+ /**
+ * 修改群发任务
+ * @param string $taskId
+ * @param string $taskName
+ * @param string $taskRemark
+ * @param int $pushTime
+ * @param array $openidArray
+ * @param array $materialArray
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function updateMasSendJob(string $taskId, string $taskName, string $taskRemark, int $pushTime, array $openidArray, array $materialArray)
+ {
+ $params = [
+ 'task_id' => $taskId
+ ];
+ if (!empty($taskName)) {
+ $params['task_name'] = $taskName;
+ }
+ if (!empty($taskRemark)) {
+ $params['task_remark'] = $taskRemark;
+ }
+ if (!empty($pushTime)) {
+ $params['push_time'] = $pushTime;
+ }
+ if (!empty($openidArray)) {
+ $params['openid'] = $openidArray;
+ }
+ if (!empty($materialArray)) {
+ $params['material'] = $materialArray;
+ }
+
+ return $this->httpPostJson('cgi-bin/guide/updateguidemassendjob', $params);
+ }
+
+ /**
+ * 取消群发任务
+ * @param string $taskId
+ * @return array|Collection|object|ResponseInterface|string
+ * @throws InvalidConfigException
+ */
+ public function cancelMasSendJob(string $taskId)
+ {
+ $params = [
+ 'task_id' => $taskId
+ ];
+
+ return $this->httpPostJson('cgi-bin/guide/cancelguidemassendjob', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Guide/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Guide/ServiceProvider.php
new file mode 100644
index 0000000..46ebbca
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Guide/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Guide;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author millsguo
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['guide'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php
new file mode 100644
index 0000000..592689c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/Client.php
@@ -0,0 +1,303 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Material;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Http\StreamResponse;
+use EasyWeChat\Kernel\Messages\Article;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Allow media type.
+ *
+ * @var array
+ */
+ protected $allowTypes = ['image', 'voice', 'video', 'thumb', 'news_image'];
+
+ /**
+ * Upload image.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadImage(string $path)
+ {
+ return $this->upload('image', $path);
+ }
+
+ /**
+ * Upload voice.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadVoice(string $path)
+ {
+ return $this->upload('voice', $path);
+ }
+
+ /**
+ * Upload thumb.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadThumb(string $path)
+ {
+ return $this->upload('thumb', $path);
+ }
+
+ /**
+ * Upload video.
+ *
+ * @param string $path
+ * @param string $title
+ * @param string $description
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadVideo(string $path, string $title, string $description)
+ {
+ $params = [
+ 'description' => json_encode(
+ [
+ 'title' => $title,
+ 'introduction' => $description,
+ ],
+ JSON_UNESCAPED_UNICODE
+ ),
+ ];
+
+ return $this->upload('video', $path, $params);
+ }
+
+ /**
+ * Upload articles.
+ *
+ * @param array|Article $articles
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadArticle($articles)
+ {
+ if ($articles instanceof Article || !empty($articles['title'])) {
+ $articles = [$articles];
+ }
+
+ $params = ['articles' => array_map(function ($article) {
+ if ($article instanceof Article) {
+ return $article->transformForJsonRequestWithoutType();
+ }
+
+ return $article;
+ }, $articles)];
+
+ return $this->httpPostJson('cgi-bin/material/add_news', $params);
+ }
+
+ /**
+ * Update article.
+ *
+ * @param string $mediaId
+ * @param array|Article $article
+ * @param int $index
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateArticle(string $mediaId, $article, int $index = 0)
+ {
+ if ($article instanceof Article) {
+ $article = $article->transformForJsonRequestWithoutType();
+ }
+
+ $params = [
+ 'media_id' => $mediaId,
+ 'index' => $index,
+ 'articles' => isset($article['title']) ? $article : (isset($article[$index]) ? $article[$index] : []),
+ ];
+
+ return $this->httpPostJson('cgi-bin/material/update_news', $params);
+ }
+
+ /**
+ * Upload image for article.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function uploadArticleImage(string $path)
+ {
+ return $this->upload('news_image', $path);
+ }
+
+ /**
+ * Fetch material.
+ *
+ * @param string $mediaId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $mediaId)
+ {
+ $response = $this->requestRaw('cgi-bin/material/get_material', 'POST', ['json' => ['media_id' => $mediaId]]);
+
+ if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+ return StreamResponse::buildFromPsrResponse($response);
+ }
+
+ return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+ }
+
+ /**
+ * Delete material by media ID.
+ *
+ * @param string $mediaId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $mediaId)
+ {
+ return $this->httpPostJson('cgi-bin/material/del_material', ['media_id' => $mediaId]);
+ }
+
+ /**
+ * List materials.
+ *
+ * example:
+ *
+ * {
+ * "total_count": TOTAL_COUNT,
+ * "item_count": ITEM_COUNT,
+ * "item": [{
+ * "media_id": MEDIA_ID,
+ * "name": NAME,
+ * "update_time": UPDATE_TIME
+ * },
+ * // more...
+ * ]
+ * }
+ *
+ * @param string $type
+ * @param int $offset
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(string $type, int $offset = 0, int $count = 20)
+ {
+ $params = [
+ 'type' => $type,
+ 'offset' => $offset,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('cgi-bin/material/batchget_material', $params);
+ }
+
+ /**
+ * Get stats of materials.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function stats()
+ {
+ return $this->httpGet('cgi-bin/material/get_materialcount');
+ }
+
+ /**
+ * Upload material.
+ *
+ * @param string $type
+ * @param string $path
+ * @param array $form
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function upload(string $type, string $path, array $form = [])
+ {
+ if (!file_exists($path) || !is_readable($path)) {
+ throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path));
+ }
+
+ $form['type'] = $type;
+
+ return $this->httpUpload($this->getApiByType($type), ['media' => $path], $form);
+ }
+
+ /**
+ * Get API by type.
+ *
+ * @param string $type
+ *
+ * @return string
+ */
+ public function getApiByType(string $type)
+ {
+ switch ($type) {
+ case 'news_image':
+ return 'cgi-bin/media/uploadimg';
+ default:
+ return 'cgi-bin/material/add_material';
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php
new file mode 100644
index 0000000..089a8f8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Material/ServiceProvider.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Material;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['material'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php
new file mode 100644
index 0000000..9c132c6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/Client.php
@@ -0,0 +1,103 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Menu;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get all menus.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function list()
+ {
+ return $this->httpGet('cgi-bin/menu/get');
+ }
+
+ /**
+ * Get current menus.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function current()
+ {
+ return $this->httpGet('cgi-bin/get_current_selfmenu_info');
+ }
+
+ /**
+ * Add menu.
+ *
+ * @param array $buttons
+ * @param array $matchRule
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(array $buttons, array $matchRule = [])
+ {
+ if (!empty($matchRule)) {
+ return $this->httpPostJson('cgi-bin/menu/addconditional', [
+ 'button' => $buttons,
+ 'matchrule' => $matchRule,
+ ]);
+ }
+
+ return $this->httpPostJson('cgi-bin/menu/create', ['button' => $buttons]);
+ }
+
+ /**
+ * Destroy menu.
+ *
+ * @param int $menuId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(int $menuId = null)
+ {
+ if (is_null($menuId)) {
+ return $this->httpGet('cgi-bin/menu/delete');
+ }
+
+ return $this->httpPostJson('cgi-bin/menu/delconditional', ['menuid' => $menuId]);
+ }
+
+ /**
+ * Test conditional menu.
+ *
+ * @param string $userId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function match(string $userId)
+ {
+ return $this->httpPostJson('cgi-bin/menu/trymatch', ['user_id' => $userId]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php
new file mode 100644
index 0000000..e79b105
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Menu/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Menu;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['menu'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php
new file mode 100644
index 0000000..c9588c4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/OAuth/ServiceProvider.php
@@ -0,0 +1,75 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\OAuth;
+
+use Overtrue\Socialite\SocialiteManager as Socialite;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['oauth'] = function ($app) {
+ $wechat = [
+ 'wechat' => [
+ 'client_id' => $app['config']['app_id'],
+ 'client_secret' => $app['config']['secret'],
+ 'redirect' => $this->prepareCallbackUrl($app),
+ ],
+ ];
+
+ if (!empty($app['config']['component_app_id'] && !empty($app['config']['component_app_token']))) {
+ $wechat['wechat']['component'] = [
+ 'id' => $app['config']['component_app_id'],
+ 'token' => $app['config']['token'],
+ ] ;
+ }
+
+ $socialite = (new Socialite($wechat))->create('wechat');
+
+ $scopes = (array)$app['config']->get('oauth.scopes', ['snsapi_userinfo']);
+
+ if (!empty($scopes)) {
+ $socialite->scopes($scopes);
+ }
+
+ return $socialite;
+ };
+ }
+
+ /**
+ * Prepare the OAuth callback url for wechat.
+ *
+ * @param Container $app
+ *
+ * @return string
+ */
+ private function prepareCallbackUrl($app)
+ {
+ $callback = $app['config']->get('oauth.callback');
+ if (0 === stripos($callback, 'http')) {
+ return $callback;
+ }
+ $baseUrl = $app['request']->getSchemeAndHttpHost();
+
+ return $baseUrl . '/' . ltrim($callback, '/');
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php
new file mode 100644
index 0000000..57b5da5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/Client.php
@@ -0,0 +1,149 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\OCR;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author joyeekk
+ */
+class Client extends BaseClient
+{
+ /**
+ * Allow image parameter type.
+ *
+ * @var array
+ */
+ protected $allowTypes = ['photo', 'scan'];
+
+ /**
+ * ID card OCR.
+ *
+ * @param string $path
+ * @param string $type
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function idCard(string $path, string $type = 'photo')
+ {
+ if (!\in_array($type, $this->allowTypes, true)) {
+ throw new InvalidArgumentException(sprintf("Unsupported type: '%s'", $type));
+ }
+
+ return $this->httpPost('cv/ocr/idcard', [
+ 'type' => $type,
+ 'img_url' => $path,
+ ]);
+ }
+
+ /**
+ * Bank card OCR.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function bankCard(string $path)
+ {
+ return $this->httpPost('cv/ocr/bankcard', [
+ 'img_url' => $path,
+ ]);
+ }
+
+ /**
+ * Vehicle license OCR.
+ *
+ * @param string $path
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function vehicleLicense(string $path)
+ {
+ return $this->httpPost('cv/ocr/drivinglicense', [
+ 'img_url' => $path,
+ ]);
+ }
+
+ /**
+ * Driving OCR.
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function driving(string $path)
+ {
+ return $this->httpPost('cv/ocr/driving', [
+ 'img_url' => $path,
+ ]);
+ }
+
+ /**
+ * Biz License OCR.
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function bizLicense(string $path)
+ {
+ return $this->httpPost('cv/ocr/bizlicense', [
+ 'img_url' => $path,
+ ]);
+ }
+
+ /**
+ * Common OCR.
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function common(string $path)
+ {
+ return $this->httpPost('cv/ocr/comm', [
+ 'img_url' => $path,
+ ]);
+ }
+
+ /**
+ * Plate Number OCR.
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function plateNumber(string $path)
+ {
+ return $this->httpPost('cv/ocr/platenum', [
+ 'img_url' => $path,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php
new file mode 100644
index 0000000..1773079
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/OCR/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\OCR;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author joyeekk
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['ocr'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php
new file mode 100644
index 0000000..6a8e570
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/Client.php
@@ -0,0 +1,145 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\POI;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get POI supported categories.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function categories()
+ {
+ return $this->httpGet('cgi-bin/poi/getwxcategory');
+ }
+
+ /**
+ * Get POI by ID.
+ *
+ * @param int $poiId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(int $poiId)
+ {
+ return $this->httpPostJson('cgi-bin/poi/getpoi', ['poi_id' => $poiId]);
+ }
+
+ /**
+ * List POI.
+ *
+ * @param int $offset
+ * @param int $limit
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $offset = 0, int $limit = 10)
+ {
+ $params = [
+ 'begin' => $offset,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('cgi-bin/poi/getpoilist', $params);
+ }
+
+ /**
+ * Create a POI.
+ *
+ * @param array $baseInfo
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(array $baseInfo)
+ {
+ $params = [
+ 'business' => [
+ 'base_info' => $baseInfo,
+ ],
+ ];
+
+ return $this->httpPostJson('cgi-bin/poi/addpoi', $params);
+ }
+
+ /**
+ * @param array $databaseInfo
+ *
+ * @return int
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function createAndGetId(array $databaseInfo)
+ {
+ /** @var array $response */
+ $response = $this->detectAndCastResponseToType($this->create($databaseInfo), 'array');
+
+ return $response['poi_id'];
+ }
+
+ /**
+ * Update a POI.
+ *
+ * @param int $poiId
+ * @param array $baseInfo
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $poiId, array $baseInfo)
+ {
+ $params = [
+ 'business' => [
+ 'base_info' => array_merge($baseInfo, ['poi_id' => $poiId]),
+ ],
+ ];
+
+ return $this->httpPostJson('cgi-bin/poi/updatepoi', $params);
+ }
+
+ /**
+ * Delete a POI.
+ *
+ * @param int $poiId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(int $poiId)
+ {
+ return $this->httpPostJson('cgi-bin/poi/delpoi', ['poi_id' => $poiId]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php
new file mode 100644
index 0000000..156440b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/POI/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\POI;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['poi'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php
new file mode 100644
index 0000000..e83792f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/Client.php
@@ -0,0 +1,45 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Semantic;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get the semantic content of giving string.
+ *
+ * @param string $keyword
+ * @param string $categories
+ * @param array $optional
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function query(string $keyword, string $categories, array $optional = [])
+ {
+ $params = [
+ 'query' => $keyword,
+ 'category' => $categories,
+ 'appid' => $this->app['config']['app_id'],
+ ];
+
+ return $this->httpPostJson('semantic/semproxy/search', array_merge($params, $optional));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php
new file mode 100644
index 0000000..835b7fc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Semantic/ServiceProvider.php
@@ -0,0 +1,31 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Semantic;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['semantic'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php
new file mode 100644
index 0000000..c723847
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Guard.php
@@ -0,0 +1,30 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Server;
+
+use EasyWeChat\Kernel\ServerGuard;
+
+/**
+ * Class Guard.
+ *
+ * @author overtrue
+ */
+class Guard extends ServerGuard
+{
+ /**
+ * @return bool
+ */
+ protected function shouldReturnRawResponse(): bool
+ {
+ return !is_null($this->app['request']->get('echostr'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php
new file mode 100644
index 0000000..e985692
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/Handlers/EchoStrHandler.php
@@ -0,0 +1,53 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Decorators\FinallyResult;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class EchoStrHandler.
+ *
+ * @author overtrue
+ */
+class EchoStrHandler implements EventHandlerInterface
+{
+ /**
+ * @var ServiceContainer
+ */
+ protected $app;
+
+ /**
+ * EchoStrHandler constructor.
+ *
+ * @param ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * @param mixed $payload
+ *
+ * @return FinallyResult|null
+ */
+ public function handle($payload = null)
+ {
+ if ($str = $this->app['request']->get('echostr')) {
+ return new FinallyResult($str);
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php
new file mode 100644
index 0000000..0c6716b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Server/ServiceProvider.php
@@ -0,0 +1,46 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use EasyWeChat\OfficialAccount\Server\Handlers\EchoStrHandler;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['encryptor']) && $app['encryptor'] = function ($app) {
+ return new Encryptor(
+ $app['config']['app_id'],
+ $app['config']['token'],
+ $app['config']['aes_key']
+ );
+ };
+
+ !isset($app['server']) && $app['server'] = function ($app) {
+ $guard = new Guard($app);
+ $guard->push(new EchoStrHandler($app));
+
+ return $guard;
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php
new file mode 100644
index 0000000..c401008
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/Client.php
@@ -0,0 +1,81 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function register($data)
+ {
+ return $this->httpPostJson('shakearound/account/register', $data);
+ }
+
+ /**
+ * Get audit status.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function status()
+ {
+ return $this->httpGet('shakearound/account/auditstatus');
+ }
+
+ /**
+ * Get shake info.
+ *
+ * @param string $ticket
+ * @param bool $needPoi
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function user(string $ticket, bool $needPoi = false)
+ {
+ $params = [
+ 'ticket' => $ticket,
+ ];
+
+ if ($needPoi) {
+ $params['need_poi'] = 1;
+ }
+
+ return $this->httpPostJson('shakearound/user/getshakeinfo', $params);
+ }
+
+ /**
+ * @param string $ticket
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ */
+ public function userWithPoi(string $ticket)
+ {
+ return $this->user($ticket, true);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php
new file mode 100644
index 0000000..859a120
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/DeviceClient.php
@@ -0,0 +1,190 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class DeviceClient.
+ *
+ * @author allen05ren
+ */
+class DeviceClient extends BaseClient
+{
+ /**
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function apply(array $data)
+ {
+ return $this->httpPostJson('shakearound/device/applyid', $data);
+ }
+
+ /**
+ * Get audit status.
+ *
+ * @param int $applyId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function status(int $applyId)
+ {
+ $params = [
+ 'apply_id' => $applyId,
+ ];
+
+ return $this->httpPostJson('shakearound/device/applystatus', $params);
+ }
+
+ /**
+ * Update a device comment.
+ *
+ * @param array $deviceIdentifier
+ * @param string $comment
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(array $deviceIdentifier, string $comment)
+ {
+ $params = [
+ 'device_identifier' => $deviceIdentifier,
+ 'comment' => $comment,
+ ];
+
+ return $this->httpPostJson('shakearound/device/update', $params);
+ }
+
+ /**
+ * Bind location for device.
+ *
+ * @param array $deviceIdentifier
+ * @param int $poiId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function bindPoi(array $deviceIdentifier, int $poiId)
+ {
+ $params = [
+ 'device_identifier' => $deviceIdentifier,
+ 'poi_id' => $poiId,
+ ];
+
+ return $this->httpPostJson('shakearound/device/bindlocation', $params);
+ }
+
+ /**
+ * @param array $deviceIdentifier
+ * @param int $poiId
+ * @param string $appId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function bindThirdPoi(array $deviceIdentifier, int $poiId, string $appId)
+ {
+ $params = [
+ 'device_identifier' => $deviceIdentifier,
+ 'poi_id' => $poiId,
+ 'type' => 2,
+ 'poi_appid' => $appId,
+ ];
+
+ return $this->httpPostJson('shakearound/device/bindlocation', $params);
+ }
+
+ /**
+ * Fetch batch of devices by deviceIds.
+ *
+ * @param array $deviceIdentifiers
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function listByIds(array $deviceIdentifiers)
+ {
+ $params = [
+ 'type' => 1,
+ 'device_identifiers' => $deviceIdentifiers,
+ ];
+
+ return $this->search($params);
+ }
+
+ /**
+ * Pagination to get batch of devices.
+ *
+ * @param int $lastId
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function list(int $lastId, int $count)
+ {
+ $params = [
+ 'type' => 2,
+ 'last_seen' => $lastId,
+ 'count' => $count,
+ ];
+
+ return $this->search($params);
+ }
+
+ /**
+ * Fetch batch of devices by applyId.
+ *
+ * @param int $applyId
+ * @param int $lastId
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ */
+ public function listByApplyId(int $applyId, int $lastId, int $count)
+ {
+ $params = [
+ 'type' => 3,
+ 'apply_id' => $applyId,
+ 'last_seen' => $lastId,
+ 'count' => $count,
+ ];
+
+ return $this->search($params);
+ }
+
+ /**
+ * Fetch batch of devices.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function search(array $params)
+ {
+ return $this->httpPostJson('shakearound/device/search', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php
new file mode 100644
index 0000000..9fd578d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/GroupClient.php
@@ -0,0 +1,167 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class GroupClient.
+ *
+ * @author allen05ren
+ */
+class GroupClient extends BaseClient
+{
+ /**
+ * Add device group.
+ *
+ * @param string $name
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(string $name)
+ {
+ $params = [
+ 'group_name' => $name,
+ ];
+
+ return $this->httpPostJson('shakearound/device/group/add', $params);
+ }
+
+ /**
+ * Update a device group name.
+ *
+ * @param int $groupId
+ * @param string $name
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $groupId, string $name)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ 'group_name' => $name,
+ ];
+
+ return $this->httpPostJson('shakearound/device/group/update', $params);
+ }
+
+ /**
+ * Delete device group.
+ *
+ * @param int $groupId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(int $groupId)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ ];
+
+ return $this->httpPostJson('shakearound/device/group/delete', $params);
+ }
+
+ /**
+ * List all device groups.
+ *
+ * @param int $begin
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $begin, int $count)
+ {
+ $params = [
+ 'begin' => $begin,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('shakearound/device/group/getlist', $params);
+ }
+
+ /**
+ * Get detail of a device group.
+ *
+ * @param int $groupId
+ * @param int $begin
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(int $groupId, int $begin, int $count)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ 'begin' => $begin,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('shakearound/device/group/getdetail', $params);
+ }
+
+ /**
+ * Add one or more devices to a device group.
+ *
+ * @param int $groupId
+ * @param array $deviceIdentifiers
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addDevices(int $groupId, array $deviceIdentifiers)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ 'device_identifiers' => $deviceIdentifiers,
+ ];
+
+ return $this->httpPostJson('shakearound/device/group/adddevice', $params);
+ }
+
+ /**
+ * Remove one or more devices from a device group.
+ *
+ * @param int $groupId
+ * @param array $deviceIdentifiers
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function removeDevices(int $groupId, array $deviceIdentifiers)
+ {
+ $params = [
+ 'group_id' => $groupId,
+ 'device_identifiers' => $deviceIdentifiers,
+ ];
+
+ return $this->httpPostJson('shakearound/device/group/deletedevice', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php
new file mode 100644
index 0000000..edc40b2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/MaterialClient.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class MaterialClient.
+ *
+ * @author allen05ren
+ */
+class MaterialClient extends BaseClient
+{
+ /**
+ * Upload image material.
+ *
+ * @param string $path
+ * @param string $type
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function uploadImage(string $path, string $type = 'icon')
+ {
+ if (!file_exists($path) || !is_readable($path)) {
+ throw new InvalidArgumentException(sprintf('File does not exist, or the file is unreadable: "%s"', $path));
+ }
+
+ return $this->httpUpload('shakearound/material/add', ['media' => $path], [], ['type' => strtolower($type)]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php
new file mode 100644
index 0000000..73ba1ac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/PageClient.php
@@ -0,0 +1,110 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class PageClient.
+ *
+ * @author allen05ren
+ */
+class PageClient extends BaseClient
+{
+ /**
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(array $data)
+ {
+ return $this->httpPostJson('shakearound/page/add', $data);
+ }
+
+ /**
+ * @param int $pageId
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $pageId, array $data)
+ {
+ return $this->httpPostJson('shakearound/page/update', array_merge(['page_id' => $pageId], $data));
+ }
+
+ /**
+ * Fetch batch of pages by pageIds.
+ *
+ * @param array $pageIds
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function listByIds(array $pageIds)
+ {
+ $params = [
+ 'type' => 1,
+ 'page_ids' => $pageIds,
+ ];
+
+ return $this->httpPostJson('shakearound/page/search', $params);
+ }
+
+ /**
+ * Pagination to get batch of pages.
+ *
+ * @param int $begin
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $begin, int $count)
+ {
+ $params = [
+ 'type' => 2,
+ 'begin' => $begin,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('shakearound/page/search', $params);
+ }
+
+ /**
+ * delete a page.
+ *
+ * @param int $pageId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(int $pageId)
+ {
+ $params = [
+ 'page_id' => $pageId,
+ ];
+
+ return $this->httpPostJson('shakearound/page/delete', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php
new file mode 100644
index 0000000..de80911
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/RelationClient.php
@@ -0,0 +1,87 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class RelationClient.
+ *
+ * @author allen05ren
+ */
+class RelationClient extends BaseClient
+{
+ /**
+ * Bind pages for device.
+ *
+ * @param array $deviceIdentifier
+ * @param array $pageIds
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function bindPages(array $deviceIdentifier, array $pageIds)
+ {
+ $params = [
+ 'device_identifier' => $deviceIdentifier,
+ 'page_ids' => $pageIds,
+ ];
+
+ return $this->httpPostJson('shakearound/device/bindpage', $params);
+ }
+
+ /**
+ * Get pageIds by deviceId.
+ *
+ * @param array $deviceIdentifier
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function listByDeviceId(array $deviceIdentifier)
+ {
+ $params = [
+ 'type' => 1,
+ 'device_identifier' => $deviceIdentifier,
+ ];
+
+ return $this->httpPostJson('shakearound/relation/search', $params);
+ }
+
+ /**
+ * Get devices by pageId.
+ *
+ * @param int $pageId
+ * @param int $begin
+ * @param int $count
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function listByPageId(int $pageId, int $begin, int $count)
+ {
+ $params = [
+ 'type' => 2,
+ 'page_id' => $pageId,
+ 'begin' => $begin,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('shakearound/relation/search', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php
new file mode 100644
index 0000000..a13d2b0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ServiceProvider.php
@@ -0,0 +1,57 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author allen05ren
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['shake_around'] = function ($app) {
+ return new ShakeAround($app);
+ };
+
+ $app['shake_around.device'] = function ($app) {
+ return new DeviceClient($app);
+ };
+
+ $app['shake_around.page'] = function ($app) {
+ return new PageClient($app);
+ };
+
+ $app['shake_around.material'] = function ($app) {
+ return new MaterialClient($app);
+ };
+
+ $app['shake_around.group'] = function ($app) {
+ return new GroupClient($app);
+ };
+
+ $app['shake_around.relation'] = function ($app) {
+ return new RelationClient($app);
+ };
+
+ $app['shake_around.stats'] = function ($app) {
+ return new StatsClient($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php
new file mode 100644
index 0000000..adcfcb4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/ShakeAround.php
@@ -0,0 +1,45 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Card.
+ *
+ * @author overtrue
+ *
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\DeviceClient $device
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\GroupClient $group
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\MaterialClient $material
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\RelationClient $relation
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\StatsClient $stats
+ * @property \EasyWeChat\OfficialAccount\ShakeAround\PageClient $page
+ */
+class ShakeAround extends Client
+{
+ /**
+ * @param string $property
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function __get($property)
+ {
+ if (isset($this->app["shake_around.{$property}"])) {
+ return $this->app["shake_around.{$property}"];
+ }
+
+ throw new InvalidArgumentException(sprintf('No shake_around service named "%s".', $property));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php
new file mode 100644
index 0000000..a4b55eb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/ShakeAround/StatsClient.php
@@ -0,0 +1,110 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\ShakeAround;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class StatsClient.
+ *
+ * @author allen05ren
+ */
+class StatsClient extends BaseClient
+{
+ /**
+ * Fetch statistics data by deviceId.
+ *
+ * @param array $deviceIdentifier
+ * @param int $beginTime (Unix timestamp)
+ * @param int $endTime (Unix timestamp)
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deviceSummary(array $deviceIdentifier, int $beginTime, int $endTime)
+ {
+ $params = [
+ 'device_identifier' => $deviceIdentifier,
+ 'begin_date' => $beginTime,
+ 'end_date' => $endTime,
+ ];
+
+ return $this->httpPostJson('shakearound/statistics/device', $params);
+ }
+
+ /**
+ * Fetch all devices statistics data by date.
+ *
+ * @param int $timestamp
+ * @param int $pageIndex
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function devicesSummary(int $timestamp, int $pageIndex)
+ {
+ $params = [
+ 'date' => $timestamp,
+ 'page_index' => $pageIndex,
+ ];
+
+ return $this->httpPostJson('shakearound/statistics/devicelist', $params);
+ }
+
+ /**
+ * Fetch statistics data by pageId.
+ *
+ * @param int $pageId
+ * @param int $beginTime (Unix timestamp)
+ * @param int $endTime (Unix timestamp)
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function pageSummary(int $pageId, int $beginTime, int $endTime)
+ {
+ $params = [
+ 'page_id' => $pageId,
+ 'begin_date' => $beginTime,
+ 'end_date' => $endTime,
+ ];
+
+ return $this->httpPostJson('shakearound/statistics/page', $params);
+ }
+
+ /**
+ * Fetch all pages statistics data by date.
+ *
+ * @param int $timestamp
+ * @param int $pageIndex
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function pagesSummary(int $timestamp, int $pageIndex)
+ {
+ $params = [
+ 'date' => $timestamp,
+ 'page_index' => $pageIndex,
+ ];
+
+ return $this->httpPostJson('shakearound/statistics/pagelist', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php
new file mode 100644
index 0000000..b873469
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/Client.php
@@ -0,0 +1,209 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Store;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author bigface
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get WXA supported categories.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function categories()
+ {
+ return $this->httpGet('wxa/get_merchant_category');
+ }
+
+ /**
+ * Get district from tencent map .
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function districts()
+ {
+ return $this->httpGet('wxa/get_district');
+ }
+
+ /**
+ * Search store from tencent map.
+ *
+ * @param int $districtId
+ * @param string $keyword
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function searchFromMap(int $districtId, string $keyword)
+ {
+ $params = [
+ 'districtid' => $districtId,
+ 'keyword' => $keyword,
+ ];
+
+ return $this->httpPostJson('wxa/search_map_poi', $params);
+ }
+
+ /**
+ * Get store check status.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getStatus()
+ {
+ return $this->httpGet('wxa/get_merchant_audit_info');
+ }
+
+ /**
+ * Create a merchant.
+ *
+ * @param array $baseInfo
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createMerchant(array $baseInfo)
+ {
+ return $this->httpPostJson('wxa/apply_merchant', $baseInfo);
+ }
+
+ /**
+ * Update a merchant.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateMerchant(array $params)
+ {
+ return $this->httpPostJson('wxa/modify_merchant', $params);
+ }
+
+ /**
+ * Create a store from tencent map.
+ *
+ * @param array $baseInfo
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createFromMap(array $baseInfo)
+ {
+ return $this->httpPostJson('wxa/create_map_poi', $baseInfo);
+ }
+
+ /**
+ * Create a store.
+ *
+ * @param array $baseInfo
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(array $baseInfo)
+ {
+ return $this->httpPostJson('wxa/add_store', $baseInfo);
+ }
+
+ /**
+ * Update a store.
+ *
+ * @param int $poiId
+ * @param array $baseInfo
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $poiId, array $baseInfo)
+ {
+ $params = array_merge($baseInfo, ['poi_id' => $poiId]);
+
+ return $this->httpPostJson('wxa/update_store', $params);
+ }
+
+ /**
+ * Get store by ID.
+ *
+ * @param int $poiId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(int $poiId)
+ {
+ return $this->httpPostJson('wxa/get_store_info', ['poi_id' => $poiId]);
+ }
+
+ /**
+ * List store.
+ *
+ * @param int $offset
+ * @param int $limit
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $offset = 0, int $limit = 10)
+ {
+ $params = [
+ 'offset' => $offset,
+ 'limit' => $limit,
+ ];
+
+ return $this->httpPostJson('wxa/get_store_list', $params);
+ }
+
+ /**
+ * Delete a store.
+ *
+ * @param int $poiId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(int $poiId)
+ {
+ return $this->httpPostJson('wxa/del_store', ['poi_id' => $poiId]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php
new file mode 100644
index 0000000..f5c48d7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/Store/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\Store;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author bigface
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['store'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/Client.php
new file mode 100644
index 0000000..f303fc4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/Client.php
@@ -0,0 +1,189 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\SubscribeMessage;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use ReflectionClass;
+
+/**
+ * Class Client.
+ *
+ * @author tegic
+ */
+class Client extends BaseClient
+{
+ protected $message = [
+ 'touser' => '',
+ 'template_id' => '',
+ 'page' => '',
+ 'miniprogram' => '',
+ 'data' => [],
+ ];
+
+ /**
+ * {@inheritdoc}.
+ */
+ protected $required = ['touser', 'template_id', 'data'];
+
+ /**
+ * Combine templates and add them to your personal template library under your account.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addTemplate(string $tid, array $kidList, string $sceneDesc = null)
+ {
+ $sceneDesc = $sceneDesc ?? '';
+ $data = \compact('tid', 'kidList', 'sceneDesc');
+
+ return $this->httpPost('wxaapi/newtmpl/addtemplate', $data);
+ }
+
+ /**
+ * Delete personal template under account.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deleteTemplate(string $id)
+ {
+ return $this->httpPost('wxaapi/newtmpl/deltemplate', ['priTmplId' => $id]);
+ }
+
+ /**
+ * Get the category of the applet account.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getCategory()
+ {
+ return $this->httpGet('wxaapi/newtmpl/getcategory');
+ }
+
+ /**
+ * Get keyword list under template title.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTemplateKeywords(string $tid)
+ {
+ return $this->httpGet('wxaapi/newtmpl/getpubtemplatekeywords', compact('tid'));
+ }
+
+ /**
+ * Get the title of the public template under the category to which the account belongs.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTemplateTitles(array $ids, int $start = 0, int $limit = 30)
+ {
+ $ids = \implode(',', $ids);
+ $query = \compact('ids', 'start', 'limit');
+
+ return $this->httpGet('wxaapi/newtmpl/getpubtemplatetitles', $query);
+ }
+
+ /**
+ * Get list of personal templates under the current account.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getTemplates()
+ {
+ return $this->httpGet('wxaapi/newtmpl/gettemplate');
+ }
+
+ /**
+ * Send a subscribe message.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $data = [])
+ {
+ $params = $this->formatMessage($data);
+
+ $this->restoreMessage();
+
+ return $this->httpPostJson('cgi-bin/message/subscribe/bizsend', $params);
+ }
+
+ /**
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function formatMessage(array $data = [])
+ {
+ $params = array_merge($this->message, $data);
+
+ foreach ($params as $key => $value) {
+ if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) {
+ throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+ }
+
+ $params[$key] = empty($value) ? $this->message[$key] : $value;
+ }
+
+ foreach ($params['data'] as $key => $value) {
+ if (is_array($value)) {
+ if (\array_key_exists('value', $value)) {
+ $params['data'][$key] = ['value' => $value['value']];
+
+ continue;
+ }
+
+ if (count($value) >= 1) {
+ $value = [
+ 'value' => $value[0],
+// 'color' => $value[1],// color unsupported
+ ];
+ }
+ } else {
+ $value = [
+ 'value' => strval($value),
+ ];
+ }
+
+ $params['data'][$key] = $value;
+ }
+
+ return $params;
+ }
+
+ /**
+ * Restore message.
+ */
+ protected function restoreMessage()
+ {
+ $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message'];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/ServiceProvider.php
new file mode 100644
index 0000000..da0eef9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/SubscribeMessage/ServiceProvider.php
@@ -0,0 +1,27 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\SubscribeMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['subscribe_message'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php
new file mode 100644
index 0000000..a95ca87
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/Client.php
@@ -0,0 +1,234 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\TemplateMessage;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use ReflectionClass;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ public const API_SEND = 'cgi-bin/message/template/send';
+
+ /**
+ * Attributes.
+ *
+ * @var array
+ */
+ protected $message = [
+ 'touser' => '',
+ 'template_id' => '',
+ 'url' => '',
+ 'data' => [],
+ 'miniprogram' => '',
+ ];
+
+ /**
+ * Required attributes.
+ *
+ * @var array
+ */
+ protected $required = ['touser', 'template_id'];
+
+ /**
+ * Set industry.
+ *
+ * @param int $industryOne
+ * @param int $industryTwo
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setIndustry($industryOne, $industryTwo)
+ {
+ $params = [
+ 'industry_id1' => $industryOne,
+ 'industry_id2' => $industryTwo,
+ ];
+
+ return $this->httpPostJson('cgi-bin/template/api_set_industry', $params);
+ }
+
+ /**
+ * Get industry.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getIndustry()
+ {
+ return $this->httpPostJson('cgi-bin/template/get_industry');
+ }
+
+ /**
+ * Add a template and get template ID.
+ *
+ * @param string $shortId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addTemplate($shortId, $keywordList)
+ {
+ $params = ['template_id_short' => $shortId, 'keyword_name_list' => $keywordList];
+
+ return $this->httpPostJson('cgi-bin/template/api_add_template', $params);
+ }
+
+ /**
+ * Get private templates.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPrivateTemplates()
+ {
+ return $this->httpPostJson('cgi-bin/template/get_all_private_template');
+ }
+
+ /**
+ * Delete private template.
+ *
+ * @param string $templateId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deletePrivateTemplate($templateId)
+ {
+ $params = ['template_id' => $templateId];
+
+ return $this->httpPostJson('cgi-bin/template/del_private_template', $params);
+ }
+
+ /**
+ * Send a template message.
+ *
+ * @param array $data
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $data = [])
+ {
+ $params = $this->formatMessage($data);
+
+ $this->restoreMessage();
+
+ return $this->httpPostJson(static::API_SEND, $params);
+ }
+
+ /**
+ * Send template-message for subscription.
+ *
+ * @param array $data
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function sendSubscription(array $data = [])
+ {
+ $params = $this->formatMessage($data);
+
+ $this->restoreMessage();
+
+ return $this->httpPostJson('cgi-bin/message/template/subscribe', $params);
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function formatMessage(array $data = [])
+ {
+ $params = array_merge($this->message, $data);
+
+ foreach ($params as $key => $value) {
+ if (in_array($key, $this->required, true) && empty($value) && empty($this->message[$key])) {
+ throw new InvalidArgumentException(sprintf('Attribute "%s" can not be empty!', $key));
+ }
+
+ $params[$key] = empty($value) ? $this->message[$key] : $value;
+ }
+
+ $params['data'] = $this->formatData($params['data'] ?? []);
+
+ return $params;
+ }
+
+ /**
+ * @param array $data
+ *
+ * @return array
+ */
+ protected function formatData(array $data)
+ {
+ $formatted = [];
+
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ if (\array_key_exists('value', $value)) {
+ $formatted[$key] = $value;
+
+ continue;
+ }
+
+ if (count($value) >= 2) {
+ $value = [
+ 'value' => $value[0],
+ 'color' => $value[1],
+ ];
+ }
+ } else {
+ $value = [
+ 'value' => strval($value),
+ ];
+ }
+
+ $formatted[$key] = $value;
+ }
+
+ return $formatted;
+ }
+
+ /**
+ * Restore message.
+ */
+ protected function restoreMessage()
+ {
+ $this->message = (new ReflectionClass(static::class))->getDefaultProperties()['message'];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php
new file mode 100644
index 0000000..98476fc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/TemplateMessage/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\TemplateMessage;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['template_message'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php
new file mode 100644
index 0000000..c11d8b3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/User/ServiceProvider.php
@@ -0,0 +1,35 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\User;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['user'] = function ($app) {
+ return new UserClient($app);
+ };
+
+ $app['user_tag'] = function ($app) {
+ return new TagClient($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php
new file mode 100644
index 0000000..d41e363
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/User/TagClient.php
@@ -0,0 +1,175 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\User;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class TagClient.
+ *
+ * @author overtrue
+ */
+class TagClient extends BaseClient
+{
+ /**
+ * Create tag.
+ *
+ * @param string $name
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create(string $name)
+ {
+ $params = [
+ 'tag' => ['name' => $name],
+ ];
+
+ return $this->httpPostJson('cgi-bin/tags/create', $params);
+ }
+
+ /**
+ * List all tags.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function list()
+ {
+ return $this->httpGet('cgi-bin/tags/get');
+ }
+
+ /**
+ * Update a tag name.
+ *
+ * @param int $tagId
+ * @param string $name
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $tagId, string $name)
+ {
+ $params = [
+ 'tag' => [
+ 'id' => $tagId,
+ 'name' => $name,
+ ],
+ ];
+
+ return $this->httpPostJson('cgi-bin/tags/update', $params);
+ }
+
+ /**
+ * Delete tag.
+ *
+ * @param int $tagId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(int $tagId)
+ {
+ $params = [
+ 'tag' => ['id' => $tagId],
+ ];
+
+ return $this->httpPostJson('cgi-bin/tags/delete', $params);
+ }
+
+ /**
+ * Get user tags.
+ *
+ * @param string $openid
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function userTags(string $openid)
+ {
+ $params = ['openid' => $openid];
+
+ return $this->httpPostJson('cgi-bin/tags/getidlist', $params);
+ }
+
+ /**
+ * Get users from a tag.
+ *
+ * @param int $tagId
+ * @param string $nextOpenId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function usersOfTag(int $tagId, string $nextOpenId = '')
+ {
+ $params = [
+ 'tagid' => $tagId,
+ 'next_openid' => $nextOpenId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/user/tag/get', $params);
+ }
+
+ /**
+ * Batch tag users.
+ *
+ * @param array $openids
+ * @param int $tagId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function tagUsers(array $openids, int $tagId)
+ {
+ $params = [
+ 'openid_list' => $openids,
+ 'tagid' => $tagId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/tags/members/batchtagging', $params);
+ }
+
+ /**
+ * Untag users from a tag.
+ *
+ * @param array $openids
+ * @param int $tagId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function untagUsers(array $openids, int $tagId)
+ {
+ $params = [
+ 'openid_list' => $openids,
+ 'tagid' => $tagId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/tags/members/batchuntagging', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php
new file mode 100644
index 0000000..6b9491f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/User/UserClient.php
@@ -0,0 +1,172 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\User;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class UserClient.
+ *
+ * @author overtrue
+ */
+class UserClient extends BaseClient
+{
+ /**
+ * Fetch a user by open id.
+ *
+ * @param string $openid
+ * @param string $lang
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function get(string $openid, string $lang = 'zh_CN')
+ {
+ $params = [
+ 'openid' => $openid,
+ 'lang' => $lang,
+ ];
+
+ return $this->httpGet('cgi-bin/user/info', $params);
+ }
+
+ /**
+ * Batch get users.
+ *
+ * @param array $openids
+ * @param string $lang
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function select(array $openids, string $lang = 'zh_CN')
+ {
+ return $this->httpPostJson('cgi-bin/user/info/batchget', [
+ 'user_list' => array_map(function ($openid) use ($lang) {
+ return [
+ 'openid' => $openid,
+ 'lang' => $lang,
+ ];
+ }, $openids),
+ ]);
+ }
+
+ /**
+ * List users.
+ *
+ * @param string $nextOpenId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function list(string $nextOpenId = null)
+ {
+ $params = ['next_openid' => $nextOpenId];
+
+ return $this->httpGet('cgi-bin/user/get', $params);
+ }
+
+ /**
+ * Set user remark.
+ *
+ * @param string $openid
+ * @param string $remark
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function remark(string $openid, string $remark)
+ {
+ $params = [
+ 'openid' => $openid,
+ 'remark' => $remark,
+ ];
+
+ return $this->httpPostJson('cgi-bin/user/info/updateremark', $params);
+ }
+
+ /**
+ * Get black list.
+ *
+ * @param string|null $beginOpenid
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function blacklist(string $beginOpenid = null)
+ {
+ $params = ['begin_openid' => $beginOpenid];
+
+ return $this->httpPostJson('cgi-bin/tags/members/getblacklist', $params);
+ }
+
+ /**
+ * Batch block user.
+ *
+ * @param array|string $openidList
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function block($openidList)
+ {
+ $params = ['openid_list' => (array) $openidList];
+
+ return $this->httpPostJson('cgi-bin/tags/members/batchblacklist', $params);
+ }
+
+ /**
+ * Batch unblock user.
+ *
+ * @param array $openidList
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unblock($openidList)
+ {
+ $params = ['openid_list' => (array) $openidList];
+
+ return $this->httpPostJson('cgi-bin/tags/members/batchunblacklist', $params);
+ }
+
+ /**
+ * @param string $oldAppId
+ * @param array $openidList
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function changeOpenid(string $oldAppId, array $openidList)
+ {
+ $params = [
+ 'from_appid' => $oldAppId,
+ 'openid_list' => $openidList,
+ ];
+
+ return $this->httpPostJson('cgi-bin/changeopenid', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php
new file mode 100644
index 0000000..76a4e6a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/CardClient.php
@@ -0,0 +1,52 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class CardClient.
+ *
+ * @author her-cat
+ */
+class CardClient extends BaseClient
+{
+ /**
+ * Set shop card coupon delivery information.
+ *
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function set(array $data)
+ {
+ return $this->httpPostJson('bizwifi/couponput/set', $data);
+ }
+
+ /**
+ * Get shop card coupon delivery information.
+ *
+ * @param int $shopId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(int $shopId = 0)
+ {
+ return $this->httpPostJson('bizwifi/couponput/get', ['shop_id' => $shopId]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php
new file mode 100644
index 0000000..076957d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/Client.php
@@ -0,0 +1,98 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author her-cat
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get Wi-Fi statistics.
+ *
+ * @param string $beginDate
+ * @param string $endDate
+ * @param int $shopId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function summary(string $beginDate, string $endDate, int $shopId = -1)
+ {
+ $data = [
+ 'begin_date' => $beginDate,
+ 'end_date' => $endDate,
+ 'shop_id' => $shopId,
+ ];
+
+ return $this->httpPostJson('bizwifi/statistics/list', $data);
+ }
+
+ /**
+ * Get the material QR code.
+ *
+ * @param int $shopId
+ * @param string $ssid
+ * @param int $type
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getQrCodeUrl(int $shopId, string $ssid, int $type = 0)
+ {
+ $data = [
+ 'shop_id' => $shopId,
+ 'ssid' => $ssid,
+ 'img_id' => $type,
+ ];
+
+ return $this->httpPostJson('bizwifi/qrcode/get', $data);
+ }
+
+ /**
+ * Wi-Fi completion page jump applet.
+ *
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setFinishPage(array $data)
+ {
+ return $this->httpPostJson('bizwifi/finishpage/set', $data);
+ }
+
+ /**
+ * Set the top banner jump applet.
+ *
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setHomePage(array $data)
+ {
+ return $this->httpPostJson('bizwifi/homepage/set', $data);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php
new file mode 100644
index 0000000..8fecbf7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/DeviceClient.php
@@ -0,0 +1,127 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class DeviceClient.
+ *
+ * @author her-cat
+ */
+class DeviceClient extends BaseClient
+{
+ /**
+ * Add a password device.
+ *
+ * @param int $shopId
+ * @param string $ssid
+ * @param string $password
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addPasswordDevice(int $shopId, string $ssid, string $password)
+ {
+ $data = [
+ 'shop_id' => $shopId,
+ 'ssid' => $ssid,
+ 'password' => $password,
+ ];
+
+ return $this->httpPostJson('bizwifi/device/add', $data);
+ }
+
+ /**
+ * Add a portal device.
+ *
+ * @param int $shopId
+ * @param string $ssid
+ * @param bool $reset
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addPortalDevice(int $shopId, string $ssid, bool $reset = false)
+ {
+ $data = [
+ 'shop_id' => $shopId,
+ 'ssid' => $ssid,
+ 'reset' => $reset,
+ ];
+
+ return $this->httpPostJson('bizwifi/apportal/register', $data);
+ }
+
+ /**
+ * Delete device by MAC address.
+ *
+ * @param string $macAddress
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(string $macAddress)
+ {
+ return $this->httpPostJson('bizwifi/device/delete', ['bssid' => $macAddress]);
+ }
+
+ /**
+ * Get a list of devices.
+ *
+ * @param int $page
+ * @param int $size
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $page = 1, int $size = 10)
+ {
+ $data = [
+ 'pageindex' => $page,
+ 'pagesize' => $size,
+ ];
+
+ return $this->httpPostJson('bizwifi/device/list', $data);
+ }
+
+ /**
+ * Get a list of devices by shop ID.
+ *
+ * @param int $shopId
+ * @param int $page
+ * @param int $size
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function listByShopId(int $shopId, int $page = 1, int $size = 10)
+ {
+ $data = [
+ 'shop_id' => $shopId,
+ 'pageindex' => $page,
+ 'pagesize' => $size,
+ ];
+
+ return $this->httpPostJson('bizwifi/device/list', $data);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php
new file mode 100644
index 0000000..7a28b51
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ServiceProvider.php
@@ -0,0 +1,45 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author her-cat
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function register(Container $app)
+ {
+ $app['wifi'] = function ($app) {
+ return new Client($app);
+ };
+
+ $app['wifi_card'] = function ($app) {
+ return new CardClient($app);
+ };
+
+ $app['wifi_device'] = function ($app) {
+ return new DeviceClient($app);
+ };
+
+ $app['wifi_shop'] = function ($app) {
+ return new ShopClient($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php
new file mode 100644
index 0000000..34f1c97
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OfficialAccount/WiFi/ShopClient.php
@@ -0,0 +1,100 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OfficialAccount\WiFi;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class ShopClient.
+ *
+ * @author her-cat
+ */
+class ShopClient extends BaseClient
+{
+ /**
+ * Get shop Wi-Fi information.
+ *
+ * @param int $shopId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(int $shopId)
+ {
+ return $this->httpPostJson('bizwifi/shop/get', ['shop_id' => $shopId]);
+ }
+
+ /**
+ * Get a list of Wi-Fi shops.
+ *
+ * @param int $page
+ * @param int $size
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $page = 1, int $size = 10)
+ {
+ $data = [
+ 'pageindex' => $page,
+ 'pagesize' => $size,
+ ];
+
+ return $this->httpPostJson('bizwifi/shop/list', $data);
+ }
+
+ /**
+ * Update shop Wi-Fi information.
+ *
+ * @param int $shopId
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function update(int $shopId, array $data)
+ {
+ $data = array_merge(['shop_id' => $shopId], $data);
+
+ return $this->httpPostJson('bizwifi/shop/update', $data);
+ }
+
+ /**
+ * Clear shop network and equipment.
+ *
+ * @param int $shopId
+ * @param string|null $ssid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function clearDevice(int $shopId, string $ssid = null)
+ {
+ $data = [
+ 'shop_id' => $shopId,
+ ];
+
+ if (!is_null($ssid)) {
+ $data['ssid'] = $ssid;
+ }
+
+ return $this->httpPostJson('bizwifi/shop/clean', $data);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Application.php
new file mode 100644
index 0000000..386e7a3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Application.php
@@ -0,0 +1,251 @@
+ [
+ 'timeout' => 5.0,
+ 'base_uri' => 'https://api.weixin.qq.com/',
+ ],
+ ];
+
+ /**
+ * Creates the officialAccount application.
+ *
+ * @param string $appId
+ * @param string|null $refreshToken
+ * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken
+ *
+ * @return \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application
+ */
+ public function officialAccount(
+ string $appId,
+ string $refreshToken = null,
+ AccessToken $accessToken = null
+ ): OfficialAccount {
+ $application = new OfficialAccount(
+ $this->getAuthorizerConfig($appId, $refreshToken),
+ $this->getReplaceServices($accessToken) + [
+ 'encryptor' => $this['encryptor'],
+
+ 'account' => function ($app) {
+ return new AccountClient($app, $this);
+ },
+ ]
+ );
+
+ $application->extend(
+ 'oauth',
+ function ($socialite) {
+ /* @var \Overtrue\Socialite\Providers\WeChat $socialite */
+ $socialite->withComponent(
+ [
+ 'id' => $this['config']['app_id'],
+ 'token' => fn () => $this['access_token']->getToken()['component_access_token'],
+ ]
+ );
+
+ return $socialite;
+ }
+ );
+
+ return $application;
+ }
+
+ /**
+ * Creates the miniProgram application.
+ *
+ * @param string $appId
+ * @param string|null $refreshToken
+ * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken
+ *
+ * @return \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application
+ */
+ public function miniProgram(
+ string $appId,
+ string $refreshToken = null,
+ AccessToken $accessToken = null
+ ): MiniProgram {
+ return new MiniProgram(
+ $this->getAuthorizerConfig($appId, $refreshToken),
+ $this->getReplaceServices($accessToken) + [
+ 'encryptor' => function () {
+ return new Encryptor(
+ $this['config']['app_id'],
+ $this['config']['token'],
+ $this['config']['aes_key']
+ );
+ },
+
+ 'auth' => function ($app) {
+ return new Client($app, $this);
+ },
+ ]
+ );
+ }
+
+ /**
+ * Return the pre-authorization login page url.
+ *
+ * @param string $callbackUrl
+ * @param string|array|null $optional
+ *
+ * @return string
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function getPreAuthorizationUrl(string $callbackUrl, $optional = []): string
+ {
+ // 兼容旧版 API 设计
+ if (\is_string($optional)) {
+ $optional = [
+ 'pre_auth_code' => $optional,
+ ];
+ } else {
+ $optional['pre_auth_code'] = data_get($this->createPreAuthorizationCode(), 'pre_auth_code');
+ }
+
+ $queries = \array_merge(
+ $optional,
+ [
+ 'component_appid' => $this['config']['app_id'],
+ 'redirect_uri' => $callbackUrl,
+ ]
+ );
+
+ return 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?'.http_build_query($queries);
+ }
+
+ /**
+ * Return the pre-authorization login page url (mobile).
+ *
+ * @param string $callbackUrl
+ * @param string|array|null $optional
+ *
+ * @return string
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ public function getMobilePreAuthorizationUrl(string $callbackUrl, $optional = []): string
+ {
+ // 兼容旧版 API 设计
+ if (\is_string($optional)) {
+ $optional = [
+ 'pre_auth_code' => $optional,
+ ];
+ } else {
+ $optional['pre_auth_code'] = data_get($this->createPreAuthorizationCode(), 'pre_auth_code');
+ }
+
+ $queries = \array_merge(
+ ['auth_type' => 3],
+ $optional,
+ [
+ 'component_appid' => $this['config']['app_id'],
+ 'redirect_uri' => $callbackUrl,
+ 'action' => 'bindcomponent',
+ 'no_scan' => 1,
+ ]
+ );
+
+ return 'https://mp.weixin.qq.com/safe/bindcomponent?'.http_build_query($queries).'#wechat_redirect';
+ }
+
+ /**
+ * @param string $appId
+ * @param string|null $refreshToken
+ *
+ * @return array
+ */
+ protected function getAuthorizerConfig(string $appId, string $refreshToken = null): array
+ {
+ return $this['config']->merge(
+ [
+ 'component_app_id' => $this['config']['app_id'],
+ 'component_app_token' => $this['access_token']->getToken()['component_access_token'],
+ 'app_id' => $appId,
+ 'refresh_token' => $refreshToken,
+ ]
+ )->toArray();
+ }
+
+ /**
+ * @param \EasyWeChat\OpenPlatform\Authorizer\Auth\AccessToken|null $accessToken
+ *
+ * @return array
+ */
+ protected function getReplaceServices(AccessToken $accessToken = null): array
+ {
+ $services = [
+ 'access_token' => $accessToken ?: function ($app) {
+ return new AccessToken($app, $this);
+ },
+
+ 'server' => function ($app) {
+ return new Guard($app);
+ },
+ ];
+
+ foreach (['cache', 'http_client', 'log', 'logger', 'request'] as $reuse) {
+ if (isset($this[$reuse])) {
+ $services[$reuse] = $this[$reuse];
+ }
+ }
+
+ return $services;
+ }
+
+ /**
+ * Handle dynamic calls.
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ return $this->base->$method(...$args);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php
new file mode 100644
index 0000000..c15d78d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/AccessToken.php
@@ -0,0 +1,49 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * Class AccessToken.
+ *
+ * @author mingyoung
+ */
+class AccessToken extends BaseAccessToken
+{
+ /**
+ * @var string
+ */
+ protected $requestMethod = 'POST';
+
+ /**
+ * @var string
+ */
+ protected $tokenKey = 'component_access_token';
+
+ /**
+ * @var string
+ */
+ protected $endpointToGetToken = 'cgi-bin/component/api_component_token';
+
+ /**
+ * @return array
+ */
+ protected function getCredentials(): array
+ {
+ return [
+ 'component_appid' => $this->app['config']['app_id'],
+ 'component_appsecret' => $this->app['config']['secret'],
+ 'component_verify_ticket' => $this->app['verify_ticket']->getTicket(),
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php
new file mode 100644
index 0000000..c60784b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/ServiceProvider.php
@@ -0,0 +1,37 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['verify_ticket'] = function ($app) {
+ return new VerifyTicket($app);
+ };
+
+ $app['access_token'] = function ($app) {
+ return new AccessToken($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php
new file mode 100644
index 0000000..9ad04c7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Auth/VerifyTicket.php
@@ -0,0 +1,91 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Auth;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+use EasyWeChat\OpenPlatform\Application;
+
+/**
+ * Class VerifyTicket.
+ *
+ * @author mingyoung
+ */
+class VerifyTicket
+{
+ use InteractsWithCache;
+
+ /**
+ * @var \EasyWeChat\OpenPlatform\Application
+ */
+ protected $app;
+
+ /**
+ * Constructor.
+ *
+ * @param \EasyWeChat\OpenPlatform\Application $app
+ */
+ public function __construct(Application $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * Put the credential `component_verify_ticket` in cache.
+ *
+ * @param string $ticket
+ *
+ * @return $this
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function setTicket(string $ticket)
+ {
+ $this->getCache()->set($this->getCacheKey(), $ticket, 3600);
+
+ if (!$this->getCache()->has($this->getCacheKey())) {
+ throw new RuntimeException('Failed to cache verify ticket.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get the credential `component_verify_ticket` from cache.
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function getTicket(): string
+ {
+ if ($cached = $this->getCache()->get($this->getCacheKey())) {
+ return $cached;
+ }
+
+ throw new RuntimeException('Credential "component_verify_ticket" does not exist in cache.');
+ }
+
+ /**
+ * Get cache key.
+ *
+ * @return string
+ */
+ protected function getCacheKey(): string
+ {
+ return 'easywechat.open_platform.verify_ticket.'.$this->app['config']['app_id'];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php
new file mode 100644
index 0000000..b062e3a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/Account/Client.php
@@ -0,0 +1,96 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Scholer
+ */
+class Client extends BaseClient
+{
+ /**
+ * 创建开放平台帐号并绑定公众号/小程序.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function create()
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ ];
+
+ return $this->httpPostJson('cgi-bin/open/create', $params);
+ }
+
+ /**
+ * 将公众号/小程序绑定到开放平台帐号下.
+ *
+ * @param string $openAppId 开放平台帐号appid
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function bindTo(string $openAppId)
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ 'open_appid' => $openAppId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/open/bind', $params);
+ }
+
+ /**
+ * 将公众号/小程序从开放平台帐号下解绑.
+ *
+ * @param string $openAppId 开放平台帐号appid
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unbindFrom(string $openAppId)
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ 'open_appid' => $openAppId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/open/unbind', $params);
+ }
+
+ /**
+ * 获取公众号/小程序所绑定的开放平台帐号.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getBinding()
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ ];
+
+ return $this->httpPostJson('cgi-bin/open/get', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php
new file mode 100644
index 0000000..d93293d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Aggregate/AggregateServiceProvider.php
@@ -0,0 +1,22 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Aggregate;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class AggregateServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php
new file mode 100644
index 0000000..07b808c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Auth/AccessToken.php
@@ -0,0 +1,79 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+use EasyWeChat\OpenPlatform\Application;
+use Pimple\Container;
+
+/**
+ * Class AccessToken.
+ *
+ * @author mingyoung
+ */
+class AccessToken extends BaseAccessToken
+{
+ /**
+ * @var string
+ */
+ protected $requestMethod = 'POST';
+
+ /**
+ * @var string
+ */
+ protected $queryName = 'access_token';
+
+ /**
+ * {@inheritdoc}.
+ */
+ protected $tokenKey = 'authorizer_access_token';
+
+ /**
+ * @var \EasyWeChat\OpenPlatform\Application
+ */
+ protected $component;
+
+ /**
+ * AuthorizerAccessToken constructor.
+ *
+ * @param \Pimple\Container $app
+ * @param \EasyWeChat\OpenPlatform\Application $component
+ */
+ public function __construct(Container $app, Application $component)
+ {
+ parent::__construct($app);
+
+ $this->component = $component;
+ }
+
+ /**
+ * {@inheritdoc}.
+ */
+ protected function getCredentials(): array
+ {
+ return [
+ 'component_appid' => $this->component['config']['app_id'],
+ 'authorizer_appid' => $this->app['config']['app_id'],
+ 'authorizer_refresh_token' => $this->app['config']['refresh_token'],
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getEndpoint(): string
+ {
+ return 'cgi-bin/component/api_authorizer_token?'.http_build_query([
+ 'component_access_token' => $this->component['access_token']->getToken()['component_access_token'],
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php
new file mode 100644
index 0000000..f1b7afb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/Client.php
@@ -0,0 +1,79 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account;
+
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取账号基本信息.
+ */
+ public function getBasicInfo()
+ {
+ return $this->httpPostJson('cgi-bin/account/getaccountbasicinfo');
+ }
+
+ /**
+ * 修改头像.
+ *
+ * @param string $mediaId 头像素材mediaId
+ * @param string $left 剪裁框左上角x坐标(取值范围:[0, 1])
+ * @param string $top 剪裁框左上角y坐标(取值范围:[0, 1])
+ * @param string $right 剪裁框右下角x坐标(取值范围:[0, 1])
+ * @param string $bottom 剪裁框右下角y坐标(取值范围:[0, 1])
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateAvatar(
+ string $mediaId,
+ $left = '0.0',
+ $top = '0.0',
+ $right = '1.0',
+ $bottom = '1.0'
+ ) {
+ $params = [
+ 'head_img_media_id' => $mediaId,
+ 'x1' => \strval($left),
+ 'y1' => \strval($top),
+ 'x2' => \strval($right),
+ 'y2' => \strval($bottom),
+ ];
+
+ return $this->httpPostJson('cgi-bin/account/modifyheadimage', $params);
+ }
+
+ /**
+ * 修改功能介绍.
+ *
+ * @param string $signature 功能介绍(简介)
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateSignature(string $signature)
+ {
+ $params = ['signature' => $signature];
+
+ return $this->httpPostJson('cgi-bin/account/modifysignature', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php
new file mode 100644
index 0000000..f062954
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Account/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['account'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php
new file mode 100644
index 0000000..09b52cb
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Application.php
@@ -0,0 +1,57 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram;
+
+use EasyWeChat\MiniProgram\Application as MiniProgram;
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung
+ *
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Account\Client $account
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code\Client $code
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain\Client $domain
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting\Client $setting
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester\Client $tester
+ * @property \EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Material\Client $material
+ */
+class Application extends MiniProgram
+{
+ /**
+ * Application constructor.
+ *
+ * @param array $config
+ * @param array $prepends
+ */
+ public function __construct(array $config = [], array $prepends = [])
+ {
+ parent::__construct($config, $prepends);
+
+ $providers = [
+ AggregateServiceProvider::class,
+ Code\ServiceProvider::class,
+ Domain\ServiceProvider::class,
+ Account\ServiceProvider::class,
+ Setting\ServiceProvider::class,
+ Tester\ServiceProvider::class,
+ Material\ServiceProvider::class,
+ Privacy\ServiceProvider::class,
+ Security\ServiceProvider::class,
+ ];
+
+ foreach ($providers as $provider) {
+ $this->register(new $provider());
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php
new file mode 100644
index 0000000..05bb704
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Auth/Client.php
@@ -0,0 +1,64 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Auth;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\OpenPlatform\Application;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * @var \EasyWeChat\OpenPlatform\Application
+ */
+ protected $component;
+
+ /**
+ * Client constructor.
+ *
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ * @param \EasyWeChat\OpenPlatform\Application $component
+ */
+ public function __construct(ServiceContainer $app, Application $component)
+ {
+ parent::__construct($app);
+
+ $this->component = $component;
+ }
+
+ /**
+ * Get session info by code.
+ *
+ * @param string $code
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function session(string $code)
+ {
+ $params = [
+ 'appid' => $this->app['config']['app_id'],
+ 'js_code' => $code,
+ 'grant_type' => 'authorization_code',
+ 'component_appid' => $this->component['config']['app_id'],
+ 'component_access_token' => $this->component['access_token']->getToken()['component_access_token'],
+ ];
+
+ return $this->httpGet('sns/component/jscode2session', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php
new file mode 100644
index 0000000..3d1b823
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/Client.php
@@ -0,0 +1,271 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * @param int $templateId
+ * @param string $extJson
+ * @param string $version
+ * @param string $description
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function commit(int $templateId, string $extJson, string $version, string $description)
+ {
+ return $this->httpPostJson('wxa/commit', [
+ 'template_id' => $templateId,
+ 'ext_json' => $extJson,
+ 'user_version' => $version,
+ 'user_desc' => $description,
+ ]);
+ }
+
+ /**
+ * @param string|null $path
+ *
+ * @return \EasyWeChat\Kernel\Http\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getQrCode(string $path = null)
+ {
+ return $this->requestRaw('wxa/get_qrcode', 'GET', [
+ 'query' => ['path' => $path],
+ ]);
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getCategory()
+ {
+ return $this->httpGet('wxa/get_category');
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getPage()
+ {
+ return $this->httpGet('wxa/get_page');
+ }
+
+ /**
+ * @param array $data
+ * @param string|null $feedbackInfo
+ * @param string|null $feedbackStuff
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function submitAudit(array $data, string $feedbackInfo = null, string $feedbackStuff = null)
+ {
+ if (isset($data['item_list'])) {
+ return $this->httpPostJson('wxa/submit_audit', $data);
+ }
+
+ return $this->httpPostJson('wxa/submit_audit', [
+ 'item_list' => $data,
+ 'feedback_info' => $feedbackInfo,
+ 'feedback_stuff' => $feedbackStuff,
+ ]);
+ }
+
+ /**
+ * @param int $auditId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getAuditStatus(int $auditId)
+ {
+ return $this->httpPostJson('wxa/get_auditstatus', [
+ 'auditid' => $auditId,
+ ]);
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getLatestAuditStatus()
+ {
+ return $this->httpGet('wxa/get_latest_auditstatus');
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function release()
+ {
+ return $this->httpPostJson('wxa/release');
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function withdrawAudit()
+ {
+ return $this->httpGet('wxa/undocodeaudit');
+ }
+
+ /**
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function rollbackRelease()
+ {
+ return $this->httpGet('wxa/revertcoderelease');
+ }
+
+ /**
+ * @param string $action
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function changeVisitStatus(string $action)
+ {
+ return $this->httpPostJson('wxa/change_visitstatus', [
+ 'action' => $action,
+ ]);
+ }
+
+ /**
+ * 分阶段发布.
+ *
+ * @param int $grayPercentage
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function grayRelease(int $grayPercentage)
+ {
+ return $this->httpPostJson('wxa/grayrelease', [
+ 'gray_percentage' => $grayPercentage,
+ ]);
+ }
+
+ /**
+ * 取消分阶段发布.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function revertGrayRelease()
+ {
+ return $this->httpGet('wxa/revertgrayrelease');
+ }
+
+ /**
+ * 查询当前分阶段发布详情.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getGrayRelease()
+ {
+ return $this->httpGet('wxa/getgrayreleaseplan');
+ }
+
+ /**
+ * 查询当前设置的最低基础库版本及各版本用户占比.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getSupportVersion()
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/getweappsupportversion');
+ }
+
+ /**
+ * 设置最低基础库版本.
+ *
+ * @param string $version
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setSupportVersion(string $version)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/setweappsupportversion', [
+ 'version' => $version,
+ ]);
+ }
+
+ /**
+ * 查询服务商的当月提审限额(quota)和加急次数.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function queryQuota()
+ {
+ return $this->httpGet('wxa/queryquota');
+ }
+
+ /**
+ * 加急审核申请.
+ *
+ * @param int $auditId 审核单ID
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function speedupAudit(int $auditId)
+ {
+ return $this->httpPostJson('wxa/speedupaudit', [
+ 'auditid' => $auditId,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php
new file mode 100644
index 0000000..bce8611
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Code/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Code;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['code'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php
new file mode 100644
index 0000000..2a6547c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/Client.php
@@ -0,0 +1,54 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function modify(array $params)
+ {
+ return $this->httpPostJson('wxa/modify_domain', $params);
+ }
+
+ /**
+ * 设置小程序业务域名.
+ *
+ * @param array $domains
+ * @param string $action
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setWebviewDomain(array $domains, $action = 'add')
+ {
+ return $this->httpPostJson('wxa/setwebviewdomain', [
+ 'action' => $action,
+ 'webviewdomain' => $domains,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php
new file mode 100644
index 0000000..4eaef13
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Domain/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Domain;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['domain'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/Client.php
new file mode 100644
index 0000000..a9686c9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/Client.php
@@ -0,0 +1,51 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Material;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Http\StreamResponse;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends BaseClient
+{
+ /**
+ * Allow media type.
+ *
+ * @var array
+ */
+ protected $allowTypes = ['image', 'voice', 'video', 'thumb', 'news_image'];
+
+ /**
+ * Fetch material.
+ *
+ * @param string $mediaId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $mediaId)
+ {
+ $response = $this->requestRaw('cgi-bin/material/get_material', 'POST', ['json' => ['media_id' => $mediaId]]);
+
+ if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) {
+ return StreamResponse::buildFromPsrResponse($response);
+ }
+
+ return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/ServiceProvider.php
new file mode 100644
index 0000000..820a1ac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Material/ServiceProvider.php
@@ -0,0 +1,44 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * ServiceProvider.php.
+ *
+ * This file is part of the wechat.
+ *
+ * (c) overtrue
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Material;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['material'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/Client.php
new file mode 100644
index 0000000..3b159d3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/Client.php
@@ -0,0 +1,66 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Privacy;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+
+/**
+ * Class Client.
+ *
+ * @author lujunyi
+ */
+class Client extends BaseClient
+{
+ /**
+ * 查询小程序用户隐私保护指引.
+ */
+ public function get()
+ {
+ return $this->httpPostJson('cgi-bin/component/getprivacysetting', []);
+ }
+
+ /**
+ * 配置小程序用户隐私保护指引
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function set(array $params)
+ {
+ return $this->httpPostJson('cgi-bin/component/setprivacysetting', $params);
+ }
+
+ /**
+ * 上传小程序用户隐私保护指引
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException
+ */
+ public function upload(string $path)
+ {
+ if (!file_exists($path) || !is_readable($path)) {
+ throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path));
+ }
+
+ return $this->httpUpload('cgi-bin/component/uploadprivacyextfile', ['file' => $path]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/ServiceProvider.php
new file mode 100644
index 0000000..85ec390
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Privacy/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Privacy;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['privacy'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Security/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Security/Client.php
new file mode 100644
index 0000000..f227ad5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Security/Client.php
@@ -0,0 +1,45 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Security;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author lujunyi
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取隐私接口列表.
+ */
+ public function get()
+ {
+ return $this->httpGet('wxa/security/get_privacy_interface');
+ }
+
+ /**
+ * 申请隐私接口
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function set(array $params)
+ {
+ return $this->httpPostJson('wxa/security/apply_privacy_interface', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Security/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Security/ServiceProvider.php
new file mode 100644
index 0000000..66223e3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Security/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Security;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['security'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php
new file mode 100644
index 0000000..8d979b0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/Client.php
@@ -0,0 +1,250 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取账号可以设置的所有类目.
+ */
+ public function getAllCategories()
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/getallcategories');
+ }
+
+ /**
+ * 添加类目.
+ *
+ * @param array $categories 类目数组
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addCategories(array $categories)
+ {
+ $params = ['categories' => $categories];
+
+ return $this->httpPostJson('cgi-bin/wxopen/addcategory', $params);
+ }
+
+ /**
+ * 删除类目.
+ *
+ * @param int $firstId 一级类目ID
+ * @param int $secondId 二级类目ID
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deleteCategories(int $firstId, int $secondId)
+ {
+ $params = ['first' => $firstId, 'second' => $secondId];
+
+ return $this->httpPostJson('cgi-bin/wxopen/deletecategory', $params);
+ }
+
+ /**
+ * 获取账号已经设置的所有类目.
+ */
+ public function getCategories()
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/getcategory');
+ }
+
+ /**
+ * 修改类目.
+ *
+ * @param array $category 单个类目
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function updateCategory(array $category)
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/modifycategory', $category);
+ }
+
+ /**
+ * 小程序名称设置及改名.
+ *
+ * @param string $nickname 昵称
+ * @param string $idCardMediaId 身份证照片素材ID
+ * @param string $licenseMediaId 组织机构代码证或营业执照素材ID
+ * @param array $otherStuffs 其他证明材料素材ID
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setNickname(
+ string $nickname,
+ string $idCardMediaId = '',
+ string $licenseMediaId = '',
+ array $otherStuffs = []
+ ) {
+ $params = [
+ 'nick_name' => $nickname,
+ 'id_card' => $idCardMediaId,
+ 'license' => $licenseMediaId,
+ ];
+
+ for ($i = \count($otherStuffs) - 1; $i >= 0; --$i) {
+ $params['naming_other_stuff_'.($i + 1)] = $otherStuffs[$i];
+ }
+
+ return $this->httpPostJson('wxa/setnickname', $params);
+ }
+
+ /**
+ * 小程序改名审核状态查询.
+ *
+ * @param int $auditId 审核单id
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getNicknameAuditStatus($auditId)
+ {
+ $params = ['audit_id' => $auditId];
+
+ return $this->httpPostJson('wxa/api_wxa_querynickname', $params);
+ }
+
+ /**
+ * 微信认证名称检测.
+ *
+ * @param string $nickname 名称(昵称)
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function isAvailableNickname($nickname)
+ {
+ $params = ['nick_name' => $nickname];
+
+ return $this->httpPostJson(
+ 'cgi-bin/wxverify/checkwxverifynickname',
+ $params
+ );
+ }
+
+ /**
+ * 查询小程序是否可被搜索.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getSearchStatus()
+ {
+ return $this->httpGet('wxa/getwxasearchstatus');
+ }
+
+ /**
+ * 设置小程序可被搜素.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setSearchable()
+ {
+ return $this->httpPostJson('wxa/changewxasearchstatus', [
+ 'status' => 0,
+ ]);
+ }
+
+ /**
+ * 设置小程序不可被搜素.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setUnsearchable()
+ {
+ return $this->httpPostJson('wxa/changewxasearchstatus', [
+ 'status' => 1,
+ ]);
+ }
+
+ /**
+ * 获取展示的公众号信息.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getDisplayedOfficialAccount()
+ {
+ return $this->httpGet('wxa/getshowwxaitem');
+ }
+
+ /**
+ * 设置展示的公众号.
+ *
+ * @param string|bool $appid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setDisplayedOfficialAccount($appid)
+ {
+ return $this->httpPostJson('wxa/updateshowwxaitem', [
+ 'appid' => $appid ?: null,
+ 'wxa_subscribe_biz_flag' => $appid ? 1 : 0,
+ ]);
+ }
+
+ /**
+ * 获取可以用来设置的公众号列表.
+ *
+ * @param int $page
+ * @param int $num
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getDisplayableOfficialAccounts(int $page, int $num)
+ {
+ return $this->httpGet('wxa/getwxamplinkforshow', [
+ 'page' => $page,
+ 'num' => $num,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php
new file mode 100644
index 0000000..917aec5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Setting/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Setting;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['setting'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php
new file mode 100644
index 0000000..58c9854
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/Client.php
@@ -0,0 +1,80 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author caikeal
+ */
+class Client extends BaseClient
+{
+ /**
+ * 绑定小程序体验者.
+ *
+ * @param string $wechatId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function bind(string $wechatId)
+ {
+ return $this->httpPostJson('wxa/bind_tester', [
+ 'wechatid' => $wechatId,
+ ]);
+ }
+
+ /**
+ * 解绑小程序体验者.
+ *
+ * @param string $wechatId
+ * @param string $userStr
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unbind(string $wechatId = null, string $userStr = null)
+ {
+ return $this->httpPostJson('wxa/unbind_tester', [
+ ($userStr ? 'userstr' : 'wechatid') => $userStr ?? $wechatId,
+ ]);
+ }
+
+ public function unbindWithUserStr(string $userStr)
+ {
+ return $this->httpPostJson('wxa/unbind_tester', [
+ 'userstr' => $userStr,
+ ]);
+ }
+
+ /**
+ * 获取体验者列表.
+ *
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list()
+ {
+ return $this->httpPostJson('wxa/memberauth', [
+ 'action' => 'get_experiencer',
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php
new file mode 100644
index 0000000..ff1ffb3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/MiniProgram/Tester/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Tester;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['tester'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php
new file mode 100644
index 0000000..9e7b38b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Account/Client.php
@@ -0,0 +1,81 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\OpenPlatform\Application;
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\Account\Client as BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Keal
+ */
+class Client extends BaseClient
+{
+ /**
+ * @var \EasyWeChat\OpenPlatform\Application
+ */
+ protected $component;
+
+ /**
+ * Client constructor.
+ *
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ * @param \EasyWeChat\OpenPlatform\Application $component
+ */
+ public function __construct(ServiceContainer $app, Application $component)
+ {
+ parent::__construct($app);
+
+ $this->component = $component;
+ }
+
+ /**
+ * 从第三方平台跳转至微信公众平台授权注册页面, 授权注册小程序.
+ *
+ * @param string $callbackUrl
+ * @param bool $copyWxVerify
+ *
+ * @return string
+ */
+ public function getFastRegistrationUrl(string $callbackUrl, bool $copyWxVerify = true): string
+ {
+ $queries = [
+ 'copy_wx_verify' => $copyWxVerify,
+ 'component_appid' => $this->component['config']['app_id'],
+ 'appid' => $this->app['config']['app_id'],
+ 'redirect_uri' => $callbackUrl,
+ ];
+
+ return 'https://mp.weixin.qq.com/cgi-bin/fastregisterauth?'.http_build_query($queries);
+ }
+
+ /**
+ * 小程序快速注册.
+ *
+ * @param string $ticket
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function register(string $ticket)
+ {
+ $params = [
+ 'ticket' => $ticket,
+ ];
+
+ return $this->httpPostJson('cgi-bin/account/fastregister', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php
new file mode 100644
index 0000000..08509d2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/Application.php
@@ -0,0 +1,46 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount;
+
+use EasyWeChat\OfficialAccount\Application as OfficialAccount;
+use EasyWeChat\OpenPlatform\Authorizer\Aggregate\AggregateServiceProvider;
+
+/**
+ * Class Application.
+ *
+ * @author mingyoung
+ *
+ * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Account\Client $account
+ * @property \EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram\Client $mini_program
+ */
+class Application extends OfficialAccount
+{
+ /**
+ * Application constructor.
+ *
+ * @param array $config
+ * @param array $prepends
+ */
+ public function __construct(array $config = [], array $prepends = [])
+ {
+ parent::__construct($config, $prepends);
+
+ $providers = [
+ AggregateServiceProvider::class,
+ MiniProgram\ServiceProvider::class,
+ ];
+
+ foreach ($providers as $provider) {
+ $this->register(new $provider());
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php
new file mode 100644
index 0000000..d32b51b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/Client.php
@@ -0,0 +1,77 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author Keal
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取公众号关联的小程序.
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list()
+ {
+ return $this->httpPostJson('cgi-bin/wxopen/wxamplinkget');
+ }
+
+ /**
+ * 关联小程序.
+ *
+ * @param string $appId
+ * @param bool $notifyUsers
+ * @param bool $showProfile
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function link(string $appId, bool $notifyUsers = true, bool $showProfile = false)
+ {
+ $params = [
+ 'appid' => $appId,
+ 'notify_users' => (string) $notifyUsers,
+ 'show_profile' => (string) $showProfile,
+ ];
+
+ return $this->httpPostJson('cgi-bin/wxopen/wxamplink', $params);
+ }
+
+ /**
+ * 解除已关联的小程序.
+ *
+ * @param string $appId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unlink(string $appId)
+ {
+ $params = [
+ 'appid' => $appId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/wxopen/wxampunlink', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php
new file mode 100644
index 0000000..31ce10b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/OfficialAccount/MiniProgram/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\MiniProgram;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['mini_program'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php
new file mode 100644
index 0000000..d7efbd3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Authorizer/Server/Guard.php
@@ -0,0 +1,32 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Authorizer\Server;
+
+use EasyWeChat\Kernel\ServerGuard;
+
+/**
+ * Class Guard.
+ *
+ * @author mingyoung
+ */
+class Guard extends ServerGuard
+{
+ /**
+ * Get token from OpenPlatform encryptor.
+ *
+ * @return string
+ */
+ protected function getToken()
+ {
+ return $this->app['encryptor']->getToken();
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php
new file mode 100644
index 0000000..ec78a5f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/Client.php
@@ -0,0 +1,166 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Base;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * Get authorization info.
+ *
+ * @param string|null $authCode
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function handleAuthorize(string $authCode = null)
+ {
+ $params = [
+ 'component_appid' => $this->app['config']['app_id'],
+ 'authorization_code' => $authCode ?? $this->app['request']->get('auth_code'),
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/api_query_auth', $params);
+ }
+
+ /**
+ * Get authorizer info.
+ *
+ * @param string $appId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getAuthorizer(string $appId)
+ {
+ $params = [
+ 'component_appid' => $this->app['config']['app_id'],
+ 'authorizer_appid' => $appId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/api_get_authorizer_info', $params);
+ }
+
+ /**
+ * Get options.
+ *
+ * @param string $appId
+ * @param string $name
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getAuthorizerOption(string $appId, string $name)
+ {
+ $params = [
+ 'component_appid' => $this->app['config']['app_id'],
+ 'authorizer_appid' => $appId,
+ 'option_name' => $name,
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/api_get_authorizer_option', $params);
+ }
+
+ /**
+ * Set authorizer option.
+ *
+ * @param string $appId
+ * @param string $name
+ * @param string $value
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setAuthorizerOption(string $appId, string $name, string $value)
+ {
+ $params = [
+ 'component_appid' => $this->app['config']['app_id'],
+ 'authorizer_appid' => $appId,
+ 'option_name' => $name,
+ 'option_value' => $value,
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/api_set_authorizer_option', $params);
+ }
+
+ /**
+ * Get authorizer list.
+ *
+ * @param int $offset
+ * @param int $count
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getAuthorizers($offset = 0, $count = 500)
+ {
+ $params = [
+ 'component_appid' => $this->app['config']['app_id'],
+ 'offset' => $offset,
+ 'count' => $count,
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/api_get_authorizer_list', $params);
+ }
+
+ /**
+ * Create pre-authorization code.
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createPreAuthorizationCode()
+ {
+ $params = [
+ 'component_appid' => $this->app['config']['app_id'],
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/api_create_preauthcode', $params);
+ }
+
+ /**
+ * OpenPlatform Clear quota.
+ *
+ * @see https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318587
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function clearQuota()
+ {
+ $params = [
+ 'component_appid' => $this->app['config']['app_id'],
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/clear_quota', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php
new file mode 100644
index 0000000..e647c41
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['base'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php
new file mode 100644
index 0000000..3441041
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/Client.php
@@ -0,0 +1,94 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\CodeTemplate;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author caikeal
+ */
+class Client extends BaseClient
+{
+ /**
+ * 获取草稿箱内的所有临时代码草稿
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getDrafts()
+ {
+ return $this->httpGet('wxa/gettemplatedraftlist');
+ }
+
+ /**
+ * 将草稿箱的草稿选为小程序代码模版.
+ *
+ * @param int $draftId
+ * @param int $templateType
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function createFromDraft(int $draftId, int $templateType = 0)
+ {
+ $params = [
+ 'draft_id' => $draftId,
+ 'template_type' => $templateType,
+ ];
+
+ return $this->httpPostJson('wxa/addtotemplate', $params);
+ }
+
+ /**
+ * 获取代码模版库中的所有小程序代码模版.
+ *
+ * @param int $templateType
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function list(int $templateType = null)
+ {
+ $params = [
+ 'template_type' => $templateType,
+ ];
+
+ return $this->httpGet('wxa/gettemplatelist', $params);
+ }
+
+ /**
+ * 删除指定小程序代码模版.
+ *
+ * @param string $templateId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete($templateId)
+ {
+ $params = [
+ 'template_id' => $templateId,
+ ];
+
+ return $this->httpPostJson('wxa/deletetemplate', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php
new file mode 100644
index 0000000..ab543a7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/CodeTemplate/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\CodeTemplate;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['code_template'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php
new file mode 100644
index 0000000..eafb3f5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/Client.php
@@ -0,0 +1,60 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Component;
+
+use EasyWeChat\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author dudashuang
+ */
+class Client extends BaseClient
+{
+ /**
+ * 通过法人微信快速创建小程序.
+ *
+ * @param array $params
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function registerMiniProgram(array $params)
+ {
+ return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'create']);
+ }
+
+ /**
+ * 查询创建任务状态.
+ *
+ * @param string $companyName
+ * @param string $legalPersonaWechat
+ * @param string $legalPersonaName
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getRegistrationStatus(string $companyName, string $legalPersonaWechat, string $legalPersonaName)
+ {
+ $params = [
+ 'name' => $companyName,
+ 'legal_persona_wechat' => $legalPersonaWechat,
+ 'legal_persona_name' => $legalPersonaName,
+ ];
+
+ return $this->httpPostJson('cgi-bin/component/fastregisterweapp', $params, ['action' => 'search']);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php
new file mode 100644
index 0000000..83e309f
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Component/ServiceProvider.php
@@ -0,0 +1,25 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Component;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['component'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php
new file mode 100644
index 0000000..8d29d5e
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Guard.php
@@ -0,0 +1,65 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server;
+
+use EasyWeChat\Kernel\ServerGuard;
+use EasyWeChat\OpenPlatform\Server\Handlers\Authorized;
+use EasyWeChat\OpenPlatform\Server\Handlers\Unauthorized;
+use EasyWeChat\OpenPlatform\Server\Handlers\UpdateAuthorized;
+use EasyWeChat\OpenPlatform\Server\Handlers\VerifyTicketRefreshed;
+use Symfony\Component\HttpFoundation\Response;
+use function EasyWeChat\Kernel\data_get;
+
+/**
+ * Class Guard.
+ *
+ * @author mingyoung
+ */
+class Guard extends ServerGuard
+{
+ public const EVENT_AUTHORIZED = 'authorized';
+ public const EVENT_UNAUTHORIZED = 'unauthorized';
+ public const EVENT_UPDATE_AUTHORIZED = 'updateauthorized';
+ public const EVENT_COMPONENT_VERIFY_TICKET = 'component_verify_ticket';
+ public const EVENT_THIRD_FAST_REGISTERED = 'notify_third_fasteregister';
+
+ /**
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ protected function resolve(): Response
+ {
+ $this->registerHandlers();
+
+ $message = $this->getMessage();
+
+ if ($infoType = data_get($message, 'InfoType')) {
+ $this->dispatch($infoType, $message);
+ }
+
+ return new Response(static::SUCCESS_EMPTY_RESPONSE);
+ }
+
+ /**
+ * Register event handlers.
+ */
+ protected function registerHandlers()
+ {
+ $this->on(self::EVENT_AUTHORIZED, Authorized::class);
+ $this->on(self::EVENT_UNAUTHORIZED, Unauthorized::class);
+ $this->on(self::EVENT_UPDATE_AUTHORIZED, UpdateAuthorized::class);
+ $this->on(self::EVENT_COMPONENT_VERIFY_TICKET, VerifyTicketRefreshed::class);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php
new file mode 100644
index 0000000..fe7f9c6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Authorized.php
@@ -0,0 +1,30 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+
+/**
+ * Class Authorized.
+ *
+ * @author mingyoung
+ */
+class Authorized implements EventHandlerInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function handle($payload = null)
+ {
+ // Do nothing for the time being.
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php
new file mode 100644
index 0000000..158228b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/Unauthorized.php
@@ -0,0 +1,30 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+
+/**
+ * Class Unauthorized.
+ *
+ * @author mingyoung
+ */
+class Unauthorized implements EventHandlerInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function handle($payload = null)
+ {
+ // Do nothing for the time being.
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php
new file mode 100644
index 0000000..e73caa5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/UpdateAuthorized.php
@@ -0,0 +1,30 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+
+/**
+ * Class UpdateAuthorized.
+ *
+ * @author mingyoung
+ */
+class UpdateAuthorized implements EventHandlerInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function handle($payload = null)
+ {
+ // Do nothing for the time being.
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php
new file mode 100644
index 0000000..f268a85
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/Handlers/VerifyTicketRefreshed.php
@@ -0,0 +1,55 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Traits\ResponseCastable;
+use EasyWeChat\OpenPlatform\Application;
+
+use function EasyWeChat\Kernel\data_get;
+
+/**
+ * Class VerifyTicketRefreshed.
+ *
+ * @author mingyoung
+ */
+class VerifyTicketRefreshed implements EventHandlerInterface
+{
+ use ResponseCastable;
+
+ /**
+ * @var \EasyWeChat\OpenPlatform\Application
+ */
+ protected $app;
+
+ /**
+ * Constructor.
+ *
+ * @param \EasyWeChat\OpenPlatform\Application $app
+ */
+ public function __construct(Application $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * {@inheritdoc}.
+ */
+ public function handle($payload = null)
+ {
+ $ticket = data_get($payload, 'ComponentVerifyTicket');
+
+ if (!empty($ticket)) {
+ $this->app['verify_ticket']->setTicket($ticket);
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php
new file mode 100644
index 0000000..7747061
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenPlatform/Server/ServiceProvider.php
@@ -0,0 +1,34 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenPlatform\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class ServiceProvider implements ServiceProviderInterface
+{
+ public function register(Container $app)
+ {
+ $app['encryptor'] = function ($app) {
+ return new Encryptor(
+ $app['config']['app_id'],
+ $app['config']['token'],
+ $app['config']['aes_key']
+ );
+ };
+
+ $app['server'] = function ($app) {
+ return new Guard($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Application.php b/vendor/overtrue/wechat/src/OpenWork/Application.php
new file mode 100644
index 0000000..4fc9ac0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Application.php
@@ -0,0 +1,95 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork;
+
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\OpenWork\Work\Application as Work;
+
+/**
+ * Application.
+ *
+ * @author xiaomin
+ *
+ * @property \EasyWeChat\OpenWork\Server\Guard $server
+ * @property \EasyWeChat\OpenWork\Corp\Client $corp
+ * @property \EasyWeChat\OpenWork\Provider\Client $provider
+ * @property \EasyWeChat\OpenWork\SuiteAuth\AccessToken $suite_access_token
+ * @property \EasyWeChat\OpenWork\Auth\AccessToken $provider_access_token
+ * @property \EasyWeChat\OpenWork\SuiteAuth\SuiteTicket $suite_ticket
+ * @property \EasyWeChat\OpenWork\MiniProgram\Client $mini_program
+ * @property \EasyWeChat\OpenWork\Media\Client $media
+ * @property \EasyWeChat\OpenWork\Contact\Client $contact
+ * @property \EasyWeChat\OpenWork\License\Client $license_order
+ * @property \EasyWeChat\OpenWork\License\Account $license_account
+ * @property \EasyWeChat\OpenWork\Device\Client $device
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+class Application extends ServiceContainer
+{
+ /**
+ * @var array
+ */
+ protected $providers = [
+ Auth\ServiceProvider::class,
+ SuiteAuth\ServiceProvider::class,
+ Server\ServiceProvider::class,
+ Corp\ServiceProvider::class,
+ Provider\ServiceProvider::class,
+ MiniProgram\ServiceProvider::class,
+ Media\ServiceProvider::class,
+ Contact\ServiceProvider::class,
+ License\ServiceProvider::class,
+ Device\ServiceProvider::class,
+ ];
+
+ /**
+ * @var array
+ */
+ protected $defaultConfig = [
+ // http://docs.guzzlephp.org/en/stable/request-options.html
+ 'http' => [
+ 'base_uri' => 'https://qyapi.weixin.qq.com/',
+ ],
+ ];
+
+ /**
+ * Creates the miniProgram application.
+ *
+ * @return \EasyWeChat\Work\MiniProgram\Application
+ */
+ public function miniProgram(): \EasyWeChat\Work\MiniProgram\Application
+ {
+ return new \EasyWeChat\Work\MiniProgram\Application($this->getConfig());
+ }
+
+ /**
+ * @param string $authCorpId 企业 corp_id
+ * @param string $permanentCode 企业永久授权码
+ *
+ * @return Work
+ */
+ public function work(string $authCorpId, string $permanentCode): Work
+ {
+ return new Work($authCorpId, $permanentCode, $this);
+ }
+
+ /**
+ * @param string $method
+ * @param array $arguments
+ *
+ * @return mixed
+ */
+ public function __call($method, $arguments)
+ {
+ return $this['base']->$method(...$arguments);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php
new file mode 100644
index 0000000..3f00a25
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Auth/AccessToken.php
@@ -0,0 +1,52 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * AccessToken.
+ *
+ * @author xiaomin
+ */
+class AccessToken extends BaseAccessToken
+{
+ protected $requestMethod = 'POST';
+
+ /**
+ * @var string
+ */
+ protected $endpointToGetToken = 'cgi-bin/service/get_provider_token';
+
+ /**
+ * @var string
+ */
+ protected $tokenKey = 'provider_access_token';
+
+ /**
+ * @var string
+ */
+ protected $cachePrefix = 'easywechat.kernel.provider_access_token.';
+
+ /**
+ * Credential for get token.
+ *
+ * @return array
+ */
+ protected function getCredentials(): array
+ {
+ return [
+ 'corpid' => $this->app['config']['corp_id'], //服务商的corpid
+ 'provider_secret' => $this->app['config']['secret'],
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php
new file mode 100644
index 0000000..78aba05
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Auth/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Auth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ isset($app['provider_access_token']) || $app['provider_access_token'] = function ($app) {
+ return new AccessToken($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Contact/Client.php b/vendor/overtrue/wechat/src/OpenWork/Contact/Client.php
new file mode 100644
index 0000000..3e314e7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Contact/Client.php
@@ -0,0 +1,92 @@
+
+ */
+class Client extends BaseClient
+{
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+
+ /**
+ * 异步通讯录id转译
+ *
+ * @param string $authCorpId 授权企业corp_id
+ * @param array $mediaIdList 需要转译的文件的media_id列表,只支持后缀名为xls/xlsx,doc/docx,csv,txt的文件。
+ * 不超过20个文件,获取方式使用{@see \EasyWeChat\OpenWork\Media\Client::uploadFile() 上传需要转译的文件}
+ *
+ * @param string|null $outputFileName 转译完打包的文件名,不需带后缀。企业微信后台会打包成zip压缩文件,并自动拼接上.zip后缀。
+ * 若media_id_list中文件个数大于1,则该字段必填。若media_id_list中文件个数等于1,且未填该字段,则转译完不打包成压缩文件。支持id转译
+ *
+ * @param string|null $outputFileFormat 若不指定,则输出格式跟输入格式相同。若要转换输出格式,当前仅支持输出文件为pdf格式。
+ * 若$mediaIdList中文件存在相同前缀名的文件,则输出文件命名规则为:文件前缀名_ 文件格式后缀.pdf,例如:20200901_ xlsx.pdf
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function idTranslate(string $authCorpId, array $mediaIdList, string $outputFileName = null, string $outputFileFormat = null)
+ {
+ /** @noinspection SpellCheckingInspection */
+ return $this->httpPostJson('cgi-bin/service/contact/id_translate', [
+ 'auth_corpid' => $authCorpId,
+ 'media_id_list' => $mediaIdList,
+ 'output_file_name' => $outputFileName,
+ 'output_file_format' => $outputFileFormat
+ ]);
+ }
+
+ /**
+ * 获取异步任务结果
+ *
+ * @param string $jobId 异步任务id,最大长度为64字节
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function getResult(string $jobId)
+ {
+ /** @noinspection SpellCheckingInspection */
+ return $this->httpGet('cgi-bin/service/batch/getresult', [
+ 'jobid' => $jobId
+ ]);
+ }
+
+ /**
+ * 通讯录userid排序
+ *
+ * @param string $authCorpId 查询的企业corp_id
+ * @param array $userIdList 要排序的user_id列表,最多支持1000个
+ * @param int $sortType 排序方式 0:根据姓名拼音升序排列,返回用户userid列表 1:根据姓名拼音降序排列,返回用户userid列表
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function sort(string $authCorpId, array $userIdList, int $sortType = 0)
+ {
+ /** @noinspection SpellCheckingInspection */
+ return $this->httpPostJson('cgi-bin/service/contact/sort', [
+ 'auth_corpid' => $authCorpId,
+ 'sort_type' => $sortType,
+ 'useridlist' => $userIdList
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Contact/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Contact/ServiceProvider.php
new file mode 100644
index 0000000..f3acf56
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Contact/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Contact;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author moniang
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['contact']) && $app['contact'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php
new file mode 100644
index 0000000..e32ffea
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Corp/Client.php
@@ -0,0 +1,247 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Corp;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Client.
+ *
+ * @author xiaomin
+ */
+class Client extends BaseClient
+{
+ /**
+ * Client constructor.
+ * 三方接口有三个access_token,这里用的是suite_access_token.
+ *
+ * @param ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['suite_access_token']);
+ }
+
+ /**
+ * 企业微信安装应用授权 url.
+ *
+ * @param string $preAuthCode 预授权码
+ * @param string $redirectUri 回调地址
+ * @param string $state
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \Exception
+ */
+ public function getPreAuthorizationUrl(string $preAuthCode = '', string $redirectUri = '', string $state = '')
+ {
+ $redirectUri || $redirectUri = $this->app->config['redirect_uri_install'];
+ $preAuthCode || $preAuthCode = $this->getPreAuthCode()['pre_auth_code'];
+ $state || $state = random_bytes(64);
+
+ $params = [
+ 'suite_id' => $this->app['config']['suite_id'],
+ 'redirect_uri' => $redirectUri,
+ 'pre_auth_code' => $preAuthCode,
+ 'state' => $state,
+ ];
+
+ return 'https://open.work.weixin.qq.com/3rdapp/install?' . http_build_query($params);
+ }
+
+ /**
+ * 获取预授权码.
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPreAuthCode()
+ {
+ return $this->httpGet('cgi-bin/service/get_pre_auth_code');
+ }
+
+ /**
+ * 设置授权配置.
+ * 该接口可对某次授权进行配置.
+ *
+ * @param string $preAuthCode
+ * @param array $sessionInfo
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setSession(string $preAuthCode, array $sessionInfo)
+ {
+ $params = [
+ 'pre_auth_code' => $preAuthCode,
+ 'session_info' => $sessionInfo,
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/set_session_info', $params);
+ }
+
+ /**
+ * 获取企业永久授权码.
+ *
+ * @param string $authCode 临时授权码,会在授权成功时附加在redirect_uri中跳转回第三方服务商网站,或通过回调推送给服务商。长度为64至512个字节
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getPermanentByCode(string $authCode)
+ {
+ $params = [
+ 'auth_code' => $authCode,
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/get_permanent_code', $params);
+ }
+
+ /**
+ * 获取企业授权信息.
+ *
+ * @param string $authCorpId
+ * @param string $permanentCode
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getAuthorization(string $authCorpId, string $permanentCode)
+ {
+ $params = [
+ 'auth_corpid' => $authCorpId,
+ 'permanent_code' => $permanentCode,
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/get_auth_info', $params);
+ }
+
+ /**
+ * 获取应用的管理员列表.
+ *
+ * @param string $authCorpId
+ * @param string $agentId
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getManagers(string $authCorpId, string $agentId)
+ {
+ $params = [
+ 'auth_corpid' => $authCorpId,
+ 'agentid' => $agentId,
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/get_admin_list', $params);
+ }
+
+ /**
+ * 获取登录url.
+ *
+ * @param string $redirectUri
+ * @param string $scope
+ * @param string|null $state
+ *
+ * @return string
+ * @throws \Exception
+ */
+ public function getOAuthRedirectUrl(string $redirectUri = '', string $scope = 'snsapi_userinfo', string $state = null)
+ {
+ $redirectUri || $redirectUri = $this->app->config['redirect_uri_oauth'];
+ $state || $state = random_bytes(64);
+ $params = [
+ 'appid' => $this->app['config']['suite_id'],
+ 'redirect_uri' => $redirectUri,
+ 'response_type' => 'code',
+ 'scope' => $scope,
+ 'state' => $state,
+ ];
+
+ return 'https://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query($params) . '#wechat_redirect';
+ }
+
+ /**
+ * 第三方根据code获取企业成员信息.
+ *
+ * @param string $code
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getUserByCode(string $code)
+ {
+ $params = [
+ 'code' => $code,
+ ];
+
+ return $this->httpGet('cgi-bin/service/getuserinfo3rd', $params);
+ }
+
+ /**
+ * 第三方使用user_ticket获取成员详情.
+ *
+ * @param string $userTicket
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getUserByTicket(string $userTicket)
+ {
+ $params = [
+ 'user_ticket' => $userTicket,
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/getuserdetail3rd', $params);
+ }
+
+ /**
+ * 第三方根据unionid查询external_userid
+ *
+ * @param string $unionid 微信用户的unionid
+ * @param string $openid 微信用户的openid
+ * @param string $corpid 需要换取的企业corpid,不填则拉取所有企业
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ *
+ * @author SinyLi
+ */
+ public function unionidToExternalUserid(string $unionid, string $openid, string $corpid = '')
+ {
+ $params = [
+ 'unionid' => $unionid,
+ 'openid' => $openid,
+ 'corpid' => $corpid
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/externalcontact/unionid_to_external_userid_3rd', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php
new file mode 100644
index 0000000..0cc8ea9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Corp/ServiceProvider.php
@@ -0,0 +1,38 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Corp;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * Registers services on the given container.
+ *
+ * This method should only be used to configure services and parameters.
+ * It should not get services.
+ *
+ * @param \Pimple\Container $app
+ */
+ public function register(Container $app)
+ {
+ isset($app['corp']) || $app['corp'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Device/Client.php b/vendor/overtrue/wechat/src/OpenWork/Device/Client.php
new file mode 100644
index 0000000..e3b9427
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Device/Client.php
@@ -0,0 +1,187 @@
+
+ */
+class Client extends BaseClient
+{
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+
+ /**
+ * 添加设备实例
+ *
+ * 该API用于添加一个设备的实例
+ *
+ * @param string $modelId 设备的型号id,在服务商管理端添加设备型号之后,可以查看型号id
+ * @param string $deviceSn 硬件序列号,只能包含数字和大小写字母,长度最大为128字节,不可与之前已导入的相同
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function add(string $modelId, string $deviceSn)
+ {
+ return $this->httpPostJson('cgi-bin/service/add_device', [
+ 'model_id' => $modelId,
+ 'device_sn' => $deviceSn
+ ]);
+ }
+
+ /**
+ * 查询设备绑定信息
+ *
+ * 该API用于查询设备绑定的企业信息
+ *
+ * @param string $deviceSn 硬件序列号,只能包含数字和大小写字母,长度最大为128字节
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function get(string $deviceSn)
+ {
+ return $this->httpPostJson('cgi-bin/service/get_device_auth_info', [
+ 'device_sn' => $deviceSn
+ ]);
+ }
+
+ /**
+ * 重置设备SecretNo
+ *
+ * 该API用于重置所有类型设备的SecretNo,主要针对同一批次生成相同secretNo(seedSecretNo)的设备,
+ * 可将SecretNo的状态转换为未设置状态,使设备可以重新调用get_secret_no获取新的sercretNo,
+ * 如果对存量设备调用此接口,那么设备固件必须支持通过seed_secret_no获取secretNo。
+ *
+ * @param string $deviceSn 硬件序列号,只能包含数字和大小写字母,长度最大为128字节
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function reset(string $deviceSn)
+ {
+ return $this->httpPostJson('cgi-bin/service/reset_secret_no', [
+ 'device_sn' => $deviceSn
+ ]);
+ }
+
+ /**
+ * 获取设备列表
+ *
+ * 硬件服务商可以通过本接口获取已登记的设备信息
+ *
+ * @param int $offset 用于分页拉取数据,表示偏移量
+ * @param int $limit 用于分页拉取数据,表示请求的数据条数
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function list(int $offset, int $limit)
+ {
+ return $this->httpPostJson('cgi-bin/service/list_device', [
+ 'offset' => $offset,
+ 'limit' => $limit
+ ]);
+ }
+
+ /**
+ * 上传设备日志
+ *
+ * 该API用于异步拉取某个设备的日志文件
+ *
+ * @param string $deviceSn 硬件序列号,只能包含数字和大小写字母,长度最大为128字节
+ * @param string $hint 提示参数,企业微信后台会将此参数透传给设备,设备可根据此参数来决定要上传哪部分日志,服务商可根据实际业务需求来使用此参数,最长为128字节
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function uploadLog(string $deviceSn, string $hint)
+ {
+ return $this->httpPostJson('cgi-bin/service/fetch_device_log', [
+ 'device_sn' => $deviceSn,
+ 'hint' => $hint
+ ]);
+ }
+
+ /**
+ * 获取设备自定义参数
+ *
+ * @param string $deviceSn 硬件序列号,只能包含数字和大小写字母,长度最大为128字节
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function getFeature(string $deviceSn)
+ {
+ return $this->httpPostJson('cgi-bin/hardware/get_device_feature', [
+ 'device_sn' => $deviceSn
+ ]);
+ }
+
+ /**
+ * 删除设备实例
+ *
+ * 该API用于删除一个设备的实例
+ *
+ * @param string $deviceSn 硬件序列号,只能包含数字和大小写字母,长度最大为128字节
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function delete(string $deviceSn)
+ {
+ return $this->httpPostJson('cgi-bin/service/del_device', [
+ 'device_sn' => $deviceSn
+ ]);
+ }
+
+ /**
+ * 设置打印机支持状态
+ *
+ * 该API用于设置打印盒子是否支持打印机的信息
+ *
+ * @param string $deviceSn 硬件序列号,只能包含数字和大小写字母,长度最大为128字节
+ * @param bool $supported 是否支持打印机
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function setPrinterSupportState(string $deviceSn, bool $supported)
+ {
+ return $this->httpPostJson('cgi-bin/service/del_device', [
+ 'device_sn' => $deviceSn,
+ 'not_supported_printer' => (int)!$supported
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Device/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Device/ServiceProvider.php
new file mode 100644
index 0000000..510dca6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Device/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Device;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author moniang
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['device']) && $app['device'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/License/Account.php b/vendor/overtrue/wechat/src/OpenWork/License/Account.php
new file mode 100644
index 0000000..772259c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/License/Account.php
@@ -0,0 +1,206 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\License;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * License Account Client
+ *
+ * @author moniang
+ */
+class Account extends BaseClient
+{
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+ /**
+ * 激活帐号
+ *
+ * >下单购买帐号并支付完成之后,先调用{@see Client::getAccountList() 获取订单中的帐号列表}接口获取到帐号激活码,
+ * 然后可以调用该接口将激活码绑定到某个企业员工,以对其激活相应的平台服务能力。
+ *
+ * **一个userid允许激活一个基础帐号以及一个互通帐号。**
+ *
+ * @param string $activeCode 帐号激活码
+ * @param string $corpId 待绑定激活的成员所属企业corpId,只支持加密的corpId
+ * @param string $userId 待绑定激活的企业成员userId 。只支持加密的userId
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ * @noinspection SpellCheckingInspection
+ */
+ public function active(string $activeCode, string $corpId, string $userId)
+ {
+ return $this->httpPostJson('cgi-bin/license/active_account', [
+ 'active_code' => $activeCode,
+ 'corpid' => $corpId,
+ 'userid' => $userId
+ ]);
+ }
+
+ /**
+ * 批量激活帐号
+ *
+ * >可在一次请求里为一个企业的多个成员激活许可帐号,便于服务商批量化处理。
+ *
+ * **一个userid允许激活一个基础帐号以及一个互通帐号。单次激活的员工数量不超过1000**
+ *
+ * @param string $corpId 待绑定激活的成员所属企业corpid,只支持加密的corpid
+ * @param array $activeList 需要激活的帐号列表,每个数组包含active_code 帐号激活码和userid 待绑定激活的企业成员加密userid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ * @noinspection SpellCheckingInspection
+ */
+ public function batchActive(string $corpId, array $activeList)
+ {
+ return $this->httpPostJson('cgi-bin/license/batch_active_account', [
+ 'corpid' => $corpId,
+ 'active_list' => $activeList
+ ]);
+ }
+
+ /**
+ * 获取激活码详情
+ *
+ * >查询某个帐号激活码的状态以及激活绑定情况。
+ *
+ * @param string $corpId
+ * @param string $activeCode
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ * @noinspection SpellCheckingInspection
+ */
+ public function getActiveCodeInfo(string $corpId, string $activeCode)
+ {
+ return $this->httpPostJson('cgi-bin/license/get_active_info_by_code', [
+ 'corpid' => $corpId,
+ 'active_code' => $activeCode
+ ]);
+ }
+
+ /**
+ * 批量获取激活码详情
+ *
+ * >批量查询帐号激活码的状态以及激活绑定情况。
+ *
+ * @param string $corpId 要查询的企业的corpid,只支持加密的corpid
+ * @param string[] $activeCodeList 激活码列表,最多不超过1000个
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ * @noinspection SpellCheckingInspection
+ */
+ public function batchGetActiveCodeInfo(string $corpId, array $activeCodeList)
+ {
+ return $this->httpPostJson('cgi-bin/license/batch_get_active_info_by_code', [
+ 'corpid' => $corpId,
+ 'active_code_list' => $activeCodeList
+ ]);
+ }
+
+ /**
+ * 获取企业的帐号列表
+ *
+ * >查询指定企业下的平台能力服务帐号列表。
+ *
+ * @param string $corpId 企业corpId ,只支持加密的corpId
+ * @param string|null $cursor 返回的最大记录数,整型,最大值1000,默认值500
+ * @param int $limit 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ * @noinspection SpellCheckingInspection
+ */
+ public function list(string $corpId, ?string $cursor = null, int $limit = 500)
+ {
+ return $this->httpPostJson('cgi-bin/license/list_actived_account', [
+ 'corpid' => $corpId,
+ 'limit' => $limit,
+ 'cursor' => $cursor
+ ]);
+ }
+
+ /**
+ * 获取成员的激活详情
+ *
+ * >查询某个企业成员的激活情况。
+ *
+ * @param string $corpId 企业corpId ,只支持加密的corpId
+ * @param string $userId 待查询员工的userid,只支持加密的userid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ * @noinspection SpellCheckingInspection
+ */
+ public function getActiveAccountInfo(string $corpId, string $userId)
+ {
+ return $this->httpPostJson('cgi-bin/license/get_active_info_by_user', [
+ 'corpid' => $corpId,
+ 'userid' => $userId
+ ]);
+ }
+
+
+ /**
+ * 帐号继承
+ *
+ * >在企业员工离职或者工作范围的有变更时,允许将其许可帐号继承给其他员工。
+ *
+ * **调用限制:**
+ * - 转移成员和接收成员属于同一个企业
+ * - 转移成员的帐号已激活,且在有效期
+ * - 转移许可的成员为离职成员时,不限制下次转接的时间间隔
+ * - 转移许可的成员为在职成员时,转接后30天后才可进行下次转接
+ * - 接收成员许可不能与转移成员的许可重叠(同时拥有基础帐号或者互通帐号)
+ * - 单次转移的帐号数限制在1000以内
+ *
+ * @param string $corpId 待绑定激活的成员所属企业corpId,只支持加密的corpId
+ * @param array $transferList 待转移成员列表,每个数组包含handover_userid 转移成员的userid和takeover_userid 接收成员的userid
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ * @noinspection SpellCheckingInspection
+ */
+ public function batchTransfer(string $corpId, array $transferList)
+ {
+ return $this->httpPostJson('cgi-bin/license/batch_transfer_license', [
+ 'corpid' => $corpId,
+ 'transfer_list' => $transferList
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/License/App.php b/vendor/overtrue/wechat/src/OpenWork/License/App.php
new file mode 100644
index 0000000..12d8b28
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/License/App.php
@@ -0,0 +1,51 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\License;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * License App Client
+ *
+ * @author keller31
+ */
+class App extends BaseClient
+{
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+ /**
+ * 获取应用的接口许可状态
+ * 服务商可获取某个授权企业的应用接口许可试用期,免费试用期为企业首次安装应用后的90天。
+ *
+ * @link https://developer.work.weixin.qq.com/document/path/95844
+ *
+ * @param string $corpid 企业id
+ * @param string $suite_id 套件id
+ * @param string $appid 旧的多应用套件中的应用id,新开发者请忽略
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $corpid, string $suite_id, string $appid = '')
+ {
+ return $this->httpPostJson('cgi-bin/license/get_app_license_info', [
+ 'corpid' => $corpid,
+ 'suite_id' => $suite_id,
+ 'appid' => $appid
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/License/AutoActive.php b/vendor/overtrue/wechat/src/OpenWork/License/AutoActive.php
new file mode 100644
index 0000000..f101e0c
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/License/AutoActive.php
@@ -0,0 +1,69 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\License;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * License Client
+ *
+ * @author keller31
+ */
+class AutoActive extends BaseClient
+{
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+ /**
+ * 设置企业的许可自动激活状态
+ * 服务商可以调用该接口设置授权企业的许可自动激活状态。设置为自动激活后,对应授权企业的员工使用服务商应用时,接口许可表现为自动激活。
+ *
+ * @link https://developer.work.weixin.qq.com/document/path/95873
+ *
+ * @param string $corpid 企业ID
+ * @param integer $status 许可自动激活状态。0:关闭,1:打开
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function setStatus(string $corpid, int $status)
+ {
+ return $this->httpPostJson('cgi-bin/license/set_auto_active_status', [
+ 'corpid' => $corpid,
+ 'auto_active_status' => $status
+ ]);
+ }
+
+ /**
+ * 查询企业的许可自动激活状态
+ * 服务商可以调用该接口查询授权企业的许可自动激活状态。
+ *
+ * @link https://developer.work.weixin.qq.com/document/path/95874
+ *
+ * @param string $corpid 企业ID
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getStatus(string $corpid)
+ {
+ return $this->httpPostJson('cgi-bin/license/get_auto_active_status', [
+ 'corpid' => $corpid
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/License/Client.php b/vendor/overtrue/wechat/src/OpenWork/License/Client.php
new file mode 100644
index 0000000..2671033
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/License/Client.php
@@ -0,0 +1,198 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\License;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Order Client
+ *
+ * @author moniang
+ */
+class Client extends BaseClient
+{
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+ /**
+ * 下单购买帐号
+ *
+ * 服务商下单为企业购买新的帐号,可以同时购买基础帐号与互通帐号。下单之后,需要到服务商管理端发起支付,支付完成之后,订单才能生效。
+ *
+ * @param string $corpId 企业id,只支持加密的corpid
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection SpellCheckingInspection
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function create(string $corpId, array $data)
+ {
+ return $this->httpPostJson('cgi-bin/license/create_new_order', array_merge([
+ 'corpid' => $corpId
+ ], $data));
+ }
+
+ /**
+ * 创建续期任务
+ *
+ * 在同一个订单里,首次创建任务无须指定jobid,后续指定同一个jobid,表示往同一个订单任务追加续期的成员。
+ *
+ * @param string $corpId 企业id,只支持加密的corpid
+ * @param array $data
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection SpellCheckingInspection
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function renew(string $corpId, array $data)
+ {
+ return $this->httpPostJson('cgi-bin/license/create_renew_order_job', array_merge([
+ 'corpid' => $corpId
+ ], $data));
+ }
+
+ /**
+ * 提交续期订单
+ *
+ * 创建续期任务之后,需要调用该接口,以提交订单任务。注意,提交之后,需要到服务商管理端发起支付,支付完成之后,订单才能生效。
+ *
+ * @param string $jobId 任务id
+ * @param string $buyerUserId 下单人。服务商企业内成员userid。该userid必须登录过企业微信,并且企业微信已绑定微信
+ * @param int $accountDurationMonths 购买的月数,每个月按照31天计算。最多购买36个月。(若企业为服务商测试企业,每次续期只能续期1个月)
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection SpellCheckingInspection
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function submitJob(string $jobId, string $buyerUserId, int $accountDurationMonths)
+ {
+ return $this->httpPostJson('cgi-bin/license/submit_order_job', [
+ 'jobid' => $jobId,
+ 'buyer_userid' => $buyerUserId,
+ 'account_duration' => [
+ 'months' => $accountDurationMonths
+ ]
+ ]);
+ }
+
+ /**
+ * 获取订单列表
+ *
+ * 服务商查询自己某段时间内的平台能力服务订单列表
+ *
+ * @param string $corpId 企业id,只支持加密的corpid。若指定corpid且corpid为服务商测试企业,则返回的订单列表为测试订单列表。否则只返回正式订单列表
+ * @param int|null $startTime 开始时间,下单时间。可不填。但是不能单独指定该字段,startTime跟endTime必须同时指定。
+ * @param int|null $endTime 结束时间,下单时间。起始时间跟结束时间不能超过31天。可不填。但是不能单独指定该字段,startTime跟endTime必须同时指定。
+ * @param string|null $cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填
+ * @param int $limit 返回的最大记录数,整型,最大值1000,默认值500
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection SpellCheckingInspection
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function list(string $corpId, ?int $startTime = null, ?int $endTime = null, ?string $cursor = null, int $limit = 500)
+ {
+ return $this->httpPostJson('cgi-bin/license/list_order', [
+ 'corpid' => $corpId,
+ 'start_time' => $startTime,
+ 'end_time' => $endTime,
+ 'cursor' => $cursor,
+ 'limit' => $limit
+ ]);
+ }
+
+ /**
+ * 获取订单详情
+ *
+ * 查询某个订单的详情,包括订单的状态、基础帐号个数、互通帐号个数、帐号购买时长等。
+ * 注意,该接口不返回订单中的帐号激活码列表或者续期的帐号成员列表,请调用{@see Client::getAccountList() 获取订单中的帐号列表}接口以获取帐号列表。
+ *
+ * @param string $orderId 订单id
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection SpellCheckingInspection
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function get(string $orderId)
+ {
+ return $this->httpPostJson('cgi-bin/license/get_order', [
+ 'order_id' => $orderId
+ ]);
+ }
+
+ /**
+ * 获取订单中的帐号列表
+ *
+ * 查询指定订单下的平台能力服务帐号列表。若为购买帐号的订单或者存量企业的版本付费迁移订单,则返回帐号激活码列表;
+ * 若为续期帐号的订单,则返回续期帐号的成员列表。
+ *
+ * 注意,若是购买帐号的订单,则仅订单支付完成时,系统才会生成帐号,故支付完成之前,该接口不会返回帐号激活码。
+ *
+ * @param string $orderId 订单号
+ * @param string|null $cursor 用于分页查询的游标,字符串类型,由上一次调用返回,首次调用可不填
+ * @param int $limit 返回的最大记录数,整型,最大值1000,默认值500
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection SpellCheckingInspection
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function getAccountList(string $orderId, ?string $cursor = null, int $limit = 500)
+ {
+ return $this->httpPostJson('cgi-bin/license/list_order_account', [
+ 'order_id' => $orderId,
+ 'limit' => $limit,
+ 'cursor' => $cursor
+ ]);
+ }
+
+ /**
+ * 取消订单
+ *
+ * 取消接口许可购买和续费订单,只可取消未支付且未失效的订单。
+ *
+ * @param string $corpId 企业id,只支持加密的corpid
+ * @param string $orderId 订单号
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection SpellCheckingInspection
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function cancel(string $corpId, string $orderId)
+ {
+ return $this->httpPostJson('cgi-bin/license/cancel_order', [
+ 'corpid' => $corpId,
+ 'order_id' => $orderId,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/License/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/License/ServiceProvider.php
new file mode 100644
index 0000000..19bed00
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/License/ServiceProvider.php
@@ -0,0 +1,45 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\License;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author moniang
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['license_order']) && $app['license_order'] = function ($app) {
+ return new Client($app);
+ };
+
+ !isset($app['license_account']) && $app['license_account'] = function ($app) {
+ return new Account($app);
+ };
+
+ !isset($app['license_app']) && $app['license_app'] = function ($app) {
+ return new App($app);
+ };
+
+ !isset($app['license_auto_active']) && $app['license_auto_active'] = function ($app) {
+ return new AutoActive($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Media/Client.php b/vendor/overtrue/wechat/src/OpenWork/Media/Client.php
new file mode 100644
index 0000000..549aa5d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Media/Client.php
@@ -0,0 +1,105 @@
+
+ */
+class Client extends BaseClient
+{
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+ /**
+ * 上传图片文件
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function uploadImage(string $path)
+ {
+ return $this->upload('image', $path);
+ }
+
+ /**
+ * 上传语音文件
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function uploadVoice(string $path)
+ {
+ return $this->upload('voice', $path);
+ }
+
+ /**
+ * 上传视频文件
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function uploadVideo(string $path)
+ {
+ return $this->upload('video', $path);
+ }
+
+
+ /**
+ * 上传普通文件
+ *
+ * @param string $path
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function uploadFile(string $path)
+ {
+ return $this->upload('file', $path);
+ }
+
+
+ /**
+ * 上传文件
+ *
+ * @param string $type 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file)
+ * @param string $path
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @noinspection PhpFullyQualifiedNameUsageInspection
+ */
+ public function upload(string $type, string $path)
+ {
+ $files = [
+ 'media' => $path,
+ ];
+
+ return $this->httpUpload('cgi-bin/service/media/upload', $files, [], compact('type'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Media/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Media/ServiceProvider.php
new file mode 100644
index 0000000..9e50614
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Media/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Media;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author moniang
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['media']) && $app['media'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php
new file mode 100644
index 0000000..29dae19
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/Client.php
@@ -0,0 +1,50 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\MiniProgram;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Class Client.
+ */
+class Client extends BaseClient
+{
+ /**
+ * Client constructor.
+ *
+ * @param \EasyWeChat\Kernel\ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['suite_access_token']);
+ }
+
+ /**
+ * Get session info by code.
+ *
+ * @param string $code
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function session(string $code)
+ {
+ $params = [
+ 'js_code' => $code,
+ 'grant_type' => 'authorization_code',
+ ];
+
+ return $this->httpGet('cgi-bin/service/miniprogram/jscode2session', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php
new file mode 100644
index 0000000..8e7dba2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/MiniProgram/ServiceProvider.php
@@ -0,0 +1,31 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\MiniProgram;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ !isset($app['mini_program']) && $app['mini_program'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php
new file mode 100644
index 0000000..fe80883
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Provider/Client.php
@@ -0,0 +1,259 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Provider;
+
+use EasyWeChat\Kernel\BaseClient;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * Client.
+ *
+ * @author xiaomin
+ */
+class Client extends BaseClient
+{
+ /**
+ * Client constructor.
+ *
+ *
+ * @param ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ parent::__construct($app, $app['provider_access_token']);
+ }
+
+ /**
+ * 单点登录 - 获取登录的地址.
+ *
+ * @param string $redirectUri
+ * @param string $userType
+ * @param string $state
+ *
+ * @return string
+ */
+ public function getLoginUrl(string $redirectUri = '', string $userType = 'admin', string $state = '')
+ {
+ $redirectUri || $redirectUri = $this->app->config['redirect_uri_single'];
+ $state || $state = random_bytes(64);
+ $params = [
+ 'appid' => $this->app['config']['corp_id'],
+ 'redirect_uri' => $redirectUri,
+ 'usertype' => $userType,
+ 'state' => $state,
+ ];
+
+ return 'https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?'.http_build_query($params);
+ }
+
+ /**
+ * 单点登录 - 获取登录用户信息.
+ *
+ * @param string $authCode
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getLoginInfo(string $authCode)
+ {
+ $params = [
+ 'auth_code' => $authCode,
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/get_login_info', $params);
+ }
+
+ /**
+ * 获取注册定制化URL.
+ *
+ * @param string $registerCode
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function getRegisterUri(string $registerCode = '')
+ {
+ if (!$registerCode) {
+ /** @var array $response */
+ $response = $this->detectAndCastResponseToType($this->getRegisterCode(), 'array');
+
+ $registerCode = $response['register_code'];
+ }
+
+ $params = ['register_code' => $registerCode];
+
+ return 'https://open.work.weixin.qq.com/3rdservice/wework/register?'.http_build_query($params);
+ }
+
+ /**
+ * 获取注册码.
+ *
+ * @param string $corpName
+ * @param string $adminName
+ * @param string $adminMobile
+ * @param string $state
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getRegisterCode(
+ string $corpName = '',
+ string $adminName = '',
+ string $adminMobile = '',
+ string $state = '',
+ string $templateId = ''
+ ) {
+ $params = [];
+ $params['template_id'] = $this->app['config']['reg_template_id'];
+ !empty($corpName) && $params['corp_name'] = $corpName;
+ !empty($adminName) && $params['admin_name'] = $adminName;
+ !empty($adminMobile) && $params['admin_mobile'] = $adminMobile;
+ !empty($state) && $params['state'] = $state;
+ !empty($templateId) && $params['template_id'] = $templateId;
+
+ return $this->httpPostJson('cgi-bin/service/get_register_code', $params);
+ }
+
+ /**
+ * 查询注册状态.
+ *
+ * Desc:该API用于查询企业注册状态,企业注册成功返回注册信息.
+ *
+ * @param string $registerCode
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function getRegisterInfo(string $registerCode)
+ {
+ $params = [
+ 'register_code' => $registerCode,
+ ];
+
+ return $this->httpPostJson('cgi-bin/service/get_register_info', $params);
+ }
+
+ /**
+ * 设置授权应用可见范围.
+ *
+ * Desc:调用该接口前提是开启通讯录迁移,收到授权成功通知后可调用。
+ * 企业注册初始化安装应用后,应用默认可见范围为根部门。
+ * 如需修改应用可见范围,服务商可以调用该接口设置授权应用的可见范围。
+ * 该接口只能使用注册完成回调事件或者查询注册状态返回的access_token。
+ * 调用设置通讯录同步完成后或者access_token超过30分钟失效(即解除通讯录锁定状态)则不能继续调用该接口。
+ *
+ * @param string $accessToken
+ * @param string $agentId
+ * @param array $allowUser
+ * @param array $allowParty
+ * @param array $allowTag
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function setAgentScope(
+ string $accessToken,
+ string $agentId,
+ array $allowUser = [],
+ array $allowParty = [],
+ array $allowTag = []
+ ) {
+ $params = [
+ 'agentid' => $agentId,
+ 'allow_user' => $allowUser,
+ 'allow_party' => $allowParty,
+ 'allow_tag' => $allowTag,
+ 'access_token' => $accessToken,
+ ];
+
+ return $this->httpGet('cgi-bin/agent/set_scope', $params);
+ }
+
+ /**
+ * 设置通讯录同步完成.
+ *
+ * Desc:该API用于设置通讯录同步完成,解除通讯录锁定状态,同时使通讯录迁移access_token失效。
+ *
+ * @param string $accessToken
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function contactSyncSuccess(string $accessToken)
+ {
+ $params = ['access_token' => $accessToken];
+
+ return $this->httpGet('cgi-bin/sync/contact_sync_success', $params);
+ }
+
+ /**
+ * 通讯录单个搜索
+ *
+ * @param string $corpId
+ * @param string $queryWord
+ * @param int|string $agentId
+ * @param int $offset
+ * @param int $limit
+ * @param int $queryType
+ * @param null $fullMatchField
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function searchContact(
+ string $corpId,
+ string $queryWord,
+ $agentId,
+ int $offset = 0,
+ int $limit = 50,
+ int $queryType = 0,
+ $fullMatchField = null
+ ) {
+ $params = [];
+ $params['auth_corpid'] = $corpId;
+ $params['query_word'] = $queryWord;
+ $params['query_type'] = $queryType;
+ $params['agentid'] = $agentId;
+ $params['offset'] = $offset;
+ $params['limit'] = $limit;
+ !empty($fullMatchField) && $params['full_match_field'] = $fullMatchField;
+
+ return $this->httpPostJson('cgi-bin/service/contact/search', $params);
+ }
+
+ /**
+ * 自建应用代开发获取带参授权链接
+ *
+ * @see https://developer.work.weixin.qq.com/document/path/95436
+ *
+ * @param array $params 请求参数
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function getCustomizedAuthUrl(array $params)
+ {
+ return $this->httpPostJson('cgi-bin/service/get_customized_auth_url', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php
new file mode 100644
index 0000000..2185fec
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Provider/ServiceProvider.php
@@ -0,0 +1,36 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Provider;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ protected $app;
+
+ /**
+ * @param Container $app
+ */
+ public function register(Container $app)
+ {
+ $this->app = $app;
+ isset($app['provider']) || $app['provider'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php
new file mode 100644
index 0000000..5700a77
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Server/Guard.php
@@ -0,0 +1,68 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use EasyWeChat\Kernel\ServerGuard;
+
+/**
+ * Guard.
+ *
+ * @author xiaomin
+ */
+class Guard extends ServerGuard
+{
+ /**
+ * @var bool
+ */
+ protected $alwaysValidate = true;
+
+ /**
+ * @return $this
+ */
+ public function validate()
+ {
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function shouldReturnRawResponse(): bool
+ {
+ return !is_null($this->app['request']->get('echostr'));
+ }
+
+ protected function isSafeMode(): bool
+ {
+ return true;
+ }
+
+ /**
+ * @param array $message
+ *
+ * @return mixed
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ */
+ protected function decryptMessage(array $message)
+ {
+ $encryptor = new Encryptor($message['ToUserName'], $this->app['config']->get('token'), $this->app['config']->get('aes_key'));
+
+ return $message = $encryptor->decrypt(
+ $message['Encrypt'],
+ $this->app['request']->get('msg_signature'),
+ $this->app['request']->get('nonce'),
+ $this->app['request']->get('timestamp')
+ );
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php
new file mode 100644
index 0000000..7843cc8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Server/Handlers/EchoStrHandler.php
@@ -0,0 +1,66 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Server\Handlers;
+
+use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
+use EasyWeChat\Kernel\Decorators\FinallyResult;
+use EasyWeChat\Kernel\ServiceContainer;
+
+/**
+ * EchoStrHandler.
+ *
+ * @author xiaomin
+ */
+class EchoStrHandler implements EventHandlerInterface
+{
+ /**
+ * @var ServiceContainer
+ */
+ protected $app;
+
+ /**
+ * EchoStrHandler constructor.
+ *
+ * @param ServiceContainer $app
+ */
+ public function __construct(ServiceContainer $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * @param mixed $payload
+ *
+ * @return FinallyResult|null
+ */
+ public function handle($payload = null)
+ {
+ if ($decrypted = $this->app['request']->get('echostr')) {
+ $str = $this->app['encryptor_corp']->decrypt(
+ $decrypted,
+ $this->app['request']->get('msg_signature'),
+ $this->app['request']->get('nonce'),
+ $this->app['request']->get('timestamp')
+ );
+
+ return new FinallyResult($str);
+ }
+ //把SuiteTicket缓存起来
+ if (!empty($payload['SuiteTicket'])) {
+ $this->app['suite_ticket']->setTicket($payload['SuiteTicket']);
+
+ return new FinallyResult("success");
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php
new file mode 100644
index 0000000..d33d5c8
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Server/ServiceProvider.php
@@ -0,0 +1,56 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Server;
+
+use EasyWeChat\Kernel\Encryptor;
+use EasyWeChat\OpenWork\Server\Handlers\EchoStrHandler;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ //微信第三方在校验url是使用的是GET方式请求和corp_id进行加密
+ !isset($app['encryptor_corp']) && $app['encryptor_corp'] = function ($app) {
+ return new Encryptor(
+ $app['config']['corp_id'],
+ $app['config']['token'],
+ $app['config']['aes_key']
+ );
+ };
+
+ //微信第三方推送数据时使用的是suite_id进行加密
+ !isset($app['encryptor']) && $app['encryptor'] = function ($app) {
+ return new Encryptor(
+ $app['config']['suite_id'],
+ $app['config']['token'],
+ $app['config']['aes_key']
+ );
+ };
+
+ !isset($app['server']) && $app['server'] = function ($app) {
+ $guard = new Guard($app);
+ $guard->push(new EchoStrHandler($app));
+
+ return $guard;
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php
new file mode 100644
index 0000000..e4e248d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/AccessToken.php
@@ -0,0 +1,56 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\SuiteAuth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+
+/**
+ * AccessToken.
+ *
+ * @author xiaomin
+ */
+class AccessToken extends BaseAccessToken
+{
+ /**
+ * @var string
+ */
+ protected $requestMethod = 'POST';
+
+ /**
+ * @var string
+ */
+ protected $endpointToGetToken = 'cgi-bin/service/get_suite_token';
+
+ /**
+ * @var string
+ */
+ protected $tokenKey = 'suite_access_token';
+
+ /**
+ * @var string
+ */
+ protected $cachePrefix = 'easywechat.kernel.suite_access_token.';
+
+ /**
+ * Credential for get token.
+ *
+ * @return array
+ */
+ protected function getCredentials(): array
+ {
+ return [
+ 'suite_id' => $this->app['config']['suite_id'],
+ 'suite_secret' => $this->app['config']['suite_secret'],
+ 'suite_ticket' => $this->app['suite_ticket']->getTicket(),
+ ];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php
new file mode 100644
index 0000000..a4f5386
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/ServiceProvider.php
@@ -0,0 +1,37 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\SuiteAuth;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * ServiceProvider.
+ *
+ * @author xiaomin
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['suite_ticket'] = function ($app) {
+ return new SuiteTicket($app);
+ };
+
+ isset($app['suite_access_token']) || $app['suite_access_token'] = function ($app) {
+ return new AccessToken($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php
new file mode 100644
index 0000000..4c01710
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/SuiteAuth/SuiteTicket.php
@@ -0,0 +1,85 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\SuiteAuth;
+
+use EasyWeChat\Kernel\Exceptions\RuntimeException;
+use EasyWeChat\Kernel\Traits\InteractsWithCache;
+use EasyWeChat\OpenWork\Application;
+
+/**
+ * SuiteTicket.
+ *
+ * @author xiaomin
+ */
+class SuiteTicket
+{
+ use InteractsWithCache;
+
+ /**
+ * @var Application
+ */
+ protected $app;
+
+ /**
+ * SuiteTicket constructor.
+ *
+ * @param Application $app
+ */
+ public function __construct(Application $app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * @param string $ticket
+ *
+ * @return $this
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function setTicket(string $ticket)
+ {
+ $this->getCache()->set($this->getCacheKey(), $ticket, 1800);
+
+ if (!$this->getCache()->has($this->getCacheKey())) {
+ throw new RuntimeException('Failed to cache suite ticket.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\RuntimeException
+ * @throws \Psr\SimpleCache\InvalidArgumentException
+ */
+ public function getTicket(): string
+ {
+ if ($cached = $this->getCache()->get($this->getCacheKey())) {
+ return $cached;
+ }
+
+ throw new RuntimeException('Credential "suite_ticket" does not exist in cache.');
+ }
+
+ /**
+ * @return string
+ */
+ protected function getCacheKey(): string
+ {
+ return 'easywechat.open_work.suite_ticket.'.$this->app['config']['suite_id'];
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Application.php b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php
new file mode 100644
index 0000000..c2f611a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Work/Application.php
@@ -0,0 +1,41 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Work;
+
+use EasyWeChat\OpenWork\Application as OpenWork;
+use EasyWeChat\OpenWork\Work\Auth\AccessToken;
+use EasyWeChat\Work\Application as Work;
+
+/**
+ * Application.
+ *
+ * @author xiaomin
+ */
+class Application extends Work
+{
+ /**
+ * Application constructor.
+ *
+ * @param string $authCorpId
+ * @param string $permanentCode
+ * @param OpenWork $component
+ * @param array $prepends
+ */
+ public function __construct(string $authCorpId, string $permanentCode, OpenWork $component, array $prepends = [])
+ {
+ parent::__construct(\array_merge($component->getConfig(), ['corp_id' => $authCorpId]), $prepends + [
+ 'access_token' => function ($app) use ($authCorpId, $permanentCode, $component) {
+ return new AccessToken($app, $authCorpId, $permanentCode, $component);
+ },
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php
new file mode 100644
index 0000000..e80758a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/OpenWork/Work/Auth/AccessToken.php
@@ -0,0 +1,80 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\OpenWork\Work\Auth;
+
+use EasyWeChat\Kernel\AccessToken as BaseAccessToken;
+use EasyWeChat\OpenWork\Application;
+use Pimple\Container;
+
+/**
+ * AccessToken.
+ *
+ * @author xiaomin
+ */
+class AccessToken extends BaseAccessToken
+{
+ /**
+ * @var string
+ */
+ protected $requestMethod = 'POST';
+
+ /**
+ * @var string 授权方企业ID
+ */
+ protected $authCorpid;
+
+ /**
+ * @var string 授权方企业永久授权码,通过get_permanent_code获取
+ */
+ protected $permanentCode;
+
+ protected $component;
+
+ /**
+ * AccessToken constructor.
+ *
+ * @param Container $app
+ * @param string $authCorpId
+ * @param string $permanentCode
+ * @param Application $component
+ */
+ public function __construct(Container $app, string $authCorpId, string $permanentCode, Application $component)
+ {
+ $this->authCorpid = $authCorpId;
+ $this->permanentCode = $permanentCode;
+ $this->component = $component;
+ parent::__construct($app);
+ }
+
+ /**
+ * Credential for get token.
+ *
+ * @return array
+ */
+ protected function getCredentials(): array
+ {
+ return [
+ 'auth_corpid' => $this->authCorpid,
+ 'permanent_code' => $this->permanentCode,
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getEndpoint(): string
+ {
+ return 'cgi-bin/service/get_corp_token?'.http_build_query([
+ 'suite_access_token' => $this->component['suite_access_token']->getToken()['suite_access_token'],
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Application.php b/vendor/overtrue/wechat/src/Payment/Application.php
new file mode 100644
index 0000000..ce820ac
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Application.php
@@ -0,0 +1,210 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment;
+
+use Closure;
+use EasyWeChat\BasicService;
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\ServiceContainer;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\OfficialAccount;
+
+/**
+ * Class Application.
+ *
+ * @property \EasyWeChat\Payment\Bill\Client $bill
+ * @property \EasyWeChat\Payment\Fundflow\Client $fundflow
+ * @property \EasyWeChat\Payment\Jssdk\Client $jssdk
+ * @property \EasyWeChat\Payment\Order\Client $order
+ * @property \EasyWeChat\Payment\Refund\Client $refund
+ * @property \EasyWeChat\Payment\Coupon\Client $coupon
+ * @property \EasyWeChat\Payment\Reverse\Client $reverse
+ * @property \EasyWeChat\Payment\Redpack\Client $redpack
+ * @property \EasyWeChat\BasicService\Url\Client $url
+ * @property \EasyWeChat\Payment\Transfer\Client $transfer
+ * @property \EasyWeChat\Payment\Security\Client $security
+ * @property \EasyWeChat\Payment\ProfitSharing\Client $profit_sharing
+ * @property \EasyWeChat\Payment\Contract\Client $contract
+ * @property \EasyWeChat\OfficialAccount\Auth\AccessToken $access_token
+ *
+ * @method mixed pay(array $attributes)
+ * @method mixed authCodeToOpenid(string $authCode)
+ */
+class Application extends ServiceContainer
+{
+ /**
+ * @var array
+ */
+ protected $providers = [
+ OfficialAccount\Auth\ServiceProvider::class,
+ BasicService\Url\ServiceProvider::class,
+ Base\ServiceProvider::class,
+ Bill\ServiceProvider::class,
+ Fundflow\ServiceProvider::class,
+ Coupon\ServiceProvider::class,
+ Jssdk\ServiceProvider::class,
+ Merchant\ServiceProvider::class,
+ Order\ServiceProvider::class,
+ Redpack\ServiceProvider::class,
+ Refund\ServiceProvider::class,
+ Reverse\ServiceProvider::class,
+ Sandbox\ServiceProvider::class,
+ Transfer\ServiceProvider::class,
+ Security\ServiceProvider::class,
+ ProfitSharing\ServiceProvider::class,
+ Contract\ServiceProvider::class,
+ ];
+
+ /**
+ * @var array
+ */
+ protected $defaultConfig = [
+ 'http' => [
+ 'base_uri' => 'https://api.mch.weixin.qq.com/',
+ ],
+ ];
+
+ /**
+ * Build payment scheme for product.
+ *
+ * @param string $productId
+ *
+ * @return string
+ */
+ public function scheme(string $productId): string
+ {
+ $params = [
+ 'appid' => $this['config']->app_id,
+ 'mch_id' => $this['config']->mch_id,
+ 'time_stamp' => time(),
+ 'nonce_str' => uniqid(),
+ 'product_id' => $productId,
+ ];
+
+ $params['sign'] = Support\generate_sign($params, $this['config']->key);
+
+ return 'weixin://wxpay/bizpayurl?'.http_build_query($params);
+ }
+
+ /**
+ * @param string $codeUrl
+ *
+ * @return string
+ */
+ public function codeUrlScheme(string $codeUrl)
+ {
+ return \sprintf('weixin://wxpay/bizpayurl?sr=%s', $codeUrl);
+ }
+
+ /**
+ * @param \Closure $closure
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @codeCoverageIgnore
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function handlePaidNotify(Closure $closure)
+ {
+ return (new Notify\Paid($this))->handle($closure);
+ }
+
+ /**
+ * @param \Closure $closure
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @codeCoverageIgnore
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function handleRefundedNotify(Closure $closure)
+ {
+ return (new Notify\Refunded($this))->handle($closure);
+ }
+
+ /**
+ * @param \Closure $closure
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @codeCoverageIgnore
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function handleScannedNotify(Closure $closure)
+ {
+ return (new Notify\Scanned($this))->handle($closure);
+ }
+
+ /**
+ * Set sub-merchant.
+ *
+ * @param string $mchId
+ * @param string|null $appId
+ *
+ * @return $this
+ */
+ public function setSubMerchant(string $mchId, string $appId = null)
+ {
+ $this['config']->set('sub_mch_id', $mchId);
+ $this['config']->set('sub_appid', $appId);
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function inSandbox(): bool
+ {
+ return (bool) $this['config']->get('sandbox');
+ }
+
+ /**
+ * @param string|null $endpoint
+ *
+ * @return string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function getKey(string $endpoint = null)
+ {
+ if ('sandboxnew/pay/getsignkey' === $endpoint) {
+ return $this['config']->key;
+ }
+
+ $key = $this->inSandbox() ? $this['sandbox']->getKey() : $this['config']->key;
+
+ if (empty($key)) {
+ throw new InvalidArgumentException('config key should not empty.');
+ }
+
+ if (32 !== strlen($key)) {
+ throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key));
+ }
+
+ return $key;
+ }
+
+ /**
+ * @param string $name
+ * @param array $arguments
+ *
+ * @return mixed
+ */
+ public function __call($name, $arguments)
+ {
+ return call_user_func_array([$this['base'], $name], $arguments);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Base/Client.php b/vendor/overtrue/wechat/src/Payment/Base/Client.php
new file mode 100644
index 0000000..75d4c9a
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Base/Client.php
@@ -0,0 +1,54 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Base;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+ /**
+ * Pay the order.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function pay(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->request($this->wrap('pay/micropay'), $params);
+ }
+
+ /**
+ * Get openid by auth code.
+ *
+ * @param string $authCode
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function authCodeToOpenid(string $authCode)
+ {
+ return $this->request('tools/authcodetoopenid', [
+ 'appid' => $this->app['config']->app_id,
+ 'auth_code' => $authCode,
+ ]);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php
new file mode 100644
index 0000000..71aebd9
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Base/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Base;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['base'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Bill/Client.php b/vendor/overtrue/wechat/src/Payment/Bill/Client.php
new file mode 100644
index 0000000..20a9b63
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Bill/Client.php
@@ -0,0 +1,48 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Bill;
+
+use EasyWeChat\Kernel\Http\StreamResponse;
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+ /**
+ * Download bill history as a table file.
+ *
+ * @param string $date
+ * @param string $type
+ * @param array $optional
+ *
+ * @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $date, string $type = 'ALL', array $optional = [])
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'bill_date' => $date,
+ 'bill_type' => $type,
+ ] + $optional;
+
+ $response = $this->requestRaw($this->wrap('pay/downloadbill'), $params);
+
+ if (0 === strpos($response->getBody()->getContents(), '')) {
+ return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+ }
+
+ return StreamResponse::buildFromPsrResponse($response);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php
new file mode 100644
index 0000000..3fd98d4
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Bill/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Bill;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['bill'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Contract/Client.php b/vendor/overtrue/wechat/src/Payment/Contract/Client.php
new file mode 100644
index 0000000..22acebc
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Contract/Client.php
@@ -0,0 +1,112 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Contract;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+ /**
+ * entrust official account.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function web(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->safeRequest('papay/entrustweb', $params);
+ }
+
+ /**
+ * entrust app.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function app(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->safeRequest('papay/preentrustweb', $params);
+ }
+
+ /**
+ * entrust html 5.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function h5(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->safeRequest('papay/h5entrustweb', $params);
+ }
+
+ /**
+ * apply papay.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function apply(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->safeRequest('pay/pappayapply', $params);
+ }
+
+ /**
+ * delete papay contrace.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function delete(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->safeRequest('papay/deletecontract', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Contract/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Contract/ServiceProvider.php
new file mode 100644
index 0000000..945ea68
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Contract/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Contract;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['contract'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/Client.php b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php
new file mode 100644
index 0000000..101a42b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Coupon/Client.php
@@ -0,0 +1,77 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Coupon;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+ /**
+ * send a cash coupon.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function send(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+ $params['openid_count'] = 1;
+
+ return $this->safeRequest('mmpaymkttransfers/send_coupon', $params);
+ }
+
+ /**
+ * query a coupon stock.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function stock(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->request('mmpaymkttransfers/query_coupon_stock', $params);
+ }
+
+ /**
+ * query a info of coupon.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function info(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->request('mmpaymkttransfers/querycouponsinfo', $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php
new file mode 100644
index 0000000..513734b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Coupon/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Coupon;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['coupon'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Fundflow/Client.php b/vendor/overtrue/wechat/src/Payment/Fundflow/Client.php
new file mode 100644
index 0000000..359d4f3
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Fundflow/Client.php
@@ -0,0 +1,56 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Fundflow;
+
+use EasyWeChat\Kernel\Http\StreamResponse;
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+ /**
+ * Download fundflow history as a table file.
+ *
+ * @param string $date
+ * @param string $type
+ * @param array $options
+ *
+ * @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function get(string $date, string $type = 'Basic', $options = [])
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'bill_date' => $date,
+ 'account_type' => $type,
+ 'sign_type' => 'HMAC-SHA256',
+ 'nonce_str' => uniqid('micro'),
+ ];
+ $options = array_merge(
+ [
+ 'cert' => $this->app['config']->get('cert_path'),
+ 'ssl_key' => $this->app['config']->get('key_path'),
+ ],
+ $options
+ );
+ $response = $this->requestRaw('pay/downloadfundflow', $params, 'post', $options);
+
+ if (0 === strpos($response->getBody()->getContents(), '')) {
+ return $this->castResponseToType($response, $this->app['config']->get('response_type'));
+ }
+
+ return StreamResponse::buildFromPsrResponse($response);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Fundflow/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Fundflow/ServiceProvider.php
new file mode 100644
index 0000000..9901124
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Fundflow/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Fundflow;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['fundflow'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php
new file mode 100644
index 0000000..0780134
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Jssdk/Client.php
@@ -0,0 +1,175 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Jssdk;
+
+use EasyWeChat\BasicService\Jssdk\Client as JssdkClient;
+use EasyWeChat\Kernel\Support;
+
+/**
+ * Class Client.
+ *
+ * @author overtrue
+ */
+class Client extends JssdkClient
+{
+ /**
+ * [WeixinJSBridge] Generate js config for payment.
+ *
+ *
+ * WeixinJSBridge.invoke(
+ * 'getBrandWCPayRequest',
+ * ...
+ * );
+ *
+ *
+ * @param string $prepayId
+ * @param bool $json
+ *
+ * @return string|array
+ */
+ public function bridgeConfig(string $prepayId, bool $json = true)
+ {
+ $params = [
+ 'appId' => $this->app['config']->sub_appid ?: $this->app['config']->app_id,
+ 'timeStamp' => strval(time()),
+ 'nonceStr' => uniqid(),
+ 'package' => "prepay_id=$prepayId",
+ 'signType' => 'MD5',
+ ];
+
+ $params['paySign'] = Support\generate_sign($params, $this->app['config']->key, 'md5');
+
+ return $json ? json_encode($params) : $params;
+ }
+
+ /**
+ * [JSSDK] Generate js config for payment.
+ *
+ *
+ * wx.chooseWXPay({...});
+ *
+ *
+ * @param string $prepayId
+ *
+ * @return array
+ */
+ public function sdkConfig(string $prepayId): array
+ {
+ $config = $this->bridgeConfig($prepayId, false);
+
+ $config['timestamp'] = $config['timeStamp'];
+ unset($config['timeStamp']);
+
+ return $config;
+ }
+
+ /**
+ * Generate app payment parameters.
+ *
+ * @param string $prepayId
+ *
+ * @return array
+ */
+ public function appConfig(string $prepayId): array
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'partnerid' => $this->app['config']->mch_id,
+ 'prepayid' => $prepayId,
+ 'noncestr' => uniqid(),
+ 'timestamp' => time(),
+ 'package' => 'Sign=WXPay',
+ ];
+
+ $params['sign'] = Support\generate_sign($params, $this->app['config']->key);
+
+ return $params;
+ }
+
+ /**
+ * Generate js config for share user address.
+ *
+ * @param string $accessToken
+ * @param bool $json
+ *
+ * @return string|array
+ */
+ public function shareAddressConfig(string $accessToken, bool $json = true)
+ {
+ $params = [
+ 'appId' => $this->app['config']->app_id,
+ 'scope' => 'jsapi_address',
+ 'timeStamp' => strval(time()),
+ 'nonceStr' => uniqid(),
+ 'signType' => 'SHA1',
+ ];
+
+ $signParams = [
+ 'appid' => $params['appId'],
+ 'url' => $this->getUrl(),
+ 'timestamp' => $params['timeStamp'],
+ 'noncestr' => $params['nonceStr'],
+ 'accesstoken' => strval($accessToken),
+ ];
+
+ ksort($signParams);
+
+ $params['addrSign'] = sha1(urldecode(http_build_query($signParams)));
+
+ return $json ? json_encode($params) : $params;
+ }
+
+ /**
+ * Generate js config for contract of mini program.
+ *
+ * @param array $params
+ *
+ * @return array
+ */
+ public function contractConfig(array $params): array
+ {
+ $params['appid'] = $this->app['config']->app_id;
+ $params['timestamp'] = time();
+
+ $params['sign'] = Support\generate_sign($params, $this->app['config']->key);
+
+ return $params;
+ }
+
+ /**
+ * Generate js config for biz red packet of mini program.
+ *
+ * @param string $package
+ * @return array
+ */
+ public function miniprogramRedpackConfig(string $package): array
+ {
+ $param = [
+ 'appId' => $this->app['config']->app_id,
+ 'timeStamp' => '' . time(),
+ 'nonceStr' => uniqid(),
+ 'package' => urlencode($package),
+ ];
+ ksort($param);
+
+ $buff = '';
+ foreach ($param as $k => $v) {
+ $buff .= $k . "=" . $v . "&";
+ }
+
+ $param['paySign'] = md5($buff . 'key=' . $this->app['config']->key);
+ $param['signType'] = 'MD5';
+ unset($param['appId']);
+
+ return $param;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php
new file mode 100644
index 0000000..24f2a76
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Jssdk/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Jssdk;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['jssdk'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php
new file mode 100644
index 0000000..f564994
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Kernel/BaseClient.php
@@ -0,0 +1,190 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Kernel;
+
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Traits\HasHttpRequests;
+use EasyWeChat\Payment\Application;
+use GuzzleHttp\MessageFormatter;
+use GuzzleHttp\Middleware;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class BaseClient.
+ *
+ * @author overtrue
+ */
+class BaseClient
+{
+ use HasHttpRequests { request as performRequest; }
+
+ /**
+ * @var \EasyWeChat\Payment\Application
+ */
+ protected $app;
+
+ /**
+ * Constructor.
+ *
+ * @param \EasyWeChat\Payment\Application $app
+ */
+ public function __construct(Application $app)
+ {
+ $this->app = $app;
+
+ $this->setHttpClient($this->app['http_client']);
+ }
+
+ /**
+ * Extra request params.
+ *
+ * @return array
+ */
+ protected function prepends()
+ {
+ return [];
+ }
+
+ /**
+ * Make a API request.
+ *
+ * @param string $endpoint
+ * @param array $params
+ * @param string $method
+ * @param array $options
+ * @param bool $returnResponse
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false)
+ {
+ $base = [
+ 'mch_id' => $this->app['config']['mch_id'],
+ 'nonce_str' => uniqid(),
+ 'sub_mch_id' => $this->app['config']['sub_mch_id'],
+ 'sub_appid' => $this->app['config']['sub_appid'],
+ ];
+
+ $params = array_filter(array_filter(array_merge($base, $this->prepends(), $params)), 'strlen');
+
+ $secretKey = $this->app->getKey($endpoint);
+
+ $encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $secretKey);
+
+ $params['sign'] = Support\generate_sign($params, $secretKey, $encryptMethod);
+
+ $options = array_merge([
+ 'body' => Support\XML::build($params),
+ ], $options);
+
+ $this->pushMiddleware($this->logMiddleware(), 'log');
+
+ $response = $this->performRequest($endpoint, $method, $options);
+
+ return $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type'));
+ }
+
+ /**
+ * Log the request.
+ *
+ * @return \Closure
+ */
+ protected function logMiddleware()
+ {
+ $formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG);
+
+ return Middleware::log($this->app['logger'], $formatter);
+ }
+
+ /**
+ * Make a request and return raw response.
+ *
+ * @param string $endpoint
+ * @param array $params
+ * @param string $method
+ * @param array $options
+ *
+ * @return ResponseInterface
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function requestRaw(string $endpoint, array $params = [], $method = 'post', array $options = [])
+ {
+ /** @var ResponseInterface $response */
+ $response = $this->request($endpoint, $params, $method, $options, true);
+
+ return $response;
+ }
+
+ /**
+ * Make a request and return an array.
+ *
+ * @param string $endpoint
+ * @param array $params
+ * @param string $method
+ * @param array $options
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function requestArray(string $endpoint, array $params = [], $method = 'post', array $options = []): array
+ {
+ $response = $this->requestRaw($endpoint, $params, $method, $options);
+
+ return $this->castResponseToType($response, 'array');
+ }
+
+ /**
+ * Request with SSL.
+ *
+ * @param string $endpoint
+ * @param array $params
+ * @param string $method
+ * @param array $options
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function safeRequest($endpoint, array $params, $method = 'post', array $options = [])
+ {
+ $options = array_merge([
+ 'cert' => $this->app['config']->get('cert_path'),
+ 'ssl_key' => $this->app['config']->get('key_path'),
+ ], $options);
+
+ return $this->request($endpoint, $params, $method, $options);
+ }
+
+ /**
+ * Wrapping an API endpoint.
+ *
+ * @param string $endpoint
+ *
+ * @return string
+ */
+ protected function wrap(string $endpoint): string
+ {
+ return $this->app->inSandbox() ? "sandboxnew/{$endpoint}" : $endpoint;
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php
new file mode 100644
index 0000000..cdd25ba
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/InvalidSignException.php
@@ -0,0 +1,18 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+class InvalidSignException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php
new file mode 100644
index 0000000..01f9dd5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Kernel/Exceptions/SandboxException.php
@@ -0,0 +1,18 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Kernel\Exceptions;
+
+use EasyWeChat\Kernel\Exceptions\Exception;
+
+class SandboxException extends Exception
+{
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/Client.php b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php
new file mode 100644
index 0000000..f31a7e2
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Merchant/Client.php
@@ -0,0 +1,94 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Merchant;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author mingyoung
+ */
+class Client extends BaseClient
+{
+ /**
+ * Add sub-merchant.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function addSubMerchant(array $params)
+ {
+ return $this->manage($params, ['action' => 'add']);
+ }
+
+ /**
+ * Query sub-merchant by merchant id.
+ *
+ * @param string $id
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function querySubMerchantByMerchantId(string $id)
+ {
+ $params = [
+ 'micro_mch_id' => $id,
+ ];
+
+ return $this->manage($params, ['action' => 'query']);
+ }
+
+ /**
+ * Query sub-merchant by wechat id.
+ *
+ * @param string $id
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function querySubMerchantByWeChatId(string $id)
+ {
+ $params = [
+ 'recipient_wechatid' => $id,
+ ];
+
+ return $this->manage($params, ['action' => 'query']);
+ }
+
+ /**
+ * @param array $params
+ * @param array $query
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function manage(array $params, array $query)
+ {
+ $params = array_merge($params, [
+ 'appid' => $this->app['config']->app_id,
+ 'nonce_str' => '',
+ 'sub_mch_id' => '',
+ 'sub_appid' => '',
+ ]);
+
+ return $this->safeRequest('secapi/mch/submchmanage', $params, 'post', compact('query'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php
new file mode 100644
index 0000000..5d05c95
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Merchant/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Merchant;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['merchant'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Handler.php b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php
new file mode 100644
index 0000000..96a9956
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Handler.php
@@ -0,0 +1,205 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+use EasyWeChat\Kernel\Exceptions\Exception;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Support\XML;
+use EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException;
+use Symfony\Component\HttpFoundation\Response;
+
+abstract class Handler
+{
+ public const SUCCESS = 'SUCCESS';
+ public const FAIL = 'FAIL';
+
+ /**
+ * @var \EasyWeChat\Payment\Application
+ */
+ protected $app;
+
+ /**
+ * @var array
+ */
+ protected $message;
+
+ /**
+ * @var string|null
+ */
+ protected $fail;
+
+ /**
+ * @var array
+ */
+ protected $attributes = [];
+
+ /**
+ * Check sign.
+ * If failed, throws an exception.
+ *
+ * @var bool
+ */
+ protected $check = true;
+
+ /**
+ * Respond with sign.
+ *
+ * @var bool
+ */
+ protected $sign = false;
+
+ /**
+ * @param \EasyWeChat\Payment\Application $app
+ */
+ public function __construct($app)
+ {
+ $this->app = $app;
+ }
+
+ /**
+ * Handle incoming notify.
+ *
+ * @param \Closure $closure
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ */
+ abstract public function handle(Closure $closure);
+
+ /**
+ * @param string $message
+ */
+ public function fail(string $message)
+ {
+ $this->fail = $message;
+ }
+
+ /**
+ * @param array $attributes
+ * @param bool $sign
+ *
+ * @return $this
+ */
+ public function respondWith(array $attributes, bool $sign = false)
+ {
+ $this->attributes = $attributes;
+ $this->sign = $sign;
+
+ return $this;
+ }
+
+ /**
+ * Build xml and return the response to WeChat.
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ public function toResponse(): Response
+ {
+ $base = [
+ 'return_code' => is_null($this->fail) ? static::SUCCESS : static::FAIL,
+ 'return_msg' => $this->fail,
+ ];
+
+ $attributes = array_merge($base, $this->attributes);
+
+ if ($this->sign) {
+ $attributes['sign'] = Support\generate_sign($attributes, $this->app->getKey());
+ }
+
+ return new Response(XML::build($attributes));
+ }
+
+ /**
+ * Return the notify message from request.
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function getMessage(): array
+ {
+ if (!empty($this->message)) {
+ return $this->message;
+ }
+
+ try {
+ $message = XML::parse(strval($this->app['request']->getContent()));
+ } catch (\Throwable $e) {
+ throw new Exception('Invalid request XML: '.$e->getMessage(), 400);
+ }
+
+ if (!is_array($message) || empty($message)) {
+ throw new Exception('Invalid request XML.', 400);
+ }
+
+ if ($this->check) {
+ $this->validate($message);
+ }
+
+ return $this->message = $message;
+ }
+
+ /**
+ * Decrypt message.
+ *
+ * @param string $key
+ *
+ * @return string|null
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function decryptMessage(string $key)
+ {
+ $message = $this->getMessage();
+ if (empty($message[$key])) {
+ return null;
+ }
+
+ return Support\AES::decrypt(
+ base64_decode($message[$key], true),
+ md5($this->app['config']->key),
+ '',
+ OPENSSL_RAW_DATA,
+ 'AES-256-ECB'
+ );
+ }
+
+ /**
+ * Validate the request params.
+ *
+ * @param array $message
+ *
+ * @throws \EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ */
+ protected function validate(array $message)
+ {
+ $sign = $message['sign'];
+ unset($message['sign']);
+
+ if (Support\generate_sign($message, $this->app->getKey()) !== $sign) {
+ throw new InvalidSignException();
+ }
+ }
+
+ /**
+ * @param mixed $result
+ */
+ protected function strict($result)
+ {
+ if (true !== $result && is_null($this->fail)) {
+ $this->fail(strval($result));
+ }
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Paid.php b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php
new file mode 100644
index 0000000..c54b9cd
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Paid.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+
+class Paid extends Handler
+{
+ /**
+ * @param \Closure $closure
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function handle(Closure $closure)
+ {
+ $this->strict(
+ \call_user_func($closure, $this->getMessage(), [$this, 'fail'])
+ );
+
+ return $this->toResponse();
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php
new file mode 100644
index 0000000..8241892
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Refunded.php
@@ -0,0 +1,48 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+use EasyWeChat\Kernel\Support\XML;
+
+class Refunded extends Handler
+{
+ protected $check = false;
+
+ /**
+ * @param \Closure $closure
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function handle(Closure $closure)
+ {
+ $this->strict(
+ \call_user_func($closure, $this->getMessage(), $this->reqInfo(), [$this, 'fail'])
+ );
+
+ return $this->toResponse();
+ }
+
+ /**
+ * Decrypt the `req_info` from request message.
+ *
+ * @return array
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function reqInfo()
+ {
+ return XML::parse($this->decryptMessage('req_info'));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php
new file mode 100644
index 0000000..023a905
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Notify/Scanned.php
@@ -0,0 +1,60 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Notify;
+
+use Closure;
+
+class Scanned extends Handler
+{
+ protected $check = false;
+
+ /**
+ * @var string|null
+ */
+ protected $alert;
+
+ /**
+ * @param string $message
+ */
+ public function alert(string $message)
+ {
+ $this->alert = $message;
+ }
+
+ /**
+ * @param \Closure $closure
+ *
+ * @return \Symfony\Component\HttpFoundation\Response
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\Exception
+ */
+ public function handle(Closure $closure)
+ {
+ $result = \call_user_func($closure, $this->getMessage(), [$this, 'fail'], [$this, 'alert']);
+
+ $attributes = [
+ 'result_code' => is_null($this->alert) && is_null($this->fail) ? static::SUCCESS : static::FAIL,
+ 'err_code_des' => $this->alert,
+ ];
+
+ if (is_null($this->alert) && is_string($result)) {
+ $attributes += [
+ 'appid' => $this->app['config']->app_id,
+ 'mch_id' => $this->app['config']->mch_id,
+ 'nonce_str' => uniqid(),
+ 'prepay_id' => $result,
+ ];
+ }
+
+ return $this->respondWith($attributes, true)->toResponse();
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Order/Client.php b/vendor/overtrue/wechat/src/Payment/Order/Client.php
new file mode 100644
index 0000000..ff2ca2b
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Order/Client.php
@@ -0,0 +1,126 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Order;
+
+use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
+use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Kernel\Support\Collection;
+use EasyWeChat\Payment\Kernel\BaseClient;
+use Psr\Http\Message\ResponseInterface;
+
+class Client extends BaseClient
+{
+ /**
+ * Unify order.
+ *
+ * @param array $params
+ * @param bool $isContract
+ *
+ * @return ResponseInterface|Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function unify(array $params, $isContract = false)
+ {
+ if (empty($params['spbill_create_ip'])) {
+ $params['spbill_create_ip'] = ('NATIVE' === $params['trade_type']) ? Support\get_server_ip() : Support\get_client_ip();
+ }
+
+ $params['appid'] = $this->app['config']->app_id;
+ $params['notify_url'] = $params['notify_url'] ?? $this->app['config']['notify_url'];
+
+ if ($isContract) {
+ $params['contract_appid'] = $this->app['config']['app_id'];
+ $params['contract_mchid'] = $this->app['config']['mch_id'];
+ $params['request_serial'] = $params['request_serial'] ?? time();
+ $params['contract_notify_url'] = $params['contract_notify_url'] ?? $this->app['config']['contract_notify_url'];
+
+ return $this->request($this->wrap('pay/contractorder'), $params);
+ }
+
+ return $this->request($this->wrap('pay/unifiedorder'), $params);
+ }
+
+ /**
+ * Query order by out trade number.
+ *
+ * @param string $number
+ *
+ * @return ResponseInterface|Collection|array|object|string
+ *
+ * @throws InvalidArgumentException
+ * @throws InvalidConfigException
+ */
+ public function queryByOutTradeNumber(string $number)
+ {
+ return $this->query([
+ 'out_trade_no' => $number,
+ ]);
+ }
+
+ /**
+ * Query order by transaction id.
+ *
+ * @param string $transactionId
+ *
+ * @return ResponseInterface|Collection|array|object|string
+ *
+ * @throws InvalidArgumentException
+ * @throws InvalidConfigException
+ */
+ public function queryByTransactionId(string $transactionId)
+ {
+ return $this->query([
+ 'transaction_id' => $transactionId,
+ ]);
+ }
+
+ /**
+ * @param array $params
+ *
+ * @return ResponseInterface|Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function query(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+
+ return $this->request($this->wrap('pay/orderquery'), $params);
+ }
+
+ /**
+ * Close order by out_trade_no.
+ *
+ * @param string $tradeNo
+ *
+ * @return ResponseInterface|Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function close(string $tradeNo)
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'out_trade_no' => $tradeNo,
+ ];
+
+ return $this->request($this->wrap('pay/closeorder'), $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php
new file mode 100644
index 0000000..9e781c0
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Order/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Order;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['order'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php
new file mode 100644
index 0000000..c051a3d
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/Client.php
@@ -0,0 +1,251 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\ProfitSharing;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author ClouderSky
+ */
+class Client extends BaseClient
+{
+ /**
+ * {@inheritdoc}.
+ */
+ protected function prepends()
+ {
+ return [
+ 'sign_type' => 'HMAC-SHA256',
+ ];
+ }
+
+ /**
+ * Add profit sharing receiver.
+ * 服务商代子商户发起添加分账接收方请求.
+ * 后续可通过发起分账请求将结算后的钱分到该分账接收方.
+ *
+ * @param array $receiver 分账接收方对象,json格式
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function addReceiver(array $receiver)
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'receiver' => json_encode(
+ $receiver,
+ JSON_UNESCAPED_UNICODE
+ ),
+ ];
+
+ return $this->request(
+ 'pay/profitsharingaddreceiver',
+ $params
+ );
+ }
+
+ /**
+ * Delete profit sharing receiver.
+ * 服务商代子商户发起删除分账接收方请求.
+ * 删除后不支持将结算后的钱分到该分账接收方.
+ *
+ * @param array $receiver 分账接收方对象,json格式
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function deleteReceiver(array $receiver)
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'receiver' => json_encode(
+ $receiver,
+ JSON_UNESCAPED_UNICODE
+ ),
+ ];
+
+ return $this->request(
+ 'pay/profitsharingremovereceiver',
+ $params
+ );
+ }
+
+ /**
+ * Single profit sharing.
+ * 请求单次分账.
+ *
+ * @param string $transactionId 微信支付订单号
+ * @param string $outOrderNo 商户系统内部的分账单号
+ * @param array $receivers 分账接收方列表
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function share(
+ string $transactionId,
+ string $outOrderNo,
+ array $receivers
+ ) {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'transaction_id' => $transactionId,
+ 'out_order_no' => $outOrderNo,
+ 'receivers' => json_encode(
+ $receivers,
+ JSON_UNESCAPED_UNICODE
+ ),
+ ];
+
+ return $this->safeRequest(
+ 'secapi/pay/profitsharing',
+ $params
+ );
+ }
+
+ /**
+ * Multi profit sharing.
+ * 请求多次分账.
+ *
+ * @param string $transactionId 微信支付订单号
+ * @param string $outOrderNo 商户系统内部的分账单号
+ * @param array $receivers 分账接收方列表
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function multiShare(
+ string $transactionId,
+ string $outOrderNo,
+ array $receivers
+ ) {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'transaction_id' => $transactionId,
+ 'out_order_no' => $outOrderNo,
+ 'receivers' => json_encode(
+ $receivers,
+ JSON_UNESCAPED_UNICODE
+ ),
+ ];
+
+ return $this->safeRequest(
+ 'secapi/pay/multiprofitsharing',
+ $params
+ );
+ }
+
+ /**
+ * Finish profit sharing.
+ * 完结分账.
+ *
+ * @param array $params
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function markOrderAsFinished(array $params)
+ {
+ $params['appid'] = $this->app['config']->app_id;
+ $params['sub_appid'] = null;
+
+ return $this->safeRequest(
+ 'secapi/pay/profitsharingfinish',
+ $params
+ );
+ }
+
+ /**
+ * Query profit sharing result.
+ * 查询分账结果.
+ *
+ * @param string $transactionId 微信支付订单号
+ * @param string $outOrderNo 商户系统内部的分账单号
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function query(
+ string $transactionId,
+ string $outOrderNo
+ ) {
+ $params = [
+ 'sub_appid' => null,
+ 'transaction_id' => $transactionId,
+ 'out_order_no' => $outOrderNo,
+ ];
+
+ return $this->request(
+ 'pay/profitsharingquery',
+ $params
+ );
+ }
+
+ /**
+ * Profit sharing return.
+ * 分账回退.
+ *
+ * @param string $outOrderNo 商户系统内部的分账单号
+ * @param string $outReturnNo 商户系统内部分账回退单号
+ * @param int $returnAmount 回退金额
+ * @param string $returnAccount 回退方账号
+ * @param string $description 回退描述
+ *
+ * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function returnShare(
+ string $outOrderNo,
+ string $outReturnNo,
+ int $returnAmount,
+ string $returnAccount,
+ string $description
+ ) {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ 'out_order_no' => $outOrderNo,
+ 'out_return_no' => $outReturnNo,
+ 'return_account_type' => 'MERCHANT_ID',
+ 'return_account' => $returnAccount,
+ 'return_amount' => $returnAmount,
+ 'description' => $description,
+ ];
+
+ return $this->safeRequest(
+ 'secapi/pay/profitsharingreturn',
+ $params
+ );
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php
new file mode 100644
index 0000000..d247d53
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/ProfitSharing/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\ProfitSharing;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author ClouderSky
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['profit_sharing'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/Client.php b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php
new file mode 100644
index 0000000..5b49ef5
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Redpack/Client.php
@@ -0,0 +1,109 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Redpack;
+
+use EasyWeChat\Kernel\Support;
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+/**
+ * Class Client.
+ *
+ * @author tianyong90 <412039588@qq.com>
+ */
+class Client extends BaseClient
+{
+ /**
+ * Query redpack.
+ *
+ * @param mixed $mchBillno
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function info($mchBillno)
+ {
+ $params = is_array($mchBillno) ? $mchBillno : ['mch_billno' => $mchBillno];
+ $base = [
+ 'appid' => $this->app['config']->app_id,
+ 'bill_type' => 'MCHT',
+ ];
+
+ return $this->safeRequest('mmpaymkttransfers/gethbinfo', array_merge($base, $params));
+ }
+
+ /**
+ * Send miniprogram normal redpack.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function sendMiniprogramNormal(array $params)
+ {
+ $base = [
+ 'total_num' => 1,
+ 'client_ip' => $params['client_ip'] ?? Support\get_server_ip(),
+ 'wxappid' => $this->app['config']->app_id,
+ 'notify_way' => 'MINI_PROGRAM_JSAPI',
+ ];
+
+ return $this->safeRequest('mmpaymkttransfers/sendminiprogramhb', array_merge($base, $params));
+ }
+
+ /**
+ * Send normal redpack.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function sendNormal(array $params)
+ {
+ $base = [
+ 'total_num' => 1,
+ 'client_ip' => $params['client_ip'] ?? Support\get_server_ip(),
+ 'wxappid' => $this->app['config']->app_id,
+ ];
+
+ return $this->safeRequest('mmpaymkttransfers/sendredpack', array_merge($base, $params));
+ }
+
+ /**
+ * Send group redpack.
+ *
+ * @param array $params
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function sendGroup(array $params)
+ {
+ $base = [
+ 'amt_type' => 'ALL_RAND',
+ 'wxappid' => $this->app['config']->app_id,
+ ];
+
+ return $this->safeRequest('mmpaymkttransfers/sendgroupredpack', array_merge($base, $params));
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php
new file mode 100644
index 0000000..af36f35
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Redpack/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Redpack;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['redpack'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Refund/Client.php b/vendor/overtrue/wechat/src/Payment/Refund/Client.php
new file mode 100644
index 0000000..128c6e7
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Refund/Client.php
@@ -0,0 +1,159 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Refund;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+ /**
+ * Refund by out trade number.
+ *
+ * @param string $number
+ * @param string $refundNumber
+ * @param int $totalFee
+ * @param int $refundFee
+ * @param array $optional
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function byOutTradeNumber(string $number, string $refundNumber, int $totalFee, int $refundFee, array $optional = [])
+ {
+ return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['out_trade_no' => $number]));
+ }
+
+ /**
+ * Refund by transaction id.
+ *
+ * @param string $transactionId
+ * @param string $refundNumber
+ * @param int $totalFee
+ * @param int $refundFee
+ * @param array $optional
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function byTransactionId(string $transactionId, string $refundNumber, int $totalFee, int $refundFee, array $optional = [])
+ {
+ return $this->refund($refundNumber, $totalFee, $refundFee, array_merge($optional, ['transaction_id' => $transactionId]));
+ }
+
+ /**
+ * Query refund by transaction id.
+ *
+ * @param string $transactionId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function queryByTransactionId(string $transactionId)
+ {
+ return $this->query($transactionId, 'transaction_id');
+ }
+
+ /**
+ * Query refund by out trade number.
+ *
+ * @param string $outTradeNumber
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function queryByOutTradeNumber(string $outTradeNumber)
+ {
+ return $this->query($outTradeNumber, 'out_trade_no');
+ }
+
+ /**
+ * Query refund by out refund number.
+ *
+ * @param string $outRefundNumber
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function queryByOutRefundNumber(string $outRefundNumber)
+ {
+ return $this->query($outRefundNumber, 'out_refund_no');
+ }
+
+ /**
+ * Query refund by refund id.
+ *
+ * @param string $refundId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function queryByRefundId(string $refundId)
+ {
+ return $this->query($refundId, 'refund_id');
+ }
+
+ /**
+ * Refund.
+ *
+ * @param string $refundNumber
+ * @param int $totalFee
+ * @param int $refundFee
+ * @param array $optional
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function refund(string $refundNumber, int $totalFee, int $refundFee, $optional = [])
+ {
+ $params = array_merge([
+ 'out_refund_no' => $refundNumber,
+ 'total_fee' => $totalFee,
+ 'refund_fee' => $refundFee,
+ 'appid' => $this->app['config']->app_id,
+ ], $optional);
+
+ return $this->safeRequest($this->wrap(
+ $this->app->inSandbox() ? 'pay/refund' : 'secapi/pay/refund'
+ ), $params);
+ }
+
+ /**
+ * Query refund.
+ *
+ * @param string $number
+ * @param string $type
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function query(string $number, string $type)
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ $type => $number,
+ ];
+
+ return $this->request($this->wrap('pay/refundquery'), $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php
new file mode 100644
index 0000000..faa4e89
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Refund/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Refund;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author mingyoung
+ */
+class ServiceProvider implements ServiceProviderInterface
+{
+ /**
+ * {@inheritdoc}.
+ */
+ public function register(Container $app)
+ {
+ $app['refund'] = function ($app) {
+ return new Client($app);
+ };
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/Client.php b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php
new file mode 100644
index 0000000..990e6e6
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Reverse/Client.php
@@ -0,0 +1,67 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Reverse;
+
+use EasyWeChat\Payment\Kernel\BaseClient;
+
+class Client extends BaseClient
+{
+ /**
+ * Reverse order by out trade number.
+ *
+ * @param string $outTradeNumber
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function byOutTradeNumber(string $outTradeNumber)
+ {
+ return $this->reverse($outTradeNumber, 'out_trade_no');
+ }
+
+ /**
+ * Reverse order by transaction_id.
+ *
+ * @param string $transactionId
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ */
+ public function byTransactionId(string $transactionId)
+ {
+ return $this->reverse($transactionId, 'transaction_id');
+ }
+
+ /**
+ * Reverse order.
+ *
+ * @param string $number
+ * @param string $type
+ *
+ * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string
+ *
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
+ * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ protected function reverse(string $number, string $type)
+ {
+ $params = [
+ 'appid' => $this->app['config']->app_id,
+ $type => $number,
+ ];
+
+ return $this->safeRequest($this->wrap('secapi/pay/reverse'), $params);
+ }
+}
diff --git a/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php
new file mode 100644
index 0000000..2417874
--- /dev/null
+++ b/vendor/overtrue/wechat/src/Payment/Reverse/ServiceProvider.php
@@ -0,0 +1,33 @@
+
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace EasyWeChat\Payment\Reverse;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+/**
+ * Class ServiceProvider.
+ *
+ * @author overtrue