chore(*.php): use pint for php formatting
This commit is contained in:
parent
029caaaa9e
commit
cf1ee4c97f
40 changed files with 2261 additions and 1900 deletions
|
@ -1,11 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
abstract class ApiHandler extends BaseHandler
|
||||
{
|
||||
abstract class ApiHandler extends BaseHandler
|
||||
{
|
||||
protected function ensureCliAccess(): void
|
||||
{
|
||||
if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') redirectTo404();
|
||||
if (php_sapi_name() !== 'cli' && $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
redirectTo404();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
class ArtistFetcher extends PageFetcher
|
||||
{
|
||||
class ArtistFetcher extends PageFetcher
|
||||
{
|
||||
public function fetch(string $url): ?array
|
||||
{
|
||||
$cacheKey = "artist_" . md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
$cacheKey = 'artist_'.md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
|
||||
if ($cached) return $cached;
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$artist = $this->fetchSingleFromApi("optimized_artists", $url);
|
||||
$artist = $this->fetchSingleFromApi('optimized_artists', $url);
|
||||
|
||||
if (!$artist) return null;
|
||||
if (! $artist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$artist['globals'] = $this->getGlobals();
|
||||
$artist['globals'] = $this->getGlobals();
|
||||
|
||||
$this->cacheSet($cacheKey, $artist);
|
||||
$this->cacheSet($cacheKey, $artist);
|
||||
|
||||
return $artist;
|
||||
return $artist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,100 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
|
||||
abstract class BaseHandler
|
||||
{
|
||||
abstract class BaseHandler
|
||||
{
|
||||
protected string $postgrestUrl;
|
||||
|
||||
protected string $postgrestApiKey;
|
||||
|
||||
protected ?\Redis $cache = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->loadEnvironment();
|
||||
$this->initializeCache();
|
||||
$this->loadEnvironment();
|
||||
$this->initializeCache();
|
||||
}
|
||||
|
||||
private function loadEnvironment(): void
|
||||
{
|
||||
$this->postgrestUrl = $_ENV["POSTGREST_URL"] ?? getenv("POSTGREST_URL") ?? "";
|
||||
$this->postgrestApiKey = $_ENV["POSTGREST_API_KEY"] ?? getenv("POSTGREST_API_KEY") ?? "";
|
||||
$this->postgrestUrl = $_ENV['POSTGREST_URL'] ?? getenv('POSTGREST_URL') ?? '';
|
||||
$this->postgrestApiKey = $_ENV['POSTGREST_API_KEY'] ?? getenv('POSTGREST_API_KEY') ?? '';
|
||||
}
|
||||
|
||||
protected function initializeCache(): void
|
||||
{
|
||||
if (class_exists("Redis")) {
|
||||
try {
|
||||
$redis = new \Redis();
|
||||
$redis->connect("127.0.0.1", 6379);
|
||||
if (class_exists('Redis')) {
|
||||
try {
|
||||
$redis = new \Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
|
||||
$this->cache = $redis;
|
||||
} catch (\Exception $e) {
|
||||
error_log("Redis connection failed: " . $e->getMessage());
|
||||
$this->cache = $redis;
|
||||
} catch (\Exception $e) {
|
||||
error_log('Redis connection failed: '.$e->getMessage());
|
||||
|
||||
$this->cache = null;
|
||||
$this->cache = null;
|
||||
}
|
||||
} else {
|
||||
error_log('Redis extension not found — caching disabled.');
|
||||
|
||||
$this->cache = null;
|
||||
}
|
||||
} else {
|
||||
error_log("Redis extension not found — caching disabled.");
|
||||
|
||||
$this->cache = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function makeRequest(string $method, string $endpoint, array $options = []): array
|
||||
{
|
||||
$client = new Client();
|
||||
$url = rtrim($this->postgrestUrl, "/") . "/" . ltrim($endpoint, "/");
|
||||
$client = new Client();
|
||||
$url = rtrim($this->postgrestUrl, '/').'/'.ltrim($endpoint, '/');
|
||||
|
||||
try {
|
||||
$response = $client->request($method, $url, array_merge_recursive([
|
||||
"headers" => [
|
||||
"Authorization" => "Bearer {$this->postgrestApiKey}",
|
||||
"Content-Type" => "application/json",
|
||||
]
|
||||
], $options));
|
||||
try {
|
||||
$response = $client->request($method, $url, array_merge_recursive([
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$this->postgrestApiKey}",
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
], $options));
|
||||
|
||||
$responseBody = $response->getBody()->getContents();
|
||||
$responseBody = $response->getBody()->getContents();
|
||||
|
||||
if (empty($responseBody)) return [];
|
||||
if (empty($responseBody)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = json_decode($responseBody, true);
|
||||
$data = json_decode($responseBody, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) throw new \Exception("Invalid JSON: " . json_last_error_msg());
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new \Exception('Invalid JSON: '.json_last_error_msg());
|
||||
}
|
||||
|
||||
return $data;
|
||||
} catch (RequestException $e) {
|
||||
$response = $e->getResponse();
|
||||
$statusCode = $response ? $response->getStatusCode() : 'N/A';
|
||||
$responseBody = $response ? $response->getBody()->getContents() : 'No response';
|
||||
return $data;
|
||||
} catch (RequestException $e) {
|
||||
$response = $e->getResponse();
|
||||
$statusCode = $response ? $response->getStatusCode() : 'N/A';
|
||||
$responseBody = $response ? $response->getBody()->getContents() : 'No response';
|
||||
|
||||
throw new \Exception("HTTP {$method} {$url} failed with status {$statusCode}: {$responseBody}");
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception("Request error: " . $e->getMessage());
|
||||
}
|
||||
throw new \Exception("HTTP {$method} {$url} failed with status {$statusCode}: {$responseBody}");
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Request error: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function fetchFromApi(string $endpoint, string $query = ""): array
|
||||
protected function fetchFromApi(string $endpoint, string $query = ''): array
|
||||
{
|
||||
$url = $endpoint . ($query ? "?{$query}" : "");
|
||||
$url = $endpoint.($query ? "?{$query}" : '');
|
||||
|
||||
return $this->makeRequest("GET", $url);
|
||||
return $this->makeRequest('GET', $url);
|
||||
}
|
||||
|
||||
protected function sendResponse(array $data, int $statusCode = 200): void
|
||||
{
|
||||
http_response_code($statusCode);
|
||||
header("Content-Type: application/json");
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
echo json_encode($data);
|
||||
echo json_encode($data);
|
||||
|
||||
exit();
|
||||
exit();
|
||||
}
|
||||
|
||||
protected function sendErrorResponse(string $message, int $statusCode = 500): void
|
||||
{
|
||||
$this->sendResponse(["error" => $message], $statusCode);
|
||||
$this->sendResponse(['error' => $message], $statusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
class BookFetcher extends PageFetcher
|
||||
{
|
||||
class BookFetcher extends PageFetcher
|
||||
{
|
||||
public function fetch(string $url): ?array
|
||||
{
|
||||
$cacheKey = "book_" . md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
$cacheKey = 'book_'.md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
|
||||
if ($cached) return $cached;
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$book = $this->fetchSingleFromApi("optimized_books", $url);
|
||||
$book = $this->fetchSingleFromApi('optimized_books', $url);
|
||||
|
||||
if (!$book) return null;
|
||||
if (! $book) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$book['globals'] = $this->getGlobals();
|
||||
$book['globals'] = $this->getGlobals();
|
||||
|
||||
$this->cacheSet($cacheKey, $book);
|
||||
$this->cacheSet($cacheKey, $book);
|
||||
|
||||
return $book;
|
||||
return $book;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
class GenreFetcher extends PageFetcher
|
||||
{
|
||||
class GenreFetcher extends PageFetcher
|
||||
{
|
||||
public function fetch(string $url): ?array
|
||||
{
|
||||
$cacheKey = "genre_" . md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
$cacheKey = 'genre_'.md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
|
||||
if ($cached) return $cached;
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$genre = $this->fetchSingleFromApi("optimized_genres", $url);
|
||||
$genre = $this->fetchSingleFromApi('optimized_genres', $url);
|
||||
|
||||
if (!$genre) return null;
|
||||
if (! $genre) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$genre['globals'] = $this->getGlobals();
|
||||
$genre['globals'] = $this->getGlobals();
|
||||
|
||||
$this->cacheSet($cacheKey, $genre);
|
||||
$this->cacheSet($cacheKey, $genre);
|
||||
|
||||
return $genre;
|
||||
return $genre;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
class GlobalsFetcher extends PageFetcher
|
||||
{
|
||||
class GlobalsFetcher extends PageFetcher
|
||||
{
|
||||
public function fetch(): ?array
|
||||
{
|
||||
$cacheKey = "globals";
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
$cacheKey = 'globals';
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
|
||||
if ($cached) return $cached;
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$globals = $this->fetchFromApi("optimized_globals");
|
||||
$globals = $this->fetchFromApi('optimized_globals');
|
||||
|
||||
if (empty($globals)) return null;
|
||||
if (empty($globals)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->cacheSet($cacheKey, $globals[0]);
|
||||
$this->cacheSet($cacheKey, $globals[0]);
|
||||
|
||||
return $globals[0];
|
||||
return $globals[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,68 +2,73 @@
|
|||
|
||||
namespace App\Classes;
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
|
||||
class LatestListenHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 60;
|
||||
protected int $cacheTTL = 60;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->initializeCache();
|
||||
}
|
||||
|
||||
public function handleRequest(): void
|
||||
{
|
||||
$data = $this->getLatestListen();
|
||||
|
||||
if (!$data) {
|
||||
$this->sendResponse(["message" => "No recent tracks found"], 404);
|
||||
|
||||
return;
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->initializeCache();
|
||||
}
|
||||
|
||||
$this->sendResponse($data);
|
||||
}
|
||||
public function handleRequest(): void
|
||||
{
|
||||
$data = $this->getLatestListen();
|
||||
|
||||
public function getLatestListen(): ?array
|
||||
{
|
||||
try {
|
||||
$cachedData = $this->cache ? $this->cache->get("latest_listen") : null;
|
||||
if (! $data) {
|
||||
$this->sendResponse(['message' => 'No recent tracks found'], 404);
|
||||
|
||||
if ($cachedData) return json_decode($cachedData, true);
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->makeRequest("GET", "optimized_latest_listen?select=*");
|
||||
|
||||
if (!is_array($data) || empty($data[0])) return null;
|
||||
|
||||
$latestListen = $this->formatLatestListen($data[0]);
|
||||
|
||||
if ($this->cache) $this->cache->set("latest_listen", json_encode($latestListen), $this->cacheTTL);
|
||||
|
||||
return $latestListen;
|
||||
} catch (\Exception $e) {
|
||||
error_log("LatestListenHandler::getLatestListen error: " . $e->getMessage());
|
||||
return null;
|
||||
$this->sendResponse($data);
|
||||
}
|
||||
}
|
||||
|
||||
private function formatLatestListen(array $latestListen): array
|
||||
{
|
||||
$emoji = $latestListen["artist_emoji"] ?? ($latestListen["genre_emoji"] ?? "🎧");
|
||||
$trackName = htmlspecialchars($latestListen["track_name"] ?? "Unknown Track", ENT_QUOTES, "UTF-8");
|
||||
$artistName = htmlspecialchars($latestListen["artist_name"] ?? "Unknown Artist", ENT_QUOTES, "UTF-8");
|
||||
$url = htmlspecialchars($latestListen["url"] ?? "/", ENT_QUOTES, "UTF-8");
|
||||
public function getLatestListen(): ?array
|
||||
{
|
||||
try {
|
||||
$cachedData = $this->cache ? $this->cache->get('latest_listen') : null;
|
||||
|
||||
return [
|
||||
"content" => sprintf(
|
||||
'%s %s by <a href="%s">%s</a>',
|
||||
$emoji,
|
||||
$trackName,
|
||||
$url,
|
||||
$artistName
|
||||
),
|
||||
];
|
||||
}
|
||||
if ($cachedData) {
|
||||
return json_decode($cachedData, true);
|
||||
}
|
||||
|
||||
$data = $this->makeRequest('GET', 'optimized_latest_listen?select=*');
|
||||
|
||||
if (! is_array($data) || empty($data[0])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$latestListen = $this->formatLatestListen($data[0]);
|
||||
|
||||
if ($this->cache) {
|
||||
$this->cache->set('latest_listen', json_encode($latestListen), $this->cacheTTL);
|
||||
}
|
||||
|
||||
return $latestListen;
|
||||
} catch (\Exception $e) {
|
||||
error_log('LatestListenHandler::getLatestListen error: '.$e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function formatLatestListen(array $latestListen): array
|
||||
{
|
||||
$emoji = $latestListen['artist_emoji'] ?? ($latestListen['genre_emoji'] ?? '🎧');
|
||||
$trackName = htmlspecialchars($latestListen['track_name'] ?? 'Unknown Track', ENT_QUOTES, 'UTF-8');
|
||||
$artistName = htmlspecialchars($latestListen['artist_name'] ?? 'Unknown Artist', ENT_QUOTES, 'UTF-8');
|
||||
$url = htmlspecialchars($latestListen['url'] ?? '/', ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return [
|
||||
'content' => sprintf(
|
||||
'%s %s by <a href="%s">%s</a>',
|
||||
$emoji,
|
||||
$trackName,
|
||||
$url,
|
||||
$artistName
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
class MovieFetcher extends PageFetcher
|
||||
{
|
||||
class MovieFetcher extends PageFetcher
|
||||
{
|
||||
public function fetch(string $url): ?array
|
||||
{
|
||||
$cacheKey = "movie_" . md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
$cacheKey = 'movie_'.md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
|
||||
if ($cached) return $cached;
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$movie = $this->fetchSingleFromApi("optimized_movies", $url);
|
||||
$movie = $this->fetchSingleFromApi('optimized_movies', $url);
|
||||
|
||||
if (!$movie) return null;
|
||||
if (! $movie) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$movie['globals'] = $this->getGlobals();
|
||||
$movie['globals'] = $this->getGlobals();
|
||||
|
||||
$this->cacheSet($cacheKey, $movie);
|
||||
$this->cacheSet($cacheKey, $movie);
|
||||
|
||||
return $movie;
|
||||
return $movie;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,23 +4,27 @@ namespace App\Classes;
|
|||
|
||||
class MusicDataHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 300;
|
||||
protected int $cacheTTL = 300;
|
||||
|
||||
public function getThisWeekData(): array
|
||||
{
|
||||
$cacheKey = 'music_week_data';
|
||||
$cached = $this->cache ? $this->cache->get($cacheKey) : null;
|
||||
public function getThisWeekData(): array
|
||||
{
|
||||
$cacheKey = 'music_week_data';
|
||||
$cached = $this->cache ? $this->cache->get($cacheKey) : null;
|
||||
|
||||
if ($cached) return json_decode($cached, true);
|
||||
if ($cached) {
|
||||
return json_decode($cached, true);
|
||||
}
|
||||
|
||||
$response = $this->makeRequest('GET', 'optimized_week_music?select=*');
|
||||
$music = $response[0]['week_music'] ?? [];
|
||||
$music['total_tracks'] = $music['week_summary']['total_tracks'] ?? 0;
|
||||
$music['total_artists'] = $music['week_summary']['total_artists'] ?? 0;
|
||||
$music['total_albums'] = $music['week_summary']['total_albums'] ?? 0;
|
||||
$response = $this->makeRequest('GET', 'optimized_week_music?select=*');
|
||||
$music = $response[0]['week_music'] ?? [];
|
||||
$music['total_tracks'] = $music['week_summary']['total_tracks'] ?? 0;
|
||||
$music['total_artists'] = $music['week_summary']['total_artists'] ?? 0;
|
||||
$music['total_albums'] = $music['week_summary']['total_albums'] ?? 0;
|
||||
|
||||
if ($this->cache) $this->cache->set($cacheKey, json_encode($music), $this->cacheTTL);
|
||||
if ($this->cache) {
|
||||
$this->cache->set($cacheKey, json_encode($music), $this->cacheTTL);
|
||||
}
|
||||
|
||||
return $music;
|
||||
}
|
||||
return $music;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,43 +2,44 @@
|
|||
|
||||
namespace App\Classes;
|
||||
|
||||
use App\Classes\BaseHandler;
|
||||
use App\Classes\GlobalsFetcher;
|
||||
|
||||
abstract class PageFetcher extends BaseHandler
|
||||
{
|
||||
protected ?array $globals = null;
|
||||
protected ?array $globals = null;
|
||||
|
||||
protected function cacheGet(string $key): mixed
|
||||
{
|
||||
return $this->cache && $this->cache->exists($key) ? json_decode($this->cache->get($key), true) : null;
|
||||
}
|
||||
protected function cacheGet(string $key): mixed
|
||||
{
|
||||
return $this->cache && $this->cache->exists($key) ? json_decode($this->cache->get($key), true) : null;
|
||||
}
|
||||
|
||||
protected function cacheSet(string $key, mixed $value, int $ttl = 300): void
|
||||
{
|
||||
if ($this->cache) $this->cache->setex($key, $ttl, json_encode($value));
|
||||
}
|
||||
protected function cacheSet(string $key, mixed $value, int $ttl = 300): void
|
||||
{
|
||||
if ($this->cache) {
|
||||
$this->cache->setex($key, $ttl, json_encode($value));
|
||||
}
|
||||
}
|
||||
|
||||
protected function fetchSingleFromApi(string $endpoint, string $url): ?array
|
||||
{
|
||||
$data = $this->fetchFromApi($endpoint, "url=eq./{$url}");
|
||||
protected function fetchSingleFromApi(string $endpoint, string $url): ?array
|
||||
{
|
||||
$data = $this->fetchFromApi($endpoint, "url=eq./{$url}");
|
||||
|
||||
return $data[0] ?? null;
|
||||
}
|
||||
return $data[0] ?? null;
|
||||
}
|
||||
|
||||
protected function fetchPostRpc(string $endpoint, array $body): ?array
|
||||
{
|
||||
return $this->makeRequest("POST", $endpoint, ['json' => $body]);
|
||||
}
|
||||
protected function fetchPostRpc(string $endpoint, array $body): ?array
|
||||
{
|
||||
return $this->makeRequest('POST', $endpoint, ['json' => $body]);
|
||||
}
|
||||
|
||||
public function getGlobals(): ?array
|
||||
{
|
||||
if ($this->globals !== null) return $this->globals;
|
||||
public function getGlobals(): ?array
|
||||
{
|
||||
if ($this->globals !== null) {
|
||||
return $this->globals;
|
||||
}
|
||||
|
||||
$fetcher = new GlobalsFetcher();
|
||||
$fetcher = new GlobalsFetcher();
|
||||
|
||||
$this->globals = $fetcher->fetch();
|
||||
$this->globals = $fetcher->fetch();
|
||||
|
||||
return $this->globals;
|
||||
}
|
||||
return $this->globals;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,33 +4,37 @@ namespace App\Classes;
|
|||
|
||||
class RecentMediaHandler extends BaseHandler
|
||||
{
|
||||
protected int $cacheTTL = 300;
|
||||
protected int $cacheTTL = 300;
|
||||
|
||||
public function getRecentMedia(): array
|
||||
{
|
||||
try {
|
||||
$cacheKey = 'recent_media';
|
||||
public function getRecentMedia(): array
|
||||
{
|
||||
try {
|
||||
$cacheKey = 'recent_media';
|
||||
|
||||
if ($this->cache) {
|
||||
$cached = $this->cache->get($cacheKey);
|
||||
if ($this->cache) {
|
||||
$cached = $this->cache->get($cacheKey);
|
||||
|
||||
if ($cached) return json_decode($cached, true);
|
||||
}
|
||||
if ($cached) {
|
||||
return json_decode($cached, true);
|
||||
}
|
||||
}
|
||||
|
||||
$response = $this->makeRequest("GET", "optimized_recent_media?select=*");
|
||||
$activity = $response[0]['recent_activity'] ?? [];
|
||||
$data = [
|
||||
'recentMusic' => $activity['recentMusic'] ?? [],
|
||||
'recentWatchedRead' => $activity['recentWatchedRead'] ?? [],
|
||||
];
|
||||
$response = $this->makeRequest('GET', 'optimized_recent_media?select=*');
|
||||
$activity = $response[0]['recent_activity'] ?? [];
|
||||
$data = [
|
||||
'recentMusic' => $activity['recentMusic'] ?? [],
|
||||
'recentWatchedRead' => $activity['recentWatchedRead'] ?? [],
|
||||
];
|
||||
|
||||
if ($this->cache) $this->cache->set($cacheKey, json_encode($data), $this->cacheTTL);
|
||||
if ($this->cache) {
|
||||
$this->cache->set($cacheKey, json_encode($data), $this->cacheTTL);
|
||||
}
|
||||
|
||||
return $data;
|
||||
} catch (\Exception $e) {
|
||||
error_log("RecentMediaHandler error: " . $e->getMessage());
|
||||
return $data;
|
||||
} catch (\Exception $e) {
|
||||
error_log('RecentMediaHandler error: '.$e->getMessage());
|
||||
|
||||
return ['recentMusic' => [], 'recentWatchedRead' => []];
|
||||
return ['recentMusic' => [], 'recentWatchedRead' => []];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Classes;
|
||||
namespace App\Classes;
|
||||
|
||||
class ShowFetcher extends PageFetcher
|
||||
{
|
||||
class ShowFetcher extends PageFetcher
|
||||
{
|
||||
public function fetch(string $url): ?array
|
||||
{
|
||||
$cacheKey = "show_" . md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
$cacheKey = 'show_'.md5($url);
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
|
||||
if ($cached) return $cached;
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$show = $this->fetchSingleFromApi("optimized_shows", $url);
|
||||
$show = $this->fetchSingleFromApi('optimized_shows', $url);
|
||||
|
||||
if (!$show) return null;
|
||||
if (! $show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$show['globals'] = $this->getGlobals();
|
||||
$show['globals'] = $this->getGlobals();
|
||||
|
||||
$this->cacheSet($cacheKey, $show);
|
||||
$this->cacheSet($cacheKey, $show);
|
||||
|
||||
return $show;
|
||||
return $show;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,26 +4,30 @@ namespace App\Classes;
|
|||
|
||||
class TagFetcher extends PageFetcher
|
||||
{
|
||||
public function fetch(string $tag, int $page = 1, int $pageSize = 20): ?array
|
||||
{
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
$cacheKey = "tag_" . md5("{$tag}_{$page}");
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
public function fetch(string $tag, int $page = 1, int $pageSize = 20): ?array
|
||||
{
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
$cacheKey = 'tag_'.md5("{$tag}_{$page}");
|
||||
$cached = $this->cacheGet($cacheKey);
|
||||
|
||||
if ($cached) return $cached;
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$results = $this->fetchPostRpc("rpc/get_tagged_content", [
|
||||
"tag_query" => $tag,
|
||||
"page_size" => $pageSize,
|
||||
"page_offset" => $offset
|
||||
]);
|
||||
$results = $this->fetchPostRpc('rpc/get_tagged_content', [
|
||||
'tag_query' => $tag,
|
||||
'page_size' => $pageSize,
|
||||
'page_offset' => $offset,
|
||||
]);
|
||||
|
||||
if (!$results || count($results) === 0) return null;
|
||||
if (! $results || count($results) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$results[0]['globals'] = $this->getGlobals();
|
||||
$results[0]['globals'] = $this->getGlobals();
|
||||
|
||||
$this->cacheSet($cacheKey, $results);
|
||||
$this->cacheSet($cacheKey, $results);
|
||||
|
||||
return $results;
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('getTablerIcon')) {
|
||||
if (! function_exists('getTablerIcon')) {
|
||||
function getTablerIcon($iconName, $class = '', $size = 24)
|
||||
{
|
||||
$icons = [
|
||||
'arrow-left' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" /></svg>',
|
||||
'arrow-right' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M13 18l6 -6" /><path d="M13 6l6 6" /></svg>',
|
||||
'article' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>',
|
||||
'books' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-books"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M9 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M5 8h4" /><path d="M9 16h4" /><path d="M13.803 4.56l2.184 -.53c.562 -.135 1.133 .19 1.282 .732l3.695 13.418a1.02 1.02 0 0 1 -.634 1.219l-.133 .041l-2.184 .53c-.562 .135 -1.133 -.19 -1.282 -.732l-3.695 -13.418a1.02 1.02 0 0 1 .634 -1.219l.133 -.041z" /><path d="M14 9l4 -1" /><path d="M16 16l3.923 -.98" /></svg>',
|
||||
'device-tv-old' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-tv-old"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M16 3l-4 4l-4 -4" /><path d="M15 7v13" /><path d="M18 15v.01" /><path d="M18 12v.01" /></svg>',
|
||||
'headphones' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-headphones"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M15 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M4 15v-3a8 8 0 0 1 16 0v3" /></svg>',
|
||||
'movie' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M8 4l0 16" /><path d="M16 4l0 16" /><path d="M4 8l4 0" /><path d="M4 16l4 0" /><path d="M4 12l16 0" /><path d="M16 8l4 0" /><path d="M16 16l4 0" /></svg>',
|
||||
'star' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-star"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>',
|
||||
'vinyl' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-vinyl"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 3.937a9 9 0 1 0 5 8.063" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4l-3.5 10l-2.5 2" /></svg>'
|
||||
];
|
||||
$icons = [
|
||||
'arrow-left' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M5 12l6 6" /><path d="M5 12l6 -6" /></svg>',
|
||||
'arrow-right' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0" /><path d="M13 18l6 -6" /><path d="M13 6l6 6" /></svg>',
|
||||
'article' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>',
|
||||
'books' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-books"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M9 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M5 8h4" /><path d="M9 16h4" /><path d="M13.803 4.56l2.184 -.53c.562 -.135 1.133 .19 1.282 .732l3.695 13.418a1.02 1.02 0 0 1 -.634 1.219l-.133 .041l-2.184 .53c-.562 .135 -1.133 -.19 -1.282 -.732l-3.695 -13.418a1.02 1.02 0 0 1 .634 -1.219l.133 -.041z" /><path d="M14 9l4 -1" /><path d="M16 16l3.923 -.98" /></svg>',
|
||||
'device-tv-old' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-tv-old"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M16 3l-4 4l-4 -4" /><path d="M15 7v13" /><path d="M18 15v.01" /><path d="M18 12v.01" /></svg>',
|
||||
'headphones' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-headphones"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M15 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M4 15v-3a8 8 0 0 1 16 0v3" /></svg>',
|
||||
'movie' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M8 4l0 16" /><path d="M16 4l0 16" /><path d="M4 8l4 0" /><path d="M4 16l4 0" /><path d="M4 12l16 0" /><path d="M16 8l4 0" /><path d="M16 16l4 0" /></svg>',
|
||||
'star' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-star"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873z" /></svg>',
|
||||
'vinyl' => '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-vinyl"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 3.937a9 9 0 1 0 5 8.063" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M20 4l-3.5 10l-2.5 2" /></svg>',
|
||||
];
|
||||
|
||||
return $icons[$iconName] ?? '<span class="icon-placeholder">[Missing: ' . htmlspecialchars($iconName) . ']</span>';
|
||||
return $icons[$iconName] ?? '<span class="icon-placeholder">[Missing: '.htmlspecialchars($iconName).']</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/icons.php';
|
||||
require_once __DIR__ . '/media.php';
|
||||
require_once __DIR__ . '/metadata.php';
|
||||
require_once __DIR__ . '/paginator.php';
|
||||
require_once __DIR__ . '/routing.php';
|
||||
require_once __DIR__ . '/strings.php';
|
||||
require_once __DIR__ . '/tags.php';
|
||||
|
||||
require_once __DIR__.'/icons.php';
|
||||
require_once __DIR__.'/media.php';
|
||||
require_once __DIR__.'/metadata.php';
|
||||
require_once __DIR__.'/paginator.php';
|
||||
require_once __DIR__.'/routing.php';
|
||||
require_once __DIR__.'/strings.php';
|
||||
require_once __DIR__.'/tags.php';
|
||||
|
|
|
@ -1,154 +1,167 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('renderMediaGrid')) {
|
||||
if (! function_exists('renderMediaGrid')) {
|
||||
function renderMediaGrid(array $items, int $count = 0, string $loading = 'lazy')
|
||||
{
|
||||
$limit = $count > 0 ? $count : count($items);
|
||||
$firstType = $items[0]['type'] ?? ($items[0]['grid']['type'] ?? '');
|
||||
$shapeClass = in_array($firstType, ['books', 'movies', 'tv']) ? 'vertical' : 'square';
|
||||
$limit = $count > 0 ? $count : count($items);
|
||||
$firstType = $items[0]['type'] ?? ($items[0]['grid']['type'] ?? '');
|
||||
$shapeClass = in_array($firstType, ['books', 'movies', 'tv']) ? 'vertical' : 'square';
|
||||
|
||||
echo '<div class="media-grid ' . $shapeClass . '">';
|
||||
echo '<div class="media-grid '.$shapeClass.'">';
|
||||
|
||||
foreach (array_slice($items, 0, $limit) as $item) {
|
||||
$grid = $item['grid'] ?? $item;
|
||||
$alt = htmlspecialchars($grid['alt'] ?? '');
|
||||
$image = htmlspecialchars($grid['image'] ?? '');
|
||||
$title = htmlspecialchars($grid['title'] ?? '');
|
||||
$subtext = htmlspecialchars($grid['subtext'] ?? '');
|
||||
$url = $grid['url'] ?? null;
|
||||
$type = $item['type'] ?? '';
|
||||
$isVertical = in_array($type, ['books', 'movies', 'tv']);
|
||||
$imageClass = $isVertical ? 'vertical' : 'square';
|
||||
$width = $isVertical ? 120 : 150;
|
||||
$height = $isVertical ? 184 : 150;
|
||||
foreach (array_slice($items, 0, $limit) as $item) {
|
||||
$grid = $item['grid'] ?? $item;
|
||||
$alt = htmlspecialchars($grid['alt'] ?? '');
|
||||
$image = htmlspecialchars($grid['image'] ?? '');
|
||||
$title = htmlspecialchars($grid['title'] ?? '');
|
||||
$subtext = htmlspecialchars($grid['subtext'] ?? '');
|
||||
$url = $grid['url'] ?? null;
|
||||
$type = $item['type'] ?? '';
|
||||
$isVertical = in_array($type, ['books', 'movies', 'tv']);
|
||||
$imageClass = $isVertical ? 'vertical' : 'square';
|
||||
$width = $isVertical ? 120 : 150;
|
||||
$height = $isVertical ? 184 : 150;
|
||||
|
||||
$openLink = $url ? '<a class="' . htmlspecialchars($type) . '" href="' . htmlspecialchars($url) . '" title="' . $alt . '">' : '';
|
||||
$closeLink = $url ? '</a>' : '';
|
||||
$openLink = $url ? '<a class="'.htmlspecialchars($type).'" href="'.htmlspecialchars($url).'" title="'.$alt.'">' : '';
|
||||
$closeLink = $url ? '</a>' : '';
|
||||
|
||||
echo $openLink;
|
||||
echo '<div class="media-grid-item">';
|
||||
echo $openLink;
|
||||
echo '<div class="media-grid-item">';
|
||||
|
||||
if ($title || $subtext) {
|
||||
echo '<div class="meta-text media-highlight">';
|
||||
if ($title) echo '<div class="header">' . $title . '</div>';
|
||||
if ($subtext) echo '<div class="subheader">' . $subtext . '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '<img
|
||||
srcset="' . $image . '?class=' . $imageClass . 'sm&type=webp ' . $width . 'w, ' .
|
||||
$image . '?class=' . $imageClass . 'md&type=webp ' . ($width * 2) . 'w"
|
||||
sizes="(max-width: 450px) ' . $width . 'px, ' . ($width * 2) . 'px"
|
||||
src="' . $image . '?class=' . $imageClass . 'sm&type=webp"
|
||||
alt="' . $alt . '"
|
||||
loading="' . $loading . '"
|
||||
decoding="async"
|
||||
width="' . $width . '"
|
||||
height="' . $height . '"
|
||||
>';
|
||||
echo '</div>';
|
||||
echo $closeLink;
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('renderAssociatedMedia')) {
|
||||
function renderAssociatedMedia(
|
||||
array $artists = [],
|
||||
array $books = [],
|
||||
array $genres = [],
|
||||
array $movies = [],
|
||||
array $posts = [],
|
||||
array $shows = [],
|
||||
)
|
||||
{
|
||||
$sections = [
|
||||
"artists" => ["icon" => "headphones", "css_class" => "music", "label" => "Related artist(s)", "hasGrid" => true],
|
||||
"books" => ["icon" => "books", "css_class" => "books", "label" => "Related book(s)", "hasGrid" => true],
|
||||
"genres" => ["icon" => "headphones", "css_class" => "music", "label" => "Related genre(s)", "hasGrid" => false],
|
||||
"movies" => ["icon" => "movie", "css_class" => "movies", "label" => "Related movie(s)", "hasGrid" => true],
|
||||
"posts" => ["icon" => "article", "css_class" => "article", "label" => "Related post(s)", "hasGrid" => false],
|
||||
"shows" => ["icon" => "device-tv-old", "css_class" => "tv", "label" => "Related show(s)", "hasGrid" => true]
|
||||
];
|
||||
|
||||
$allMedia = compact('artists', 'books', 'genres', 'movies', 'posts', 'shows');
|
||||
|
||||
echo '<div class="associated-media">';
|
||||
|
||||
foreach ($sections as $key => $section) {
|
||||
$items = $allMedia[$key];
|
||||
|
||||
if (empty($items)) continue;
|
||||
|
||||
echo '<h3 id="' . htmlspecialchars($key) . '" class="' . htmlspecialchars($section['css_class']) . '">';
|
||||
echo getTablerIcon($section['icon']);
|
||||
echo htmlspecialchars($section['label']);
|
||||
echo '</h3>';
|
||||
|
||||
if ($section['hasGrid']) {
|
||||
renderMediaGrid($items);
|
||||
} else {
|
||||
echo '<ul>';
|
||||
foreach ($items as $item) {
|
||||
echo '<li class="' . htmlspecialchars($section['css_class']) . '">';
|
||||
echo '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title'] ?? $item['name'] ?? '') . '</a>';
|
||||
|
||||
if ($key === "artists" && isset($item['total_plays']) && $item['total_plays'] > 0) {
|
||||
echo ' (' . htmlspecialchars($item['total_plays']) . ' play' . ($item['total_plays'] > 1 ? 's' : '') . ')';
|
||||
} elseif ($key === "books" && isset($item['author'])) {
|
||||
echo ' by ' . htmlspecialchars($item['author']);
|
||||
} elseif (($key === "movies" || $key === "shows") && isset($item['year'])) {
|
||||
echo ' (' . htmlspecialchars($item['year']) . ')';
|
||||
} elseif ($key === "posts" && isset($item['date'])) {
|
||||
echo ' (' . date("F j, Y", strtotime($item['date'])) . ')';
|
||||
if ($title || $subtext) {
|
||||
echo '<div class="meta-text media-highlight">';
|
||||
if ($title) {
|
||||
echo '<div class="header">'.$title.'</div>';
|
||||
}
|
||||
if ($subtext) {
|
||||
echo '<div class="subheader">'.$subtext.'</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
echo '<img
|
||||
srcset="'.$image.'?class='.$imageClass.'sm&type=webp '.$width.'w, '.
|
||||
$image.'?class='.$imageClass.'md&type=webp '.($width * 2).'w"
|
||||
sizes="(max-width: 450px) '.$width.'px, '.($width * 2).'px"
|
||||
src="'.$image.'?class='.$imageClass.'sm&type=webp"
|
||||
alt="'.$alt.'"
|
||||
loading="'.$loading.'"
|
||||
decoding="async"
|
||||
width="'.$width.'"
|
||||
height="'.$height.'"
|
||||
>';
|
||||
echo '</div>';
|
||||
echo $closeLink;
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('renderMediaLinks')) {
|
||||
function renderMediaLinks(array $data, string $type, int $count = 10): string {
|
||||
if (empty($data) || empty($type)) return "";
|
||||
if (! function_exists('renderAssociatedMedia')) {
|
||||
function renderAssociatedMedia(
|
||||
array $artists = [],
|
||||
array $books = [],
|
||||
array $genres = [],
|
||||
array $movies = [],
|
||||
array $posts = [],
|
||||
array $shows = [],
|
||||
) {
|
||||
$sections = [
|
||||
'artists' => ['icon' => 'headphones', 'css_class' => 'music', 'label' => 'Related artist(s)', 'hasGrid' => true],
|
||||
'books' => ['icon' => 'books', 'css_class' => 'books', 'label' => 'Related book(s)', 'hasGrid' => true],
|
||||
'genres' => ['icon' => 'headphones', 'css_class' => 'music', 'label' => 'Related genre(s)', 'hasGrid' => false],
|
||||
'movies' => ['icon' => 'movie', 'css_class' => 'movies', 'label' => 'Related movie(s)', 'hasGrid' => true],
|
||||
'posts' => ['icon' => 'article', 'css_class' => 'article', 'label' => 'Related post(s)', 'hasGrid' => false],
|
||||
'shows' => ['icon' => 'device-tv-old', 'css_class' => 'tv', 'label' => 'Related show(s)', 'hasGrid' => true],
|
||||
];
|
||||
|
||||
$slice = array_slice($data, 0, $count);
|
||||
$allMedia = compact('artists', 'books', 'genres', 'movies', 'posts', 'shows');
|
||||
|
||||
if (count($slice) === 0) return "";
|
||||
echo '<div class="associated-media">';
|
||||
|
||||
$buildLink = function ($item) use ($type) {
|
||||
switch ($type) {
|
||||
case "genre":
|
||||
return '<a href="' . htmlspecialchars($item['genre_url']) . '">' . htmlspecialchars($item['genre_name']) . '</a>';
|
||||
case "artist":
|
||||
return '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['name']) . '</a>';
|
||||
case "book":
|
||||
return '<a href="' . htmlspecialchars($item['url']) . '">' . htmlspecialchars($item['title']) . '</a>';
|
||||
default:
|
||||
foreach ($sections as $key => $section) {
|
||||
$items = $allMedia[$key];
|
||||
|
||||
if (empty($items)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
echo '<h3 id="'.htmlspecialchars($key).'" class="'.htmlspecialchars($section['css_class']).'">';
|
||||
echo getTablerIcon($section['icon']);
|
||||
echo htmlspecialchars($section['label']);
|
||||
echo '</h3>';
|
||||
|
||||
if ($section['hasGrid']) {
|
||||
renderMediaGrid($items);
|
||||
} else {
|
||||
echo '<ul>';
|
||||
foreach ($items as $item) {
|
||||
echo '<li class="'.htmlspecialchars($section['css_class']).'">';
|
||||
echo '<a href="'.htmlspecialchars($item['url']).'">'.htmlspecialchars($item['title'] ?? $item['name'] ?? '').'</a>';
|
||||
|
||||
if ($key === 'artists' && isset($item['total_plays']) && $item['total_plays'] > 0) {
|
||||
echo ' ('.htmlspecialchars($item['total_plays']).' play'.($item['total_plays'] > 1 ? 's' : '').')';
|
||||
} elseif ($key === 'books' && isset($item['author'])) {
|
||||
echo ' by '.htmlspecialchars($item['author']);
|
||||
} elseif (($key === 'movies' || $key === 'shows') && isset($item['year'])) {
|
||||
echo ' ('.htmlspecialchars($item['year']).')';
|
||||
} elseif ($key === 'posts' && isset($item['date'])) {
|
||||
echo ' ('.date('F j, Y', strtotime($item['date'])).')';
|
||||
}
|
||||
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('renderMediaLinks')) {
|
||||
function renderMediaLinks(array $data, string $type, int $count = 10): string
|
||||
{
|
||||
if (empty($data) || empty($type)) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
if (count($slice) === 1) return $buildLink($slice[0]);
|
||||
$slice = array_slice($data, 0, $count);
|
||||
|
||||
$links = array_map($buildLink, $slice);
|
||||
$last = array_pop($links);
|
||||
return implode(', ', $links) . ' and ' . $last;
|
||||
if (count($slice) === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$buildLink = function ($item) use ($type) {
|
||||
switch ($type) {
|
||||
case 'genre':
|
||||
return '<a href="'.htmlspecialchars($item['genre_url']).'">'.htmlspecialchars($item['genre_name']).'</a>';
|
||||
case 'artist':
|
||||
return '<a href="'.htmlspecialchars($item['url']).'">'.htmlspecialchars($item['name']).'</a>';
|
||||
case 'book':
|
||||
return '<a href="'.htmlspecialchars($item['url']).'">'.htmlspecialchars($item['title']).'</a>';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
if (count($slice) === 1) {
|
||||
return $buildLink($slice[0]);
|
||||
}
|
||||
|
||||
$links = array_map($buildLink, $slice);
|
||||
$last = array_pop($links);
|
||||
|
||||
return implode(', ', $links).' and '.$last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('sanitizeMediaString')) {
|
||||
if (! function_exists('sanitizeMediaString')) {
|
||||
function sanitizeMediaString(string $str): string
|
||||
{
|
||||
$sanitizedString = preg_replace("/[^a-zA-Z0-9\s-]/", "", iconv("UTF-8", "ASCII//TRANSLIT", $str));
|
||||
$sanitizedString = preg_replace("/[^a-zA-Z0-9\s-]/", '', iconv('UTF-8', 'ASCII//TRANSLIT', $str));
|
||||
|
||||
return strtolower(trim(preg_replace("/[\s-]+/", "-", $sanitizedString), "-"));
|
||||
return strtolower(trim(preg_replace("/[\s-]+/", '-', $sanitizedString), '-'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,38 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('setupPageMetadata')) {
|
||||
if (! function_exists('setupPageMetadata')) {
|
||||
function setupPageMetadata(array $page, string $requestUri): array
|
||||
{
|
||||
$globals = $page['globals'] ?? [];
|
||||
$globals = $page['globals'] ?? [];
|
||||
|
||||
$title = htmlspecialchars($page["metadata"]["title"] ?? "", ENT_QUOTES, "UTF-8");
|
||||
$description = htmlspecialchars($page["metadata"]["description"] ?? "", ENT_QUOTES, "UTF-8");
|
||||
$image = htmlspecialchars(
|
||||
$page["metadata"]["open_graph_image"] ?? $globals["metadata"]["open_graph_image"] ?? "",
|
||||
ENT_QUOTES,
|
||||
"UTF-8"
|
||||
);
|
||||
$fullUrl = $globals["url"] . $requestUri;
|
||||
$oembedUrl = $globals["url"] . "/oembed" . $requestUri;
|
||||
$title = htmlspecialchars($page['metadata']['title'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$description = htmlspecialchars($page['metadata']['description'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$image = htmlspecialchars(
|
||||
$page['metadata']['open_graph_image'] ?? $globals['metadata']['open_graph_image'] ?? '',
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
);
|
||||
$fullUrl = $globals['url'].$requestUri;
|
||||
$oembedUrl = $globals['url'].'/oembed'.$requestUri;
|
||||
|
||||
return [
|
||||
'pageTitle' => $title,
|
||||
'pageDescription' => $description,
|
||||
'ogImage' => $image,
|
||||
'fullUrl' => $fullUrl,
|
||||
'oembedUrl' => $oembedUrl,
|
||||
'globals' => $globals
|
||||
];
|
||||
return [
|
||||
'pageTitle' => $title,
|
||||
'pageDescription' => $description,
|
||||
'ogImage' => $image,
|
||||
'fullUrl' => $fullUrl,
|
||||
'oembedUrl' => $oembedUrl,
|
||||
'globals' => $globals,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cleanMeta')) {
|
||||
if (! function_exists('cleanMeta')) {
|
||||
function cleanMeta($value)
|
||||
{
|
||||
$value = trim($value ?? '');
|
||||
$value = str_replace(["\r", "\n"], ' ', $value);
|
||||
$value = preg_replace('/\s+/', ' ', $value);
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
$value = trim($value ?? '');
|
||||
$value = str_replace(["\r", "\n"], ' ', $value);
|
||||
$value = preg_replace('/\s+/', ' ', $value);
|
||||
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +1,43 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('renderPaginator')) {
|
||||
if (! function_exists('renderPaginator')) {
|
||||
function renderPaginator(array $pagination, int $totalPages): void
|
||||
{
|
||||
if (!$pagination || $totalPages <= 1) return;
|
||||
?>
|
||||
if (! $pagination || $totalPages <= 1) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<script type="module" src="/assets/scripts/components/select-pagination.js" defer></script>
|
||||
<nav aria-label="Pagination" class="pagination">
|
||||
<?php if (!empty($pagination['href']['previous'])): ?>
|
||||
<?php if (! empty($pagination['href']['previous'])) { ?>
|
||||
<a href="<?= $pagination['href']['previous'] ?>" aria-label="Previous page">
|
||||
<?= getTablerIcon('arrow-left') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php } else { ?>
|
||||
<span><?= getTablerIcon('arrow-left') ?></span>
|
||||
<?php endif; ?>
|
||||
<?php } ?>
|
||||
<select-pagination data-base-index="1">
|
||||
<select class="client-side" aria-label="Page selection">
|
||||
<?php foreach ($pagination['pages'] as $i): ?>
|
||||
<?php foreach ($pagination['pages'] as $i) { ?>
|
||||
<option value="<?= $i ?>" <?= ($pagination['pageNumber'] === $i) ? 'selected' : '' ?>>
|
||||
<?= $i ?> of <?= $totalPages ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<noscript>
|
||||
<p><span aria-current="page"><?= $pagination['pageNumber'] ?></span> of <?= $totalPages ?></p>
|
||||
</noscript>
|
||||
</select-pagination>
|
||||
<?php if (!empty($pagination['href']['next'])): ?>
|
||||
<?php if (! empty($pagination['href']['next'])) { ?>
|
||||
<a href="<?= $pagination['href']['next'] ?>" aria-label="Next page">
|
||||
<?= getTablerIcon('arrow-right') ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php } else { ?>
|
||||
<span><?= getTablerIcon('arrow-right') ?></span>
|
||||
<?php endif; ?>
|
||||
<?php } ?>
|
||||
</nav>
|
||||
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('redirectTo404')) {
|
||||
if (! function_exists('redirectTo404')) {
|
||||
function redirectTo404(): void
|
||||
{
|
||||
header("Location: /404/", true, 302);
|
||||
exit();
|
||||
header('Location: /404/', true, 302);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,82 +1,94 @@
|
|||
<?php
|
||||
|
||||
use Kaoken\MarkdownIt\MarkdownIt;
|
||||
use Kaoken\MarkdownIt\Plugins\MarkdownItFootnote;
|
||||
use Kaoken\MarkdownIt\MarkdownIt;
|
||||
use Kaoken\MarkdownIt\Plugins\MarkdownItFootnote;
|
||||
|
||||
if (!function_exists('truncateText')) {
|
||||
function truncateText($text, $limit = 50, $ellipsis = "...")
|
||||
if (! function_exists('truncateText')) {
|
||||
function truncateText($text, $limit = 50, $ellipsis = '...')
|
||||
{
|
||||
if (mb_strwidth($text, "UTF-8") <= $limit) return $text;
|
||||
|
||||
$truncated = mb_substr($text, 0, $limit, "UTF-8");
|
||||
$lastSpace = mb_strrpos($truncated, " ", 0, "UTF-8");
|
||||
|
||||
if ($lastSpace !== false) $truncated = mb_substr($truncated, 0, $lastSpace, "UTF-8");
|
||||
|
||||
return $truncated . $ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('parseMarkdown')) {
|
||||
function parseMarkdown($markdown)
|
||||
{
|
||||
if (empty($markdown)) return '';
|
||||
|
||||
$md = new MarkdownIt([
|
||||
"html" => true,
|
||||
"linkify" => true,
|
||||
]);
|
||||
|
||||
$md->plugin(new MarkdownItFootnote());
|
||||
|
||||
return $md->render($markdown);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('parseCountryField')) {
|
||||
function parseCountryField($countryField)
|
||||
{
|
||||
if (empty($countryField)) return null;
|
||||
|
||||
$delimiters = [',', '/', '&', ' and '];
|
||||
$countries = [$countryField];
|
||||
|
||||
foreach ($delimiters as $delimiter) {
|
||||
$tempCountries = [];
|
||||
|
||||
foreach ($countries as $country) {
|
||||
$tempCountries = array_merge($tempCountries, explode($delimiter, $country));
|
||||
if (mb_strwidth($text, 'UTF-8') <= $limit) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$countries = $tempCountries;
|
||||
}
|
||||
$truncated = mb_substr($text, 0, $limit, 'UTF-8');
|
||||
$lastSpace = mb_strrpos($truncated, ' ', 0, 'UTF-8');
|
||||
|
||||
$countries = array_map('trim', $countries);
|
||||
$countries = array_map('getCountryName', $countries);
|
||||
$countries = array_filter($countries);
|
||||
if ($lastSpace !== false) {
|
||||
$truncated = mb_substr($truncated, 0, $lastSpace, 'UTF-8');
|
||||
}
|
||||
|
||||
return implode(', ', array_unique($countries));
|
||||
return $truncated.$ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getCountryName')) {
|
||||
if (! function_exists('parseMarkdown')) {
|
||||
function parseMarkdown($markdown)
|
||||
{
|
||||
if (empty($markdown)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$md = new MarkdownIt([
|
||||
'html' => true,
|
||||
'linkify' => true,
|
||||
]);
|
||||
|
||||
$md->plugin(new MarkdownItFootnote());
|
||||
|
||||
return $md->render($markdown);
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('parseCountryField')) {
|
||||
function parseCountryField($countryField)
|
||||
{
|
||||
if (empty($countryField)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$delimiters = [',', '/', '&', ' and '];
|
||||
$countries = [$countryField];
|
||||
|
||||
foreach ($delimiters as $delimiter) {
|
||||
$tempCountries = [];
|
||||
|
||||
foreach ($countries as $country) {
|
||||
$tempCountries = array_merge($tempCountries, explode($delimiter, $country));
|
||||
}
|
||||
|
||||
$countries = $tempCountries;
|
||||
}
|
||||
|
||||
$countries = array_map('trim', $countries);
|
||||
$countries = array_map('getCountryName', $countries);
|
||||
$countries = array_filter($countries);
|
||||
|
||||
return implode(', ', array_unique($countries));
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('getCountryName')) {
|
||||
function getCountryName($countryName)
|
||||
{
|
||||
$isoCodes = new \Sokil\IsoCodes\IsoCodesFactory();
|
||||
$countries = $isoCodes->getCountries();
|
||||
$country = $countries->getByAlpha2($countryName);
|
||||
$isoCodes = new \Sokil\IsoCodes\IsoCodesFactory();
|
||||
$countries = $isoCodes->getCountries();
|
||||
$country = $countries->getByAlpha2($countryName);
|
||||
|
||||
if ($country) return $country->getName();
|
||||
if ($country) {
|
||||
return $country->getName();
|
||||
}
|
||||
|
||||
return ucfirst(strtolower($countryName));
|
||||
return ucfirst(strtolower($countryName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('pluralize')) {
|
||||
if (! function_exists('pluralize')) {
|
||||
function pluralize($count, $string, $trailing = '')
|
||||
{
|
||||
if ((int)$count === 1) return $string;
|
||||
if ((int) $count === 1) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
return $string . 's' . ($trailing ? $trailing : '');
|
||||
return $string.'s'.($trailing ? $trailing : '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
<?php
|
||||
|
||||
if (!function_exists('renderTags')) {
|
||||
if (! function_exists('renderTags')) {
|
||||
function renderTags(array $tags): void
|
||||
{
|
||||
if (empty($tags)) return;
|
||||
if (empty($tags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="tags">';
|
||||
echo '<div class="tags">';
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$slug = strtolower(trim($tag));
|
||||
echo '<a href="/tags/' . rawurlencode($slug) . '">#' . htmlspecialchars($slug) . '</a>';
|
||||
}
|
||||
foreach ($tags as $tag) {
|
||||
$slug = strtolower(trim($tag));
|
||||
echo '<a href="/tags/'.rawurlencode($slug).'">#'.htmlspecialchars($slug).'</a>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue