175 lines
4.7 KiB
PHP
175 lines
4.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace app\service;
|
|
|
|
final class SitemapService
|
|
{
|
|
private string $baseUrl;
|
|
|
|
/** @var string[] */
|
|
private array $excludedPrefixes;
|
|
|
|
public function __construct(string $baseUrl, array $excludedPrefixes = [])
|
|
{
|
|
$this->baseUrl = $this->normalizeBaseUrl($baseUrl);
|
|
$this->excludedPrefixes = $excludedPrefixes ?: [
|
|
'/home/login',
|
|
'/home/user',
|
|
'/home/pay',
|
|
'/home/payesta',
|
|
'/home/invoice',
|
|
'/home/miniinvoice',
|
|
'/home/multiple',
|
|
'/home/update',
|
|
'/home/upload',
|
|
'/home/panel',
|
|
'/home/wxmini',
|
|
'/home/wechatpay',
|
|
'/home/usvisa',
|
|
];
|
|
}
|
|
|
|
public function render(array $entries): string
|
|
{
|
|
$urls = [];
|
|
foreach ($entries as $entry) {
|
|
if (!is_array($entry)) {
|
|
continue;
|
|
}
|
|
|
|
$path = $this->normalizePath((string) ($entry['path'] ?? ''));
|
|
if ($path === '' || !$this->isPublicPath($path)) {
|
|
continue;
|
|
}
|
|
|
|
$url = $this->buildUrl($path, (array) ($entry['query'] ?? []));
|
|
if ($url === '' || isset($urls[$url])) {
|
|
continue;
|
|
}
|
|
|
|
$lastmod = $this->normalizeLastmod((string) ($entry['lastmod'] ?? ''));
|
|
$urls[$url] = $lastmod;
|
|
}
|
|
|
|
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
|
$xml .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
|
foreach ($urls as $url => $lastmod) {
|
|
$xml .= " <url>\n";
|
|
$xml .= ' <loc>' . $this->escapeXml($url) . "</loc>\n";
|
|
if ($lastmod !== '') {
|
|
$xml .= ' <lastmod>' . $this->escapeXml($lastmod) . "</lastmod>\n";
|
|
}
|
|
$xml .= " </url>\n";
|
|
}
|
|
$xml .= "</urlset>\n";
|
|
|
|
return $xml;
|
|
}
|
|
|
|
public function renderRobots(array $disallowPaths): string
|
|
{
|
|
$lines = [
|
|
'User-agent: *',
|
|
'Allow: /',
|
|
];
|
|
|
|
foreach (array_unique($disallowPaths) as $path) {
|
|
$path = $this->normalizePath((string) $path);
|
|
if ($path !== '') {
|
|
$lines[] = 'Disallow: ' . $path;
|
|
}
|
|
}
|
|
|
|
if ($this->baseUrl !== '') {
|
|
$lines[] = '';
|
|
$lines[] = 'Sitemap: ' . $this->baseUrl . '/sitemap.xml';
|
|
}
|
|
|
|
return implode("\n", $lines) . "\n";
|
|
}
|
|
|
|
private function buildUrl(string $path, array $query): string
|
|
{
|
|
if ($this->baseUrl === '') {
|
|
return '';
|
|
}
|
|
|
|
$url = $this->baseUrl . ($path === '/' ? '/' : rtrim($path, '/'));
|
|
$query = array_filter(
|
|
$query,
|
|
static fn ($value): bool => is_scalar($value) && $value !== ''
|
|
);
|
|
if ($query !== []) {
|
|
$url .= '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986);
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
private function normalizeBaseUrl(string $baseUrl): string
|
|
{
|
|
$baseUrl = rtrim(trim($baseUrl), '/');
|
|
if ($baseUrl === '' || filter_var($baseUrl, FILTER_VALIDATE_URL) === false) {
|
|
return '';
|
|
}
|
|
|
|
$scheme = strtolower((string) parse_url($baseUrl, PHP_URL_SCHEME));
|
|
if (!in_array($scheme, ['http', 'https'], true)) {
|
|
return '';
|
|
}
|
|
|
|
return $baseUrl;
|
|
}
|
|
|
|
private function normalizePath(string $path): string
|
|
{
|
|
$path = trim($path);
|
|
if ($path === '' || str_starts_with($path, '//')) {
|
|
return '';
|
|
}
|
|
if (parse_url($path, PHP_URL_HOST) !== null) {
|
|
return '';
|
|
}
|
|
|
|
$path = (string) parse_url($path, PHP_URL_PATH);
|
|
if ($path === '') {
|
|
return '';
|
|
}
|
|
|
|
return '/' . ltrim($path, '/');
|
|
}
|
|
|
|
private function isPublicPath(string $path): bool
|
|
{
|
|
foreach ($this->excludedPrefixes as $prefix) {
|
|
$prefix = rtrim('/' . ltrim((string) $prefix, '/'), '/');
|
|
if ($path === $prefix || str_starts_with($path, $prefix . '/')) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private function normalizeLastmod(string $lastmod): string
|
|
{
|
|
$lastmod = trim($lastmod);
|
|
if ($lastmod === '') {
|
|
return '';
|
|
}
|
|
|
|
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $lastmod) !== 1) {
|
|
return '';
|
|
}
|
|
|
|
[$year, $month, $day] = array_map('intval', explode('-', $lastmod));
|
|
return checkdate($month, $day, $year) ? $lastmod : '';
|
|
}
|
|
|
|
private function escapeXml(string $value): string
|
|
{
|
|
return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8');
|
|
}
|
|
}
|