1
0

提交代码

This commit is contained in:
2020-08-06 14:50:07 +08:00
parent 9d0d5f4be9
commit d7a848c824
11299 changed files with 1321854 additions and 0 deletions

20
vendor/overtrue/easy-sms/.editorconfig vendored Normal file
View File

@@ -0,0 +1,20 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false
[*.{vue,js,scss}]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

633
vendor/overtrue/easy-sms/README.md vendored Normal file
View File

@@ -0,0 +1,633 @@
<h1 align="center">Easy SMS</h1>
<p align="center">:calling: 一款满足你的多种发送需求的短信发送组件</p>
<p align="center">
<a href="https://travis-ci.org/overtrue/easy-sms"><img src="https://travis-ci.org/overtrue/easy-sms.svg?branch=master" alt="Build Status"></a>
<a href="https://packagist.org/packages/overtrue/easy-sms"><img src="https://poser.pugx.org/overtrue/easy-sms/v/stable.svg" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/overtrue/easy-sms"><img src="https://poser.pugx.org/overtrue/easy-sms/v/unstable.svg" alt="Latest Unstable Version"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/easy-sms/?branch=master"><img src="https://scrutinizer-ci.com/g/overtrue/easy-sms/badges/quality-score.png?b=master" alt="Scrutinizer Code Quality"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/easy-sms/?branch=master"><img src="https://scrutinizer-ci.com/g/overtrue/easy-sms/badges/coverage.png?b=master" alt="Code Coverage"></a>
<a href="https://packagist.org/packages/overtrue/easy-sms"><img src="https://poser.pugx.org/overtrue/easy-sms/downloads" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/overtrue/easy-sms"><img src="https://poser.pugx.org/overtrue/easy-sms/license" alt="License"></a>
</p>
## 特点
1. 支持目前市面多家服务商
1. 一套写法兼容所有平台
1. 简单配置即可灵活增减服务商
1. 内置多种服务商轮询策略、支持自定义轮询策略
1. 统一的返回值格式,便于日志与监控
1. 自动轮询选择可用的服务商
1. 更多等你去发现与改进...
## 平台支持
- [阿里云](https://www.aliyun.com/)
- [云片](https://www.yunpian.com)
- [Submail](https://www.mysubmail.com)
- [螺丝帽](https://luosimao.com/)
- [容联云通讯](http://www.yuntongxun.com)
- [互亿无线](http://www.ihuyi.com)
- [聚合数据](https://www.juhe.cn)
- [SendCloud](http://www.sendcloud.net/)
- [百度云](https://cloud.baidu.com/)
- [华信短信平台](http://www.ipyy.com/)
- [253云通讯创蓝](https://www.253.com/)
- [融云](http://www.rongcloud.cn)
- [天毅无线](http://www.85hu.com/)
- [腾讯云 SMS](https://cloud.tencent.com/product/sms)
- [阿凡达数据](http://www.avatardata.cn/)
- [华为云](https://www.huaweicloud.com/product/msgsms.html)
- [网易云信](https://yunxin.163.com/sms)
- [云之讯](https://www.ucpaas.com/index.html)
## 环境需求
- PHP >= 5.6
## 安装
```shell
$ composer require "overtrue/easy-sms"
```
**For Laravel notification**
如果你喜欢使用 [Laravel Notification](https://laravel.com/docs/5.8/notifications), 可以考虑直接使用朋友封装的拓展包:
https://github.com/yangliulnn/easysms-notification-channel
## 使用
```php
use Overtrue\EasySms\EasySms;
$config = [
// HTTP 请求的超时时间(秒)
'timeout' => 5.0,
// 默认发送配置
'default' => [
// 网关调用策略,默认:顺序调用
'strategy' => \Overtrue\EasySms\Strategies\OrderStrategy::class,
// 默认可用的发送网关
'gateways' => [
'yunpian', 'aliyun',
],
],
// 可用的网关配置
'gateways' => [
'errorlog' => [
'file' => '/tmp/easy-sms.log',
],
'yunpian' => [
'api_key' => '824f0ff2f71cab52936axxxxxxxxxx',
],
'aliyun' => [
'access_key_id' => '',
'access_key_secret' => '',
'sign_name' => '',
],
//...
],
];
$easySms = new EasySms($config);
$easySms->send(13188888888, [
'content' => '您的验证码为: 6379',
'template' => 'SMS_001',
'data' => [
'code' => 6379
],
]);
```
## 短信内容
由于使用多网关发送,所以一条短信要支持多平台发送,每家的发送方式不一样,但是我们抽象定义了以下公用属性:
- `content` 文字内容,使用在像云片类似的以文字内容发送的平台
- `template` 模板 ID使用在以模板ID来发送短信的平台
- `data` 模板变量使用在以模板ID来发送短信的平台
所以,在使用过程中你可以根据所要使用的平台定义发送的内容。
```php
$easySms->send(13188888888, [
'content' => '您的验证码为: 6379',
'template' => 'SMS_001',
'data' => [
'code' => 6379
],
]);
```
你也可以使用闭包来返回对应的值:
```php
$easySms->send(13188888888, [
'content' => function($gateway){
return '您的验证码为: 6379';
},
'template' => function($gateway){
return 'SMS_001';
},
'data' => function($gateway){
return [
'code' => 6379
];
},
]);
```
你可以根据 `$gateway` 参数类型来判断返回值,例如:
```php
$easySms->send(13188888888, [
'content' => function($gateway){
if ($gateway->getName() == 'yunpian') {
return '云片专用验证码1235';
}
return '您的验证码为: 6379';
},
'template' => function($gateway){
if ($gateway->getName() == 'aliyun') {
return 'TP2818';
}
return 'SMS_001';
},
'data' => function($gateway){
return [
'code' => 6379
];
},
]);
```
## 发送网关
默认使用 `default` 中的设置来发送,如果某一条短信你想要覆盖默认的设置。在 `send` 方法中使用第三个参数即可:
```php
$easySms->send(13188888888, [
'content' => '您的验证码为: 6379',
'template' => 'SMS_001',
'data' => [
'code' => 6379
],
], ['yunpian', 'juhe']); // 这里的网关配置将会覆盖全局默认值
```
## 返回值
由于使用多网关发送,所以返回值为一个数组,结构如下:
```php
[
'yunpian' => [
'gateway' => 'yunpian',
'status' => 'success',
'result' => [...] // 平台返回值
],
'juhe' => [
'gateway' => 'juhe',
'status' => 'failure',
'exception' => \Overtrue\EasySms\Exceptions\GatewayErrorException 对象
],
//...
]
```
如果所选网关列表均发送失败时,将会抛出 `Overtrue\EasySms\Exceptions\NoGatewayAvailableException` 异常,你可以使用 `$e->results` 获取发送结果。
你也可以使用 `$e` 提供的更多便捷方法:
```php
$e->getResults(); // 返回所有 API 的结果,结构同上
$e->getExceptions(); // 返回所有调用异常列表
$e->getException($gateway); // 返回指定网关名称的异常对象
$e->getLastException(); // 获取最后一个失败的异常对象
```
## 自定义网关
本拓展已经支持用户自定义网关,你可以很方便的配置即可当成与其它拓展一样的使用:
```php
$config = [
...
'default' => [
'gateways' => [
'mygateway', // 配置你的网站到可用的网关列表
],
],
'gateways' => [
'mygateway' => [...], // 你网关所需要的参数,如果没有可以不配置
],
];
$easySms = new EasySms($config);
// 注册
$easySms->extend('mygateway', function($gatewayConfig){
// $gatewayConfig 来自配置文件里的 `gateways.mygateway`
return new MyGateway($gatewayConfig);
});
$easySms->send(13188888888, [
'content' => '您的验证码为: 6379',
'template' => 'SMS_001',
'data' => [
'code' => 6379
],
]);
```
## 国际短信
国际短信与国内短信的区别是号码前面需要加国际码,但是由于各平台对国际号码的写法不一致,所以在发送国际短信的时候有一点区别:
```php
use Overtrue\EasySms\PhoneNumber;
// 发送到国际码为 31 的国际号码
$number = new PhoneNumber(13188888888, 31);
$easySms->send($number, [
'content' => '您的验证码为: 6379',
'template' => 'SMS_001',
'data' => [
'code' => 6379
],
]);
```
## 定义短信
你可以根据发送场景的不同,定义不同的短信类,从而实现一处定义多处调用,你可以继承 `Overtrue\EasySms\Message` 来定义短信模型:
```php
<?php
use Overtrue\EasySms\Message;
use Overtrue\EasySms\Contracts\GatewayInterface;
use Overtrue\EasySms\Strategies\OrderStrategy;
class OrderPaidMessage extends Message
{
protected $order;
protected $strategy = OrderStrategy::class; // 定义本短信的网关使用策略,覆盖全局配置中的 `default.strategy`
protected $gateways = ['alidayu', 'yunpian', 'juhe']; // 定义本短信的适用平台,覆盖全局配置中的 `default.gateways`
public function __construct($order)
{
$this->order = $order;
}
// 定义直接使用内容发送平台的内容
public function getContent(GatewayInterface $gateway = null)
{
return sprintf('您的订单:%s, 已经完成付款', $this->order->no);
}
// 定义使用模板发送方式平台所需要的模板 ID
public function getTemplate(GatewayInterface $gateway = null)
{
return 'SMS_003';
}
// 模板参数
public function getData(GatewayInterface $gateway = null)
{
return [
'order_no' => $this->order->no
];
}
}
```
> 更多自定义方式请参考:[`Overtrue\EasySms\Message`](Overtrue\EasySms\Message;)
发送自定义短信:
```php
$order = ...;
$message = new OrderPaidMessage($order);
$easySms->send(13188888888, $message);
```
## 各平台配置说明
### [阿里云](https://www.aliyun.com/)
短信内容使用 `template` + `data`
```php
'aliyun' => [
'access_key_id' => '',
'access_key_secret' => '',
'sign_name' => '',
],
```
### [阿里云Rest](https://www.aliyun.com/)
短信内容使用 `template` + `data`
```php
'aliyunrest' => [
'app_key' => '',
'app_secret_key' => '',
'sign_name' => '',
],
```
### [云片](https://www.yunpian.com)
短信内容使用 `content`
```php
'yunpian' => [
'api_key' => '',
'signature' => '【默认签名】', // 内容中无签名时使用
],
```
### [Submail](https://www.mysubmail.com)
短信内容使用 `data`
```php
'submail' => [
'app_id' => '',
'app_key' => '',
'project' => '', // 默认 project可在发送时 data 中指定
],
```
### [螺丝帽](https://luosimao.com/)
短信内容使用 `content`
```php
'luosimao' => [
'api_key' => '',
],
```
### [容联云通讯](http://www.yuntongxun.com)
短信内容使用 `template` + `data`
```php
'yuntongxun' => [
'app_id' => '',
'account_sid' => '',
'account_token' => '',
'is_sub_account' => false,
],
```
### [互亿无线](http://www.ihuyi.com)
短信内容使用 `content`
```php
'huyi' => [
'api_id' => '',
'api_key' => '',
'signature' => '',
],
```
### [聚合数据](https://www.juhe.cn)
短信内容使用 `template` + `data`
```php
'juhe' => [
'app_key' => '',
],
```
### [SendCloud](http://www.sendcloud.net/)
短信内容使用 `template` + `data`
```php
'sendcloud' => [
'sms_user' => '',
'sms_key' => '',
'timestamp' => false, // 是否启用时间戳
],
```
### [百度云](https://cloud.baidu.com/)
短信内容使用 `template` + `data`
```php
'baidu' => [
'ak' => '',
'sk' => '',
'invoke_id' => '',
'domain' => '',
],
```
### [华信短信平台](http://www.ipyy.com/)
短信内容使用 `content`
```php
'huaxin' => [
'user_id' => '',
'password' => '',
'account' => '',
'ip' => '',
'ext_no' => '',
],
```
### [253云通讯创蓝](https://www.253.com/)
短信内容使用 `content`
```php
'chuanglan' => [
'account' => '',
'password' => '',
// 国际短信时必填
'intel_account' => '',
'intel_password' => '',
// \Overtrue\EasySms\Gateways\ChuanglanGateway::CHANNEL_VALIDATE_CODE => 验证码通道(默认)
// \Overtrue\EasySms\Gateways\ChuanglanGateway::CHANNEL_PROMOTION_CODE => 会员营销通道
'channel' => \Overtrue\EasySms\Gateways\ChuanglanGateway::CHANNEL_VALIDATE_CODE,
// 会员营销通道 特定参数。创蓝规定api提交营销短信的时候需要自己加短信的签名及退订信息
'sign' => '【通讯云】',
'unsubscribe' => '回TD退订',
],
```
### [融云](http://www.rongcloud.cn)
短信分为两大类,验证类和通知类短信。 发送验证类短信使用 `template` + `data`
```php
'rongcloud' => [
'app_key' => '',
'app_secret' => '',
]
```
### [天毅无线](http://www.85hu.com/)
短信内容使用 `content`
```php
'tianyiwuxian' => [
'username' => '', //用户名
'password' => '', //密码
'gwid' => '', //网关ID
]
```
### [twilio](https://www.twilio.com)
短信使用 `content`
发送对象需要 使用`+`添加区号
```php
'twilio' => [
'account_sid' => '', // sid
'from' => '', // 发送的号码 可以在控制台购买
'token' => '', // apitoken
],
```
### [腾讯云 SMS](https://cloud.tencent.com/product/sms)
短信内容使用 `content`
```php
'qcloud' => [
'sdk_app_id' => '', // SDK APP ID
'app_key' => '', // APP KEY
'sign_name' => '', // 短信签名如果使用默认签名该字段可缺省对应官方文档中的sign
],
```
### [阿凡达数据](http://www.avatardata.cn/)
短信内容使用 `template` + `data`
```php
'avatardata' => [
'app_key' => '', // APP KEY
],
```
### [华为云 SMS](https://www.huaweicloud.com/product/msgsms.html)
短信内容使用 `template` + `data`
```php
'huawei' => [
'endpoint' => '', // APP接入地址
'app_key' => '', // APP KEY
'app_secret' => '', // APP SECRET
'from' => [
'default' => '1069012345', // 默认使用签名通道号
'custom' => 'csms12345', // 其他签名通道号 可以在 data 中定义 from 来指定
'abc' => 'csms67890', // 其他签名通道号
...
],
'callback' => '' // 短信状态回调地址
],
```
使用默认签名通道 `default`
```php
$easySms->send(13188888888, [
'template' => 'SMS_001',
'data' => [
6379
],
]);
```
使用指定签名通道
```php
$easySms->send(13188888888, [
'template' => 'SMS_001',
'data' => [
6379,
'from' => 'custom' // 对应 config 中的 from 数组中 custom
],
]);
```
### [网易云信](https://yunxin.163.com/sms)
短信内容使用 `template` + `data`
```php
'yunxin' => [
'app_key' => '',
'app_secret' => '',
'code_length' => 4, // 随机验证码长度,范围 410默认为 4
'need_up' => false, // 是否需要支持短信上行
],
```
```php
$easySms->send(18888888888, [
'template' => 'SMS_001', // 不填则使用默认模板
'data' => [
'code' => 8946, // 如果设置了该参数,则 code_length 参数无效
'action' => 'sendCode', // 默认为 `sendCode`,校验短信验证码使用 `verifyCode`
],
]);
```
### [云之讯](https://www.ucpaas.com/index.html)
短信内容使用 `template` + `data`
```php
'yunzhixun' => [
'sid' => '',
'token' => '',
'app_id' => '',
],
```
```php
$easySms->send(18888888888, [
'template' => 'SMS_001',
'data' => [
'params' => '8946,3', // 模板参数,多个参数使用 `,` 分割,模板无参数时可为空
'uid' => 'hexianghui', // 用户 ID随状态报告返回可为空
'mobiles' => '18888888888,188888888889', // 批量发送短信,手机号使用 `,` 分割,不使用批量发送请不要设置该参数
],
]);
```
## PHP 扩展包开发
> 想知道如何从零开始构建 PHP 扩展包?
>
> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
## License
MIT

29
vendor/overtrue/easy-sms/composer.json vendored Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "overtrue/easy-sms",
"description": "The easiest way to send short message.",
"type": "library",
"require": {
"guzzlehttp/guzzle": "^6.2",
"php": ">=5.6",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^7.5",
"mockery/mockery": "1.2.3"
},
"autoload": {
"psr-4": {
"Overtrue\\EasySms\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Overtrue\\EasySms\\Tests\\": "tests"
}
},
"license": "MIT",
"authors": [{
"name": "overtrue",
"email": "i@overtrue.me"
}]
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Contracts;
use Overtrue\EasySms\Support\Config;
/**
* Class GatewayInterface.
*/
interface GatewayInterface
{
/**
* Get gateway name.
*
* @return string
*/
public function getName();
/**
* Send a short message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config);
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Contracts;
/**
* Interface MessageInterface.
*/
interface MessageInterface
{
const TEXT_MESSAGE = 'text';
const VOICE_MESSAGE = 'voice';
/**
* Return the message type.
*
* @return string
*/
public function getMessageType();
/**
* Return message content.
*
* @param \Overtrue\EasySms\Contracts\GatewayInterface|null $gateway
*
* @return string
*/
public function getContent(GatewayInterface $gateway = null);
/**
* Return the template id of message.
*
* @param \Overtrue\EasySms\Contracts\GatewayInterface|null $gateway
*
* @return string
*/
public function getTemplate(GatewayInterface $gateway = null);
/**
* Return the template data of message.
*
* @param \Overtrue\EasySms\Contracts\GatewayInterface|null $gateway
*
* @return array
*/
public function getData(GatewayInterface $gateway = null);
/**
* Return message supported gateways.
*
* @return array
*/
public function getGateways();
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Contracts;
/**
* Interface PhoneNumberInterface.
*
* @author overtrue <i@overtrue.me>
*/
interface PhoneNumberInterface extends \JsonSerializable
{
/**
* 86.
*
* @return int
*/
public function getIDDCode();
/**
* 18888888888.
*
* @return int
*/
public function getNumber();
/**
* +8618888888888.
*
* @return string
*/
public function getUniversalNumber();
/**
* 008618888888888.
*
* @return string
*/
public function getZeroPrefixedNumber();
/**
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Contracts;
/**
* Interface StrategyInterface.
*/
interface StrategyInterface
{
/**
* Apply the strategy and return result.
*
* @param array $gateways
*
* @return array
*/
public function apply(array $gateways);
}

360
vendor/overtrue/easy-sms/src/EasySms.php vendored Normal file
View File

@@ -0,0 +1,360 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms;
use Closure;
use Overtrue\EasySms\Contracts\GatewayInterface;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Contracts\StrategyInterface;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Gateways\Gateway;
use Overtrue\EasySms\Strategies\OrderStrategy;
use Overtrue\EasySms\Support\Config;
use RuntimeException;
/**
* Class EasySms.
*/
class EasySms
{
/**
* @var \Overtrue\EasySms\Support\Config
*/
protected $config;
/**
* @var string
*/
protected $defaultGateway;
/**
* @var array
*/
protected $customCreators = [];
/**
* @var array
*/
protected $gateways = [];
/**
* @var \Overtrue\EasySms\Messenger
*/
protected $messenger;
/**
* @var array
*/
protected $strategies = [];
/**
* Constructor.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->config = new Config($config);
if (!empty($config['default'])) {
$this->setDefaultGateway($config['default']);
}
}
/**
* Send a message.
*
* @param string|array $to
* @param \Overtrue\EasySms\Contracts\MessageInterface|array $message
* @param array $gateways
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
* @throws \Overtrue\EasySms\Exceptions\NoGatewayAvailableException
*/
public function send($to, $message, array $gateways = [])
{
$to = $this->formatPhoneNumber($to);
$message = $this->formatMessage($message);
$gateways = empty($gateways) ? $message->getGateways() : $gateways;
if (empty($gateways)) {
$gateways = $this->config->get('default.gateways', []);
}
return $this->getMessenger()->send($to, $message, $this->formatGateways($gateways));
}
/**
* Create a gateway.
*
* @param string|null $name
*
* @return \Overtrue\EasySms\Contracts\GatewayInterface
*
* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
*/
public function gateway($name = null)
{
$name = $name ?: $this->getDefaultGateway();
if (!isset($this->gateways[$name])) {
$this->gateways[$name] = $this->createGateway($name);
}
return $this->gateways[$name];
}
/**
* Get a strategy instance.
*
* @param string|null $strategy
*
* @return \Overtrue\EasySms\Contracts\StrategyInterface
*
* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
*/
public function strategy($strategy = null)
{
if (\is_null($strategy)) {
$strategy = $this->config->get('default.strategy', OrderStrategy::class);
}
if (!\class_exists($strategy)) {
$strategy = __NAMESPACE__.'\Strategies\\'.\ucfirst($strategy);
}
if (!\class_exists($strategy)) {
throw new InvalidArgumentException("Unsupported strategy \"{$strategy}\"");
}
if (empty($this->strategies[$strategy]) || !($this->strategies[$strategy] instanceof StrategyInterface)) {
$this->strategies[$strategy] = new $strategy($this);
}
return $this->strategies[$strategy];
}
/**
* Register a custom driver creator Closure.
*
* @param string $name
* @param \Closure $callback
*
* @return $this
*/
public function extend($name, Closure $callback)
{
$this->customCreators[$name] = $callback;
return $this;
}
/**
* @return \Overtrue\EasySms\Support\Config
*/
public function getConfig()
{
return $this->config;
}
/**
* Get default gateway name.
*
* @return string
*
* @throws \RuntimeException if no default gateway configured
*/
public function getDefaultGateway()
{
if (empty($this->defaultGateway)) {
throw new RuntimeException('No default gateway configured.');
}
return $this->defaultGateway;
}
/**
* Set default gateway name.
*
* @param string $name
*
* @return $this
*/
public function setDefaultGateway($name)
{
$this->defaultGateway = $name;
return $this;
}
/**
* @return \Overtrue\EasySms\Messenger
*/
public function getMessenger()
{
return $this->messenger ?: $this->messenger = new Messenger($this);
}
/**
* Create a new driver instance.
*
* @param string $name
*
* @throws \InvalidArgumentException
*
* @return GatewayInterface
*
* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
*/
protected function createGateway($name)
{
if (isset($this->customCreators[$name])) {
$gateway = $this->callCustomCreator($name);
} else {
$className = $this->formatGatewayClassName($name);
$config = $this->config->get("gateways.{$name}", []);
if (!isset($config['timeout'])) {
$config['timeout'] = $this->config->get('timeout', Gateway::DEFAULT_TIMEOUT);
}
$gateway = $this->makeGateway($className, $config);
}
if (!($gateway instanceof GatewayInterface)) {
throw new InvalidArgumentException(\sprintf('Gateway "%s" must implement interface %s.', $name, GatewayInterface::class));
}
return $gateway;
}
/**
* Make gateway instance.
*
* @param string $gateway
* @param array $config
*
* @return \Overtrue\EasySms\Contracts\GatewayInterface
*
* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
*/
protected function makeGateway($gateway, $config)
{
if (!\class_exists($gateway) || !\in_array(GatewayInterface::class, \class_implements($gateway))) {
throw new InvalidArgumentException(\sprintf('Class "%s" is a invalid easy-sms gateway.', $gateway));
}
return new $gateway($config);
}
/**
* Format gateway name.
*
* @param string $name
*
* @return string
*/
protected function formatGatewayClassName($name)
{
if (\class_exists($name) && \in_array(GatewayInterface::class, \class_implements($name))) {
return $name;
}
$name = \ucfirst(\str_replace(['-', '_', ''], '', $name));
return __NAMESPACE__."\\Gateways\\{$name}Gateway";
}
/**
* Call a custom gateway creator.
*
* @param string $gateway
*
* @return mixed
*/
protected function callCustomCreator($gateway)
{
return \call_user_func($this->customCreators[$gateway], $this->config->get("gateways.{$gateway}", []));
}
/**
* @param string|\Overtrue\EasySms\Contracts\PhoneNumberInterface $number
*
* @return \Overtrue\EasySms\PhoneNumber
*/
protected function formatPhoneNumber($number)
{
if ($number instanceof PhoneNumberInterface) {
return $number;
}
return new PhoneNumber(\trim($number));
}
/**
* @param array|string|\Overtrue\EasySms\Contracts\MessageInterface $message
*
* @return \Overtrue\EasySms\Contracts\MessageInterface
*/
protected function formatMessage($message)
{
if (!($message instanceof MessageInterface)) {
if (!\is_array($message)) {
$message = [
'content' => $message,
'template' => $message,
];
}
$message = new Message($message);
}
return $message;
}
/**
* @param array $gateways
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\InvalidArgumentException
*/
protected function formatGateways(array $gateways)
{
$formatted = [];
foreach ($gateways as $gateway => $setting) {
if (\is_int($gateway) && \is_string($setting)) {
$gateway = $setting;
$setting = [];
}
$formatted[$gateway] = $setting;
$globalSettings = $this->config->get("gateways.{$gateway}", []);
if (\is_string($gateway) && !empty($globalSettings) && \is_array($setting)) {
$formatted[$gateway] = new Config(\array_merge($globalSettings, $setting));
}
}
$result = [];
foreach ($this->strategy()->apply($formatted) as $name) {
$result[$name] = $formatted[$name];
}
return $result;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Exceptions;
/**
* Class Exception.
*
* @author overtrue <i@overtrue.me>
*/
class Exception extends \Exception
{
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Exceptions;
/**
* Class GatewayErrorException.
*/
class GatewayErrorException extends Exception
{
/**
* @var array
*/
public $raw = [];
/**
* GatewayErrorException constructor.
*
* @param string $message
* @param int $code
* @param array $raw
*/
public function __construct($message, $code, array $raw = [])
{
parent::__construct($message, intval($code));
$this->raw = $raw;
}
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Exceptions;
/**
* Class InvalidArgumentException.
*/
class InvalidArgumentException extends Exception
{
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Exceptions;
use Throwable;
/**
* Class NoGatewayAvailableException.
*
* @author overtrue <i@overtrue.me>
*/
class NoGatewayAvailableException extends Exception
{
/**
* @var array
*/
public $results = [];
/**
* @var array
*/
public $exceptions = [];
/**
* NoGatewayAvailableException constructor.
*
* @param array $results
* @param int $code
* @param \Throwable|null $previous
*/
public function __construct(array $results = [], $code = 0, Throwable $previous = null)
{
$this->results = $results;
$this->exceptions = \array_column($results, 'exception', 'gateway');
parent::__construct('All the gateways have failed. You can get error details by `$exception->getExceptions()`', $code, $previous);
}
/**
* @return array
*/
public function getResults()
{
return $this->results;
}
/**
* @param string $gateway
*
* @return mixed|null
*/
public function getException($gateway)
{
return isset($this->exceptions[$gateway]) ? $this->exceptions[$gateway] : null;
}
/**
* @return array
*/
public function getExceptions()
{
return $this->exceptions;
}
/**
* @return mixed
*/
public function getLastException()
{
return end($this->exceptions);
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class AliyunGateway.
*
* @author carson <docxcn@gmail.com>
*
* @see https://help.aliyun.com/document_detail/55451.html
*/
class AliyunGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://dysmsapi.aliyuncs.com';
const ENDPOINT_METHOD = 'SendSms';
const ENDPOINT_VERSION = '2017-05-25';
const ENDPOINT_FORMAT = 'JSON';
const ENDPOINT_REGION_ID = 'cn-hangzhou';
const ENDPOINT_SIGNATURE_METHOD = 'HMAC-SHA1';
const ENDPOINT_SIGNATURE_VERSION = '1.0';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name');
unset($data['sign_name']);
$params = [
'RegionId' => self::ENDPOINT_REGION_ID,
'AccessKeyId' => $config->get('access_key_id'),
'Format' => self::ENDPOINT_FORMAT,
'SignatureMethod' => self::ENDPOINT_SIGNATURE_METHOD,
'SignatureVersion' => self::ENDPOINT_SIGNATURE_VERSION,
'SignatureNonce' => uniqid(),
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
'Action' => self::ENDPOINT_METHOD,
'Version' => self::ENDPOINT_VERSION,
'PhoneNumbers' => !\is_null($to->getIDDCode()) ? strval($to->getZeroPrefixedNumber()) : $to->getNumber(),
'SignName' => $signName,
'TemplateCode' => $message->getTemplate($this),
'TemplateParam' => json_encode($data, JSON_FORCE_OBJECT),
];
$params['Signature'] = $this->generateSign($params);
$result = $this->get(self::ENDPOINT_URL, $params);
if ('OK' != $result['Code']) {
throw new GatewayErrorException($result['Message'], $result['Code'], $result);
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
*
* @return string
*/
protected function generateSign($params)
{
ksort($params);
$accessKeySecret = $this->config->get('access_key_secret');
$stringToSign = 'GET&%2F&'.urlencode(http_build_query($params, null, '&', PHP_QUERY_RFC3986));
return base64_encode(hash_hmac('sha1', $stringToSign, $accessKeySecret.'&', true));
}
}

View File

@@ -0,0 +1,107 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class AliyunrestGateway.
*/
class AliyunrestGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://gw.api.taobao.com/router/rest';
const ENDPOINT_VERSION = '2.0';
const ENDPOINT_FORMAT = 'json';
const ENDPOINT_METHOD = 'alibaba.aliqin.fc.sms.num.send';
const ENDPOINT_SIGNATURE_METHOD = 'md5';
const ENDPOINT_PARTNER_ID = 'EasySms';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array|void
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$urlParams = [
'app_key' => $config->get('app_key'),
'v' => self::ENDPOINT_VERSION,
'format' => self::ENDPOINT_FORMAT,
'sign_method' => self::ENDPOINT_SIGNATURE_METHOD,
'method' => self::ENDPOINT_METHOD,
'timestamp' => date('Y-m-d H:i:s'),
'partner_id' => self::ENDPOINT_PARTNER_ID,
];
$params = [
'extend' => '',
'sms_type' => 'normal',
'sms_free_sign_name' => $config->get('sign_name'),
'sms_param' => json_encode($message->getData($this)),
'rec_num' => !\is_null($to->getIDDCode()) ? strval($to->getZeroPrefixedNumber()) : $to->getNumber(),
'sms_template_code' => $message->getTemplate($this),
];
$urlParams['sign'] = $this->generateSign(array_merge($params, $urlParams));
$result = $this->post($this->getEndpointUrl($urlParams), $params);
if (isset($result['error_response']) && 0 != $result['error_response']['code']) {
throw new GatewayErrorException($result['error_response']['msg'], $result['error_response']['code'], $result);
}
return $result;
}
/**
* @param array $params
*
* @return string
*/
protected function getEndpointUrl($params)
{
return self::ENDPOINT_URL.'?'.http_build_query($params);
}
/**
* @param array $params
*
* @return string
*/
protected function generateSign($params)
{
ksort($params);
$stringToBeSigned = $this->config->get('app_secret_key');
foreach ($params as $k => $v) {
if (!is_array($v) && '@' != substr($v, 0, 1)) {
$stringToBeSigned .= "$k$v";
}
}
unset($k, $v);
$stringToBeSigned .= $this->config->get('app_secret_key');
return strtoupper(md5($stringToBeSigned));
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class AvatardataGateway.
*
* @see http://www.avatardata.cn/Docs/Api/fd475e40-7809-4be7-936c-5926dd41b0fe
*/
class AvatardataGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://v1.avatardata.cn/Sms/Send';
const ENDPOINT_FORMAT = 'json';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'mobile' => $to->getNumber(),
'templateId' => $message->getTemplate($this),
'param' => implode(',', $message->getData($this)),
'dtype' => self::ENDPOINT_FORMAT,
'key' => $config->get('app_key'),
];
$result = $this->get(self::ENDPOINT_URL, $params);
if ($result['error_code']) {
throw new GatewayErrorException($result['reason'], $result['error_code'], $result);
}
return $result;
}
}

View File

@@ -0,0 +1,165 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class BaiduGateway.
*
* @see https://cloud.baidu.com/doc/SMS/API.html
*/
class BaiduGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_HOST = 'sms.bj.baidubce.com';
const ENDPOINT_URI = '/bce/v2/message';
const BCE_AUTH_VERSION = 'bce-auth-v1';
const DEFAULT_EXPIRATION_IN_SECONDS = 1800; //签名有效期默认1800秒
const SUCCESS_CODE = 1000;
/**
* Send message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'invokeId' => $config->get('invoke_id'),
'phoneNumber' => $to->getNumber(),
'templateCode' => $message->getTemplate($this),
'contentVar' => $message->getData($this),
];
$datetime = gmdate('Y-m-d\TH:i:s\Z');
$headers = [
'host' => self::ENDPOINT_HOST,
'content-type' => 'application/json',
'x-bce-date' => $datetime,
'x-bce-content-sha256' => hash('sha256', json_encode($params)),
];
//获得需要签名的数据
$signHeaders = $this->getHeadersToSign($headers, ['host', 'x-bce-content-sha256']);
$headers['Authorization'] = $this->generateSign($signHeaders, $datetime, $config);
$result = $this->request('post', self::buildEndpoint($config), ['headers' => $headers, 'json' => $params]);
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['message'], $result['code'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function buildEndpoint(Config $config)
{
return 'http://'.$config->get('domain', self::ENDPOINT_HOST).self::ENDPOINT_URI;
}
/**
* Generate Authorization header.
*
* @param array $signHeaders
* @param int $datetime
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function generateSign(array $signHeaders, $datetime, Config $config)
{
// 生成 authString
$authString = self::BCE_AUTH_VERSION.'/'.$config->get('ak').'/'
.$datetime.'/'.self::DEFAULT_EXPIRATION_IN_SECONDS;
// 使用 sk 和 authString 生成 signKey
$signingKey = hash_hmac('sha256', $authString, $config->get('sk'));
// 生成标准化 URI
// 根据 RFC 3986除了1.大小写英文字符 2.阿拉伯数字 3.点'.'、波浪线'~'、减号'-'以及下划线'_' 以外都要编码
$canonicalURI = str_replace('%2F', '/', rawurlencode(self::ENDPOINT_URI));
// 生成标准化 QueryString
$canonicalQueryString = ''; // 此 api 不需要此项。返回空字符串
// 整理 headersToSign以 ';' 号连接
$signedHeaders = empty($signHeaders) ? '' : strtolower(trim(implode(';', array_keys($signHeaders))));
// 生成标准化 header
$canonicalHeader = $this->getCanonicalHeaders($signHeaders);
// 组成标准请求串
$canonicalRequest = "POST\n{$canonicalURI}\n{$canonicalQueryString}\n{$canonicalHeader}";
// 使用 signKey 和标准请求串完成签名
$signature = hash_hmac('sha256', $canonicalRequest, $signingKey);
// 组成最终签名串
return "{$authString}/{$signedHeaders}/{$signature}";
}
/**
* 生成标准化 http 请求头串.
*
* @param array $headers
*
* @return string
*/
protected function getCanonicalHeaders(array $headers)
{
$headerStrings = [];
foreach ($headers as $name => $value) {
//trim后再encode之后使用':'号连接起来
$headerStrings[] = rawurlencode(strtolower(trim($name))).':'.rawurlencode(trim($value));
}
sort($headerStrings);
return implode("\n", $headerStrings);
}
/**
* 根据 指定的 keys 过滤应该参与签名的 header.
*
* @param array $headers
* @param array $keys
*
* @return array
*/
protected function getHeadersToSign(array $headers, array $keys)
{
return array_intersect_key($headers, array_flip($keys));
}
}

View File

@@ -0,0 +1,156 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class ChuanglanGateway.
*
* @see https://zz.253.com/v5.html#/api_doc
*/
class ChuanglanGateway extends Gateway
{
use HasHttpRequest;
/**
* URL模板
*/
const ENDPOINT_URL_TEMPLATE = 'https://%s.253.com/msg/send/json';
/**
* 国际短信
*/
const INT_URL = 'http://intapi.253.com/send/json';
/**
* 验证码渠道code.
*/
const CHANNEL_VALIDATE_CODE = 'smsbj1';
/**
* 会员营销渠道code.
*/
const CHANNEL_PROMOTION_CODE = 'smssh1';
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException
* @throws InvalidArgumentException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$IDDCode = !empty($to->getIDDCode()) ? $to->getIDDCode() : 86;
$params = [
'account' => $config->get('account'),
'password' => $config->get('password'),
'phone' => $to->getNumber(),
'msg' => $this->wrapChannelContent($message->getContent($this), $config, $IDDCode),
];
if (86 != $IDDCode) {
$params['mobile'] = $to->getIDDCode().$to->getNumber();
$params['account'] = $config->get('intel_account') ?: $config->get('account');
$params['password'] = $config->get('intel_password') ?: $config->get('password');
}
$result = $this->postJson($this->buildEndpoint($config, $IDDCode), $params);
if (!isset($result['code']) || '0' != $result['code']) {
throw new GatewayErrorException(json_encode($result, JSON_UNESCAPED_UNICODE), isset($result['code']) ? $result['code'] : 0, $result);
}
return $result;
}
/**
* @param Config $config
* @param int $IDDCode
*
* @return string
*
* @throws InvalidArgumentException
*/
protected function buildEndpoint(Config $config, $IDDCode = 86)
{
$channel = $this->getChannel($config, $IDDCode);
if (self::INT_URL === $channel) {
return $channel;
}
return sprintf(self::ENDPOINT_URL_TEMPLATE, $channel);
}
/**
* @param Config $config
* @param int $IDDCode
*
* @return mixed
*
* @throws InvalidArgumentException
*/
protected function getChannel(Config $config, $IDDCode)
{
if (86 != $IDDCode) {
return self::INT_URL;
}
$channel = $config->get('channel', self::CHANNEL_VALIDATE_CODE);
if (!in_array($channel, [self::CHANNEL_VALIDATE_CODE, self::CHANNEL_PROMOTION_CODE])) {
throw new InvalidArgumentException('Invalid channel for ChuanglanGateway.');
}
return $channel;
}
/**
* @param string $content
* @param Config $config
* @param int $IDDCode
*
* @return string|string
*
* @throws InvalidArgumentException
*/
protected function wrapChannelContent($content, Config $config, $IDDCode)
{
$channel = $this->getChannel($config, $IDDCode);
if (self::CHANNEL_PROMOTION_CODE == $channel) {
$sign = (string) $config->get('sign', '');
if (empty($sign)) {
throw new InvalidArgumentException('Invalid sign for ChuanglanGateway when using promotion channel');
}
$unsubscribe = (string) $config->get('unsubscribe', '');
if (empty($unsubscribe)) {
throw new InvalidArgumentException('Invalid unsubscribe for ChuanglanGateway when using promotion channel');
}
$content = $sign.$content.$unsubscribe;
}
return $content;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Support\Config;
/**
* Class ErrorlogGateway.
*/
class ErrorlogGateway extends Gateway
{
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
if (is_array($to)) {
$to = implode(',', $to);
}
$message = sprintf(
"[%s] to: %s | message: \"%s\" | template: \"%s\" | data: %s\n",
date('Y-m-d H:i:s'),
$to,
$message->getContent($this),
$message->getTemplate($this),
json_encode($message->getData($this))
);
$file = $this->config->get('file', ini_get('error_log'));
$status = error_log($message, 3, $file);
return compact('status', 'file');
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\GatewayInterface;
use Overtrue\EasySms\Support\Config;
/**
* Class Gateway.
*/
abstract class Gateway implements GatewayInterface
{
const DEFAULT_TIMEOUT = 5.0;
/**
* @var \Overtrue\EasySms\Support\Config
*/
protected $config;
/**
* @var float
*/
protected $timeout;
/**
* Gateway constructor.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->config = new Config($config);
}
/**
* Return timeout.
*
* @return int|mixed
*/
public function getTimeout()
{
return $this->timeout ?: $this->config->get('timeout', self::DEFAULT_TIMEOUT);
}
/**
* Set timeout.
*
* @param int $timeout
*
* @return $this
*/
public function setTimeout($timeout)
{
$this->timeout = floatval($timeout);
return $this;
}
/**
* @return \Overtrue\EasySms\Support\Config
*/
public function getConfig()
{
return $this->config;
}
/**
* @param \Overtrue\EasySms\Support\Config $config
*
* @return $this
*/
public function setConfig(Config $config)
{
$this->config = $config;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return \strtolower(str_replace([__NAMESPACE__.'\\', 'Gateway'], '', \get_class($this)));
}
}

View File

@@ -0,0 +1,143 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\RequestException;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
class HuaweiGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_HOST = 'https://api.rtc.huaweicloud.com:10443';
const ENDPOINT_URI = '/sms/batchSendSms/v1';
const SUCCESS_CODE = '000000';
/**
* 发送信息.
*
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*
* @throws GatewayErrorException
* @throws InvalidArgumentException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$appKey = $config->get('app_key');
$appSecret = $config->get('app_secret');
$channels = $config->get('from');
$statusCallback = $config->get('callback', '');
$endpoint = $this->getEndpoint($config);
$headers = $this->getHeaders($appKey, $appSecret);
$templateId = $message->getTemplate($this);
$messageData = $message->getData($this);
// 短信签名通道号码
$from = 'default';
if (isset($messageData['from'])) {
$from = $messageData['from'];
unset($messageData['from']);
}
$channel = isset($channels[$from]) ? $channels[$from] : '';
if (empty($channel)) {
throw new InvalidArgumentException("From Channel [{$from}] Not Exist");
}
$params = [
'from' => $channel,
'to' => $to->getUniversalNumber(),
'templateId' => $templateId,
'templateParas' => json_encode($messageData),
'statusCallback' => $statusCallback,
];
try {
$result = $this->request('post', $endpoint, [
'headers' => $headers,
'form_params' => $params,
//为防止因HTTPS证书认证失败造成API调用失败需要先忽略证书信任问题
'verify' => false,
]);
} catch (RequestException $e) {
$result = $this->unwrapResponse($e->getResponse());
}
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['description'], ltrim($result['code'], 'E'), $result);
}
return $result;
}
/**
* 构造 Endpoint.
*
* @param Config $config
*
* @return string
*/
protected function getEndpoint(Config $config)
{
$endpoint = rtrim($config->get('endpoint', self::ENDPOINT_HOST), '/');
return $endpoint.self::ENDPOINT_URI;
}
/**
* 获取请求 Headers 参数.
*
* @param string $appKey
* @param string $appSecret
*
* @return array
*/
protected function getHeaders($appKey, $appSecret)
{
return [
'Content-Type' => 'application/x-www-form-urlencoded',
'Authorization' => 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"',
'X-WSSE' => $this->buildWsseHeader($appKey, $appSecret),
];
}
/**
* 构造X-WSSE参数值
*
* @param string $appKey
* @param string $appSecret
*
* @return string
*/
protected function buildWsseHeader($appKey, $appSecret)
{
$now = date('Y-m-d\TH:i:s\Z');
$nonce = uniqid();
$passwordDigest = base64_encode(hash('sha256', ($nonce.$now.$appSecret)));
return sprintf('UsernameToken Username="%s",PasswordDigest="%s",Nonce="%s",Created="%s"',
$appKey, $passwordDigest, $nonce, $now);
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class HuaxinGateway.
*
* @see http://www.ipyy.com/help/
*/
class HuaxinGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://%s/smsJson.aspx';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint($config->get('ip'));
$result = $this->post($endpoint, [
'userid' => $config->get('user_id'),
'account' => $config->get('account'),
'password' => $config->get('password'),
'mobile' => $to->getNumber(),
'content' => $message->getContent($this),
'sendTime' => '',
'action' => 'send',
'extno' => $config->get('ext_no'),
]);
if ('Success' !== $result['returnstatus']) {
throw new GatewayErrorException($result['message'], 400, $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $ip
*
* @return string
*/
protected function buildEndpoint($ip)
{
return sprintf(self::ENDPOINT_TEMPLATE, $ip);
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class HuyiGateway.
*
* @see http://www.ihuyi.com/api/sms.html
*/
class HuyiGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://106.ihuyi.com/webservice/sms.php?method=Submit';
const ENDPOINT_FORMAT = 'json';
const SUCCESS_CODE = 2;
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'account' => $config->get('api_id'),
'mobile' => $to->getIDDCode() ? \sprintf('%s %s', $to->getIDDCode(), $to->getNumber()) : $to->getNumber(),
'content' => $message->getContent($this),
'time' => time(),
'format' => self::ENDPOINT_FORMAT,
'sign' => $config->get('signature'),
];
$params['password'] = $this->generateSign($params);
$result = $this->post(self::ENDPOINT_URL, $params);
if (self::SUCCESS_CODE != $result['code']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
*
* @return string
*/
protected function generateSign($params)
{
return md5($params['account'].$this->config->get('api_key').$params['mobile'].$params['content'].$params['time']);
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class JuheGateway.
*
* @see https://www.juhe.cn/docs/api/id/54
*/
class JuheGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'http://v.juhe.cn/sms/send';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'mobile' => $to->getNumber(),
'tpl_id' => $message->getTemplate($this),
'tpl_value' => $this->formatTemplateVars($message->getData($this)),
'dtype' => self::ENDPOINT_FORMAT,
'key' => $config->get('app_key'),
];
$result = $this->get(self::ENDPOINT_URL, $params);
if ($result['error_code']) {
throw new GatewayErrorException($result['reason'], $result['error_code'], $result);
}
return $result;
}
/**
* @param array $vars
*
* @return string
*/
protected function formatTemplateVars(array $vars)
{
$formatted = [];
foreach ($vars as $key => $value) {
$formatted[sprintf('#%s#', trim($key, '#'))] = $value;
}
return http_build_query($formatted);
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class LuosimaoGateway.
*
* @see https://luosimao.com/docs/api/
*/
class LuosimaoGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://%s.luosimao.com/%s/%s.%s';
const ENDPOINT_VERSION = 'v1';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint('sms-api', 'send');
$result = $this->post($endpoint, [
'mobile' => $to->getNumber(),
'message' => $message->getContent($this),
], [
'Authorization' => 'Basic '.base64_encode('api:key-'.$config->get('api_key')),
]);
if ($result['error']) {
throw new GatewayErrorException($result['msg'], $result['error'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $type
* @param string $function
*
* @return string
*/
protected function buildEndpoint($type, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $function, self::ENDPOINT_FORMAT);
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class QcloudGateway.
*
* @see https://cloud.tencent.com/document/product/382/13297
*/
class QcloudGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://yun.tim.qq.com/v5/';
const ENDPOINT_METHOD = 'tlssmssvr/sendsms';
const ENDPOINT_VERSION = 'v5';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$signName = !empty($data['sign_name']) ? $data['sign_name'] : $config->get('sign_name', '');
unset($data['sign_name']);
$msg = $message->getContent($this);
if (!empty($msg) && '【' != mb_substr($msg, 0, 1) && !empty($signName)) {
$msg = '【'.$signName.'】'.$msg;
}
$type = !empty($data['type']) ? $data['type'] : 0;
$params = [
'tel' => [
'nationcode' => $to->getIDDCode() ?: 86,
'mobile' => $to->getNumber(),
],
'type' => $type,
'msg' => $msg,
'time' => time(),
'extend' => '',
'ext' => '',
];
if (!is_null($message->getTemplate($this)) && is_array($data)) {
unset($params['msg']);
$params['params'] = array_values($data);
$params['tpl_id'] = $message->getTemplate($this);
$params['sign'] = $signName;
}
$random = substr(uniqid(), -10);
$params['sig'] = $this->generateSign($params, $random);
$url = self::ENDPOINT_URL.self::ENDPOINT_METHOD.'?sdkappid='.$config->get('sdk_app_id').'&random='.$random;
$result = $this->request('post', $url, [
'headers' => ['Accept' => 'application/json'],
'json' => $params,
]);
if (0 != $result['result']) {
throw new GatewayErrorException($result['errmsg'], $result['result'], $result);
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
* @param string $random
*
* @return string
*/
protected function generateSign($params, $random)
{
ksort($params);
return hash('sha256', sprintf(
'appkey=%s&random=%s&time=%s&mobile=%s',
$this->config->get('app_key'),
$random,
$params['time'],
$params['tel']['mobile']
), false);
}
}

View File

@@ -0,0 +1,134 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\ClientException;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class RongcloudGateway.
*
* @author Darren Gao <realgaodacheng@gmail.com>
*
* @see http://www.rongcloud.cn/docs/sms_service.html#send_sms_code
*/
class RongcloudGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://api.sms.ronghub.com/%s.%s';
const ENDPOINT_ACTION = 'sendCode';
const ENDPOINT_FORMAT = 'json';
const ENDPOINT_REGION = '86'; // 中国区,目前只支持此国别
const SUCCESS_CODE = 200;
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData();
$action = array_key_exists('action', $data) ? $data['action'] : self::ENDPOINT_ACTION;
$endpoint = $this->buildEndpoint($action);
$headers = [
'Nonce' => uniqid(),
'App-Key' => $config->get('app_key'),
'Timestamp' => time(),
];
$headers['Signature'] = $this->generateSign($headers, $config);
switch ($action) {
case 'sendCode':
$params = [
'mobile' => $to->getNumber(),
'region' => self::ENDPOINT_REGION,
'templateId' => $message->getTemplate($this),
];
break;
case 'verifyCode':
if (!array_key_exists('code', $data)
or !array_key_exists('sessionId', $data)) {
throw new GatewayErrorException('"code" or "sessionId" is not set', 0);
}
$params = [
'code' => $data['code'],
'sessionId' => $data['sessionId'],
];
break;
case 'sendNotify':
$params = [
'mobile' => $to->getNumber(),
'region' => self::ENDPOINT_REGION,
'templateId' => $message->getTemplate($this),
];
$params = array_merge($params, $data);
break;
default:
throw new GatewayErrorException(sprintf('action: %s not supported', $action));
}
try {
$result = $this->post($endpoint, $params, $headers);
if (self::SUCCESS_CODE !== $result['code']) {
throw new GatewayErrorException($result['errorMessage'], $result['code'], $result);
}
} catch (ClientException $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
return $result;
}
/**
* Generate Sign.
*
* @param array $params
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function generateSign($params, Config $config)
{
return sha1(sprintf('%s%s%s', $config->get('app_secret'), $params['Nonce'], $params['Timestamp']));
}
/**
* Build endpoint url.
*
* @param string $action
*
* @return string
*/
protected function buildEndpoint($action)
{
return sprintf(self::ENDPOINT_TEMPLATE, $action, self::ENDPOINT_FORMAT);
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class SendcloudGateway.
*
* @see http://sendcloud.sohu.com/doc/sms/
*/
class SendcloudGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://www.sendcloud.net/smsapi/%s';
/**
* Send a short message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$params = [
'smsUser' => $config->get('sms_user'),
'templateId' => $message->getTemplate($this),
'msgType' => $to->getIDDCode() ? 2 : 0,
'phone' => $to->getZeroPrefixedNumber(),
'vars' => $this->formatTemplateVars($message->getData($this)),
];
if ($config->get('timestamp', false)) {
$params['timestamp'] = time() * 1000;
}
$params['signature'] = $this->sign($params, $config->get('sms_key'));
$result = $this->post(sprintf(self::ENDPOINT_TEMPLATE, 'send'), $params);
if (!$result['result']) {
throw new GatewayErrorException($result['message'], $result['statusCode'], $result);
}
return $result;
}
/**
* @param array $vars
*
* @return string
*/
protected function formatTemplateVars(array $vars)
{
$formatted = [];
foreach ($vars as $key => $value) {
$formatted[sprintf('%%%s%%', trim($key, '%'))] = $value;
}
return json_encode($formatted, JSON_FORCE_OBJECT);
}
/**
* @param array $params
* @param string $key
*
* @return string
*/
protected function sign($params, $key)
{
ksort($params);
return md5(sprintf('%s&%s&%s', $key, urldecode(http_build_query($params)), $key));
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class SubmailGateway.
*
* @see https://www.mysubmail.com/chs/documents/developer/index
*/
class SubmailGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://api.mysubmail.com/%s.%s';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint($this->inChineseMainland($to) ? 'message/xsend' : 'internationalsms/xsend');
$data = $message->getData($this);
$result = $this->post($endpoint, [
'appid' => $config->get('app_id'),
'signature' => $config->get('app_key'),
'project' => !empty($data['project']) ? $data['project'] : $config->get('project'),
'to' => $to->getUniversalNumber(),
'vars' => json_encode($data, JSON_FORCE_OBJECT),
]);
if ('success' != $result['status']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $function
*
* @return string
*/
protected function buildEndpoint($function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $function, self::ENDPOINT_FORMAT);
}
/**
* Check if the phone number belongs to chinese mainland.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
*
* @return bool
*/
protected function inChineseMainland($to)
{
$code = $to->getIDDCode();
return empty($code) || 86 === $code;
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class TianyiwuxianGateway.
*
* @author Darren Gao <realgaodacheng@gmail.com>
*/
class TianyiwuxianGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'http://jk.106api.cn/sms%s.aspx';
const ENDPOINT_ENCODE = 'UTF8';
const ENDPOINT_TYPE = 'send';
const ENDPOINT_FORMAT = 'json';
const SUCCESS_STATUS = 'success';
const SUCCESS_CODE = '0';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint();
$params = [
'gwid' => $config->get('gwid'),
'type' => self::ENDPOINT_TYPE,
'rece' => self::ENDPOINT_FORMAT,
'mobile' => $to->getNumber(),
'message' => $message->getContent($this),
'username' => $config->get('username'),
'password' => strtoupper(md5($config->get('password'))),
];
$result = $this->post($endpoint, $params);
$result = json_decode($result, true);
if (self::SUCCESS_STATUS !== $result['returnstatus'] || self::SUCCESS_CODE !== $result['code']) {
throw new GatewayErrorException($result['remark'], $result['code']);
}
return $result;
}
/**
* Build endpoint url.
*
* @return string
*/
protected function buildEndpoint()
{
return sprintf(self::ENDPOINT_TEMPLATE, self::ENDPOINT_ENCODE);
}
}

View File

@@ -0,0 +1,91 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use GuzzleHttp\Exception\ClientException;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class TwilioGateway.
*
* @see https://www.twilio.com/docs/api/messaging/send-messages
*/
class TwilioGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_URL = 'https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json';
protected $errorStatuses = [
'failed',
'undelivered',
];
public function getName()
{
return 'twilio';
}
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$accountSid = $config->get('account_sid');
$endpoint = $this->buildEndPoint($accountSid);
$params = [
'To' => $to->getUniversalNumber(),
'From' => $config->get('from'),
'Body' => $message->getContent($this),
];
try {
$result = $this->request('post', $endpoint, [
'auth' => [
$accountSid,
$config->get('token'),
],
'form_params' => $params,
]);
if (in_array($result['status'], $this->errorStatuses) || !is_null($result['error_code'])) {
throw new GatewayErrorException($result['message'], $result['error_code'], $result);
}
} catch (ClientException $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
return $result;
}
/**
* build endpoint url.
*
* @param string $accountSid
*
* @return string
*/
protected function buildEndPoint($accountSid)
{
return sprintf(self::ENDPOINT_URL, $accountSid);
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YunpianGateway.
*
* @see https://www.yunpian.com/doc/zh_CN/intl/single_send.html
*/
class YunpianGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://%s.yunpian.com/%s/%s/%s.%s';
const ENDPOINT_VERSION = 'v2';
const ENDPOINT_FORMAT = 'json';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$endpoint = $this->buildEndpoint('sms', 'sms', 'single_send');
$signature = $config->get('signature', '');
$content = $message->getContent($this);
$result = $this->request('post', $endpoint, [
'form_params' => [
'apikey' => $config->get('api_key'),
'mobile' => $to->getUniversalNumber(),
'text' => 0 === \stripos($content, '【') ? $content : $signature.$content,
],
'exceptions' => false,
]);
if ($result['code']) {
throw new GatewayErrorException($result['msg'], $result['code'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $type
* @param string $resource
* @param string $function
*
* @return string
*/
protected function buildEndpoint($type, $resource, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $type, self::ENDPOINT_VERSION, $resource, $function, self::ENDPOINT_FORMAT);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YuntongxunGateway.
*
* @see http://www.yuntongxun.com/doc/rest/sms/3_2_2_2.html
*/
class YuntongxunGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://%s:%s/%s/%s/%s/%s/%s?sig=%s';
const SERVER_IP = 'app.cloopen.com';
const DEBUG_SERVER_IP = 'sandboxapp.cloopen.com';
const DEBUG_TEMPLATE_ID = 1;
const SERVER_PORT = '8883';
const SDK_VERSION = '2013-12-26';
const SUCCESS_CODE = '000000';
/**
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\GatewayErrorException ;
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$datetime = date('YmdHis');
$endpoint = $this->buildEndpoint('SMS', 'TemplateSMS', $datetime, $config);
$result = $this->request('post', $endpoint, [
'json' => [
'to' => $to,
'templateId' => (int) ($this->config->get('debug') ? self::DEBUG_TEMPLATE_ID : $message->getTemplate($this)),
'appId' => $config->get('app_id'),
'datas' => $message->getData($this),
],
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json;charset=utf-8',
'Authorization' => base64_encode($config->get('account_sid').':'.$datetime),
],
]);
if (self::SUCCESS_CODE != $result['statusCode']) {
throw new GatewayErrorException($result['statusCode'], $result['statusCode'], $result);
}
return $result;
}
/**
* Build endpoint url.
*
* @param string $type
* @param string $resource
* @param string $datetime
* @param \Overtrue\EasySms\Support\Config $config
*
* @return string
*/
protected function buildEndpoint($type, $resource, $datetime, Config $config)
{
$serverIp = $this->config->get('debug') ? self::DEBUG_SERVER_IP : self::SERVER_IP;
$accountType = $this->config->get('is_sub_account') ? 'SubAccounts' : 'Accounts';
$sig = strtoupper(md5($config->get('account_sid').$config->get('account_token').$datetime));
return sprintf(self::ENDPOINT_TEMPLATE, $serverIp, self::SERVER_PORT, self::SDK_VERSION, $accountType, $config->get('account_sid'), $type, $resource, $sig);
}
}

View File

@@ -0,0 +1,162 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YunxinGateway.
*
* @author her-cat <i@her-cat.com>
*
* @see https://dev.yunxin.163.com/docs/product/%E7%9F%AD%E4%BF%A1/%E7%9F%AD%E4%BF%A1%E6%8E%A5%E5%8F%A3%E6%8C%87%E5%8D%97
*/
class YunxinGateway extends Gateway
{
use HasHttpRequest;
const ENDPOINT_TEMPLATE = 'https://api.netease.im/%s/%s.action';
const ENDPOINT_ACTION = 'sendCode';
const SUCCESS_CODE = 200;
/**
* Send a short message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$action = isset($data['action']) ? $data['action'] : self::ENDPOINT_ACTION;
$endpoint = $this->buildEndpoint('sms', $action);
switch ($action) {
case 'sendCode':
$params = $this->buildSendCodeParams($to, $message, $config);
break;
case 'verifyCode':
$params = $this->buildVerifyCodeParams($to, $message);
break;
default:
throw new GatewayErrorException(sprintf('action: %s not supported', $action), 0);
}
$headers = $this->buildHeaders($config);
try {
$result = $this->post($endpoint, $params, $headers);
if (!isset($result['code']) || self::SUCCESS_CODE !== $result['code']) {
$code = isset($result['code']) ? $result['code'] : 0;
$error = isset($result['msg']) ? $result['msg'] : json_encode($result, JSON_UNESCAPED_UNICODE);
throw new GatewayErrorException($error, $code);
}
} catch (\Exception $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
return $result;
}
/**
* @param $resource
* @param $function
*
* @return string
*/
protected function buildEndpoint($resource, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $resource, strtolower($function));
}
/**
* Get the request headers.
*
* @param Config $config
*
* @return array
*/
protected function buildHeaders(Config $config)
{
$headers = [
'AppKey' => $config->get('app_key'),
'Nonce' => md5(uniqid('easysms')),
'CurTime' => (string) time(),
'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
];
$headers['CheckSum'] = sha1("{$config->get('app_secret')}{$headers['Nonce']}{$headers['CurTime']}");
return $headers;
}
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*/
public function buildSendCodeParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$template = $message->getTemplate($this);
return [
'mobile' => $to->getUniversalNumber(),
'authCode' => array_key_exists('code', $data) ? $data['code'] : '',
'deviceId' => array_key_exists('device_id', $data) ? $data['device_id'] : '',
'templateid' => is_string($template) ? $template : '',
'codeLen' => $config->get('code_length', 4),
'needUp' => $config->get('need_up', false),
];
}
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
*
* @return array
*
* @throws GatewayErrorException
*/
public function buildVerifyCodeParams(PhoneNumberInterface $to, MessageInterface $message)
{
$data = $message->getData($this);
if (!array_key_exists('code', $data)) {
throw new GatewayErrorException('"code" cannot be empty', 0);
}
return [
'mobile' => $to->getUniversalNumber(),
'code' => $data['code'],
];
}
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Gateways;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Support\Config;
use Overtrue\EasySms\Traits\HasHttpRequest;
/**
* Class YunzhixunGateway.
*
* @author her-cat <i@her-cat.com>
*
* @see http://docs.ucpaas.com/doku.php?id=%E7%9F%AD%E4%BF%A1:sendsms
*/
class YunzhixunGateway extends Gateway
{
use HasHttpRequest;
const SUCCESS_CODE = '000000';
const FUNCTION_SEND_SMS = 'sendsms';
const FUNCTION_BATCH_SEND_SMS = 'sendsms_batch';
const ENDPOINT_TEMPLATE = 'https://open.ucpaas.com/ol/%s/%s';
/**
* Send a short message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param \Overtrue\EasySms\Support\Config $config
*
* @return array
*
* @throws GatewayErrorException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
$function = isset($data['mobiles']) ? self::FUNCTION_BATCH_SEND_SMS : self::FUNCTION_SEND_SMS;
$endpoint = $this->buildEndpoint('sms', $function);
$params = $this->buildParams($to, $message, $config);
return $this->execute($endpoint, $params);
}
/**
* @param $resource
* @param $function
*
* @return string
*/
protected function buildEndpoint($resource, $function)
{
return sprintf(self::ENDPOINT_TEMPLATE, $resource, $function);
}
/**
* @param PhoneNumberInterface $to
* @param MessageInterface $message
* @param Config $config
*
* @return array
*/
protected function buildParams(PhoneNumberInterface $to, MessageInterface $message, Config $config)
{
$data = $message->getData($this);
return [
'sid' => $config->get('sid'),
'token' => $config->get('token'),
'appid' => $config->get('app_id'),
'templateid' => $message->getTemplate($this),
'uid' => isset($data['uid']) ? $data['uid'] : '',
'param' => isset($data['params']) ? $data['params'] : '',
'mobile' => isset($data['mobiles']) ? $data['mobiles'] : $to->getNumber(),
];
}
/**
* @param $endpoint
* @param $params
*
* @return array
*
* @throws GatewayErrorException
*/
protected function execute($endpoint, $params)
{
try {
$result = $this->postJson($endpoint, $params);
if (!isset($result['code']) || self::SUCCESS_CODE !== $result['code']) {
$code = isset($result['code']) ? $result['code'] : 0;
$error = isset($result['msg']) ? $result['msg'] : json_encode($result, JSON_UNESCAPED_UNICODE);
throw new GatewayErrorException($error, $code);
}
return $result;
} catch (\Exception $e) {
throw new GatewayErrorException($e->getMessage(), $e->getCode());
}
}
}

187
vendor/overtrue/easy-sms/src/Message.php vendored Normal file
View File

@@ -0,0 +1,187 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms;
use Overtrue\EasySms\Contracts\GatewayInterface;
use Overtrue\EasySms\Contracts\MessageInterface;
/**
* Class Message.
*/
class Message implements MessageInterface
{
/**
* @var array
*/
protected $gateways = [];
/**
* @var string
*/
protected $type;
/**
* @var string
*/
protected $content;
/**
* @var string
*/
protected $template;
/**
* @var array
*/
protected $data = [];
/**
* Message constructor.
*
* @param array $attributes
* @param string $type
*/
public function __construct(array $attributes = [], $type = MessageInterface::TEXT_MESSAGE)
{
$this->type = $type;
foreach ($attributes as $property => $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
}
}
/**
* Return the message type.
*
* @return string
*/
public function getMessageType()
{
return $this->type;
}
/**
* Return message content.
*
* @param \Overtrue\EasySms\Contracts\GatewayInterface|null $gateway
*
* @return string
*/
public function getContent(GatewayInterface $gateway = null)
{
return is_callable($this->content) ? call_user_func($this->content, $gateway) : $this->content;
}
/**
* Return the template id of message.
*
* @param \Overtrue\EasySms\Contracts\GatewayInterface|null $gateway
*
* @return string
*/
public function getTemplate(GatewayInterface $gateway = null)
{
return is_callable($this->template) ? call_user_func($this->template, $gateway) : $this->template;
}
/**
* @param string $type
*
* @return $this
*/
public function setType(string $type)
{
$this->type = $type;
return $this;
}
/**
* @param mixed $content
*
* @return $this
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* @param mixed $template
*
* @return $this
*/
public function setTemplate($template)
{
$this->template = $template;
return $this;
}
/**
* @param \Overtrue\EasySms\Contracts\GatewayInterface|null $gateway
*
* @return array
*/
public function getData(GatewayInterface $gateway = null)
{
return is_callable($this->data) ? call_user_func($this->data, $gateway) : $this->data;
}
/**
* @param array|callable $data
*
* @return $this
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* @return array
*/
public function getGateways()
{
return $this->gateways;
}
/**
* @param array $gateways
*
* @return $this
*/
public function setGateways(array $gateways)
{
$this->gateways = $gateways;
return $this;
}
/**
* @param $property
*
* @return string
*/
public function __get($property)
{
if (property_exists($this, $property)) {
return $this->$property;
}
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms;
use Overtrue\EasySms\Contracts\MessageInterface;
use Overtrue\EasySms\Contracts\PhoneNumberInterface;
use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;
/**
* Class Messenger.
*/
class Messenger
{
const STATUS_SUCCESS = 'success';
const STATUS_FAILURE = 'failure';
/**
* @var \Overtrue\EasySms\EasySms
*/
protected $easySms;
/**
* Messenger constructor.
*
* @param \Overtrue\EasySms\EasySms $easySms
*/
public function __construct(EasySms $easySms)
{
$this->easySms = $easySms;
}
/**
* Send a message.
*
* @param \Overtrue\EasySms\Contracts\PhoneNumberInterface $to
* @param \Overtrue\EasySms\Contracts\MessageInterface $message
* @param array $gateways
*
* @return array
*
* @throws \Overtrue\EasySms\Exceptions\NoGatewayAvailableException
*/
public function send(PhoneNumberInterface $to, MessageInterface $message, array $gateways = [])
{
$results = [];
$isSuccessful = false;
foreach ($gateways as $gateway => $config) {
try {
$results[$gateway] = [
'gateway' => $gateway,
'status' => self::STATUS_SUCCESS,
'result' => $this->easySms->gateway($gateway)->send($to, $message, $config),
];
$isSuccessful = true;
break;
} catch (\Exception $e) {
$results[$gateway] = [
'gateway' => $gateway,
'status' => self::STATUS_FAILURE,
'exception' => $e,
];
} catch (\Throwable $e) {
$results[$gateway] = [
'gateway' => $gateway,
'status' => self::STATUS_FAILURE,
'exception' => $e,
];
}
}
if (!$isSuccessful) {
throw new NoGatewayAvailableException($results);
}
return $results;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms;
/**
* Class PhoneNumberInterface.
*
* @author overtrue <i@overtrue.me>
*/
class PhoneNumber implements \Overtrue\EasySms\Contracts\PhoneNumberInterface
{
/**
* @var int
*/
protected $number;
/**
* @var int
*/
protected $IDDCode;
/**
* PhoneNumberInterface constructor.
*
* @param int $numberWithoutIDDCode
* @param string $IDDCode
*/
public function __construct($numberWithoutIDDCode, $IDDCode = null)
{
$this->number = $numberWithoutIDDCode;
$this->IDDCode = $IDDCode ? intval(ltrim($IDDCode, '+0')) : null;
}
/**
* 86.
*
* @return int
*/
public function getIDDCode()
{
return $this->IDDCode;
}
/**
* 18888888888.
*
* @return int
*/
public function getNumber()
{
return $this->number;
}
/**
* +8618888888888.
*
* @return string
*/
public function getUniversalNumber()
{
return $this->getPrefixedIDDCode('+').$this->number;
}
/**
* 008618888888888.
*
* @return string
*/
public function getZeroPrefixedNumber()
{
return $this->getPrefixedIDDCode('00').$this->number;
}
/**
* @param string $prefix
*
* @return string|null
*/
public function getPrefixedIDDCode($prefix)
{
return $this->IDDCode ? $prefix.$this->IDDCode : null;
}
/**
* @return string
*/
public function __toString()
{
return $this->getUniversalNumber();
}
/**
* 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 <b>json_encode</b>,
* which is a value of any type other than a resource
*
* @since 5.4.0
*/
public function jsonSerialize()
{
return $this->getUniversalNumber();
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Strategies;
use Overtrue\EasySms\Contracts\StrategyInterface;
/**
* Class OrderStrategy.
*/
class OrderStrategy implements StrategyInterface
{
/**
* Apply the strategy and return result.
*
* @param array $gateways
*
* @return array
*/
public function apply(array $gateways)
{
return array_keys($gateways);
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Strategies;
use Overtrue\EasySms\Contracts\StrategyInterface;
/**
* Class RandomStrategy.
*/
class RandomStrategy implements StrategyInterface
{
/**
* @param array $gateways
*
* @return array
*/
public function apply(array $gateways)
{
uasort($gateways, function () {
return mt_rand() - mt_rand();
});
return array_keys($gateways);
}
}

View File

@@ -0,0 +1,143 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Support;
use ArrayAccess;
/**
* Class Config.
*/
class Config implements ArrayAccess
{
/**
* @var array
*/
protected $config;
/**
* Config constructor.
*
* @param array $config
*/
public function __construct(array $config = [])
{
$this->config = $config;
}
/**
* Get an item from an array using "dot" notation.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function get($key, $default = null)
{
$config = $this->config;
if (isset($config[$key])) {
return $config[$key];
}
if (false === strpos($key, '.')) {
return $default;
}
foreach (explode('.', $key) as $segment) {
if (!is_array($config) || !array_key_exists($segment, $config)) {
return $default;
}
$config = $config[$segment];
}
return $config;
}
/**
* Whether a offset exists.
*
* @see http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @return bool true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned
*
* @since 5.0.0
*/
public function offsetExists($offset)
{
return array_key_exists($offset, $this->config);
}
/**
* Offset to retrieve.
*
* @see http://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
*
* @return mixed Can return all value types
*
* @since 5.0.0
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Offset to set.
*
* @see http://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*
* @since 5.0.0
*/
public function offsetSet($offset, $value)
{
if (isset($this->config[$offset])) {
$this->config[$offset] = $value;
}
}
/**
* Offset to unset.
*
* @see http://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
*
* @since 5.0.0
*/
public function offsetUnset($offset)
{
if (isset($this->config[$offset])) {
unset($this->config[$offset]);
}
}
}

View File

@@ -0,0 +1,136 @@
<?php
/*
* This file is part of the overtrue/easy-sms.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\EasySms\Traits;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
/**
* Trait HasHttpRequest.
*/
trait HasHttpRequest
{
/**
* Make a get request.
*
* @param string $endpoint
* @param array $query
* @param array $headers
*
* @return array
*/
protected function get($endpoint, $query = [], $headers = [])
{
return $this->request('get', $endpoint, [
'headers' => $headers,
'query' => $query,
]);
}
/**
* Make a post request.
*
* @param string $endpoint
* @param array $params
* @param array $headers
*
* @return array
*/
protected function post($endpoint, $params = [], $headers = [])
{
return $this->request('post', $endpoint, [
'headers' => $headers,
'form_params' => $params,
]);
}
/**
* Make a post request with json params.
*
* @param $endpoint
* @param array $params
* @param array $headers
*
* @return array
*/
protected function postJson($endpoint, $params = [], $headers = [])
{
return $this->request('post', $endpoint, [
'headers' => $headers,
'json' => $params,
]);
}
/**
* Make a http request.
*
* @param string $method
* @param string $endpoint
* @param array $options http://docs.guzzlephp.org/en/latest/request-options.html
*
* @return array
*/
protected function request($method, $endpoint, $options = [])
{
return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($endpoint, $options));
}
/**
* Return base Guzzle options.
*
* @return array
*/
protected function getBaseOptions()
{
$options = [
'base_uri' => method_exists($this, 'getBaseUri') ? $this->getBaseUri() : '',
'timeout' => method_exists($this, 'getTimeout') ? $this->getTimeout() : 5.0,
];
return $options;
}
/**
* Return http client.
*
* @param array $options
*
* @return \GuzzleHttp\Client
*
* @codeCoverageIgnore
*/
protected function getHttpClient(array $options = [])
{
return new Client($options);
}
/**
* Convert response contents to json.
*
* @param \Psr\Http\Message\ResponseInterface $response
*
* @return ResponseInterface|array|string
*/
protected function unwrapResponse(ResponseInterface $response)
{
$contentType = $response->getHeaderLine('Content-Type');
$contents = $response->getBody()->getContents();
if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
return json_decode($contents, true);
} elseif (false !== stripos($contentType, 'xml')) {
return json_decode(json_encode(simplexml_load_string($contents)), true);
}
return $contents;
}
}

View File

@@ -0,0 +1,5 @@
.idea
/vendor/
composer.lock
.php_cs.cache
/coverage/

29
vendor/overtrue/laravel-follow/.php_cs vendored Normal file
View File

@@ -0,0 +1,29 @@
<?php
$header = <<<EOF
This file is part of the overtrue/laravel-follow.
(c) overtrue <i@overtrue.me>
This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.
EOF;
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules(array(
'header_comment' => array('header' => $header),
'array_syntax' => array('syntax' => 'short'),
'ordered_class_elements' => true,
'list_syntax' => array('syntax' => 'long'),
'ordered_imports' => true,
'php_unit_strict' => true,
'phpdoc_order' => true,
'strict_comparison' => true,
'strict_param' => true,
))
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
)
;

View File

@@ -0,0 +1,13 @@
language: php
php:
- 7.1
- 7.2
- 7.3
dist: trusty
sudo: false
install: travis_retry composer install --no-interaction --prefer-source
script: vendor/bin/phpunit --verbose

23
vendor/overtrue/laravel-follow/LICENSE vendored Normal file
View File

@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2016 overtrue <i@overtrue.me>
Copyright (c) 2016 Mohammed Isa <mohd.itcs@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

333
vendor/overtrue/laravel-follow/README.md vendored Normal file
View File

@@ -0,0 +1,333 @@
<h1 align="center">Laravel 5 Follow System</h1>
<p align="center">:heart: This package helps you to add user based follow system to your model.</p>
<p align="center">
<a href="https://travis-ci.org/overtrue/laravel-follow"><img src="https://travis-ci.org/overtrue/laravel-follow.svg?branch=master" alt="Build Status"></a>
<a href="https://packagist.org/packages/overtrue/laravel-follow"><img src="https://poser.pugx.org/overtrue/laravel-follow/v/stable.svg" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/overtrue/laravel-follow"><img src="https://poser.pugx.org/overtrue/laravel-follow/v/unstable.svg" alt="Latest Unstable Version"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/laravel-follow/build-status/master"><img src="https://scrutinizer-ci.com/g/overtrue/laravel-follow/badges/build.png?b=master" alt="Build Status"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/laravel-follow/?branch=master"><img src="https://scrutinizer-ci.com/g/overtrue/laravel-follow/badges/quality-score.png?b=master" alt="Scrutinizer Code Quality"></a>
<a href="https://scrutinizer-ci.com/g/overtrue/laravel-follow/?branch=master"><img src="https://scrutinizer-ci.com/g/overtrue/laravel-follow/badges/coverage.png?b=master" alt="Code Coverage"></a>
<a href="https://packagist.org/packages/overtrue/laravel-follow"><img src="https://poser.pugx.org/overtrue/laravel-follow/downloads" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/overtrue/laravel-follow"><img src="https://poser.pugx.org/overtrue/laravel-follow/license" alt="License"></a>
</p>
## Features
- Support actions:
- Follow
- Like
- Bookmark
- Subscribe
- Favorite
- Vote (Upvote & Downvote)
## Installation
### Required
- PHP 7.0 +
- Laravel 5.5 +
You can install the package using composer
```sh
$ composer require overtrue/laravel-follow -vvv
```
Then add the service provider to `config/app.php`
```php
Overtrue\LaravelFollow\FollowServiceProvider::class
```
Publish the migrations file:
```sh
$ php artisan vendor:publish --provider="Overtrue\LaravelFollow\FollowServiceProvider" --tag="migrations"
```
As optional if you want to modify the default configuration, you can publish the configuration file:
```sh
$ php artisan vendor:publish --provider="Overtrue\LaravelFollow\FollowServiceProvider" --tag="config"
```
And create tables:
```php
$ php artisan migrate
```
Finally, add feature trait into User model:
```php
use Overtrue\LaravelFollow\Traits\CanFollow;
use Overtrue\LaravelFollow\Traits\CanBeFollowed;
class User extends Model
{
use CanFollow, CanBeFollowed;
}
```
## Usage
Add `CanXXX` Traits to User model.
```php
use Overtrue\LaravelFollow\Traits\CanFollow;
use Overtrue\LaravelFollow\Traits\CanLike;
use Overtrue\LaravelFollow\Traits\CanFavorite;
use Overtrue\LaravelFollow\Traits\CanSubscribe;
use Overtrue\LaravelFollow\Traits\CanVote;
use Overtrue\LaravelFollow\Traits\CanBookmark;
class User extends Model
{
use CanFollow, CanBookmark, CanLike, CanFavorite, CanSubscribe, CanVote;
}
```
Add `CanBeXXX` Trait to target model, such as 'Post' or 'Music' ...:
```php
use Overtrue\LaravelFollow\Traits\CanBeLiked;
use Overtrue\LaravelFollow\Traits\CanBeFavorited;
use Overtrue\LaravelFollow\Traits\CanBeVoted;
use Overtrue\LaravelFollow\Traits\CanBeBookmarked;
class Post extends Model
{
use CanBeLiked, CanBeFavorited, CanBeVoted, CanBeBookmarked;
}
```
All available APIs are listed below.
### Follow
#### `\Overtrue\LaravelFollow\Traits\CanFollow`
```php
$user->follow($targets)
$user->unfollow($targets)
$user->toggleFollow($targets)
$user->followings()->get() // App\User:class
$user->followings(App\Post::class)->get()
$user->areFollowingEachOther($anotherUser);
$user->isFollowing($target)
```
#### `\Overtrue\LaravelFollow\Traits\CanBeFollowed`
```php
$object->followers()->get()
$object->isFollowedBy($user)
```
### Bookmark
#### `\Overtrue\LaravelFollow\Traits\CanBookmark`
```php
$user->bookmark($targets)
$user->unbookmark($targets)
$user->toggleBookmark($targets)
$user->hasBookmarked($target)
$user->bookmarks()->get() // App\User:class
$user->bookmarks(App\Post::class)->get()
```
#### `\Overtrue\LaravelFollow\Traits\CanBeBookmarked`
```php
$object->bookmarkers()->get() // or $object->bookmarkers
$object->isBookmarkedBy($user)
```
### Like
#### `\Overtrue\LaravelFollow\Traits\CanLike`
```php
$user->like($targets)
$user->unlike($targets)
$user->toggleLike($targets)
$user->hasLiked($target)
$user->likes()->get() // default object: App\User:class
$user->likes(App\Post::class)->get()
```
#### `\Overtrue\LaravelFollow\Traits\CanBeLiked`
```php
$object->likers()->get() // or $object->likers
$object->fans()->get() // or $object->fans
$object->isLikedBy($user)
```
### Favorite
#### `\Overtrue\LaravelFollow\Traits\CanFavorite`
```php
$user->favorite($targets)
$user->unfavorite($targets)
$user->toggleFavorite($targets)
$user->hasFavorited($target)
$user->favorites()->get() // App\User:class
$user->favorites(App\Post::class)->get()
```
#### `\Overtrue\LaravelFollow\Traits\CanBeFavorited`
```php
$object->favoriters()->get() // or $object->favoriters
$object->isFavoritedBy($user)
```
### Subscribe
#### `\Overtrue\LaravelFollow\Traits\CanSubscribe`
```php
$user->subscribe($targets)
$user->unsubscribe($targets)
$user->toggleSubscribe($targets)
$user->hasSubscribed($target)
$user->subscriptions()->get() // default object: App\User:class
$user->subscriptions(App\Post::class)->get()
```
#### `Overtrue\LaravelFollow\Traits\CanBeSubscribed`
```php
$object->subscribers() // or $object->subscribers
$object->isSubscribedBy($user)
```
### Vote
#### `\Overtrue\LaravelFollow\Traits\CanVote`
```php
$user->vote($target) // Vote with 'upvote' for default
$user->upvote($target)
$user->downvote($target)
$user->cancelVote($target)
$user->hasUpvoted($target)
$user->hasDownvoted($target)
$user->votes(App\Post::class)->get()
$user->upvotes(App\Post::class)->get()
$user->downvotes(App\Post::class)->get()
```
#### `\Overtrue\LaravelFollow\Traits\CanBeVoted`
```php
$object->voters()->get()
$object->upvoters()->get()
$object->downvoters()->get()
$object->isVotedBy($user)
$object->isUpvotedBy($user)
$object->isDownvotedBy($user)
```
### Parameters
All of the above mentioned methods of creating relationships, such as 'follow', 'like', 'unfollow', 'unlike', their syntax is as follows:
```php
follow(array|int|\Illuminate\Database\Eloquent\Model $targets, $class = __CLASS__)
```
So you can call them like this:
```php
// Id / Id array
$user->follow(1); // targets: 1, $class = App\User
$user->follow(1, App\Post::class); // targets: 1, $class = App\Post
$user->follow([1, 2, 3]); // targets: [1, 2, 3], $class = App\User
// Model
$post = App\Post::find(7);
$user->follow($post); // targets: $post->id, $class = App\Post
// Model array
$posts = App\Post::popular()->get();
$user->follow($posts); // targets: [1, 2, ...], $class = App\Post
```
### Query relations
```php
$followers = $user->followers
$followers = $user->followers()->where('id', '>', 10)->get()
$followers = $user->followers()->orderByDesc('id')->get()
```
The other is the same usage.
### Working with model.
```php
use Overtrue\LaravelFollow\FollowRelation;
// get most popular object
// all types
$relations = FollowRelation::popular()->get();
// followable_type = App\Post
$relations = FollowRelation::popular(App\Post::class)->get();
// followable_type = App\User
$relations = FollowRelation::popular('user')->get();
// followable_type = App\Post
$relations = FollowRelation::popular('post')->get();
// Pagination
$relations = FollowRelation::popular(App\Post::class)->paginate(15);
```
### Events
- `Overtrue\LaravelFollow\RelationAttaching`
- `Overtrue\LaravelFollow\RelationAttached`
- `Overtrue\LaravelFollow\RelationDetaching`
- `Overtrue\LaravelFollow\RelationDetached`
- `Overtrue\LaravelFollow\RelationToggling`
- `Overtrue\LaravelFollow\RelationToggled`
```php
Event::listen(\Overtrue\LaravelFollow\RelationAttached::class, function($event) {
// $event->causer;
// $event->getTargetsCollection();
// $event->getRelationType();
});
```
# About toggled event.
There has a extra properties for `Overtrue\LaravelFollow\RelationToggled` event.
```php
$event->results; // ['attached' => [1, 2, 3], 'detached' => [5, 6]]
$event->attached; // [1, 2, 3]
$event->detached; // [5, 6]
```
## PHP 扩展包开发
> 想知道如何从零开始构建 PHP 扩展包?
>
> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
## License
MIT

View File

@@ -0,0 +1,36 @@
{
"name": "overtrue/laravel-follow",
"description": "Laravel 5 User Based System",
"require": {
"php": ">=7.0"
},
"require-dev": {
"laravel/laravel": "~5.5||~6.0",
"phpunit/phpunit": "~7.5",
"mockery/mockery": "1.2.3"
},
"license": "MIT",
"authors": [
{
"name": "overtrue",
"email": "i@overtrue.me"
}
],
"autoload": {
"psr-4": {
"Overtrue\\LaravelFollow\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Overtrue\\LaravelFollow\\Test\\": "tests"
}
},
"extra": {
"laravel": {
"providers": [
"Overtrue\\LaravelFollow\\FollowServiceProvider"
]
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
* Model class name of users.
*/
'user_model' => config('auth.providers.users.model', App\User::class),
/*
* Table name of users table.
*/
'users_table_name' => 'users',
/*
* Primary key of users table.
*/
'users_table_primary_key' => 'id',
/*
* Foreign key of users table.
*/
'users_table_foreign_key' => 'user_id',
/*
* Table name of followable relations.
*/
'followable_table' => 'followables',
/*
* Prefix of many-to-many relation fields.
*/
'morph_prefix' => 'followable',
/*
* Date format for created_at.
*/
'date_format' => 'Y-m-d H:i:s',
/*
* Namespace of models.
*/
'model_namespace' => 'App',
];

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLaravelFollowTables extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create(config('follow.followable_table', 'followables'), function (Blueprint $table) {
$userForeignKey = config('follow.users_table_foreign_key', 'user_id');
// Laravel 5.8 session user is unsignedBigInteger
// https://github.com/laravel/framework/pull/28206/files
if ((float) app()->version() >= 5.8) {
$table->unsignedBigInteger($userForeignKey);
} else {
$table->unsignedInteger($userForeignKey);
}
$table->unsignedInteger('followable_id');
$table->string('followable_type')->index();
$table->string('relation')->default('follow')->comment('follow/like/subscribe/favorite/upvote/downvote');
$table->softDeletes();
$table->timestamps();
$table->foreign($userForeignKey)
->references(config('follow.users_table_primary_key', 'id'))
->on(config('follow.users_table_name', 'users'))
->onUpdate('cascade')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*/
public function down()
{
Schema::table(config('follow.followable_table', 'followables'), function ($table) {
$table->dropForeign(config('follow.followable_table', 'followables').'_user_id_foreign');
});
Schema::drop(config('follow.followable_table', 'followables'));
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Follow Test Suite">
<directory>./tests/</directory>
<exclude>vendor</exclude>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
<exclude>
<directory>vendor/</directory>
</exclude>
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="testing"/>
</php>
</phpunit>

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Overtrue\LaravelFollow\Follow;
/**
* Class Event.
*
* @author overtrue <i@overtrue.me>
*/
class Event
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
public $causer;
public $relation;
public $targets;
public $class;
/**
* Event constructor.
*
* @param \Illuminate\Database\Eloquent\Model $causer
* @param \Overtrue\LaravelFollow\Events\string $relation
* @param int|array $targets
* @param \Overtrue\LaravelFollow\Events\string $class
*/
public function __construct(Model $causer, string $relation, $targets, string $class)
{
$this->causer = $causer;
$this->relation = $relation;
$this->targets = $targets;
$this->class = $class;
}
public function getRelationType()
{
return Follow::RELATION_TYPES[$this->relation];
}
public function getTargetsCollection()
{
return \forward_static_call([$this->targets->classname, 'find'], (array) $this->targets->ids);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Events;
/**
* Class RelationAttached.
*
* @author overtrue <i@overtrue.me>
*/
class RelationAttached extends Event
{
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Events;
/**
* Class RelationAttaching.
*
* @author overtrue <i@overtrue.me>
*/
class RelationAttaching extends Event
{
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Events;
/**
* Class RelationDetached.
*
* @author overtrue <i@overtrue.me>
*/
class RelationDetached extends Event
{
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Events;
/**
* Class RelationDetaching.
*
* @author overtrue <i@overtrue.me>
*/
class RelationDetaching extends Event
{
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Events;
/**
* Class RelationToggled.
*
* @author overtrue <i@overtrue.me>
*/
class RelationToggled extends Event
{
public $results = [];
public $attached = [];
public $detached = [];
/**
* RelationToggled constructor.
*
* @param \Illuminate\Database\Eloquent\Model $causer
* @param $relation
* @param $targets
* @param $class
* @param array $results
*/
public function __construct(\Illuminate\Database\Eloquent\Model $causer, $relation, $targets, $class, array $results = [])
{
parent::__construct($causer, $relation, $targets, $class);
$this->results = $results;
$this->attached = $results['attached'] ?? [];
$this->detached = $results['detached'] ?? [];
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Events;
/**
* Class RelationToggling.
*
* @author overtrue <i@overtrue.me>
*/
class RelationToggling extends Event
{
}

View File

@@ -0,0 +1,232 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Overtrue\LaravelFollow\Events\RelationAttached;
use Overtrue\LaravelFollow\Events\RelationAttaching;
use Overtrue\LaravelFollow\Events\RelationDetached;
use Overtrue\LaravelFollow\Events\RelationDetaching;
use Overtrue\LaravelFollow\Events\RelationToggled;
use Overtrue\LaravelFollow\Events\RelationToggling;
use stdClass;
/**
* Class Follow.
*/
class Follow
{
use SoftDeletes;
const RELATION_LIKE = 'like';
const RELATION_FOLLOW = 'follow';
const RELATION_BOOKMARK = 'bookmark';
const RELATION_SUBSCRIBE = 'subscribe';
const RELATION_FAVORITE = 'favorite';
const RELATION_UPVOTE = 'upvote';
const RELATION_DOWNVOTE = 'downvote';
const RELATION_TYPES = [
'likes' => 'like',
'likers' => 'like',
'fans' => 'like',
'followings' => 'follow',
'followers' => 'follow',
'favoriters' => 'favorite',
'favorites' => 'favorite',
'bookmarkers' => 'bookmark',
'bookmarks' => 'bookmark',
'subscriptions' => 'subscribe',
'subscribers' => 'subscribe',
'upvotes' => 'upvote',
'upvoters' => 'upvote',
'downvotes' => 'downvote',
'downvoters' => 'downvote',
];
/**
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $relation
* @param array|string|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public static function isRelationExists(Model $model, $relation, $target, $class = null)
{
$target = self::formatTargets($target, $class ?: config('follow.user_model'));
if ($model->relationLoaded($relation)) {
return $model->{$relation}->where('id', head($target->ids))->isNotEmpty();
}
return $model->{$relation}($target->classname)->where('id', head($target->ids))->exists();
}
/**
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $relation
* @param array|string|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public static function attachRelations(Model $model, $relation, $targets, $class)
{
if (false === \event(new RelationAttaching($model, $relation, $targets, $class))) {
return false;
}
$targets = self::attachPivotsFromRelation($model->{$relation}(), $targets, $class);
if (false === \event(new RelationAttached($model, $relation, $targets, $class))) {
return false;
}
return $model->{$relation}($targets->classname)->sync($targets->targets, false);
}
/**
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $relation
* @param array|string|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public static function detachRelations(Model $model, $relation, $targets, $class)
{
if (false === \event(new RelationDetaching($model, $relation, $targets, $class))) {
return false;
}
$targets = self::formatTargets($targets, $class);
if (false === \event(new RelationDetached($model, $relation, $targets, $class))) {
return false;
}
return $model->{$relation}($targets->classname)->detach($targets->ids);
}
/**
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $relation
* @param array|string|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public static function toggleRelations(Model $model, $relation, $targets, $class)
{
if (false === \event(new RelationToggling($model, $relation, $targets, $class))) {
return false;
}
$targets = self::attachPivotsFromRelation($model->{$relation}(), $targets, $class);
$results = $model->{$relation}($targets->classname)->toggle($targets->targets);
if (false === \event(new RelationToggled($model, $relation, $targets, $class, $results))) {
return false;
}
return $results;
}
/**
* @param \Illuminate\Database\Eloquent\Relations\MorphToMany $morph
* @param array|string|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return \stdClass
*/
public static function attachPivotsFromRelation(MorphToMany $morph, $targets, $class)
{
return self::formatTargets($targets, $class, [
'relation' => self::getRelationTypeFromRelation($morph),
'created_at' => Carbon::now()->format(config('follow.date_format', 'Y-m-d H:i:s')),
]);
}
/**
* @param array|string|\Illuminate\Database\Eloquent\Model $targets
* @param string $classname
* @param array $update
*
* @return \stdClass
*/
public static function formatTargets($targets, $classname, array $update = [])
{
$result = new stdClass();
$result->classname = $classname;
if (!is_array($targets)) {
$targets = [$targets];
}
$result->ids = array_map(function ($target) use ($result) {
if ($target instanceof Model) {
$result->classname = get_class($target);
return $target->getKey();
}
return intval($target);
}, $targets);
$result->targets = empty($update) ? $result->ids : array_combine($result->ids, array_pad([], count($result->ids), $update));
return $result;
}
/**
* @param \Illuminate\Database\Eloquent\Relations\MorphToMany $relation
*
* @throws \Exception
*
* @return array
*/
protected static function getRelationTypeFromRelation(MorphToMany $relation)
{
if (!\array_key_exists($relation->getRelationName(), self::RELATION_TYPES)) {
throw new \Exception('Invalid relation definition.');
}
return self::RELATION_TYPES[$relation->getRelationName()];
}
/**
* @param string $field
*
* @return string
*/
protected static function tablePrefixedField($field)
{
return \sprintf('%s.%s', config('follow.followable_table'), $field);
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\SoftDeletes;
use InvalidArgumentException;
/**
* Class FollowRelation.
*/
class FollowRelation extends Model
{
use SoftDeletes;
/**
* @var string
*/
protected $table;
/**
* @var array
*/
protected $with = ['followable'];
/**
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function followable()
{
return $this->morphTo(config('follow.morph_prefix', 'followable'));
}
/**
* @param \Illuminate\Database\Eloquent\Builder $query
* @param string|null $type
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopePopular($query, $type = null)
{
$query->select('followable_id', 'followable_type', \DB::raw('COUNT(*) AS count'))
->groupBy('followable_id', 'followable_type')
->orderByDesc('count');
if ($type) {
$query->where('followable_type', $this->normalizeFollowableType($type));
}
return $query;
}
/**
* {@inheritdoc}
*/
public function getTable()
{
if (!$this->table) {
$this->table = config('follow.followable_table', 'followables');
}
return parent::getTable();
}
/**
* {@inheritdoc}
*/
public function getDates()
{
return [parent::CREATED_AT];
}
/**
* @param string $type
*
* @throws \InvalidArgumentException
*
* @return string
*/
protected function normalizeFollowableType($type)
{
$morphMap = Relation::morphMap();
if (!empty($morphMap) && in_array($type, $morphMap, true)) {
$type = array_search($type, $morphMap, true);
}
if (class_exists($type)) {
return $type;
}
$namespace = config('follow.model_namespace', 'App');
$modelName = $namespace.'\\'.studly_case($type);
if (!class_exists($modelName)) {
throw new InvalidArgumentException("Model {$modelName} not exists. Please check your config 'follow.model_namespace'.");
}
return $modelName;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow;
use Illuminate\Support\ServiceProvider;
class FollowServiceProvider extends ServiceProvider
{
/**
* Application bootstrap event.
*/
public function boot()
{
$this->publishes([
realpath(__DIR__.'/../config/follow.php') => config_path('follow.php'),
], 'config');
$this->publishes([
realpath(__DIR__.'/../database/migrations') => database_path('migrations'),
], 'migrations');
}
/**
* Register the service provider.
*/
public function register()
{
$this->mergeConfigFrom(realpath(__DIR__.'/../config/follow.php'), 'follow');
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanBeBookmarked.
*/
trait CanBeBookmarked
{
/**
* Check if user is bookmarked by given user.
*
* @param int $user
*
* @return bool
*/
public function isBookmarkedBy($user)
{
return Follow::isRelationExists($this, 'bookmarkers', $user);
}
/**
* Return bookmarkers.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function bookmarkers()
{
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_BOOKMARK)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanBeFavorited.
*/
trait CanBeFavorited
{
/**
* Check if user is favorited by given user.
*
* @param int $user
*
* @return bool
*/
public function isFavoritedBy($user)
{
return Follow::isRelationExists($this, 'favoriters', $user);
}
/**
* Return favoriters.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function favoriters()
{
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_FAVORITE)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Illuminate\Support\Facades\DB;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanBeFollowed.
*/
trait CanBeFollowed
{
/**
* Check if user is followed by given user.
*
* @param int $user
*
* @return bool
*/
public function isFollowedBy($user)
{
return Follow::isRelationExists($this, 'followers', $user);
}
/**
* Return followers.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function followers()
{
$table = config('follow.followable_table');
$class = \get_class($this);
$userTable = config('follow.users_table_name', 'users');
$foreignKey = config('follow.users_table_foreign_key', 'user_id');
$tablePrefixedForeignKey = app('db.connection')->getQueryGrammar()->wrap(\sprintf('pivot_followables.%s', $foreignKey));
$eachOtherKey = app('db.connection')->getQueryGrammar()->wrap('pivot_each_other');
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_FOLLOW)
->withPivot('followable_type', 'relation', 'created_at')
->addSelect("{$userTable}.*", DB::raw("(CASE WHEN {$tablePrefixedForeignKey} IS NOT NULL THEN 1 ELSE 0 END) as {$eachOtherKey}"))
->leftJoin("{$table} as pivot_followables", function ($join) use ($table, $class, $foreignKey) {
$join->on('pivot_followables.followable_type', '=', DB::raw(\addcslashes("'{$class}'", '\\')))
->on('pivot_followables.followable_id', '=', "{$table}.{$foreignKey}")
->on("pivot_followables.{$foreignKey}", '=', "{$table}.followable_id");
});
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanBeLiked.
*/
trait CanBeLiked
{
/**
* Check if user is isLikedBy by given user.
*
* @param int $user
*
* @return bool
*/
public function isLikedBy($user)
{
return Follow::isRelationExists($this, 'likers', $user);
}
/**
* Return followers.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function likers()
{
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_LIKE)
->withPivot('followable_type', 'relation', 'created_at');
}
/**
* Alias of likers.
*
* @return mixed
*/
public function fans()
{
return $this->likers();
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanBeSubscribed.
*/
trait CanBeSubscribed
{
/**
* Check if user is subscribed by given user.
*
* @param int $user
*
* @return bool
*/
public function isSubscribedBy($user)
{
return Follow::isRelationExists($this, 'subscribers', $user);
}
/**
* Return followers.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function subscribers()
{
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_SUBSCRIBE)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,92 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanBeVoted.
*/
trait CanBeVoted
{
/**
* Check if item is voted by given user.
*
* @param int $user
*
* @return bool
*/
public function isVotedBy($user, $type = null)
{
return Follow::isRelationExists($this, 'voters', $user);
}
/**
* Check if item is upvoted by given user.
*
* @param int $user
*
* @return bool
*/
public function isUpvotedBy($user)
{
return Follow::isRelationExists($this, 'upvoters', $user);
}
/**
* Check if item is downvoted by given user.
*
* @param int $user
*
* @return bool
*/
public function isDownvotedBy($user)
{
return Follow::isRelationExists($this, 'downvoters', $user);
}
/**
* Return voters.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function voters()
{
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivotIn('relation', [Follow::RELATION_UPVOTE, Follow::RELATION_DOWNVOTE])
->withPivot('followable_type', 'relation', 'created_at');
}
/**
* Return upvoters.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function upvoters()
{
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_UPVOTE)
->withPivot('followable_type', 'relation', 'created_at');
}
/**
* Return downvoters.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function downvoters()
{
return $this->morphToMany(config('follow.user_model'), config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_DOWNVOTE)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanBookmark.
*/
trait CanBookmark
{
/**
* Follow an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function bookmark($targets, $class = __CLASS__)
{
return Follow::attachRelations($this, 'bookmarks', $targets, $class);
}
/**
* Unbookmark an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public function unbookmark($targets, $class = __CLASS__)
{
return Follow::detachRelations($this, 'bookmarks', $targets, $class);
}
/**
* Toggle bookmark an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function toggleBookmark($targets, $class = __CLASS__)
{
return Follow::toggleRelations($this, 'bookmarks', $targets, $class);
}
/**
* Check if user is bookmarked given item.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function hasBookmarked($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'bookmarks', $target, $class);
}
/**
* Return item bookmarks.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function bookmarks($class = __CLASS__)
{
return $this->morphedByMany($class, config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_BOOKMARK)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanFavorite.
*/
trait CanFavorite
{
/**
* Favorite an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public function favorite($targets, $class = __CLASS__)
{
return Follow::attachRelations($this, 'favorites', $targets, $class);
}
/**
* Unfavorite an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public function unfavorite($targets, $class = __CLASS__)
{
return Follow::detachRelations($this, 'favorites', $targets, $class);
}
/**
* Toggle favorite an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public function toggleFavorite($targets, $class = __CLASS__)
{
return Follow::toggleRelations($this, 'favorites', $targets, $class);
}
/**
* Check if user is favorited given item.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function hasFavorited($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'favorites', $target, $class);
}
/**
* Return item favorites.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function favorites($class = __CLASS__)
{
return $this->morphedByMany($class, config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_FAVORITE)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,116 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Illuminate\Support\Facades\DB;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanFollow.
*/
trait CanFollow
{
/**
* Follow an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function follow($targets, $class = __CLASS__)
{
return Follow::attachRelations($this, 'followings', $targets, $class);
}
/**
* Unfollow an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public function unfollow($targets, $class = __CLASS__)
{
return Follow::detachRelations($this, 'followings', $targets, $class);
}
/**
* Toggle follow an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function toggleFollow($targets, $class = __CLASS__)
{
return Follow::toggleRelations($this, 'followings', $targets, $class);
}
/**
* Check if user is following given item.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function isFollowing($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'followings', $target, $class);
}
/**
* Check if user and target user is following each other.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function areFollowingEachOther($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'followings', $target, $class) && Follow::isRelationExists($target, 'followings', $this, $class);
}
/**
* Return item followings.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function followings($class = __CLASS__)
{
$table = config('follow.followable_table');
$foreignKey = config('follow.users_table_foreign_key', 'user_id');
$targetTable = (new $class())->getTable();
$tablePrefixedForeignKey = app('db.connection')->getQueryGrammar()->wrap(\sprintf('pivot_followables.%s', $foreignKey));
$eachOtherKey = app('db.connection')->getQueryGrammar()->wrap('pivot_each_other');
return $this->morphedByMany($class, config('follow.morph_prefix'), $table)
->wherePivot('relation', '=', Follow::RELATION_FOLLOW)
->withPivot('followable_type', 'relation', 'created_at')
->addSelect("{$targetTable}.*", DB::raw("(CASE WHEN {$tablePrefixedForeignKey} IS NOT NULL THEN 1 ELSE 0 END) as {$eachOtherKey}"))
->leftJoin("{$table} as pivot_followables", function ($join) use ($table, $class, $foreignKey) {
$join->on('pivot_followables.followable_type', '=', DB::raw(\addcslashes("'{$class}'", '\\')))
->on('pivot_followables.followable_id', '=', "{$table}.{$foreignKey}")
->on("pivot_followables.{$foreignKey}", '=', "{$table}.followable_id");
});
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanLike.
*/
trait CanLike
{
/**
* Like an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function like($targets, $class = __CLASS__)
{
return Follow::attachRelations($this, 'likes', $targets, $class);
}
/**
* Unlike an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public function unlike($targets, $class = __CLASS__)
{
return Follow::detachRelations($this, 'likes', $targets, $class);
}
/**
* Toggle like an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function toggleLike($targets, $class = __CLASS__)
{
return Follow::toggleRelations($this, 'likes', $targets, $class);
}
/**
* Check if user is liked given item.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function hasLiked($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'likes', $target, $class);
}
/**
* Return item likes.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function likes($class = __CLASS__)
{
return $this->morphedByMany($class, config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_LIKE)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanSubscribe.
*/
trait CanSubscribe
{
/**
* Subscribe an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function subscribe($targets, $class = __CLASS__)
{
return Follow::attachRelations($this, 'subscriptions', $targets, $class);
}
/**
* Unsubscribe an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return array
*/
public function unsubscribe($targets, $class = __CLASS__)
{
return Follow::detachRelations($this, 'subscriptions', $targets, $class);
}
/**
* Toggle subscribe an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function toggleSubscribe($targets, $class = __CLASS__)
{
return Follow::toggleRelations($this, 'subscriptions', $targets, $class);
}
/**
* Check if user is subscribed given item.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function hasSubscribed($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'subscriptions', $target, $class);
}
/**
* Return user subscriptions.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function subscriptions($class = __CLASS__)
{
return $this->morphedByMany($class, config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_SUBSCRIBE)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,152 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Traits;
use Overtrue\LaravelFollow\Follow;
/**
* Trait CanVote.
*/
trait CanVote
{
/**
* Vote an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $type
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function vote($targets, $type = 'upvote', $class = __CLASS__)
{
$this->cancelVote($targets);
return Follow::attachRelations($this, str_plural($type), $targets, $class);
}
/**
* Upvote an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function upvote($targets, $class = __CLASS__)
{
return $this->vote($targets, 'upvote', $class);
}
/**
* Downvote an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @throws \Exception
*
* @return array
*/
public function downvote($targets, $class = __CLASS__)
{
return $this->vote($targets, 'downvote', $class);
}
/**
* Cancel vote for an item or items.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $targets
* @param string $class
*
* @return \Overtrue\LaravelFollow\Traits\CanVote
*/
public function cancelVote($targets, $class = __CLASS__)
{
$this->hasUpvoted($targets) && Follow::detachRelations($this, 'upvotes', $targets, $class);
$this->hasDownvoted($targets) && Follow::detachRelations($this, 'downvotes', $targets, $class);
return $this;
}
/**
* Check if user is upvoted given item.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function hasUpvoted($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'upvotes', $target, $class);
}
/**
* Check if user is downvoted given item.
*
* @param int|array|\Illuminate\Database\Eloquent\Model $target
* @param string $class
*
* @return bool
*/
public function hasDownvoted($target, $class = __CLASS__)
{
return Follow::isRelationExists($this, 'downvotes', $target, $class);
}
/**
* Return item votes.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function votes($class = __CLASS__)
{
return $this->morphedByMany($class, config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivotIn('relation', [Follow::RELATION_UPVOTE, Follow::RELATION_DOWNVOTE])
->withPivot('followable_type', 'relation', 'created_at');
}
/**
* Return item upvotes.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function upvotes($class = __CLASS__)
{
return $this->morphedByMany($class, config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_UPVOTE)
->withPivot('followable_type', 'relation', 'created_at');
}
/**
* Return item downvotes.
*
* @param string $class
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function downvotes($class = __CLASS__)
{
return $this->morphedByMany($class, config('follow.morph_prefix'), config('follow.followable_table'))
->wherePivot('relation', '=', Follow::RELATION_DOWNVOTE)
->withPivot('followable_type', 'relation', 'created_at');
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Test;
use Illuminate\Database\Eloquent\Model;
use Overtrue\LaravelFollow\Follow;
class FollowTest extends TestCase
{
public function testIsRelationExists()
{
$user = User::create(['name' => 'overtrue']);
$other = Other::create(['name' => 'php']);
$user->follow($other);
$this->assertTrue(Follow::isRelationExists($user, 'followings', $other->id, \get_class($other)));
$user1 = User::create(['name' => 'overtrue']);
$user2 = User::create(['name' => 'anzhengchao']);
$user1->follow($user2);
$this->assertTrue(Follow::isRelationExists($user1, 'followings', $user2->id, User::class));
}
public function testAttachAndDetachRelations()
{
$user1 = User::create(['name' => 'overtrue']);
$user2 = User::create(['name' => 'anzhengchao']);
$user3 = User::create(['name' => 'allen']);
$user4 = User::create(['name' => 'taylor']);
$user1->follow($user2);
$user1->follow([$user3, $user4]);
$this->assertTrue(Follow::isRelationExists($user1, 'followings', $user2->id, User::class));
$this->assertTrue(Follow::isRelationExists($user1, 'followings', $user3->id, User::class));
$this->assertTrue(Follow::isRelationExists($user1, 'followings', $user4->id, User::class));
$user1->unfollow($user2);
$this->assertFalse(Follow::isRelationExists($user1, 'followings', $user2->id, User::class));
}
public function testToggleRelations()
{
$user1 = User::create(['name' => 'overtrue']);
$user2 = User::create(['name' => 'anzhengchao']);
$user1->follow($user2);
$this->assertTrue(Follow::isRelationExists($user1, 'followings', $user2->id, User::class));
$user1->toggleFollow($user2);
$this->assertFalse(Follow::isRelationExists($user1, 'followings', $user2->id, User::class));
$user1->toggleFollow($user2);
$this->assertTrue(Follow::isRelationExists($user1, 'followings', $user2->id, User::class));
}
public function testEagerLoading()
{
$sqls = \collect([]);
$user1 = User::create(['name' => 'overtrue']);
$user2 = User::create(['name' => 'anzhengchao']);
$user3 = User::create(['name' => 'allen']);
$user4 = User::create(['name' => 'taylor']);
$user1->follow($user2);
$user1->follow([$user3, $user4]);
// start recording
\DB::listen(function ($query) use ($sqls) {
$sqls->push($query->sql);
});
$user1->isFollowing($user2);
$user1->isFollowing($user3);
$user1->isFollowing($user4);
$this->assertCount(3, $sqls);
// eager loading
$user1->load('followings');
// cleanup
$sqls = \collect([]);
$user1->isFollowing($user2);
$user1->isFollowing($user3);
$user1->isFollowing($user4);
$this->assertCount(0, $sqls);
}
public function testFormatTargets()
{
// 1. !is_array
$result = Follow::formatTargets(1, 'App\Foo');
$this->assertSame('App\Foo', $result->classname);
$this->assertSame([1], $result->ids);
$this->assertSame([1], $result->targets);
// 2. Model
$user = new User();
$user->id = 3;
$result = Follow::formatTargets([1, $user], 'App\Foo');
$this->assertSame(User::class, $result->classname);
$this->assertSame([1, 3], $result->ids);
$this->assertSame([1, 3], $result->targets);
$other = new Other();
$other->id = 45;
$result = Follow::formatTargets([1, $user, $other], 'App\Foo');
$this->assertSame(Other::class, $result->classname);
$this->assertSame([1, 3, 45], $result->ids);
$this->assertSame([1, 3, 45], $result->targets);
// 3. $update
$update = ['relation' => 'like'];
$result = Follow::formatTargets([1, 2], 'App\Foo', $update);
$this->assertSame('App\Foo', $result->classname);
$this->assertSame([1, 2], $result->ids);
$this->assertSame([1 => $update, 2 => $update], $result->targets);
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Test;
use Illuminate\Database\Eloquent\Model;
use Overtrue\LaravelFollow\Traits\CanBeFollowed;
class Other extends Model
{
use CanBeFollowed;
protected $follow = User::class;
protected $table = 'others';
protected $fillable = ['name'];
}

View File

@@ -0,0 +1,97 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Test;
use Illuminate\Filesystem\Filesystem;
class TestCase extends \Illuminate\Foundation\Testing\TestCase
{
protected $config;
/**
* Creates the application.
*
* Needs to be implemented by subclasses.
*
* @return \Symfony\Component\HttpKernel\HttpKernelInterface
*/
public function createApplication()
{
$app = require __DIR__.'/../vendor/laravel/laravel/bootstrap/app.php';
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
$app['config']->set('database.default', 'sqlite');
$app['config']->set('database.connections.sqlite.database', ':memory:');
return $app;
}
/**
* Setup DB before each test.
*/
protected function setUp(): void
{
parent::setUp();
if (empty($this->config)) {
$this->config = require __DIR__.'/../config/follow.php';
}
$this->app['config']->set('follow', $this->config);
$this->app['config']->set('follow.user_model', User::class);
$this->migrate();
$this->seed();
}
/**
* run package database migrations.
*/
public function migrate()
{
$fileSystem = new Filesystem();
$fileSystem->copy(
__DIR__.'/../database/migrations/2018_06_29_032244_create_laravel_follow_tables.php',
__DIR__.'/database/migrations/create_laravel_follow_tables.php'
);
foreach ($fileSystem->files(__DIR__.'/database/migrations') as $file) {
$fileSystem->requireOnce($file);
}
(new \CreateLaravelFollowTables())->up();
(new \CreateUsersTable())->up();
(new \CreateOthersTable())->up();
}
public function tearDown(): void
{
parent::tearDown();
unlink(__DIR__.'/database/migrations/create_laravel_follow_tables.php');
}
/**
* Seed testing database.
*/
public function seed($classname = null)
{
User::create(['name' => 'John']);
User::create(['name' => 'Allison']);
User::create(['name' => 'Ron']);
Other::create(['name' => 'Laravel']);
Other::create(['name' => 'Vuejs']);
Other::create(['name' => 'Ruby']);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Test\Traits;
use Overtrue\LaravelFollow\Test\Other;
use Overtrue\LaravelFollow\Test\TestCase;
use Overtrue\LaravelFollow\Test\User;
class CanBeFollowedTest extends TestCase
{
public function test_user_can_follow_by_id()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user1->follow($user2->id);
$this->assertCount(1, $user2->followers);
}
public function test_user_can_follow_multiple_users()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user3 = User::find(3);
$user1->follow([$user2->id, $user3->id]);
$this->assertCount(1, $user2->followers);
$this->assertCount(1, $user3->followers);
}
public function test_is_followed_by()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user1->follow($user2->id);
$this->assertTrue($user2->isFollowedBy($user1->id));
}
public function test_user_can_follow_other_by_id()
{
$user = User::find(1);
$other = Other::find(1);
$user->follow($other);
$this->assertCount(1, $other->followers);
}
public function test_is_followed_by_user()
{
$user = User::find(1);
$other = Other::find(1);
$user->follow($other);
$this->assertTrue($other->isFollowedBy($user));
}
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Test\Traits;
use Overtrue\LaravelFollow\Test\Other;
use Overtrue\LaravelFollow\Test\TestCase;
use Overtrue\LaravelFollow\Test\User;
class CanFollowTest extends TestCase
{
public function test_user_can_follow_by_id()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user1->follow($user2->id);
$this->assertCount(1, $user1->followings);
}
public function test_user_can_follow_multiple_users()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user3 = User::find(3);
$user1->follow([$user2->id, $user3->id]);
$this->assertCount(2, $user1->followings);
}
public function test_unfollow_user()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user1->follow($user2->id);
$this->assertCount(1, $user2->followers);
$user1->unfollow($user2->id);
$this->assertCount(0, $user1->followings);
}
public function test_is_following()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user1->follow($user2->id);
$this->assertTrue($user1->isFollowing($user2->id));
}
public function test_user_can_follow_other_by_id()
{
$user = User::find(1);
$other = Other::find(1);
$user->follow($other);
$this->assertCount(1, $user->followings(Other::class)->get());
}
public function test_unfollow_other()
{
$user = User::find(1);
$other = Other::find(1);
$user->follow($other);
$user->unfollow($other);
$this->assertCount(0, $user->followings);
}
public function test_is_following_other()
{
$user = User::find(1);
$other = Other::find(1);
$user->follow($other);
$this->assertTrue($user->isFollowing($other));
}
public function test_following_each_other()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user1->follow($user2);
$this->assertFalse($user1->areFollowingEachOther($user2));
$user2->follow($user1);
$this->assertTrue($user1->areFollowingEachOther($user2));
}
public function test_eager_loading()
{
$user1 = User::find(1);
$user2 = User::find(2);
$user1->follow($user2);
$user2->follow($user1);
// eager loading
$user2 = User::find(2)->load(['followings', 'followers']);
$this->assertTrue($user2->isFollowedBy($user1));
// without eager loading
$this->assertTrue($user1->isFollowedBy($user2));
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelFollow\Test;
use Illuminate\Database\Eloquent\Model;
use Overtrue\LaravelFollow\Traits\CanFollow;
use Overtrue\LaravelFollow\Traits\CanBeFollowed;
class User extends Model
{
use CanFollow;
use CanBeFollowed;
protected $table = 'users';
protected $fillable = ['name'];
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateOthersTable extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create('others', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down()
{
Schema::drop('others');
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the overtrue/laravel-follow
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down()
{
Schema::drop('users');
}
}

View File

@@ -0,0 +1,4 @@
/vendor/
composer.lock
.php_cs.cache
.idea

28
vendor/overtrue/laravel-wechat/.php_cs vendored Normal file
View File

@@ -0,0 +1,28 @@
<?php
$header = <<<EOF
This file is part of the overtrue/laravel-wechat.
(c) overtrue <i@overtrue.me>
This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.
EOF;
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules(array(
'@Symfony' => true,
'header_comment' => array('header' => $header),
'array_syntax' => array('syntax' => 'short'),
'ordered_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'php_unit_construct' => true,
'php_unit_strict' => true,
))
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
)
;

22
vendor/overtrue/laravel-wechat/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 安正超
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.

260
vendor/overtrue/laravel-wechat/README.md vendored Normal file
View File

@@ -0,0 +1,260 @@
# laravel-wechat
微信 SDK for Laravel 5 / Lumen 基于 [overtrue/wechat](https://github.com/overtrue/wechat)
> 交流QQ群319502940
## 框架要求
Laravel/Lumen >= 5.1
## 安装
```shell
# Laravel < 5.8
composer require "overtrue/laravel-wechat:~4.0"
# Laravel >= 5.8
composer require "overtrue/laravel-wechat:~5.0"
```
## 配置
### Laravel 应用
1.`config/app.php` 注册 ServiceProvider 和 Facade (Laravel 5.5 + 无需手动注册)
```php
'providers' => [
// ...
Overtrue\LaravelWeChat\ServiceProvider::class,
],
'aliases' => [
// ...
'EasyWeChat' => Overtrue\LaravelWeChat\Facade::class,
],
```
2. 创建配置文件:
```shell
php artisan vendor:publish --provider="Overtrue\LaravelWeChat\ServiceProvider"
```
3. 修改应用根目录下的 `config/wechat.php` 中对应的参数即可。
4. 每个模块基本都支持多账号,默认为 `default`
### Lumen 应用
1.`bootstrap/app.php` 中 82 行左右:
```php
$app->register(Overtrue\LaravelWeChat\ServiceProvider::class);
```
2. 如果你习惯使用 `config/wechat.php` 来配置的话,将 `vendor/overtrue/laravel-wechat/src/config.php` 拷贝到`项目根目录/config`目录下,并将文件名改成`wechat.php`
## 使用
:rotating_light: 在中间件 `App\Http\Middleware\VerifyCsrfToken` 排除微信相关的路由,如:
```php
protected $except = [
// ...
'wechat',
];
```
下面以接收普通消息为例写一个例子:
> 假设您的域名为 `overtrue.me` 那么请登录微信公众平台 “开发者中心” 修改 “URL服务器配置” 为: `http://overtrue.me/wechat`。
路由:
```php
Route::any('/wechat', 'WeChatController@serve');
```
> 注意:一定是 `Route::any`, 因为微信服务端认证的时候是 `GET`, 接收用户消息时是 `POST`
然后创建控制器 `WeChatController`
```php
<?php
namespace App\Http\Controllers;
use Log;
class WeChatController extends Controller
{
/**
* 处理微信的请求消息
*
* @return string
*/
public function serve()
{
Log::info('request arrived.'); # 注意Log 为 Laravel 组件,所以它记的日志去 Laravel 日志看,而不是 EasyWeChat 日志
$app = app('wechat.official_account');
$app->server->push(function($message){
return "欢迎关注 overtrue";
});
return $app->server->serve();
}
}
```
> 上面例子里的 Log 是 Laravel 组件,所以它的日志不会写到 EasyWeChat 里的,建议把 wechat 的日志配置到 Laravel 同一个日志文件,便于调试。
### 我们有以下方式获取 SDK 的服务实例
##### 使用外观
```php
$officialAccount = \EasyWeChat::officialAccount(); // 公众号
$work = \EasyWeChat::work(); // 企业微信
$payment = \EasyWeChat::payment(); // 微信支付
$openPlatform = \EasyWeChat::openPlatform(); // 开放平台
$miniProgram = \EasyWeChat::miniProgram(); // 小程序
// 均支持传入配置账号名称
\EasyWeChat::officialAccount('foo'); // `foo` 为配置文件中的名称,默认为 `default`
//...
```
## OAuth 中间件
使用中间件的情况下 `app/config/wechat.php` 中的 `oauth.callback` 就随便填写吧(因为用不着了 :smile:)。
1.`app/Http/Kernel.php` 中添加路由中间件:
```php
protected $routeMiddleware = [
// ...
'wechat.oauth' => \Overtrue\LaravelWeChat\Middleware\OAuthAuthenticate::class,
];
```
2. 在路由中添加中间件:
```php
//...
Route::group(['middleware' => ['web', 'wechat.oauth']], function () {
Route::get('/user', function () {
$user = session('wechat.oauth_user.default'); // 拿到授权用户资料
dd($user);
});
});
```
中间件支持指定配置名称:`'wechat.oauth:default'`,当然,你也可以在中间件参数指定当前的 `scopes`:
```php
Route::group(['middleware' => ['wechat.oauth:snsapi_userinfo']], function () {
// ...
});
// 或者指定账户的同时指定 scopes:
Route::group(['middleware' => ['wechat.oauth:default,snsapi_userinfo']], function () {
// ...
});
```
上面的路由定义了 `/user` 是需要微信授权的,那么在这条路由的**回调 或 控制器对应的方法里** 你就可以从 `session('wechat.oauth_user.default')` 拿到已经授权的用户信息了。
## 模拟授权
有时候我们希望在本地开发完成后线上才真实的走微信授权流程,这将减少我们的开发成本,那么你需要做以下两步:
1. 准备假资料:
> 以下字段在 scope 为 `snsapi_userinfo` 时尽可能配置齐全哦,当然,如果你的模式只是 `snsapi_base` 的话只需要 `openid` 就好了。
```php
use Illuminate\Support\Arr;
use Overtrue\Socialite\User as SocialiteUser;
$user = new SocialiteUser([
'id' => Arr::get($user, 'openid'),
'name' => Arr::get($user, 'nickname'),
'nickname' => Arr::get($user, 'nickname'),
'avatar' => Arr::get($user, 'headimgurl'),
'email' => null,
'original' => [],
'provider' => 'WeChat',
]);
```
2. 将资料写入 session
> 注意:一定要在 OAuth 中间件之前写入,比如你可以创建一个全局中间件来完成这件事儿,当然了,只在开发环境启用即可。
```php
session(['wechat.oauth_user.default' => $user]); // 同理,`default` 可以更换为您对应的其它配置名
```
## 事件
> 你可以监听相应的事件,并对事件发生后执行相应的操作。
- OAuth 网页授权:`Overtrue\LaravelWeChat\Events\WeChatUserAuthorized`
```php
// 该事件有以下属性
$event->user; // 同 session('wechat.oauth_user.default') 一样
$event->isNewSession; // 是不是新的会话(第一次创建 session 时为 true
$event->account; // 当前中间件所使用的账号,对应在配置文件中的配置项名称
```
## 开放平台路由支持
在配置文件 `route` 处取消注释即可启用。
```php
'open_platform' => [
'uri' => 'serve',
'action' => Overtrue\LaravelWeChat\Controllers\OpenPlatformController::class,
'attributes' => [
'prefix' => 'open-platform',
'middleware' => null,
],
],
```
Tips: 默认的控制器会根据微信开放平台的推送内容触发如下事件,你可以监听相应的事件并进行处理:
- 授权方成功授权:`Overtrue\LaravelWeChat\Events\OpenPlatform\Authorized`
- 授权方更新授权:`Overtrue\LaravelWeChat\Events\OpenPlatform\UpdateAuthorized`
- 授权方取消授权:`Overtrue\LaravelWeChat\Events\OpenPlatform\Unauthorized`
- 开放平台推送 VerifyTicket`Overtrue\LaravelWeChat\Events\OpenPlatform\VerifyTicketRefreshed`
```php
// 事件有如下属性
$message = $event->payload; // 开放平台事件通知内容
```
配置后 `http://example.com/open-platform/serve` 则为开放平台第三方应用设置的授权事件接收 URL。
更多 SDK 的具体使用请参考https://easywechat.com
## PHP 扩展包开发
> 想知道如何从零开始构建 PHP 扩展包?
>
> 请关注我的实战课程,我会在此课程中分享一些扩展开发经验 —— [《PHP 扩展包实战教程 - 从入门到发布》](https://learnku.com/courses/creating-package)
## License
MIT

View File

@@ -0,0 +1,31 @@
{
"name": "overtrue/laravel-wechat",
"description": "微信 SDK for Laravel",
"keywords": ["wechat", "weixin","laravel", "sdk"],
"license": "MIT",
"authors": [
{
"name": "overtrue",
"email": "anzhengchao@gmail.com"
}
],
"require": {
"illuminate/container": "^5.1||~6.0",
"overtrue/wechat": "^4.0"
},
"autoload": {
"psr-4": {
"Overtrue\\LaravelWeChat\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Overtrue\\LaravelWeChat\\ServiceProvider"
],
"aliases": {
"EasyWeChat": "Overtrue\\LaravelWeChat\\Facade"
}
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Controllers;
use Barryvdh\Debugbar\LaravelDebugbar;
class Controller
{
public function __construct()
{
if (class_exists(LaravelDebugbar::class)) {
resolve(LaravelDebugbar::class)->disable();
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Controllers;
use EasyWeChat\OpenPlatform\Application;
use EasyWeChat\OpenPlatform\Server\Guard;
use Overtrue\LaravelWeChat\Events\OpenPlatform as Events;
class OpenPlatformController extends Controller
{
/**
* Register for open platform.
*
* @param \EasyWeChat\OpenPlatform\Application $application
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function __invoke(Application $application)
{
$server = $application->server;
$server->on(Guard::EVENT_AUTHORIZED, function ($payload) {
event(new Events\Authorized($payload));
});
$server->on(Guard::EVENT_UNAUTHORIZED, function ($payload) {
event(new Events\Unauthorized($payload));
});
$server->on(Guard::EVENT_UPDATE_AUTHORIZED, function ($payload) {
event(new Events\UpdateAuthorized($payload));
});
$server->on(Guard::EVENT_COMPONENT_VERIFY_TICKET, function ($payload) {
event(new Events\VerifyTicketRefreshed($payload));
});
return $server->serve();
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Events\OpenPlatform;
/**
* @method string getAppId()
* @method string getCreateTime()
* @method string getInfoType()
* @method string getAuthorizerAppid()
* @method string getAuthorizationCode()
* @method string getAuthorizationCodeExpiredTime()
* @method string getPreAuthCode()
*/
class Authorized extends OpenPlatformEvent
{
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Events\OpenPlatform;
abstract class OpenPlatformEvent
{
/**
* @var array
*/
public $payload;
/**
* Create a new event instance.
*
* @param mixed $payload
*/
public function __construct($payload)
{
$this->payload = $payload;
}
public function __call($name, $args)
{
return $this->payload[substr($name, 3)] ?? null;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Events\OpenPlatform;
/**
* @method string getAppId()
* @method string getCreateTime()
* @method string getInfoType()
* @method string getAuthorizerAppid()
*/
class Unauthorized extends OpenPlatformEvent
{
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Events\OpenPlatform;
/**
* @method string getAppId()
* @method string getCreateTime()
* @method string getInfoType()
* @method string getAuthorizerAppid()
* @method string getAuthorizationCode()
* @method string getAuthorizationCodeExpiredTime()
* @method string getPreAuthCode()
*/
class UpdateAuthorized extends OpenPlatformEvent
{
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Events\OpenPlatform;
/**
* @method string getAppId()
* @method string getCreateTime()
* @method string getInfoType()
* @method string getComponentVerifyTicket()
*/
class VerifyTicketRefreshed extends OpenPlatformEvent
{
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Events;
use Illuminate\Queue\SerializesModels;
use Overtrue\Socialite\User;
class WeChatUserAuthorized
{
use SerializesModels;
public $user;
public $isNewSession;
public $account;
/**
* Create a new event instance.
*
* @param \Overtrue\Socialite\User $user
* @param bool $isNewSession
*/
public function __construct(User $user, $isNewSession = false, string $account)
{
$this->user = $user;
$this->isNewSession = $isNewSession;
$this->account = $account;
}
/**
* Retrieve the authorized user.
*
* @return \Overtrue\Socialite\User
*/
public function getUser()
{
return $this->user;
}
/**
* The name of official account.
*
* @return string
*/
public function getAccount()
{
return $this->account;
}
/**
* Check the user session is first created.
*
* @return bool
*/
public function isNewSession()
{
return $this->isNewSession;
}
/**
* Get the channels the event should be broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return [];
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat;
use Illuminate\Support\Facades\Facade as LaravelFacade;
/**
* Class Facade.
*
* @author overtrue <i@overtrue.me>
*/
class Facade extends LaravelFacade
{
/**
* 默认为 Server.
*
* @return string
*/
public static function getFacadeAccessor()
{
return 'wechat.official_account';
}
/**
* @return \EasyWeChat\OfficialAccount\Application
*/
public static function officialAccount($name = '')
{
return $name ? app('wechat.official_account.'.$name) : app('wechat.official_account');
}
/**
* @return \EasyWeChat\Work\Application
*/
public static function work($name = '')
{
return $name ? app('wechat.work.'.$name) : app('wechat.work');
}
/**
* @return \EasyWeChat\Payment\Application
*/
public static function payment($name = '')
{
return $name ? app('wechat.payment.'.$name) : app('wechat.payment');
}
/**
* @return \EasyWeChat\MiniProgram\Application
*/
public static function miniProgram($name = '')
{
return $name ? app('wechat.mini_program.'.$name) : app('wechat.mini_program');
}
/**
* @return \EasyWeChat\OpenPlatform\Application
*/
public static function openPlatform($name = '')
{
return $name ? app('wechat.open_platform.'.$name) : app('wechat.open_platform');
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat\Middleware;
use Closure;
use http\Env\Request;
use Illuminate\Support\Arr;
use Overtrue\LaravelWeChat\Events\WeChatUserAuthorized;
/**
* Class OAuthAuthenticate: 微信公众号, 企业微信的网页应用。
*/
class OAuthAuthenticate
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $scope
* @param string|null $type : service(服务号), subscription(订阅号), work(企业微信)
*
* @return mixed
*/
public function handle($request, Closure $next, $account = 'default', $scope = null, $type = 'service')
{
$isNewSession = false;
//保证兼容性
$class = ('work' !== $type) ? 'wechat' : 'work';
$prefix = ('work' !== $type) ? 'official_account' : 'work';
$sessionKey = \sprintf($class.'.oauth_user.%s', $account);
$config = config(\sprintf('wechat.'.$prefix.'.%s', $account), []);
$officialAccount = app(\sprintf('wechat.'.$prefix.'.%s', $account));
$scope = $scope ?: Arr::get($config, 'oauth.scopes', ['snsapi_base']);
if (is_string($scope)) {
$scope = array_map('trim', explode(',', $scope));
}
$session = session($sessionKey, []);
if (!$session) {
if ($request->has('code')) {
session([$sessionKey => $officialAccount->oauth->user() ?? []]);
$isNewSession = true;
event(new WeChatUserAuthorized(session($sessionKey), $isNewSession, $account));
return redirect()->to($this->getTargetUrl($request));
}
session()->forget($sessionKey);
return $officialAccount->oauth->scopes($scope)->redirect($request->fullUrl());
}
event(new WeChatUserAuthorized(session($sessionKey), $isNewSession, $account));
return $next($request);
}
/**
* Build the target business url.
*
* @param Request $request
*
* @return string
*/
protected function getTargetUrl($request)
{
$queries = Arr::except($request->query(), ['code', 'state']);
return $request->url().(empty($queries) ? '' : '?'.http_build_query($queries));
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Overtrue\LaravelWeChat;
use EasyWeChat\MiniProgram\Application as MiniProgram;
use EasyWeChat\OfficialAccount\Application as OfficialAccount;
use EasyWeChat\OpenPlatform\Application as OpenPlatform;
use EasyWeChat\OpenWork\Application as OpenWork;
use EasyWeChat\Payment\Application as Payment;
use EasyWeChat\Work\Application as Work;
use Illuminate\Foundation\Application as LaravelApplication;
use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
use Laravel\Lumen\Application as LumenApplication;
/**
* Class ServiceProvider.
*
* @author overtrue <i@overtrue.me>
*/
class ServiceProvider extends LaravelServiceProvider
{
/**
* Boot the provider.
*/
public function boot()
{
}
/**
* Setup the config.
*/
protected function setupConfig()
{
$source = realpath(__DIR__.'/config.php');
if ($this->app instanceof LaravelApplication && $this->app->runningInConsole()) {
$this->publishes([$source => config_path('wechat.php')], 'laravel-wechat');
} elseif ($this->app instanceof LumenApplication) {
$this->app->configure('wechat');
}
$this->mergeConfigFrom($source, 'wechat');
}
/**
* Register the provider.
*/
public function register()
{
$this->setupConfig();
$apps = [
'official_account' => OfficialAccount::class,
'work' => Work::class,
'mini_program' => MiniProgram::class,
'payment' => Payment::class,
'open_platform' => OpenPlatform::class,
'open_work' => OpenWork::class,
];
foreach ($apps as $name => $class) {
if (empty(config('wechat.'.$name))) {
continue;
}
if ($config = config('wechat.route.'.$name)) {
$this->getRouter()->group($config['attributes'], function ($router) use ($config) {
$router->post($config['uri'], $config['action']);
});
}
if (!empty(config('wechat.'.$name.'.app_id')) || !empty(config('wechat.'.$name.'.corp_id'))) {
$accounts = [
'default' => config('wechat.'.$name),
];
config(['wechat.'.$name.'.default' => $accounts['default']]);
} else {
$accounts = config('wechat.'.$name);
}
foreach ($accounts as $account => $config) {
$this->app->singleton("wechat.{$name}.{$account}", function ($laravelApp) use ($name, $account, $config, $class) {
$app = new $class(array_merge(config('wechat.defaults', []), $config));
if (config('wechat.defaults.use_laravel_cache')) {
$app['cache'] = $laravelApp['cache.store'];
}
$app['request'] = $laravelApp['request'];
return $app;
});
}
$this->app->alias("wechat.{$name}.default", 'wechat.'.$name);
$this->app->alias("wechat.{$name}.default", 'easywechat.'.$name);
$this->app->alias('wechat.'.$name, $class);
$this->app->alias('easywechat.'.$name, $class);
}
}
protected function getRouter()
{
if ($this->app instanceof LumenApplication && !class_exists('Laravel\Lumen\Routing\Router')) {
return $this->app;
}
return $this->app->router;
}
}

View File

@@ -0,0 +1,131 @@
<?php
/*
* This file is part of the overtrue/laravel-wechat.
*
* (c) overtrue <i@overtrue.me>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
return [
/*
* 默认配置,将会合并到各模块中
*/
'defaults' => [
/*
* 指定 API 调用返回结果的类型array(default)/collection/object/raw/自定义类名
*/
'response_type' => 'array',
/*
* 使用 Laravel 的缓存系统
*/
'use_laravel_cache' => true,
/*
* 日志配置
*
* level: 日志级别,可选为:
* debug/info/notice/warning/error/critical/alert/emergency
* file日志文件位置(绝对路径!!!),要求可写权限
*/
'log' => [
'level' => env('WECHAT_LOG_LEVEL', 'debug'),
'file' => env('WECHAT_LOG_FILE', storage_path('logs/wechat.log')),
],
],
/*
* 路由配置
*/
'route' => [
/*
* 开放平台第三方平台路由配置
*/
// 'open_platform' => [
// 'uri' => 'serve',
// 'action' => Overtrue\LaravelWeChat\Controllers\OpenPlatformController::class,
// 'attributes' => [
// 'prefix' => 'open-platform',
// 'middleware' => null,
// ],
// ],
],
/*
* 公众号
*/
'official_account' => [
'default' => [
'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', 'your-app-id'), // AppID
'secret' => env('WECHAT_OFFICIAL_ACCOUNT_SECRET', 'your-app-secret'), // AppSecret
'token' => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', 'your-token'), // Token
'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_AES_KEY', ''), // EncodingAESKey
/*
* OAuth 配置
*
* scopes公众平台snsapi_userinfo / snsapi_base开放平台snsapi_login
* callbackOAuth授权完成后的回调页地址(如果使用中间件,则随便填写。。。)
*/
// 'oauth' => [
// 'scopes' => array_map('trim', explode(',', env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_SCOPES', 'snsapi_userinfo'))),
// 'callback' => env('WECHAT_OFFICIAL_ACCOUNT_OAUTH_CALLBACK', '/examples/oauth_callback.php'),
// ],
],
],
/*
* 开放平台第三方平台
*/
// 'open_platform' => [
// 'default' => [
// 'app_id' => env('WECHAT_OPEN_PLATFORM_APPID', ''),
// 'secret' => env('WECHAT_OPEN_PLATFORM_SECRET', ''),
// 'token' => env('WECHAT_OPEN_PLATFORM_TOKEN', ''),
// 'aes_key' => env('WECHAT_OPEN_PLATFORM_AES_KEY', ''),
// ],
// ],
/*
* 小程序
*/
// 'mini_program' => [
// 'default' => [
// 'app_id' => env('WECHAT_MINI_PROGRAM_APPID', ''),
// 'secret' => env('WECHAT_MINI_PROGRAM_SECRET', ''),
// 'token' => env('WECHAT_MINI_PROGRAM_TOKEN', ''),
// 'aes_key' => env('WECHAT_MINI_PROGRAM_AES_KEY', ''),
// ],
// ],
/*
* 微信支付
*/
// 'payment' => [
// 'default' => [
// 'sandbox' => env('WECHAT_PAYMENT_SANDBOX', false),
// 'app_id' => env('WECHAT_PAYMENT_APPID', ''),
// 'mch_id' => env('WECHAT_PAYMENT_MCH_ID', 'your-mch-id'),
// 'key' => env('WECHAT_PAYMENT_KEY', 'key-for-signature'),
// 'cert_path' => env('WECHAT_PAYMENT_CERT_PATH', 'path/to/cert/apiclient_cert.pem'), // XXX: 绝对路径!!!!
// 'key_path' => env('WECHAT_PAYMENT_KEY_PATH', 'path/to/cert/apiclient_key.pem'), // XXX: 绝对路径!!!!
// 'notify_url' => 'http://example.com/payments/wechat-notify', // 默认支付结果通知地址
// ],
// // ...
// ],
/*
* 企业微信
*/
// 'work' => [
// 'default' => [
// 'corp_id' => 'xxxxxxxxxxxxxxxxx',
// 'agent_id' => 100020,
// 'secret' => env('WECHAT_WORK_AGENT_CONTACTS_SECRET', ''),
// //...
// ],
// ],
];

View File

@@ -0,0 +1,9 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: overtrue
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
custom: # Replace with a single custom sponsorship URL

9
vendor/overtrue/socialite/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
/vendor
composer.phar
composer.lock
.DS_Store
/.idea
Thumbs.db
/*.php
sftp-config.json
.php_cs.cache

28
vendor/overtrue/socialite/.php_cs vendored Normal file
View File

@@ -0,0 +1,28 @@
<?php
$header = <<<EOF
This file is part of the overtrue/socialite.
(c) overtrue <i@overtrue.me>
This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.
EOF;
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules(array(
'@Symfony' => true,
'header_comment' => array('header' => $header),
'array_syntax' => array('syntax' => 'short'),
'ordered_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'php_unit_construct' => true,
'php_unit_strict' => true,
))
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
)
;

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