67 lines
3.4 KiB
PHP
67 lines
3.4 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\Articles;
|
||
|
||
final class ArticleRecommendations
|
||
{
|
||
public const SLOT = '<!-- fna-airport-recommendations -->';
|
||
|
||
public static function providers(): array
|
||
{
|
||
if (! config('airport_recommendations.enabled')) return [];
|
||
$providers = array_filter(config('airport_recommendations.providers', []), fn ($p) =>
|
||
($p['enabled'] ?? false) && filter_var($p['url'] ?? '', FILTER_VALIDATE_URL)
|
||
&& in_array(parse_url($p['url'], PHP_URL_SCHEME), ['https', 'http'], true));
|
||
uasort($providers, fn ($a, $b) => ($a['order'] ?? 0) <=> ($b['order'] ?? 0));
|
||
return $providers;
|
||
}
|
||
|
||
/** Presentation only: article storage, API receipts and subscription bytes stay intact. */
|
||
public static function prepare(Articles $article): array
|
||
{
|
||
$html = $article->sContent ?? '';
|
||
$category = $article->oCategory;
|
||
$slug = $category?->sSlug;
|
||
$scope = in_array($slug, ['freenode', 'shadowrocketids', 'advanced', 'client'], true)
|
||
|| $category?->oParent?->sSlug === 'client';
|
||
$enabled = $scope && self::providers() !== [];
|
||
if (! $enabled) return ['body' => $html, 'enabled' => false, 'inline' => false];
|
||
|
||
$inline = false;
|
||
if (in_array($slug, ['freenode', 'shadowrocketids'], true)) {
|
||
// Match the known legacy heading and its own Fuyun link, bounded by the next heading.
|
||
if (preg_match('~(?:<p\b[^>]*>\s*)?<h[2-6]\b[^>]*>\s*机场推荐\s*[-—-::]*\s*福云\s*[::]?\s*</h[2-6]>~u', $html, $head, PREG_OFFSET_CAPTURE)) {
|
||
$start = $head[0][1];$from = $start + strlen($head[0][0]);
|
||
$tail = substr($html, $from);
|
||
if (preg_match('~<h[1-6]\b~i', $tail, $next, PREG_OFFSET_CAPTURE)) $tail = substr($tail, 0, $next[0][1]);
|
||
preg_match_all('~<a\b[^>]*>.*?</a>~si', $tail, $links, PREG_OFFSET_CAPTURE);
|
||
foreach ($links[0] as [$link, $offset]) {
|
||
$text = html_entity_decode(strip_tags($link), ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||
if (! str_contains($text, '机场官网') || ! str_contains($text, '福云')) continue;
|
||
$end = $from + $offset + strlen($link);
|
||
if (preg_match('~\A(?:\s|</(?:b|strong|p)>)*~i', substr($html, $end), $closing)) $end += strlen($closing[0]);
|
||
$html = substr($html, 0, $start).self::SLOT.substr($html, $end);
|
||
$inline = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if ($slug === 'advanced') {
|
||
$anchor = '#'.config('airport_recommendations.anchor');
|
||
if (! str_contains($html, $anchor)) {
|
||
$jump = '<p class="airport-recommendation-jump">如果需要更稳定的线路,可以从文末的<a href="'.e($anchor).'">机场推荐</a>中选择。</p>';
|
||
$inserted = false;
|
||
$html = preg_replace_callback('~<p\b[^>]*>.*?</p>~si', function ($m) use (&$inserted, $jump) {
|
||
if ($inserted || ! preg_match('/免费节点|订阅|稳定|机场|线路/u', strip_tags($m[0]))) return $m[0];
|
||
$inserted = true;
|
||
return $m[0].$jump;
|
||
}, $html);
|
||
if (! $inserted) $html .= $jump;
|
||
}
|
||
}
|
||
return ['body' => $html, 'enabled' => true, 'inline' => $inline];
|
||
}
|
||
}
|