Files
evus.acvisa.com/app/service/SitemapService.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');
}
}