301 lines
9.4 KiB
PHP
301 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Web;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
|
|
class SiteController extends Controller
|
|
{
|
|
/** @var array<string,string> */
|
|
private const CATEGORY_TO_SLUG = [
|
|
'Smileys & Emotion' => 'smileys',
|
|
'People & Body' => 'people',
|
|
'Animals & Nature' => 'animals',
|
|
'Food & Drink' => 'food',
|
|
'Travel & Places' => 'travel',
|
|
'Activities' => 'activities',
|
|
'Objects' => 'objects',
|
|
'Symbols' => 'symbols',
|
|
'Flags' => 'flags',
|
|
];
|
|
|
|
public function home(Request $request): View
|
|
{
|
|
return view('site.home', [
|
|
'initialQuery' => trim((string) $request->query('q', '')),
|
|
'initialCategory' => trim((string) $request->query('category', '')),
|
|
'initialSubcategory' => trim((string) $request->query('subcategory', '')),
|
|
'canonicalPath' => '/',
|
|
]);
|
|
}
|
|
|
|
public function browse(Request $request): RedirectResponse|View
|
|
{
|
|
$cat = strtolower(trim((string) $request->query('cat', 'all')));
|
|
if ($cat !== '' && $cat !== 'all' && array_key_exists($cat, $this->categorySlugMap())) {
|
|
return redirect('/'.$cat, 301);
|
|
}
|
|
|
|
return view('site.home', [
|
|
'initialQuery' => trim((string) $request->query('q', '')),
|
|
'initialCategory' => trim((string) $request->query('category', '')),
|
|
'initialSubcategory' => trim((string) $request->query('subcategory', '')),
|
|
'canonicalPath' => '/browse',
|
|
]);
|
|
}
|
|
|
|
public function category(string $categorySlug): View
|
|
{
|
|
if ($categorySlug === 'all') {
|
|
return view('site.home', [
|
|
'initialQuery' => '',
|
|
'initialCategory' => '',
|
|
'initialSubcategory' => '',
|
|
'canonicalPath' => '/',
|
|
]);
|
|
}
|
|
|
|
$categoryLabel = $this->categorySlugMap()[$categorySlug] ?? '';
|
|
abort_if($categoryLabel === '', 404);
|
|
|
|
return view('site.home', [
|
|
'initialQuery' => '',
|
|
'initialCategory' => $categoryLabel,
|
|
'initialSubcategory' => '',
|
|
'canonicalPath' => '/'.$categorySlug,
|
|
]);
|
|
}
|
|
|
|
public function categorySubcategory(string $categorySlug, string $subcategorySlug): View
|
|
{
|
|
if ($categorySlug === 'all') {
|
|
abort(404);
|
|
}
|
|
|
|
$categoryLabel = $this->categorySlugMap()[$categorySlug] ?? '';
|
|
abort_if($categoryLabel === '', 404);
|
|
|
|
return view('site.home', [
|
|
'initialQuery' => '',
|
|
'initialCategory' => $categoryLabel,
|
|
'initialSubcategory' => $subcategorySlug,
|
|
'canonicalPath' => '/'.$categorySlug.'/'.$subcategorySlug,
|
|
]);
|
|
}
|
|
|
|
public function apiDocs(): View
|
|
{
|
|
return view('site.api-docs');
|
|
}
|
|
|
|
public function pricing(): View
|
|
{
|
|
return view('site.pricing');
|
|
}
|
|
|
|
public function support(): View
|
|
{
|
|
return view('site.support');
|
|
}
|
|
|
|
public function privacy(): View
|
|
{
|
|
return view('site.privacy');
|
|
}
|
|
|
|
public function terms(): View
|
|
{
|
|
return view('site.terms');
|
|
}
|
|
|
|
public function emojiDetail(string $slug): View|Response
|
|
{
|
|
$dataPath = (string) config('dewemoji.data_path');
|
|
if (!is_file($dataPath)) {
|
|
abort(500, 'Emoji dataset file not found.');
|
|
}
|
|
|
|
$raw = file_get_contents($dataPath);
|
|
if ($raw === false) {
|
|
abort(500, 'Emoji dataset file could not be read.');
|
|
}
|
|
|
|
$decoded = json_decode($raw, true);
|
|
if (!is_array($decoded)) {
|
|
abort(500, 'Emoji dataset JSON is invalid.');
|
|
}
|
|
|
|
$items = $decoded['emojis'] ?? [];
|
|
$match = null;
|
|
$byEmoji = [];
|
|
foreach ($items as $item) {
|
|
$char = (string) ($item['emoji'] ?? '');
|
|
if ($char !== '' && !isset($byEmoji[$char])) {
|
|
$byEmoji[$char] = $item;
|
|
}
|
|
if (($item['slug'] ?? '') === $slug) {
|
|
$match = $item;
|
|
}
|
|
}
|
|
|
|
if (!$match) {
|
|
abort(404);
|
|
}
|
|
|
|
$relatedDetails = [];
|
|
foreach (array_slice($match['related'] ?? [], 0, 8) as $relatedEmoji) {
|
|
$relatedEmoji = (string) $relatedEmoji;
|
|
$ref = $byEmoji[$relatedEmoji] ?? null;
|
|
$relatedDetails[] = [
|
|
'emoji' => $relatedEmoji,
|
|
'slug' => (string) ($ref['slug'] ?? ''),
|
|
'name' => (string) ($ref['name'] ?? $relatedEmoji),
|
|
];
|
|
}
|
|
|
|
return view('site.emoji-detail', [
|
|
'emoji' => $match,
|
|
'relatedDetails' => $relatedDetails,
|
|
'canonicalPath' => '/emoji/'.$slug,
|
|
]);
|
|
}
|
|
|
|
public function robotsTxt(): Response
|
|
{
|
|
$base = rtrim(config('app.url', request()->getSchemeAndHttpHost()), '/');
|
|
$body = "User-agent: *\nAllow: /\n\nSitemap: ".$base."/sitemap.xml\n";
|
|
|
|
return response($body, 200)->header('Content-Type', 'text/plain; charset=UTF-8');
|
|
}
|
|
|
|
public function sitemapXml(): Response
|
|
{
|
|
$data = $this->loadDataset();
|
|
$items = is_array($data['emojis'] ?? null) ? $data['emojis'] : [];
|
|
$base = rtrim(config('app.url', request()->getSchemeAndHttpHost()), '/');
|
|
|
|
$lastUpdatedTs = isset($data['last_updated_ts']) ? (int) $data['last_updated_ts'] : time();
|
|
$lastUpdated = gmdate('Y-m-d\TH:i:s\Z', $lastUpdatedTs);
|
|
|
|
$urls = [
|
|
['loc' => $base.'/', 'priority' => '0.8', 'changefreq' => 'daily'],
|
|
['loc' => $base.'/api-docs', 'priority' => '0.5', 'changefreq' => 'weekly'],
|
|
['loc' => $base.'/pricing', 'priority' => '0.7', 'changefreq' => 'weekly'],
|
|
['loc' => $base.'/privacy', 'priority' => '0.3', 'changefreq' => 'monthly'],
|
|
['loc' => $base.'/terms', 'priority' => '0.3', 'changefreq' => 'monthly'],
|
|
['loc' => $base.'/support', 'priority' => '0.4', 'changefreq' => 'weekly'],
|
|
];
|
|
|
|
foreach ($items as $item) {
|
|
$slug = trim((string) ($item['slug'] ?? ''));
|
|
if ($slug === '' || $this->shouldHideForSitemap($item)) {
|
|
continue;
|
|
}
|
|
$urls[] = [
|
|
'loc' => $base.'/emoji/'.$slug,
|
|
'priority' => '0.6',
|
|
'changefreq' => 'weekly',
|
|
];
|
|
}
|
|
|
|
$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) {
|
|
$xml .= " <url>\n";
|
|
$xml .= ' <loc>'.htmlspecialchars((string) $url['loc'], ENT_XML1)."</loc>\n";
|
|
$xml .= ' <lastmod>'.$lastUpdated."</lastmod>\n";
|
|
$xml .= ' <changefreq>'.$url['changefreq']."</changefreq>\n";
|
|
$xml .= ' <priority>'.$url['priority']."</priority>\n";
|
|
$xml .= " </url>\n";
|
|
}
|
|
$xml .= '</urlset>'."\n";
|
|
|
|
return response($xml, 200)->header('Content-Type', 'application/xml; charset=UTF-8');
|
|
}
|
|
|
|
/**
|
|
* @return array<string,string>
|
|
*/
|
|
private function categorySlugMap(): array
|
|
{
|
|
$out = [];
|
|
foreach (self::CATEGORY_TO_SLUG as $label => $slug) {
|
|
$out[$slug] = $label;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @return array<string,mixed>
|
|
*/
|
|
private function loadDataset(): array
|
|
{
|
|
$dataPath = (string) config('dewemoji.data_path');
|
|
if (!is_file($dataPath)) {
|
|
return ['emojis' => []];
|
|
}
|
|
|
|
$raw = file_get_contents($dataPath);
|
|
if ($raw === false) {
|
|
return ['emojis' => []];
|
|
}
|
|
|
|
$decoded = json_decode($raw, true);
|
|
if (!is_array($decoded)) {
|
|
return ['emojis' => []];
|
|
}
|
|
|
|
return $decoded;
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed> $emoji
|
|
*/
|
|
private function shouldHideForSitemap(array $emoji): bool
|
|
{
|
|
$name = strtolower(trim((string) ($emoji['name'] ?? '')));
|
|
$category = strtolower(trim((string) ($emoji['category'] ?? '')));
|
|
$subcategory = strtolower(trim((string) ($emoji['subcategory'] ?? '')));
|
|
|
|
if ($subcategory === 'family' || str_starts_with($name, 'family:')) {
|
|
return true;
|
|
}
|
|
if (preg_match('~\bwoman: beard\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if (preg_match('~\bmen with bunny ears\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if (preg_match('~\bpregnant man\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if ($category === 'people & body') {
|
|
if (preg_match('~\bmen holding hands\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if (preg_match('~\bwomen holding hands\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if (preg_match('~kiss:.*\bman,\s*man\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if (preg_match('~kiss:.*\bwoman,\s*woman\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if (preg_match('~couple.*\bman,\s*man\b~i', $name)) {
|
|
return true;
|
|
}
|
|
if (preg_match('~couple.*\bwoman,\s*woman\b~i', $name)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|