|
|
@@ -5,6 +5,7 @@ namespace App\Services;
|
|
|
use App\Constants\Util;
|
|
|
use App\Support\AnnouncementCatalog as Catalog;
|
|
|
use App\Support\LocalizedContent;
|
|
|
+use App\Support\ContentLink;
|
|
|
use Carbon\Carbon;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Validator;
|
|
|
@@ -14,7 +15,7 @@ use Illuminate\Validation\ValidationException;
|
|
|
class AnnouncementService
|
|
|
{
|
|
|
private const FIELDS = ['category', 'publish_platforms', 'display_frequency',
|
|
|
- 'publish_at', 'end_at', 'status', 'click_type', 'link'];
|
|
|
+ 'publish_at', 'end_at', 'status', 'click_type', 'link', 'link_pc', 'link_h5'];
|
|
|
|
|
|
public function options(): array
|
|
|
{
|
|
|
@@ -94,6 +95,8 @@ class AnnouncementService
|
|
|
'status' => ['sometimes', 'integer', Rule::in([0, 1])],
|
|
|
'click_type' => ['sometimes', 'required', Rule::in(array_keys(Catalog::CLICKS))],
|
|
|
'link' => ['nullable', 'string', 'max:2048'],
|
|
|
+ 'link_pc' => ['nullable', 'string', 'max:2048'],
|
|
|
+ 'link_h5' => ['nullable', 'string', 'max:2048'],
|
|
|
], ['click_type.in' => '点击方式仅支持无链接、外站链接、站内路径'])->validate();
|
|
|
|
|
|
return DB::transaction(function () use ($params) {
|
|
|
@@ -107,7 +110,7 @@ class AnnouncementService
|
|
|
}
|
|
|
$row = $before ?: ['category' => 'content', 'publish_platforms' => array_keys(Catalog::PLATFORMS),
|
|
|
'display_frequency' => 'daily_first_login', 'publish_at' => $now, 'end_at' => null,
|
|
|
- 'status' => 1, 'click_type' => 'none', 'link' => null];
|
|
|
+ 'status' => 1, 'click_type' => 'none', 'link' => null, 'link_pc' => null, 'link_h5' => null];
|
|
|
foreach (self::FIELDS as $field) if (array_key_exists($field, $params)) $row[$field] = $params[$field];
|
|
|
if (array_key_exists('translations', $params)) $row['translations'] = LocalizedContent::parse($params['translations'], 200, 20000);
|
|
|
if (empty($row['translations'])) $this->invalid('translations', '至少填写一个语言的标题和正文');
|
|
|
@@ -133,9 +136,7 @@ class AnnouncementService
|
|
|
}
|
|
|
unset($block);
|
|
|
}
|
|
|
- if (in_array($row['click_type'], ['external', 'internal'], true)) {
|
|
|
- if (!$this->validLink((string) $row['link'], $row['click_type'])) $this->invalid('link', '请填写有效的外站链接或站内相对路径');
|
|
|
- } else { $row['link'] = null; }
|
|
|
+ $row = array_replace($row, ContentLink::forSave($params, $before));
|
|
|
$row['status'] = (int) $row['status'];
|
|
|
$actor = OperationAuditService::actor();
|
|
|
if (!$actor['operator_id'] || $actor['operator_name'] === '') $this->invalid('sender_id', '请使用后台管理员身份');
|
|
|
@@ -245,25 +246,15 @@ class AnnouncementService
|
|
|
if (isset($row['publish_platforms']) && is_string($row['publish_platforms'])) $row['publish_platforms'] = json_decode($row['publish_platforms'], true, 512, JSON_THROW_ON_ERROR);
|
|
|
foreach (['id', 'sender_id', 'status'] as $field) if (isset($row[$field])) $row[$field] = (int) $row[$field];
|
|
|
unset($row['activity_id']);
|
|
|
- if (($row['click_type'] ?? 'none') === 'activity') { $row['click_type'] = 'none'; $row['link'] = null; }
|
|
|
- return $row;
|
|
|
+ return array_replace($row, ContentLink::normalize($row));
|
|
|
}
|
|
|
|
|
|
private function imageAddress(string $value): bool
|
|
|
{
|
|
|
- if (!$this->validLink($value, str_starts_with($value, '/') ? 'internal' : 'external')) return false;
|
|
|
+ if (!ContentLink::valid($value, str_starts_with($value, '/') ? 'internal' : 'external')) return false;
|
|
|
return (bool) preg_match('/\.(?:jpe?g|png|webp)$/i', (string) parse_url($value, PHP_URL_PATH));
|
|
|
}
|
|
|
|
|
|
- private function validLink(string $value, string $type): bool
|
|
|
- {
|
|
|
- if ($value === '' || preg_match('/[\x00-\x20\x7f\\\\]/', $value)) return false;
|
|
|
- if ($type === 'internal') return str_starts_with($value, '/') && !str_starts_with($value, '//');
|
|
|
- $parts = parse_url($value);
|
|
|
- return filter_var($value, FILTER_VALIDATE_URL) && $parts && in_array(strtolower($parts['scheme'] ?? ''), ['http', 'https'], true)
|
|
|
- && !isset($parts['user']) && !isset($parts['pass']);
|
|
|
- }
|
|
|
-
|
|
|
private function pageRules(): array { return ['page' => ['nullable', 'integer', 'min:1'], 'limit' => ['nullable', 'integer', 'min:1', 'max:200']]; }
|
|
|
private function dateRangeRules(): array { return [
|
|
|
'start_date' => ['nullable', 'required_with:end_date', 'date_format:Y-m-d'],
|