137 lines
4.3 KiB
PHP
137 lines
4.3 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Sms;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
/**
|
|
* ZibLL-style SMS dispatcher and send-rate limiting.
|
|
*/
|
|
class SMS {
|
|
|
|
private static $instance = null;
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
public function get_provider_config(): array {
|
|
if (class_exists('\\AKSRT\\Settings\\Settings')) {
|
|
return \AKSRT\Settings\Settings::get_sms_settings();
|
|
}
|
|
return ['sms_sdk' => 'null', 'sms_number_limit_ch' => true];
|
|
}
|
|
|
|
/**
|
|
* Phone-number validation follows the selected domestic/international mode.
|
|
*/
|
|
public static function is_phonenumber(string $phone, ?bool $domestic_only = null): bool {
|
|
if ($domestic_only === null) {
|
|
$config = self::get_instance()->get_provider_config();
|
|
$domestic_only = !empty($config['sms_number_limit_ch']);
|
|
}
|
|
|
|
if ($domestic_only) {
|
|
return (bool) preg_match('/^1[3-9]\d{9}$/', $phone);
|
|
}
|
|
|
|
return (bool) preg_match('/^(?:\d{5,}|\+\d{6,}|\+?\d{2,}[-\s]\d{2,}[0-9\-\s]{0,15})$/', $phone);
|
|
}
|
|
|
|
/**
|
|
* Send a six-digit verification code. The caller stores it after success.
|
|
*
|
|
* @return array{success: bool, message: string, remaining_time?: int}
|
|
*/
|
|
public function send_code(string $phone, string $code): array {
|
|
$config = $this->get_provider_config();
|
|
$sdk = sanitize_key((string) ($config['sms_sdk'] ?? 'null'));
|
|
if ($sdk === '' || $sdk === 'null') {
|
|
return ['success' => false, 'message' => __('短信接口未启用', 'aksrt')];
|
|
}
|
|
if (!self::is_phonenumber($phone, !empty($config['sms_number_limit_ch']))) {
|
|
return ['success' => false, 'message' => __('手机号码格式有误', 'aksrt')];
|
|
}
|
|
|
|
$provider = SMSProviderFactory::get($sdk);
|
|
if ($provider === null) {
|
|
return ['success' => false, 'message' => __('暂无短信接口,请检查短信服务商设置', 'aksrt')];
|
|
}
|
|
|
|
$missing = $provider->validate_config($config);
|
|
if ($missing) {
|
|
return ['success' => false, 'message' => sprintf(__('短信接口缺少配置:%s', 'aksrt'), implode(', ', $missing))];
|
|
}
|
|
|
|
// [防刷机制]验证码发送限制(后台「扩展&增强 → 网站安全」)。
|
|
$limit = \AKSRT\Extensions\SendLimit::check($phone);
|
|
if (empty($limit['ok'])) {
|
|
return [
|
|
'success' => false,
|
|
'message' => (string) $limit['message'],
|
|
'remaining_time' => (int) $limit['remaining'],
|
|
];
|
|
}
|
|
|
|
$result = $provider->send_code($phone, $code, $config);
|
|
if (empty($result['success'])) {
|
|
return ['success' => false, 'message' => (string) ($result['message'] ?? __('验证码发送失败,请稍后重试', 'aksrt'))];
|
|
}
|
|
|
|
$cooldown = \AKSRT\Extensions\SendLimit::record($phone);
|
|
|
|
return [
|
|
'success' => true,
|
|
'message' => __('验证码短信已发送', 'aksrt'),
|
|
'remaining_time' => $cooldown > 0 ? $cooldown : 60,
|
|
];
|
|
}
|
|
public function get_provider_status(): array {
|
|
$config = $this->get_provider_config();
|
|
$key = sanitize_key((string) ($config['sms_sdk'] ?? 'null'));
|
|
$enabled = $key !== '' && $key !== 'null';
|
|
$provider = SMSProviderFactory::get($key);
|
|
$status = [
|
|
'enabled' => $enabled,
|
|
'provider' => $enabled ? $key : 'none',
|
|
'configured' => false,
|
|
'available' => false,
|
|
'verified' => false,
|
|
'transport' => '',
|
|
'missing_fields' => [],
|
|
];
|
|
if ($provider !== null) {
|
|
$status = array_merge($status, $provider->get_status($config));
|
|
}
|
|
return $status;
|
|
}
|
|
|
|
/** Send the reference theme's fixed 888888 test message without storing a code. */
|
|
public function test_send(string $phone): array {
|
|
$config = $this->get_provider_config();
|
|
$sdk = sanitize_key((string) ($config['sms_sdk'] ?? 'null'));
|
|
if ($sdk === '' || $sdk === 'null') {
|
|
return ['success' => false, 'message' => __('短信接口未启用', 'aksrt')];
|
|
}
|
|
if (!self::is_phonenumber($phone, !empty($config['sms_number_limit_ch']))) {
|
|
return ['success' => false, 'message' => __('手机号码格式有误', 'aksrt')];
|
|
}
|
|
$provider = SMSProviderFactory::get($sdk);
|
|
if ($provider === null) {
|
|
return ['success' => false, 'message' => __('暂无短信接口,请检查短信服务商设置', 'aksrt')];
|
|
}
|
|
$missing = $provider->validate_config($config);
|
|
if ($missing) {
|
|
return ['success' => false, 'message' => sprintf(__('短信接口缺少配置:%s', 'aksrt'), implode(', ', $missing))];
|
|
}
|
|
return $provider->send_code($phone, '888888', $config);
|
|
}
|
|
|
|
}
|