Files
MediaCollectorLibary/app/Models/Game.php
Lars Behrends e78c073f21 basic filter D:
2025-10-24 17:12:36 +02:00

454 lines
13 KiB
PHP

<?php
namespace App\Models;
class Game extends Model
{
protected string $table = 'games';
protected array $fillable = [
'title',
'game_key',
'description',
'genre',
'developer',
'publisher',
'release_date',
'platform',
'platform_game_id',
'steam_app_id',
'image_url',
'banner_url',
'rating',
'playtime_minutes',
'completion_percentage',
'is_installed',
'is_favorite',
'metadata',
'platform_achievements',
'background_image',
'cover_image',
'icon',
'genres_json',
'developers_json',
'publishers_json',
'tags_json',
'features_json',
'links_json',
'series_json',
'age_ratings_json',
'play_count',
'install_size',
'completion_status',
'critic_score',
'community_score',
'user_score',
'is_custom_game',
'installation_status',
'added_at',
'modified_at'
];
protected array $casts = [
'rating' => 'float',
'playtime_minutes' => 'int',
'completion_percentage' => 'int',
'is_installed' => 'bool',
'is_favorite' => 'bool',
'release_date' => 'date',
'last_played_at' => 'datetime',
'platform_achievements' => 'array',
'critic_score' => 'int',
'community_score' => 'int',
'user_score' => 'int',
'play_count' => 'int',
'install_size' => 'int',
'installation_status' => 'int',
'is_custom_game' => 'bool',
'added_at' => 'datetime',
'modified_at' => 'datetime',
'genres_json' => 'array',
'developers_json' => 'array',
'publishers_json' => 'array',
'tags_json' => 'array',
'features_json' => 'array',
'links_json' => 'array',
'series_json' => 'array',
'age_ratings_json' => 'array'
];
public function source()
{
return new Source($this->pdo);
}
public function markAsPlayed(int $minutes = 60): bool
{
$this->playtime_minutes += $minutes;
$this->last_played_at = date('Y-m-d H:i:s');
return $this->update($this->id, [
'playtime_minutes' => $this->playtime_minutes,
'last_played_at' => $this->last_played_at
]);
}
public function toggleFavorite(): bool
{
return $this->update($this->id, [
'is_favorite' => !$this->is_favorite
]);
}
public function toggleInstalled(): bool
{
return $this->update($this->id, [
'is_installed' => !$this->is_installed
]);
}
public function updateCompletion(int $percentage): bool
{
return $this->update($this->id, [
'completion_percentage' => min(100, max(0, $percentage))
]);
}
public function updateRating(float $rating): bool
{
return $this->update($this->id, [
'rating' => min(10.0, max(0.0, $rating))
]);
}
public static function getStats(\PDO $pdo): array
{
$stmt = $pdo->query("
SELECT
COUNT(*) as total_games,
SUM(playtime_minutes) as total_playtime,
AVG(rating) as avg_rating,
COUNT(CASE WHEN is_favorite = 1 THEN 1 END) as favorite_games,
COUNT(CASE WHEN is_installed = 1 THEN 1 END) as installed_games,
AVG(completion_percentage) as avg_completion
FROM games
");
return $stmt->fetch(\PDO::FETCH_ASSOC);
}
public static function getRecent(\PDO $pdo, int $limit = 10): array
{
$stmt = $pdo->prepare("
SELECT g.*, s.display_name as source_name
FROM games g
JOIN sources s ON g.source_id = s.id
WHERE g.last_played_at IS NOT NULL
ORDER BY g.last_played_at DESC
LIMIT :limit
");
$stmt->execute(['limit' => $limit]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Generate a game key for grouping games across platforms
*/
public static function generateGameKey(string $title, string $platform = null): string
{
// Normalize title for consistent grouping
$normalized = strtolower(trim($title));
$normalized = preg_replace('/[^\w\s]/', '', $normalized); // Remove special characters
$normalized = preg_replace('/\s+/', ' ', $normalized); // Normalize whitespace
$normalized = trim($normalized);
// Add platform to make it unique if provided
if ($platform) {
$normalized .= ' ' . strtolower($platform);
}
return $normalized;
}
/**
* Find all platform versions of a game
*/
public function getPlatformVersions(): array
{
if (!$this->game_key) {
return [];
}
$stmt = $this->pdo->prepare("
SELECT g.*, s.display_name as source_name
FROM games g
JOIN sources s ON g.source_id = s.id
WHERE g.game_key = :game_key
ORDER BY g.platform, g.source_id
");
$stmt->execute(['game_key' => $this->game_key]);
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Get games grouped by title for display
*/
public static function getGroupedGames(\PDO $pdo, int $limit = 50): array
{
$stmt = $pdo->prepare("
SELECT
game_key,
title,
COUNT(*) as platform_count,
GROUP_CONCAT(DISTINCT platform ORDER BY platform) as platforms,
GROUP_CONCAT(DISTINCT source_id ORDER BY source_id) as source_ids,
MAX(image_url) as image_url,
MAX(last_played_at) as last_played_at,
SUM(playtime_minutes) as total_playtime,
MAX(completion_percentage) as max_completion,
GROUP_CONCAT(DISTINCT genre ORDER BY genre) as genres
FROM games
WHERE game_key IS NOT NULL
GROUP BY game_key, title
ORDER BY last_played_at DESC, total_playtime DESC
LIMIT :limit
");
$stmt->execute(['limit' => $limit]);
$games = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Enhance each game with platform details
foreach ($games as &$game) {
$game['platforms'] = array_unique(explode(',', $game['platforms']));
$game['source_ids'] = array_unique(explode(',', $game['source_ids']));
$game['genres'] = array_unique(array_filter(explode(',', $game['genres'])));
}
return $games;
}
/**
* Update platform-specific achievements
*/
public function updatePlatformAchievements(array $achievements): bool
{
return $this->update($this->id, [
'platform_achievements' => json_encode($achievements)
]);
}
/**
* Update platform-specific statistics
*/
public function updatePlatformStats(array $stats): bool
{
return $this->update($this->id, [
'platform_stats' => json_encode($stats)
]);
}
/**
* Get platform-specific achievements
*/
public function getPlatformAchievements(): array
{
return $this->platform_achievements ?? [];
}
/**
* Get platform-specific statistics
*/
public function getPlatformStats(): array
{
return $this->platform_stats ?? [];
}
/**
* Get total count of games for pagination
*/
public static function getTotalCount(\PDO $pdo, string $search = '', array $genres = [], array $platforms = []): int
{
$sql = "SELECT COUNT(*) as count FROM games WHERE game_key IS NOT NULL";
$params = [];
if (!empty($search)) {
$sql .= " AND title LIKE :search";
$params['search'] = "%{$search}%";
}
if (!empty($genres)) {
$placeholders = [];
foreach ($genres as $index => $genre) {
$placeholders[] = ":genre_{$index}";
$params["genre_{$index}"] = $genre;
}
$sql .= " AND genre IN (" . implode(',', $placeholders) . ")";
}
if (!empty($platforms)) {
$placeholders = [];
foreach ($platforms as $index => $platform) {
$placeholders[] = ":platform_{$index}";
$params["platform_{$index}"] = $platform;
}
$sql .= " AND platform IN (" . implode(',', $placeholders) . ")";
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
return (int) $stmt->fetch()['count'];
}
/**
* Get grouped games with pagination and search support
*/
public static function getGroupedGamesWithPagination(\PDO $pdo, int $page, int $perPage, string $search = '', array $genres = [], array $platforms = []): array
{
$offset = ($page - 1) * $perPage;
$sql = "
SELECT
game_key,
title,
COUNT(*) as platform_count,
GROUP_CONCAT(DISTINCT platform ORDER BY platform) as platforms,
GROUP_CONCAT(DISTINCT source_id ORDER BY source_id) as source_ids,
MAX(image_url) as image_url,
MAX(last_played_at) as last_played_at,
SUM(playtime_minutes) as total_playtime,
MAX(completion_percentage) as max_completion,
GROUP_CONCAT(DISTINCT genre ORDER BY genre) as genres
FROM games
WHERE game_key IS NOT NULL
";
$params = [];
if (!empty($search)) {
$sql .= " AND title LIKE :search";
$params['search'] = "%{$search}%";
}
if (!empty($genres)) {
$placeholders = [];
foreach ($genres as $index => $genre) {
$placeholders[] = ":genre_{$index}";
$params["genre_{$index}"] = $genre;
}
$sql .= " AND genre IN (" . implode(',', $placeholders) . ")";
}
if (!empty($platforms)) {
$placeholders = [];
foreach ($platforms as $index => $platform) {
$placeholders[] = ":platform_{$index}";
$params["platform_{$index}"] = $platform;
}
$sql .= " AND platform IN (" . implode(',', $placeholders) . ")";
}
$sql .= " GROUP BY game_key, title ORDER BY last_played_at DESC, total_playtime DESC LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
foreach ($params as $key => $value) {
$stmt->bindValue(":{$key}", $value);
}
$stmt->execute();
$games = $stmt->fetchAll(\PDO::FETCH_ASSOC);
// Enhance each game with platform details
foreach ($games as &$game) {
$game['platforms'] = array_unique(explode(',', $game['platforms']));
$game['source_ids'] = array_unique(explode(',', $game['source_ids']));
$game['genres'] = array_unique(array_filter(explode(',', $game['genres'])));
}
return $games;
}
/**
* Get Playnite-specific genres
*/
public function getGenres(): array
{
return $this->genres_json ?? [];
}
/**
* Get Playnite-specific developers
*/
public function getDevelopers(): array
{
return $this->developers_json ?? [];
}
/**
* Get Playnite-specific publishers
*/
public function getPublishers(): array
{
return $this->publishers_json ?? [];
}
/**
* Get Playnite-specific tags
*/
public function getTags(): array
{
return $this->tags_json ?? [];
}
/**
* Get Playnite-specific features
*/
public function getFeatures(): array
{
return $this->features_json ?? [];
}
/**
* Get Playnite-specific links
*/
public function getLinks(): array
{
return $this->links_json ?? [];
}
/**
* Get available genres for filtering
*/
public static function getAvailableGenres(\PDO $pdo): array
{
$stmt = $pdo->query("
SELECT DISTINCT genre
FROM games
WHERE genre IS NOT NULL AND genre != ''
ORDER BY genre
");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Get available platforms for filtering
*/
public static function getAvailablePlatforms(\PDO $pdo): array
{
$stmt = $pdo->query("
SELECT DISTINCT platform
FROM games
WHERE platform IS NOT NULL AND platform != ''
ORDER BY platform
");
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* Check if game has rich Playnite data
*/
public function hasPlayniteData(): bool
{
return !empty($this->genres_json) || !empty($this->tags_json) ||
!empty($this->links_json) || !empty($this->background_image);
}
}