Initial commit
This commit is contained in:
77
app/Auth/XenForoGuard.php
Normal file
77
app/Auth/XenForoGuard.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Auth;
|
||||
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class XenForoGuard implements Guard
|
||||
{
|
||||
private ?XenForoUser $user = null;
|
||||
|
||||
public function __construct(private readonly Request $request) {}
|
||||
|
||||
public function check(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
public function guest(): bool
|
||||
{
|
||||
return ! $this->check();
|
||||
}
|
||||
|
||||
public function id(): mixed
|
||||
{
|
||||
return $this->user()?->getAuthIdentifier();
|
||||
}
|
||||
|
||||
public function hasUser(): bool
|
||||
{
|
||||
return $this->user !== null;
|
||||
}
|
||||
|
||||
public function user(): ?XenForoUser
|
||||
{
|
||||
if ($this->hasUser())
|
||||
return $this->user;
|
||||
|
||||
$sessionId = $this->request->cookie('xf_session');
|
||||
if(!$sessionId)
|
||||
return null;
|
||||
|
||||
$xfSession = \DB::connection('xenforo')
|
||||
->table('session')
|
||||
->where('session_id', $sessionId)
|
||||
->value('session_data');
|
||||
|
||||
if(!$xfSession)
|
||||
return null;
|
||||
|
||||
$sessionData = unserialize($xfSession);
|
||||
|
||||
if (!$sessionData || !isset($sessionData['userId']) || !$sessionData['userId'])
|
||||
return null;
|
||||
|
||||
$xfUser = \DB::connection('xenforo')
|
||||
->table('user')
|
||||
->where('user_id', $sessionData['userId'])
|
||||
->first();
|
||||
|
||||
if(!$xfUser)
|
||||
return null;
|
||||
|
||||
return $this->user = new XenForoUser($xfUser);
|
||||
}
|
||||
|
||||
public function validate(array $credentials = []): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setUser(mixed $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
}
|
||||
84
app/Auth/XenForoUser.php
Normal file
84
app/Auth/XenForoUser.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Auth;
|
||||
|
||||
use App\Services\XenforoService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
class XenForoUser implements Authenticatable {
|
||||
|
||||
public ?array $permissions = null;
|
||||
private XenforoService $services;
|
||||
|
||||
public function __construct(public readonly object $data) {
|
||||
$this->services = app(XenforoService::class);
|
||||
}
|
||||
|
||||
public function __get(string $name): mixed {
|
||||
return $this->data->$name ?? null;
|
||||
}
|
||||
|
||||
public function getAuthIdentifierName(): string
|
||||
{
|
||||
return 'user_id';
|
||||
}
|
||||
|
||||
public function getAuthIdentifier(): mixed
|
||||
{
|
||||
return $this->data->user_id;
|
||||
}
|
||||
|
||||
public function getAuthPasswordName(): string
|
||||
{
|
||||
return 'password';
|
||||
}
|
||||
|
||||
public function getAuthPassword(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getRememberToken(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function setRememberToken($value): void
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public function getRememberTokenName(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get XenForo avatar if it exist.
|
||||
*
|
||||
* @param string $xfSize
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAvatarUrl( string $xfSize = 'm' ): ?string
|
||||
{
|
||||
$userId = $this->data->user_id;
|
||||
$avatarDate = $this->data->avatar_date;
|
||||
|
||||
if( $avatarDate ){
|
||||
$group = floor($userId / 1000);
|
||||
return config('app.forum_url') . "/data/avatars/{$xfSize}/{$group}/{$userId}.jpg?{$avatarDate}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function can(string $permissionGroup, string $permissionName): bool
|
||||
{
|
||||
if( !$this->permissions ){
|
||||
$this->permissions = $this->services->getPermissions($this->data->user_id, $this->data->permission_combination_id);
|
||||
}
|
||||
|
||||
return ($this->permissions[$permissionGroup][$permissionName] ?? 0) === true;
|
||||
}
|
||||
}
|
||||
10
app/Exceptions/SubmissionException.php
Normal file
10
app/Exceptions/SubmissionException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SubmissionException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
||||
50
app/Filament/Resources/Authors/AuthorResource.php
Normal file
50
app/Filament/Resources/Authors/AuthorResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Authors;
|
||||
|
||||
use App\Filament\Resources\Authors\Pages\CreateAuthor;
|
||||
use App\Filament\Resources\Authors\Pages\EditAuthor;
|
||||
use App\Filament\Resources\Authors\Pages\ListAuthors;
|
||||
use App\Filament\Resources\Authors\Schemas\AuthorForm;
|
||||
use App\Filament\Resources\Authors\Tables\AuthorsTable;
|
||||
use App\Models\Author;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class AuthorResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Author::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::UserGroup;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return AuthorForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return AuthorsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListAuthors::route('/'),
|
||||
'create' => CreateAuthor::route('/create'),
|
||||
'edit' => EditAuthor::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Authors/Pages/CreateAuthor.php
Normal file
11
app/Filament/Resources/Authors/Pages/CreateAuthor.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Authors\Pages;
|
||||
|
||||
use App\Filament\Resources\Authors\AuthorResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateAuthor extends CreateRecord
|
||||
{
|
||||
protected static string $resource = AuthorResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Authors/Pages/EditAuthor.php
Normal file
19
app/Filament/Resources/Authors/Pages/EditAuthor.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Authors\Pages;
|
||||
|
||||
use App\Filament\Resources\Authors\AuthorResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditAuthor extends EditRecord
|
||||
{
|
||||
protected static string $resource = AuthorResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Authors/Pages/ListAuthors.php
Normal file
19
app/Filament/Resources/Authors/Pages/ListAuthors.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Authors\Pages;
|
||||
|
||||
use App\Filament\Resources\Authors\AuthorResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListAuthors extends ListRecords
|
||||
{
|
||||
protected static string $resource = AuthorResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
30
app/Filament/Resources/Authors/Schemas/AuthorForm.php
Normal file
30
app/Filament/Resources/Authors/Schemas/AuthorForm.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Authors\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class AuthorForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->required(),
|
||||
TextInput::make('website')
|
||||
->url()
|
||||
->maxLength(500)
|
||||
->default(null),
|
||||
TextInput::make('user_id')
|
||||
->numeric()
|
||||
->default(null),
|
||||
]);
|
||||
}
|
||||
}
|
||||
47
app/Filament/Resources/Authors/Tables/AuthorsTable.php
Normal file
47
app/Filament/Resources/Authors/Tables/AuthorsTable.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Authors\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class AuthorsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('website')
|
||||
->searchable(),
|
||||
TextColumn::make('user_id')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
app/Filament/Resources/Entries/EntryResource.php
Normal file
50
app/Filament/Resources/Entries/EntryResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Entries;
|
||||
|
||||
use App\Filament\Resources\Entries\Pages\CreateEntry;
|
||||
use App\Filament\Resources\Entries\Pages\EditEntry;
|
||||
use App\Filament\Resources\Entries\Pages\ListEntries;
|
||||
use App\Filament\Resources\Entries\Schemas\EntryForm;
|
||||
use App\Filament\Resources\Entries\Tables\EntriesTable;
|
||||
use App\Models\Entry;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class EntryResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Entry::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'title';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return EntryForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return EntriesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListEntries::route('/'),
|
||||
'create' => CreateEntry::route('/create'),
|
||||
'edit' => EditEntry::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Entries/Pages/CreateEntry.php
Normal file
11
app/Filament/Resources/Entries/Pages/CreateEntry.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Entries\Pages;
|
||||
|
||||
use App\Filament\Resources\Entries\EntryResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateEntry extends CreateRecord
|
||||
{
|
||||
protected static string $resource = EntryResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Entries/Pages/EditEntry.php
Normal file
19
app/Filament/Resources/Entries/Pages/EditEntry.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Entries\Pages;
|
||||
|
||||
use App\Filament\Resources\Entries\EntryResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditEntry extends EditRecord
|
||||
{
|
||||
protected static string $resource = EntryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Entries/Pages/ListEntries.php
Normal file
19
app/Filament/Resources/Entries/Pages/ListEntries.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Entries\Pages;
|
||||
|
||||
use App\Filament\Resources\Entries\EntryResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListEntries extends ListRecords
|
||||
{
|
||||
protected static string $resource = EntryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
139
app/Filament/Resources/Entries/Schemas/EntryForm.php
Normal file
139
app/Filament/Resources/Entries/Schemas/EntryForm.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Entries\Schemas;
|
||||
|
||||
use App\Models\Author;
|
||||
use App\Models\Game;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class EntryForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('type')
|
||||
->options([
|
||||
'translations' => 'Translations',
|
||||
'romhacks' => 'Romhacks',
|
||||
'homebrew' => 'Homebrew',
|
||||
'utilities' => 'Utilities',
|
||||
'documents' => 'Documents',
|
||||
'lua-scripts' => 'Lua scripts',
|
||||
'tutorials' => 'Tutorials',
|
||||
])
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('title')
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->required(),
|
||||
Textarea::make('description')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
FileUpload::make('main_image')
|
||||
->image(),
|
||||
Select::make('state')
|
||||
->options([
|
||||
'draft' => 'Draft',
|
||||
'pending' => 'Pending',
|
||||
'published' => 'Published',
|
||||
'locked' => 'Locked',
|
||||
'hidden' => 'Hidden',
|
||||
])
|
||||
->default('draft')
|
||||
->required(),
|
||||
Toggle::make('featured')
|
||||
->required(),
|
||||
Select::make('game_id')
|
||||
->relationship('game', 'name')
|
||||
->searchable()
|
||||
->default(null)
|
||||
->hidden(fn(Get $get) => $get('type') === 'homebrew')
|
||||
->createOptionForm([
|
||||
TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('slug')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Select::make('platform_id')
|
||||
->relationship('platform', 'name')
|
||||
->required(),
|
||||
Select::make('genre_id')
|
||||
->relationship('genre', 'name')
|
||||
->required(),
|
||||
])
|
||||
->createOptionUsing(function (array $data){
|
||||
return Game::create( $data )->id;
|
||||
})
|
||||
,
|
||||
Select::make('platform_id')
|
||||
->relationship('platform', 'name')
|
||||
->default(null)
|
||||
->hidden(fn(Get $get) => in_array( $get('type'), [ 'translations', 'romhacks' ] ) ),
|
||||
Select::make('status_id')
|
||||
->relationship('status', 'id')
|
||||
->default(null),
|
||||
Select::make('authors')
|
||||
->relationship('authors', 'name')
|
||||
->searchable()
|
||||
->multiple()
|
||||
->createOptionForm([
|
||||
TextInput::make('name')
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->required(),
|
||||
TextInput::make('website')
|
||||
->url()
|
||||
->maxLength(500)
|
||||
->default(null),
|
||||
TextInput::make('user_id')
|
||||
->numeric()
|
||||
->default(null),
|
||||
])
|
||||
->createOptionUsing(function (array $data){
|
||||
return Author::create( $data )->id;
|
||||
})
|
||||
,
|
||||
Select::make('languages')
|
||||
->relationship('languages', 'name')
|
||||
->multiple()
|
||||
->preload(),
|
||||
Select::make('modifications')
|
||||
->relationship('modifications', 'name')
|
||||
->multiple()
|
||||
->preload()
|
||||
->hidden(fn(Get $get) => $get('type') !== "romhacks"),
|
||||
TextInput::make('version')
|
||||
->default(null),
|
||||
DatePicker::make('release_date'),
|
||||
Textarea::make('staff_credits')
|
||||
->default(null)
|
||||
->columnSpanFull(),
|
||||
TextInput::make('relevant_link')
|
||||
->default(null),
|
||||
TextInput::make('youtube_link')
|
||||
->default(null),
|
||||
TextInput::make('user_id')
|
||||
->required()
|
||||
->numeric(),
|
||||
TextInput::make('comments_thread_id')
|
||||
->numeric()
|
||||
->default(null),
|
||||
]);
|
||||
}
|
||||
}
|
||||
58
app/Filament/Resources/Entries/Tables/EntriesTable.php
Normal file
58
app/Filament/Resources/Entries/Tables/EntriesTable.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Entries\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class EntriesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('type')
|
||||
->badge(),
|
||||
TextColumn::make('title')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('state')
|
||||
->badge(),
|
||||
IconColumn::make('featured')
|
||||
->boolean(),
|
||||
TextColumn::make('version')
|
||||
->searchable(),
|
||||
TextColumn::make('user_id')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('comments_thread_id')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
app/Filament/Resources/Games/GameResource.php
Normal file
50
app/Filament/Resources/Games/GameResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Games;
|
||||
|
||||
use App\Filament\Resources\Games\Pages\CreateGame;
|
||||
use App\Filament\Resources\Games\Pages\EditGame;
|
||||
use App\Filament\Resources\Games\Pages\ListGames;
|
||||
use App\Filament\Resources\Games\Schemas\GameForm;
|
||||
use App\Filament\Resources\Games\Tables\GamesTable;
|
||||
use App\Models\Game;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class GameResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Game::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::PlayCircle;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return GameForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return GamesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListGames::route('/'),
|
||||
'create' => CreateGame::route('/create'),
|
||||
'edit' => EditGame::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Games/Pages/CreateGame.php
Normal file
11
app/Filament/Resources/Games/Pages/CreateGame.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Games\Pages;
|
||||
|
||||
use App\Filament\Resources\Games\GameResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateGame extends CreateRecord
|
||||
{
|
||||
protected static string $resource = GameResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Games/Pages/EditGame.php
Normal file
19
app/Filament/Resources/Games/Pages/EditGame.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Games\Pages;
|
||||
|
||||
use App\Filament\Resources\Games\GameResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditGame extends EditRecord
|
||||
{
|
||||
protected static string $resource = GameResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Games/Pages/ListGames.php
Normal file
19
app/Filament/Resources/Games/Pages/ListGames.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Games\Pages;
|
||||
|
||||
use App\Filament\Resources\Games\GameResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListGames extends ListRecords
|
||||
{
|
||||
protected static string $resource = GameResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
29
app/Filament/Resources/Games/Schemas/GameForm.php
Normal file
29
app/Filament/Resources/Games/Schemas/GameForm.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Games\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class GameForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('slug')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Select::make('platform_id')
|
||||
->relationship('platform', 'name')
|
||||
->required(),
|
||||
Select::make('genre_id')
|
||||
->relationship('genre', 'name')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
46
app/Filament/Resources/Games/Tables/GamesTable.php
Normal file
46
app/Filament/Resources/Games/Tables/GamesTable.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Games\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class GamesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('platform.name')
|
||||
->searchable(),
|
||||
TextColumn::make('genre.name')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
app/Filament/Resources/Genres/GenreResource.php
Normal file
50
app/Filament/Resources/Genres/GenreResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Genres;
|
||||
|
||||
use App\Filament\Resources\Genres\Pages\CreateGenre;
|
||||
use App\Filament\Resources\Genres\Pages\EditGenre;
|
||||
use App\Filament\Resources\Genres\Pages\ListGenres;
|
||||
use App\Filament\Resources\Genres\Schemas\GenreForm;
|
||||
use App\Filament\Resources\Genres\Tables\GenresTable;
|
||||
use App\Models\Genre;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class GenreResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Genre::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::FingerPrint;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return GenreForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return GenresTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListGenres::route('/'),
|
||||
'create' => CreateGenre::route('/create'),
|
||||
'edit' => EditGenre::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Genres/Pages/CreateGenre.php
Normal file
11
app/Filament/Resources/Genres/Pages/CreateGenre.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Genres\Pages;
|
||||
|
||||
use App\Filament\Resources\Genres\GenreResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateGenre extends CreateRecord
|
||||
{
|
||||
protected static string $resource = GenreResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Genres/Pages/EditGenre.php
Normal file
19
app/Filament/Resources/Genres/Pages/EditGenre.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Genres\Pages;
|
||||
|
||||
use App\Filament\Resources\Genres\GenreResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditGenre extends EditRecord
|
||||
{
|
||||
protected static string $resource = GenreResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Genres/Pages/ListGenres.php
Normal file
19
app/Filament/Resources/Genres/Pages/ListGenres.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Genres\Pages;
|
||||
|
||||
use App\Filament\Resources\Genres\GenreResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListGenres extends ListRecords
|
||||
{
|
||||
protected static string $resource = GenreResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
22
app/Filament/Resources/Genres/Schemas/GenreForm.php
Normal file
22
app/Filament/Resources/Genres/Schemas/GenreForm.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Genres\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class GenreForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('slug')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
app/Filament/Resources/Genres/Tables/GenresTable.php
Normal file
42
app/Filament/Resources/Genres/Tables/GenresTable.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Genres\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class GenresTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
app/Filament/Resources/Languages/LanguageResource.php
Normal file
50
app/Filament/Resources/Languages/LanguageResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Languages;
|
||||
|
||||
use App\Filament\Resources\Languages\Pages\CreateLanguage;
|
||||
use App\Filament\Resources\Languages\Pages\EditLanguage;
|
||||
use App\Filament\Resources\Languages\Pages\ListLanguages;
|
||||
use App\Filament\Resources\Languages\Schemas\LanguageForm;
|
||||
use App\Filament\Resources\Languages\Tables\LanguagesTable;
|
||||
use App\Models\Language;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LanguageResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Language::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::Language;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return LanguageForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return LanguagesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListLanguages::route('/'),
|
||||
'create' => CreateLanguage::route('/create'),
|
||||
'edit' => EditLanguage::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Languages/Pages/CreateLanguage.php
Normal file
11
app/Filament/Resources/Languages/Pages/CreateLanguage.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Languages\Pages;
|
||||
|
||||
use App\Filament\Resources\Languages\LanguageResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateLanguage extends CreateRecord
|
||||
{
|
||||
protected static string $resource = LanguageResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Languages/Pages/EditLanguage.php
Normal file
19
app/Filament/Resources/Languages/Pages/EditLanguage.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Languages\Pages;
|
||||
|
||||
use App\Filament\Resources\Languages\LanguageResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditLanguage extends EditRecord
|
||||
{
|
||||
protected static string $resource = LanguageResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Languages/Pages/ListLanguages.php
Normal file
19
app/Filament/Resources/Languages/Pages/ListLanguages.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Languages\Pages;
|
||||
|
||||
use App\Filament\Resources\Languages\LanguageResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListLanguages extends ListRecords
|
||||
{
|
||||
protected static string $resource = LanguageResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
23
app/Filament/Resources/Languages/Schemas/LanguageForm.php
Normal file
23
app/Filament/Resources/Languages/Schemas/LanguageForm.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Languages\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class LanguageForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
app/Filament/Resources/Languages/Tables/LanguagesTable.php
Normal file
42
app/Filament/Resources/Languages/Tables/LanguagesTable.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Languages\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LanguagesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modifications;
|
||||
|
||||
use App\Filament\Resources\Modifications\Pages\CreateModification;
|
||||
use App\Filament\Resources\Modifications\Pages\EditModification;
|
||||
use App\Filament\Resources\Modifications\Pages\ListModifications;
|
||||
use App\Filament\Resources\Modifications\Schemas\ModificationForm;
|
||||
use App\Filament\Resources\Modifications\Tables\ModificationsTable;
|
||||
use App\Models\Modification;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ModificationResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Modification::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::ArchiveBox;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return ModificationForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return ModificationsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListModifications::route('/'),
|
||||
'create' => CreateModification::route('/create'),
|
||||
'edit' => EditModification::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modifications\Pages;
|
||||
|
||||
use App\Filament\Resources\Modifications\ModificationResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateModification extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ModificationResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modifications\Pages;
|
||||
|
||||
use App\Filament\Resources\Modifications\ModificationResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditModification extends EditRecord
|
||||
{
|
||||
protected static string $resource = ModificationResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modifications\Pages;
|
||||
|
||||
use App\Filament\Resources\Modifications\ModificationResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListModifications extends ListRecords
|
||||
{
|
||||
protected static string $resource = ModificationResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modifications\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ModificationForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Modifications\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ModificationsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Platforms/Pages/CreatePlatform.php
Normal file
11
app/Filament/Resources/Platforms/Pages/CreatePlatform.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Platforms\Pages;
|
||||
|
||||
use App\Filament\Resources\Platforms\PlatformResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreatePlatform extends CreateRecord
|
||||
{
|
||||
protected static string $resource = PlatformResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Platforms/Pages/EditPlatform.php
Normal file
19
app/Filament/Resources/Platforms/Pages/EditPlatform.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Platforms\Pages;
|
||||
|
||||
use App\Filament\Resources\Platforms\PlatformResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditPlatform extends EditRecord
|
||||
{
|
||||
protected static string $resource = PlatformResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Platforms/Pages/ListPlatforms.php
Normal file
19
app/Filament/Resources/Platforms/Pages/ListPlatforms.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Platforms\Pages;
|
||||
|
||||
use App\Filament\Resources\Platforms\PlatformResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListPlatforms extends ListRecords
|
||||
{
|
||||
protected static string $resource = PlatformResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
50
app/Filament/Resources/Platforms/PlatformResource.php
Normal file
50
app/Filament/Resources/Platforms/PlatformResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Platforms;
|
||||
|
||||
use App\Filament\Resources\Platforms\Pages\CreatePlatform;
|
||||
use App\Filament\Resources\Platforms\Pages\EditPlatform;
|
||||
use App\Filament\Resources\Platforms\Pages\ListPlatforms;
|
||||
use App\Filament\Resources\Platforms\Schemas\PlatformForm;
|
||||
use App\Filament\Resources\Platforms\Tables\PlatformsTable;
|
||||
use App\Models\Platform;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PlatformResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Platform::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::Cube;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return PlatformForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return PlatformsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListPlatforms::route('/'),
|
||||
'create' => CreatePlatform::route('/create'),
|
||||
'edit' => EditPlatform::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
26
app/Filament/Resources/Platforms/Schemas/PlatformForm.php
Normal file
26
app/Filament/Resources/Platforms/Schemas/PlatformForm.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Platforms\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class PlatformForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(100),
|
||||
TextInput::make('slug')
|
||||
->required()
|
||||
->unique( ignoreRecord: true )
|
||||
->maxLength(100),
|
||||
TextInput::make('short_name')
|
||||
->default(null)
|
||||
->maxLength(30),
|
||||
]);
|
||||
}
|
||||
}
|
||||
45
app/Filament/Resources/Platforms/Tables/PlatformsTable.php
Normal file
45
app/Filament/Resources/Platforms/Tables/PlatformsTable.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Platforms\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PlatformsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('short_name')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Statuses/Pages/CreateStatus.php
Normal file
11
app/Filament/Resources/Statuses/Pages/CreateStatus.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Statuses\Pages;
|
||||
|
||||
use App\Filament\Resources\Statuses\StatusResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateStatus extends CreateRecord
|
||||
{
|
||||
protected static string $resource = StatusResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Statuses/Pages/EditStatus.php
Normal file
19
app/Filament/Resources/Statuses/Pages/EditStatus.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Statuses\Pages;
|
||||
|
||||
use App\Filament\Resources\Statuses\StatusResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditStatus extends EditRecord
|
||||
{
|
||||
protected static string $resource = StatusResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Statuses/Pages/ListStatuses.php
Normal file
19
app/Filament/Resources/Statuses/Pages/ListStatuses.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Statuses\Pages;
|
||||
|
||||
use App\Filament\Resources\Statuses\StatusResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListStatuses extends ListRecords
|
||||
{
|
||||
protected static string $resource = StatusResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
23
app/Filament/Resources/Statuses/Schemas/StatusForm.php
Normal file
23
app/Filament/Resources/Statuses/Schemas/StatusForm.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Statuses\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class StatusForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
app/Filament/Resources/Statuses/StatusResource.php
Normal file
50
app/Filament/Resources/Statuses/StatusResource.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Statuses;
|
||||
|
||||
use App\Filament\Resources\Statuses\Pages\CreateStatus;
|
||||
use App\Filament\Resources\Statuses\Pages\EditStatus;
|
||||
use App\Filament\Resources\Statuses\Pages\ListStatuses;
|
||||
use App\Filament\Resources\Statuses\Schemas\StatusForm;
|
||||
use App\Filament\Resources\Statuses\Tables\StatusesTable;
|
||||
use App\Models\Status;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class StatusResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Status::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::ChartBar;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return StatusForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return StatusesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListStatuses::route('/'),
|
||||
'create' => CreateStatus::route('/create'),
|
||||
'edit' => EditStatus::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
42
app/Filament/Resources/Statuses/Tables/StatusesTable.php
Normal file
42
app/Filament/Resources/Statuses/Tables/StatusesTable.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Statuses\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class StatusesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable(),
|
||||
TextColumn::make('slug')
|
||||
->searchable(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
TextColumn::make('updated_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
59
app/Helpers/EntryHelpers.php
Normal file
59
app/Helpers/EntryHelpers.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class EntryHelpers {
|
||||
|
||||
/**
|
||||
* Create a unique slug.
|
||||
*
|
||||
* @param string $title
|
||||
* @param class-string $model The model with a 'slug' field.
|
||||
* @param int|null $ignoreId Ignore specific ID, like when edition
|
||||
*
|
||||
* @return string The original slug if no duplicates, otherwise with a number at the end.
|
||||
*/
|
||||
public static function uniqueSlug(string $title, string $model, ?int $ignoreId = null ): string {
|
||||
|
||||
$slug = Str::slug($title);
|
||||
$baseSlug = $slug;
|
||||
$i = 1;
|
||||
while(
|
||||
$model::where( 'slug', $slug )
|
||||
->when($ignoreId, fn($q) => $q->where('id', '!=', $ignoreId))
|
||||
->exists() && $i < 100
|
||||
){
|
||||
$slug = $baseSlug . '-' . $i++;
|
||||
}
|
||||
|
||||
if( $i >= 100 ){
|
||||
$slug = Str::uuid(); // Fallback...
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build complete title.
|
||||
*
|
||||
* @param string $section
|
||||
* @param array $fields
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function buildCompleteTitle( string $section, array $fields = [] ){
|
||||
|
||||
return match ($section) {
|
||||
'translations' => sprintf('%s (%s Translation) %s', $fields['entry_title'] ?? $fields['game_name'], $fields['languages_string'], $fields['platform_name']),
|
||||
'romhacks' => sprintf('%s (%s) Romhack', $fields['entry_title'], $fields['platform_name']),
|
||||
'homebrew' => sprintf('%s (%s) Homebrew', $fields['game_name'], $fields['platform_name']),
|
||||
'utilities' => sprintf('%s - Utility', $fields['entry_title']),
|
||||
'documents' => sprintf('%s - Document', $fields['entry_title']),
|
||||
'lua-scripts' => sprintf('%s (%s) LUA Script', $fields['entry_title'], $fields['platform_name']),
|
||||
'tutorials' => sprintf('%s - Tutorial', $fields['entry_title']),
|
||||
default => $fields['entry_title'],
|
||||
};
|
||||
}
|
||||
}
|
||||
49
app/Helpers/FormHelpers.php
Normal file
49
app/Helpers/FormHelpers.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class FormHelpers {
|
||||
|
||||
private static array $formWords = [
|
||||
'translations' => [
|
||||
'page_title' => "Submit a translation",
|
||||
'about_the' => "About the translation",
|
||||
'entry_title' => "Custom title",
|
||||
'entry_title_helper' => "If the translation have a custom title. If not, leave it blank and the game title will be taken.",
|
||||
'version' => "Patch version",
|
||||
'status' => "Status",
|
||||
'release_date' => "Release date",
|
||||
'release_date_helper' => "If only initial release exist, the release date.",
|
||||
'description' => "Description",
|
||||
'about_game' => "Game Information",
|
||||
'attachments' => "Attachments",
|
||||
'authors' => "Team members",
|
||||
'related_links' => "Related links",
|
||||
'release_site' => "Release site",
|
||||
'release_site_helper' => "Project entry on site/blog/forum/Github.",
|
||||
'youtube_video' => "YouTube video",
|
||||
],
|
||||
'romhacks' => [
|
||||
'page_title' => "Submit a romhack",
|
||||
'about_the' => "About the romhack",
|
||||
'entry_title' => "Hack title",
|
||||
'type_of_hack' => "Type of hack",
|
||||
'version' => "Patch version",
|
||||
'status' => "Status",
|
||||
'release_date' => "Release date",
|
||||
'release_date_helper' => "If only initial release exist, the release date.",
|
||||
'description' => "Description",
|
||||
'about_game' => "Game Information",
|
||||
'attachments' => "Attachments",
|
||||
'authors' => "Team members",
|
||||
'related_links' => "Related links",
|
||||
'release_site' => "Release site",
|
||||
'release_site_helper' => "Project entry on site/blog/forum/Github.",
|
||||
'youtube_video' => "YouTube video",
|
||||
],
|
||||
];
|
||||
|
||||
public static function getEntryFormWords( string $section ){
|
||||
return self::$formWords[$section] ?? [];
|
||||
}
|
||||
}
|
||||
37
app/Helpers/XenForoHelpers.php
Normal file
37
app/Helpers/XenForoHelpers.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Auth\XenForoUser;
|
||||
|
||||
class XenForoHelpers {
|
||||
|
||||
const array XF_AVATAR_COLORS = ['#FF6B6B','#FF9F43','#48DBFB','#1DD1A1','#5F27CD','#341f97','#EE5A24','#009432'];
|
||||
|
||||
/**
|
||||
* Have XenForo default profile picture letter.
|
||||
*
|
||||
* @param ?XenForoUser $user If null, default : actual user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getAvatarLetter( ?XenForoUser $user = null ): string {
|
||||
if( $user === null ) {
|
||||
$user = \Auth::user();
|
||||
if( $user === null ) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return strtoupper(mb_substr($user->data->username, 0, 1));
|
||||
}
|
||||
|
||||
public static function getAvatarColor( ?XenForoUser $user = null ): string {
|
||||
if( $user === null ) {
|
||||
$user = \Auth::user();
|
||||
if( $user === null ) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
return self::XF_AVATAR_COLORS[ crc32( $user->data->username ) % count( self::XF_AVATAR_COLORS ) ];
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
36
app/Http/Controllers/EntryController.php
Normal file
36
app/Http/Controllers/EntryController.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Entry;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class EntryController extends Controller
|
||||
{
|
||||
|
||||
private const SECTION_TYPES = [ 'translations', 'romhacks', 'homebrew', 'utilities', 'documents', 'lua-scripts', 'tutorials' ];
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$entries = Entry::published()
|
||||
->with(['game.platform', 'platform'])
|
||||
->latest('published_at')
|
||||
->paginate(30);
|
||||
|
||||
return view('entries.index', compact('entries'));
|
||||
}
|
||||
|
||||
public function show(string $section, Entry $entry): View
|
||||
{
|
||||
if( ! in_array($section, self::SECTION_TYPES) )
|
||||
abort(404);
|
||||
|
||||
if( $entry->type !== $section )
|
||||
abort(404);
|
||||
|
||||
return view('entries.show', compact('entry', 'section'));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
71
app/Http/Controllers/FileServerController.php
Normal file
71
app/Http/Controllers/FileServerController.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EntryFile;
|
||||
use App\Services\FileServersService;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FileServerController extends Controller {
|
||||
|
||||
public function __construct(private FileServersService $fs) {}
|
||||
|
||||
/**
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function uploadChunk(Request $request, string $section): JsonResponse {
|
||||
$request->validate([
|
||||
'file' => 'required|file',
|
||||
'file_uuid' => 'required|string|max:128',
|
||||
'current_chunk' => 'required|integer|min:0',
|
||||
'total_chunks' => 'required|integer|min:1',
|
||||
'filename' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
$type = $section;
|
||||
$fileUuid = $request->input('file_uuid');
|
||||
$currentChunk = (int) $request->input('current_chunk');
|
||||
$totalChunks = (int) $request->input('total_chunks');
|
||||
$filename = $request->input('filename');
|
||||
|
||||
$data = $this->fs->uploadChunk(
|
||||
$request->file('file'),
|
||||
$fileUuid,
|
||||
$currentChunk,
|
||||
$totalChunks,
|
||||
$filename,
|
||||
$type
|
||||
);
|
||||
|
||||
if( !isset( $data['file'] ) || $data['file'] === false ){
|
||||
$data['finished'] = false;
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
\Cache::put("uploaded_file_{$fileUuid}", [
|
||||
'uuid' => $fileUuid,
|
||||
'type' => $type,
|
||||
'filename' => $filename,
|
||||
'filepath' => $data['file_path'],
|
||||
'filesize' => $data['file']['size'],
|
||||
'favorite_server' => $data['favorite_server'],
|
||||
'favorite_at' => time()
|
||||
], now()->addHours(2) );
|
||||
|
||||
$data['finished'] = true;
|
||||
return response()->json($data);
|
||||
|
||||
}
|
||||
|
||||
public function download(Request $request, int $entry_id, EntryFile $file ) {
|
||||
|
||||
if( $file->entry_id != $entry_id ) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
// TODO: DL Count.
|
||||
return redirect( $this->fs->getDownloadFileUrl( $file) );
|
||||
}
|
||||
}
|
||||
15
app/Http/Controllers/HomeController.php
Normal file
15
app/Http/Controllers/HomeController.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
|
||||
public function index(): View {
|
||||
return view('home');
|
||||
}
|
||||
|
||||
}
|
||||
265
app/Http/Controllers/SubmissionController.php
Normal file
265
app/Http/Controllers/SubmissionController.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\SubmissionException;
|
||||
use App\Helpers\FormHelpers;
|
||||
use App\Http\Requests\StoreEntryRequest;
|
||||
use App\Models\Author;
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryFile;
|
||||
use App\Models\EntryGallery;
|
||||
use App\Models\EntryHash;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Language;
|
||||
use App\Models\Modification;
|
||||
use App\Models\Platform;
|
||||
use App\Models\Status;
|
||||
use App\Services\SubmissionsService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class SubmissionController extends Controller
|
||||
{
|
||||
|
||||
public function __construct(private SubmissionsService $services){}
|
||||
|
||||
public function create(Request $request, string $section)
|
||||
{
|
||||
$data = [
|
||||
'entry' => new Entry(),
|
||||
'section' => $section,
|
||||
'words' => FormHelpers::getEntryFormWords($section),
|
||||
'isEdit' => false,
|
||||
'oldModifications' => old( 'modifications', [] ),
|
||||
'oldLanguages' => old( 'languages', [] ),
|
||||
'oldFilesArray' => $this->services->prepareOldFiles( null )
|
||||
];
|
||||
|
||||
if( $data['words'] === [] )
|
||||
abort(500);
|
||||
|
||||
if( section_must_be( 'romhacks', $section ) ){
|
||||
$data['modifications'] = Modification::orderBy('name')->get();
|
||||
}
|
||||
if( section_must_be( [ 'romhacks', 'translations' ], $section ) ){
|
||||
$data['statuses'] = Status::orderBy('id')->get();
|
||||
}
|
||||
|
||||
return view('submissions.create', $data);
|
||||
}
|
||||
|
||||
|
||||
public function edit(Request $request, string $section, Entry $entry){
|
||||
|
||||
if( $entry->type !== $section )
|
||||
abort(404);
|
||||
|
||||
$data = [
|
||||
'entry' => $entry,
|
||||
'section' => $section,
|
||||
'words' => FormHelpers::getEntryFormWords($section),
|
||||
'isEdit' => true,
|
||||
'oldModifications' => old('modifications', $entry->modifications->pluck('id')->toArray() ?? [] ),
|
||||
'oldLanguages' => old('languages', $entry->languages->pluck('id')->toArray() ?? [] ),
|
||||
'oldFilesArray' => $this->services->prepareOldFiles( $entry )
|
||||
];
|
||||
|
||||
if( $data['words'] === [] )
|
||||
abort(500);
|
||||
|
||||
if( section_must_be( 'romhacks', $section ) ){
|
||||
$data['modifications'] = Modification::orderBy('name')->get();
|
||||
}
|
||||
if( section_must_be( [ 'romhacks', 'translations' ], $section ) ){
|
||||
$data['statuses'] = Status::orderBy('id')->get();
|
||||
}
|
||||
|
||||
return view('submissions.edit', $data);
|
||||
}
|
||||
|
||||
public function store(StoreEntryRequest $request, string $section){
|
||||
|
||||
try {
|
||||
$entry = $this->services->storeEntry($request, $section);
|
||||
|
||||
return match ($entry->state) {
|
||||
'published' => redirect()->route('entries.show', ['section' => $section, 'entry' => $entry->slug])->with('success', "Your entry has been published."),
|
||||
'pending' => redirect()->route('home')->with('success', "Your entry has been submitted and is pending review."),
|
||||
default => redirect()->route('home')->with('success', "Your entry has been saved as a draft.")
|
||||
};
|
||||
} catch ( SubmissionException $e ) {
|
||||
return back()->withInput()->withErrors(['error' => $e->getMessage()]);
|
||||
} catch ( \Exception $e ) {
|
||||
return back()->withInput()->withErrors(['error' => 'Unknown error: '.$e->getMessage()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function update(StoreEntryRequest $request, string $section, Entry $entry)
|
||||
{
|
||||
if( $entry->type !== $section ) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$gameId = null;
|
||||
if( !$request->input('game_id') ){
|
||||
if( $request->input('new-game-title') && $request->input('new-game-platform') && $request->input('new-game-genre') ){
|
||||
$platform = Platform::find($request->input('new-game-platform'));
|
||||
$genre = Genre::find($request->input('new-game-genre'));
|
||||
$game = Game::create([
|
||||
'name' => $request->input('new-game-title'),
|
||||
'slug' => Str::slug($request->input('new-game-title')),
|
||||
'platform_id' => $platform->id,
|
||||
'genre_id' => $genre->id,
|
||||
]);
|
||||
$gameId = $game->id;
|
||||
}
|
||||
} else {
|
||||
$gameId = $request->input('game_id');
|
||||
}
|
||||
|
||||
$mainImage = $entry->main_image;
|
||||
if ( $request->hasFile('main-image') ) {
|
||||
if ( $mainImage ) {
|
||||
Storage::disk('public')->delete($mainImage);
|
||||
}
|
||||
$mainImage = $request->file('main-image')->store('entries/main_images', 'public');
|
||||
} elseif ( $request->input('remove_main_image') === '1' ) {
|
||||
if ( $mainImage ) {
|
||||
Storage::disk('public')->delete($mainImage);
|
||||
}
|
||||
$mainImage = null;
|
||||
}
|
||||
|
||||
$staffCredits = collect($request->input('credits', []))
|
||||
->filter(fn($item) => isset($item['name']) || isset($item['description']))
|
||||
->map(function ($item) {
|
||||
$name = trim($item['name'] ?? '');
|
||||
$description = trim($item['description'] ?? '');
|
||||
if ($name === '' && $description === '') {
|
||||
return null;
|
||||
}
|
||||
return trim($name . ($name !== '' && $description !== '' ? ' — ' : '') . $description);
|
||||
})
|
||||
->filter()
|
||||
->implode("\n");
|
||||
|
||||
$fields = [
|
||||
'type' => $section,
|
||||
'title' => $request->input('entry_title'),
|
||||
'slug' => $request->input('slug') ?? Str::slug($request->input('entry_title', '')),
|
||||
'description' => $request->input('description'),
|
||||
'main_image' => $mainImage,
|
||||
'state' => $request->input('submit-state', 'draft'),
|
||||
'game_id' => $gameId,
|
||||
'status_id' => $request->input('status'),
|
||||
'version' => $request->input('version'),
|
||||
'release_date' => $request->input('release-date'),
|
||||
'staff_credits' => $staffCredits ?: null,
|
||||
'relevant_link' => $request->input('release_site'),
|
||||
'youtube_link' => $request->input('youtube_video'),
|
||||
];
|
||||
|
||||
$entry->update($fields);
|
||||
|
||||
$entry->hashes()->delete();
|
||||
foreach ( $request->input('hashes', []) as $hash ) {
|
||||
if( !isset($hash['filename'], $hash['crc32'], $hash['sha1'], $hash['verified']) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EntryHash::create([
|
||||
'entry_id' => $entry->id,
|
||||
'filename' => $hash['filename'],
|
||||
'hash_crc32' => $hash['crc32'],
|
||||
'hash_sha1' => $hash['sha1'],
|
||||
'verified' => $hash['verified'],
|
||||
]);
|
||||
}
|
||||
|
||||
$authorIds = [];
|
||||
foreach ( $request->input('authors', []) as $authorId ) {
|
||||
$author = Author::find($authorId);
|
||||
if( $author ) {
|
||||
$authorIds[] = $author->id;
|
||||
}
|
||||
}
|
||||
foreach( $request->input('new-authors', []) as $authorName ) {
|
||||
$authorName = trim($authorName);
|
||||
if ($authorName === '') continue;
|
||||
|
||||
$author = Author::firstOrCreate(
|
||||
['slug' => Str::slug($authorName)],
|
||||
['name' => $authorName],
|
||||
);
|
||||
$authorIds[] = $author->id;
|
||||
}
|
||||
$entry->authors()->sync(array_values(array_unique($authorIds)));
|
||||
|
||||
if( section_must_be( 'romhacks', $section ) ){
|
||||
$entry->modifications()->sync($request->input('modifications', []));
|
||||
} else {
|
||||
$entry->modifications()->sync([]);
|
||||
}
|
||||
|
||||
$entry->languages()->sync($request->input('languages', []));
|
||||
|
||||
$existingFileUuids = $request->input('existing_file_ids', []);
|
||||
if (!is_array($existingFileUuids)) {
|
||||
$existingFileUuids = [];
|
||||
}
|
||||
$entry->files()->whereNotIn('file_uuid', $existingFileUuids)->delete();
|
||||
|
||||
foreach ( $request->input('file_ids', []) as $file_uuid ) {
|
||||
$fileData = Cache::pull("uploaded_file_{$file_uuid}");
|
||||
if( ! $fileData ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EntryFile::create([
|
||||
'entry_id' => $entry->id,
|
||||
'file_uuid' => $fileData['uuid'],
|
||||
'filename' => $fileData['filename'],
|
||||
'filepath' => $fileData['filepath'],
|
||||
'favorite_server' => $fileData['favorite_server'],
|
||||
'favorite_at' => \DateTimeImmutable::createFromTimestamp( $fileData['favorite_at'] ),
|
||||
'filesize' => $fileData['filesize'],
|
||||
'state' => 'public'
|
||||
]);
|
||||
}
|
||||
|
||||
$existingGalleryIds = $request->input('existing_gallery_ids', []);
|
||||
if (!is_array($existingGalleryIds)) {
|
||||
$existingGalleryIds = [];
|
||||
}
|
||||
$entry->gallery()->whereNotIn('id', $existingGalleryIds)->get()->each(function ($gallery) {
|
||||
if ($gallery->image) {
|
||||
Storage::disk('public')->delete($gallery->image);
|
||||
}
|
||||
$gallery->delete();
|
||||
});
|
||||
|
||||
foreach ( $request->file('gallery', [] ) as $galleryFile ){
|
||||
if( !$galleryFile->isValid() ){
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $galleryFile->store('entries/gallery/' . $entry->id, 'public');
|
||||
EntryGallery::create([
|
||||
'entry_id' => $entry->id,
|
||||
'image' => $path
|
||||
]);
|
||||
}
|
||||
|
||||
return match( $entry->state ){
|
||||
'published' => redirect()->route('entries.show', [ 'section' => $section, 'entry' => $entry->slug ])->with('success', "Your entry has been published."),
|
||||
'pending' => redirect()->route('home')->with('success', "Your entry has been submitted and is pending review."),
|
||||
default => redirect()->route('home')->with('success', "Your entry has been saved as a draft.")
|
||||
};
|
||||
}
|
||||
}
|
||||
27
app/Http/Controllers/TemporaryFileController.php
Normal file
27
app/Http/Controllers/TemporaryFileController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\TemporaryFileUploadRequest;
|
||||
use App\Services\TemporaryFileService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TemporaryFileController extends Controller
|
||||
{
|
||||
|
||||
public function __construct(private TemporaryFileService $services) {}
|
||||
|
||||
public function upload(TemporaryFileUploadRequest $request){
|
||||
$file = $request->file('file');
|
||||
if( !$file || !$file->isValid()){
|
||||
response()->json( ['path' => null], 400);
|
||||
}
|
||||
|
||||
$path = $this->services->uploadFile( $file );
|
||||
if( !$path ){
|
||||
response()->json( ['path' => null], 500);
|
||||
}
|
||||
|
||||
return response()->json(['path' => $path]);
|
||||
}
|
||||
}
|
||||
43
app/Http/Middleware/CheckXenForoPermissions.php
Normal file
43
app/Http/Middleware/CheckXenForoPermissions.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckXenForoPermissions
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$permissions ): Response
|
||||
{
|
||||
if( !\Auth::check() )
|
||||
return redirect()->to(config('app.forum_url') . '/login' );
|
||||
|
||||
if( empty($permissions) ) // No permissions needed.
|
||||
return $next($request);
|
||||
|
||||
foreach ($permissions as $permissionStr) {
|
||||
[$group, $permission] = explode('.', $permissionStr);
|
||||
|
||||
if( !\Auth::user()->can($group, $permission) )
|
||||
return $this->deny($request, $permission);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function deny(Request $request, string $permission): Response
|
||||
{
|
||||
if($request->expectsJson())
|
||||
return \response()->json(['error' => 'forbidden'], 403);
|
||||
|
||||
return response()->view('pages.forbidden', [
|
||||
'permission' => $permission,
|
||||
], 403 );
|
||||
}
|
||||
}
|
||||
121
app/Http/Requests/StoreEntryRequest.php
Normal file
121
app/Http/Requests/StoreEntryRequest.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\PublicFileExists;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StoreEntryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// TODO: Change it by role.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function prepareForValidation(): void
|
||||
{
|
||||
|
||||
$newGameTitle = trim((string) $this->input('new-game-title', ''));
|
||||
$newGamePlatform = $this->input('new-game-platform');
|
||||
$newGameGenre = $this->input('new-game-genre');
|
||||
|
||||
$this->merge([
|
||||
'game_id' => $this->input('game_id') !== '' ? $this->input('game_id') : null,
|
||||
'new-game-title' => $newGameTitle !== '' ? $newGameTitle : null,
|
||||
'new-game-platform' => $newGamePlatform !== '' ? $newGamePlatform : null,
|
||||
'new-game-genre' => $newGameGenre !== '' ? $newGameGenre : null,
|
||||
'gallery' => $this->input('gallery') !== '' ? $this->input('gallery') : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [];
|
||||
$section = $this->route('section');
|
||||
|
||||
$rules['files_uuid'] = 'array|required|min:1';
|
||||
$rules['files_uuid.*'] = 'string';
|
||||
|
||||
if( section_must_not_be( 'translations', $section ) ){
|
||||
$rules['entry_title'] = "required|string|max:255";
|
||||
} else {
|
||||
$rules['entry_title'] = "nullable|string|max:255";
|
||||
}
|
||||
|
||||
if( section_must_be( 'romhacks', $section ) ){
|
||||
$rules['modifications'] = 'array|required|min:1';
|
||||
$rules['modifications.*'] = 'integer|exists:modifications,id';
|
||||
}
|
||||
|
||||
$rules['version'] = 'required|string|max:50';
|
||||
$rules['release-date'] = 'required|date';
|
||||
$rules['status'] = 'required|integer|exists:statuses,id';
|
||||
$rules['description'] = 'required|string';
|
||||
|
||||
if( section_must_be( ['romhacks', 'translations' ], $section ) ){
|
||||
$rules['game_id'] = 'required_without:new-game-title|nullable|integer|exists:games,id';
|
||||
$rules['new-game-title'] = 'required_without:game_id|nullable|string|max:255';
|
||||
$rules['new-game-platform'] = 'required_with:new-game-title|nullable|integer|exists:platforms,id';
|
||||
$rules['new-game-genre'] = 'required_with:new-game-title|integer|nullable|exists:genres,id';
|
||||
}
|
||||
|
||||
|
||||
$rules['hashes'] = 'array|required|min:1';
|
||||
$rules['hashes.*.filename'] = 'required|string|max:512';
|
||||
$rules['hashes.*.hash_crc32'] = 'required|string|max:512';
|
||||
$rules['hashes.*.hash_sha1'] = 'required|string|max:512';
|
||||
$rules['hashes.*.verified'] = 'required|string|max:512';
|
||||
|
||||
$rules['languages'] = 'array|required|min:1';
|
||||
$rules['languages.*'] = 'integer|exists:languages,id';
|
||||
|
||||
$rules['main-image'] = [ 'required', 'string', new PublicFileExists ];
|
||||
$rules['gallery'] = 'array|required|min:1';
|
||||
$rules['gallery.*'] = [ 'string', new PublicFileExists ];
|
||||
|
||||
$rules['authors'] = 'array|required_without:new-authors|min:1';
|
||||
$rules['authors.*'] = 'integer|exists:authors,id';
|
||||
$rules['new-authors'] = 'array|required_without:authors|min:1';
|
||||
$rules['new-authors.*'] = 'string|max:255';
|
||||
|
||||
$rules['staff_credits'] = 'nullable|json';
|
||||
$rules['release_site'] = 'nullable|url|max:500';
|
||||
$rules['youtube_video'] = 'nullable|url|max:500';
|
||||
|
||||
$rules['submit-state'] = 'required|string|in:draft,pending,published';
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'entry_title.required' => 'Please provide an entry title.',
|
||||
'slug.unique' => 'An entry with this title already exists. Please choose another title.',
|
||||
'file_ids.required' => 'Please upload at least one file.',
|
||||
'file_ids.min' => 'Please upload at least one file.',
|
||||
'hashes.required' => 'Please add at least one hash.',
|
||||
'hashes.min' => 'Please add at least one hash.',
|
||||
'gallery.required' => 'Please add at least one screenshot.',
|
||||
'gallery.min' => 'Please add at least one screenshot.',
|
||||
'new-game-platform.required_without' => 'Please choose a platform for the new game.',
|
||||
'new-game-genre.required_without' => 'Please choose a genre for the new game.',
|
||||
];
|
||||
}
|
||||
}
|
||||
29
app/Http/Requests/TemporaryFileUploadRequest.php
Normal file
29
app/Http/Requests/TemporaryFileUploadRequest.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class TemporaryFileUploadRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'file' => 'required|file|max:100000'
|
||||
];
|
||||
}
|
||||
}
|
||||
149
app/Livewire/AuthorsSelector.php
Normal file
149
app/Livewire/AuthorsSelector.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Author;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Platform;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class AuthorsSelector extends Component
|
||||
{
|
||||
public string $search = '';
|
||||
|
||||
public bool $newAuthor = false;
|
||||
public string $newAuthorName = '';
|
||||
public array $selectedAuthors = []; // 'id', 'name'
|
||||
public array $newAuthors = [];
|
||||
|
||||
public bool $dropdown = false;
|
||||
|
||||
public function mount( array $oldAuthors = [], array $oldNewAuthors = [] ): void
|
||||
{
|
||||
$selectedAuthors = [];
|
||||
|
||||
if ( is_string($oldAuthors) || is_int($oldAuthors) ) {
|
||||
$authors = [ $oldAuthors ];
|
||||
}
|
||||
|
||||
if ( is_array($oldAuthors) && $oldAuthors !== [] ) {
|
||||
$firstItem = reset($oldAuthors);
|
||||
|
||||
if ( is_array($firstItem) && array_key_exists('id', $firstItem) ) {
|
||||
$selectedAuthors = $oldAuthors;
|
||||
} else {
|
||||
$authorRecords = Author::whereIn('id', array_filter($oldAuthors, fn($id) => $id !== null))->get()->keyBy('id');
|
||||
|
||||
foreach ($oldAuthors as $authorId) {
|
||||
if ( isset($authorRecords[$authorId]) ) {
|
||||
$selectedAuthors[] = [
|
||||
'id' => $authorRecords[$authorId]->id,
|
||||
'name' => $authorRecords[$authorId]->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_string($oldNewAuthors) || is_int($oldNewAuthors) ) {
|
||||
$newAuthors = [ $oldNewAuthors ];
|
||||
}
|
||||
|
||||
foreach ( (array) $oldNewAuthors as $name ) {
|
||||
if ( trim((string) $name) === '' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedAuthors[] = [
|
||||
'id' => null,
|
||||
'name' => trim((string) $name),
|
||||
];
|
||||
}
|
||||
|
||||
$this->selectedAuthors = $selectedAuthors;
|
||||
$this->newAuthors = $oldNewAuthors;
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->dropdown = strlen($this->search) > 2;
|
||||
}
|
||||
|
||||
public function selectAuthor( int $id, string $name ): void
|
||||
{
|
||||
foreach ( $this->selectedAuthors as $author ) {
|
||||
if ( $author['id'] === $id ) {
|
||||
$this->search = '';
|
||||
$this->dropdown = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->selectedAuthors[] = [
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
];
|
||||
|
||||
$this->search = '';
|
||||
$this->dropdown = false;
|
||||
|
||||
}
|
||||
|
||||
public function removeAuthor( int $i ): void
|
||||
{
|
||||
array_splice( $this->selectedAuthors, $i, 1 );
|
||||
}
|
||||
|
||||
public function addNewAuthor(): void
|
||||
{
|
||||
if( empty( trim( $this->newAuthorName ) ) )
|
||||
return;
|
||||
|
||||
foreach ( $this->selectedAuthors as $author ) {
|
||||
if( strtolower($author['name']) === strtolower( $this->newAuthorName ) ){
|
||||
$this->newAuthor = false;
|
||||
$this->newAuthorName = '';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->selectedAuthors[] = [
|
||||
'id' => null,
|
||||
'name' => trim( $this->newAuthorName ),
|
||||
];
|
||||
|
||||
$this->newAuthor = false;
|
||||
$this->newAuthorName = '';
|
||||
}
|
||||
|
||||
public function switchNewAuthor(): void
|
||||
{
|
||||
$this->newAuthor = !$this->newAuthor;
|
||||
$this->newAuthorName = '';
|
||||
$this->search = '';
|
||||
$this->dropdown = false;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$authors = collect();
|
||||
$data = [];
|
||||
|
||||
if( $this->dropdown && strlen( $this->search ) > 2 && !$this->newAuthor ){
|
||||
$ids = array_filter(array_column( $this->selectedAuthors, 'id' ), function ( $id ) {
|
||||
return !is_null( $id );
|
||||
});
|
||||
|
||||
$authors = Author::where( 'name', 'like', '%' . $this->search . '%' )
|
||||
->when( !empty( $ids ), function ( $query ) use ( $ids ) { $query->whereNotIn( 'id', $ids ); } )
|
||||
->orderBy( 'name' )
|
||||
->limit( 20 )
|
||||
->get();
|
||||
$data['authors'] = $authors;
|
||||
}
|
||||
|
||||
return view('livewire.authors-selector', $data );
|
||||
}
|
||||
}
|
||||
184
app/Livewire/GameSelector.php
Normal file
184
app/Livewire/GameSelector.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Platform;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Game Selector for existing games and new games.
|
||||
*/
|
||||
class GameSelector extends Component
|
||||
{
|
||||
|
||||
public const int REQUIRED_CHARS = 3;
|
||||
|
||||
/**
|
||||
* If we are in new game mode.
|
||||
* @var bool
|
||||
*/
|
||||
public bool $newGame = false;
|
||||
|
||||
/**
|
||||
* Current user game search.
|
||||
* @var string
|
||||
*/
|
||||
public string $search = '';
|
||||
|
||||
/**
|
||||
* Existing game ID selected.
|
||||
* @var int|null
|
||||
*/
|
||||
public ?int $gameId = null;
|
||||
|
||||
/**
|
||||
* Existing game name selected or new game name.
|
||||
* @var string|null
|
||||
*/
|
||||
public ?string $gameName = null;
|
||||
|
||||
/**
|
||||
* New game platform ID.
|
||||
* @var int|null
|
||||
*/
|
||||
public ?int $gamePlatformId = null;
|
||||
|
||||
/**
|
||||
* New game genre ID.
|
||||
* @var int|null
|
||||
*/
|
||||
public ?int $gameGenreId = null;
|
||||
|
||||
/**
|
||||
* Existing game platform name or new game platform name.
|
||||
* @var string|null
|
||||
*/
|
||||
public ?string $platformName = null;
|
||||
|
||||
/**
|
||||
* Existing game genre name or new game genre name.
|
||||
* @var string|null
|
||||
*/
|
||||
public ?string $genreName = null;
|
||||
|
||||
/**
|
||||
* If dropdown must be rendered or not.
|
||||
* @var bool
|
||||
*/
|
||||
public bool $dropdown = false;
|
||||
|
||||
public function mount( ?int $gameId = null, ?string $newGameTitle = null, ?int $newGamePlatform = null, ?int $newGameGenre = null ): void
|
||||
{
|
||||
|
||||
// If we selected an existent game.
|
||||
if( $gameId ){
|
||||
$game = Game::with(['platform','genre'])->find($gameId);
|
||||
if( $game ){
|
||||
$this->gameId = $game->id;
|
||||
$this->gameName = $game->name;
|
||||
$this->platformName = $game->platform->name;
|
||||
$this->genreName = $game->genre->name;
|
||||
$this->search = $game->name;
|
||||
$this->newGame = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if( $newGameTitle || $newGamePlatform || $newGameGenre ){
|
||||
$this->newGame = true;
|
||||
$this->gameName = $newGameTitle;
|
||||
$this->gamePlatformId = is_numeric($newGamePlatform) ? (int) $newGamePlatform : null;
|
||||
$this->gameGenreId = is_numeric($newGameGenre) ? (int) $newGameGenre : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If we update search bar.
|
||||
* @return void
|
||||
*/
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
if( $this->gameId ){
|
||||
$this->gameId = null;
|
||||
$this->gameName = null;
|
||||
$this->platformName = null;
|
||||
$this->genreName = null;
|
||||
}
|
||||
$this->dropdown = strlen($this->search) >= self::REQUIRED_CHARS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an existent game.
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function selectGame( int $id, string $name ): void
|
||||
{
|
||||
$game = Game::with(['platform','genre'])->find($id);
|
||||
if( $game ){
|
||||
$this->gameId = $game->id;
|
||||
$this->gameName = $game->name;
|
||||
$this->platformName = $game->platform->name;
|
||||
$this->genreName = $game->genre->name;
|
||||
$this->search = $game->name;
|
||||
$this->dropdown = false;
|
||||
|
||||
$this->dispatch( 'game-selected', id: $id ); // Send an event to the JS part.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear existent game selection.
|
||||
* @return void
|
||||
*/
|
||||
public function clearGame(): void
|
||||
{
|
||||
$this->gameId = null;
|
||||
$this->gameName = null;
|
||||
$this->platformName = null;
|
||||
$this->genreName = null;
|
||||
$this->search = '';
|
||||
|
||||
$this->dispatch( 'game-selected', id: null ); // Send an event to the JS part.
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch mode.
|
||||
* @return void
|
||||
*/
|
||||
public function switchNewGame(): void
|
||||
{
|
||||
$this->clearGame();
|
||||
$this->newGame = !$this->newGame;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$games = collect();
|
||||
|
||||
// Need to search games for dropdown.
|
||||
if( $this->dropdown && strlen($this->search) >= self::REQUIRED_CHARS && $this->newGame === false ){
|
||||
$games = Game::with(['platform','genre'])
|
||||
->where('name', 'like', '%'.$this->search.'%')
|
||||
->orderBy('name')
|
||||
->limit(20)
|
||||
->get();
|
||||
}
|
||||
|
||||
$data = [ 'games' => $games, 'required_chars' => self::REQUIRED_CHARS ];
|
||||
if( $this->newGame === true ){ // If we want a new game, get platforms and genres.
|
||||
$data['platforms'] = Platform::orderBy('name')->get();
|
||||
$data['genres'] = Genre::orderBy('name')->get();
|
||||
}
|
||||
$data['hasOldNewGame'] = old('new-game-title') || old('new-game-platform') || old('new-game-genre');
|
||||
|
||||
return view('livewire.game-selector', $data );
|
||||
}
|
||||
}
|
||||
77
app/Livewire/HashesUpload.php
Normal file
77
app/Livewire/HashesUpload.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\EntryHash;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Platform;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Hashes uploader in submission form.
|
||||
* Upload hash and create fields.
|
||||
*
|
||||
* @phpstan-import-type HashObject from \App\Types\SubmissionTypes
|
||||
*/
|
||||
class HashesUpload extends Component
|
||||
{
|
||||
|
||||
/**
|
||||
* List of hashes.
|
||||
* @var list<HashObject> $hashes
|
||||
*/
|
||||
public array $hashes = [];
|
||||
|
||||
/**
|
||||
* Prepare old hashes.
|
||||
*
|
||||
* @param list<HashObject> $oldHashes
|
||||
* @return void
|
||||
*/
|
||||
public function mount( array $oldHashes = [] ): void
|
||||
{
|
||||
foreach ($oldHashes as $hash) {
|
||||
$this->addHash( $hash['filename'], $hash['hash_crc32'], $hash['hash_sha1'], $hash['verified'] );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an hash to the list.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $crc32
|
||||
* @param string $sha1
|
||||
* @param string|null $verified
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addHash(string $filename, string $crc32, string $sha1, ?string $verified = null): void
|
||||
{
|
||||
$this->hashes[] = [
|
||||
'filename' => $filename,
|
||||
'hash_crc32' => $crc32,
|
||||
'hash_sha1' => $sha1,
|
||||
'verified' => $verified ?? "No-Intro" // TODO: Change it.
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific hash.
|
||||
*
|
||||
* @param int $index
|
||||
* @return void
|
||||
*/
|
||||
public function removeHash(int $index): void
|
||||
{
|
||||
array_splice($this->hashes, $index, 1);
|
||||
}
|
||||
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.hashes-upload');
|
||||
}
|
||||
}
|
||||
12
app/Models/Author.php
Normal file
12
app/Models/Author.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Author extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name', 'slug', 'user_id', 'website'
|
||||
];
|
||||
}
|
||||
93
app/Models/Entry.php
Normal file
93
app/Models/Entry.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Entry extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'title',
|
||||
'slug',
|
||||
'description',
|
||||
'main_image',
|
||||
'state',
|
||||
'featured',
|
||||
'game_id',
|
||||
'platform_id',
|
||||
'status_id',
|
||||
'version',
|
||||
'release_date',
|
||||
'staff_credits',
|
||||
'relevant_link',
|
||||
'youtube_link',
|
||||
'user_id',
|
||||
'complete_title',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $casts = [
|
||||
'featured' => 'boolean',
|
||||
'release_date' => 'date',
|
||||
];
|
||||
|
||||
public function scopePublished( Builder $query ): Builder {
|
||||
return $query->where( 'state', 'published' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return game link.
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function game(): BelongsTo {
|
||||
return $this->belongsTo(Game::class);
|
||||
}
|
||||
|
||||
public function platform(): BelongsTo {
|
||||
return $this->belongsTo(Platform::class);
|
||||
}
|
||||
|
||||
public function getRealPlatform(): ?Platform {
|
||||
return $this->game?->platform ?? $this->platform;
|
||||
}
|
||||
|
||||
public function status(): BelongsTo {
|
||||
return $this->belongsTo(Status::class );
|
||||
}
|
||||
|
||||
public function authors(): BelongsToMany {
|
||||
return $this->belongsToMany(Author::class, 'entry_authors');
|
||||
}
|
||||
|
||||
public function languages(): BelongsToMany {
|
||||
return $this->belongsToMany(Language::class, 'entry_languages');
|
||||
}
|
||||
|
||||
public function modifications(): BelongsToMany {
|
||||
return $this->belongsToMany( Modification::class, 'entry_modifications');
|
||||
}
|
||||
|
||||
public function files(): HasMany {
|
||||
return $this->hasMany(EntryFile::class)->orderBy('filename');
|
||||
}
|
||||
|
||||
public function gallery(): HasMany {
|
||||
return $this->hasMany(EntryGallery::class)->orderBy('id');
|
||||
}
|
||||
|
||||
public function hashes(): HasMany {
|
||||
return $this->hasMany(EntryHash::class);
|
||||
}
|
||||
|
||||
}
|
||||
23
app/Models/EntryFile.php
Normal file
23
app/Models/EntryFile.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class EntryFile extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'entry_id', 'filename', 'filepath', 'favorite_server', 'favorite_at', 'filesize', 'state', 'file_uuid'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'favorite_at' => 'timestamp'
|
||||
];
|
||||
|
||||
public function entry(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Entry::class);
|
||||
}
|
||||
|
||||
}
|
||||
11
app/Models/EntryGallery.php
Normal file
11
app/Models/EntryGallery.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EntryGallery extends Model
|
||||
{
|
||||
protected $fillable = ['entry_id','image'];
|
||||
|
||||
}
|
||||
18
app/Models/EntryHash.php
Normal file
18
app/Models/EntryHash.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class EntryHash extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'entry_id', 'filename', 'hash_crc32', 'hash_sha1', 'verified'
|
||||
];
|
||||
|
||||
public function entry(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Entry::class);
|
||||
}
|
||||
}
|
||||
24
app/Models/Game.php
Normal file
24
app/Models/Game.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Game extends Model
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = ['name', 'slug', 'platform_id', 'genre_id' ];
|
||||
|
||||
public function platform(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Platform::class);
|
||||
}
|
||||
|
||||
public function genre(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Genre::class);
|
||||
}
|
||||
}
|
||||
10
app/Models/Genre.php
Normal file
10
app/Models/Genre.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Genre extends Model
|
||||
{
|
||||
protected $fillable = [ 'name', 'slug' ];
|
||||
}
|
||||
10
app/Models/Language.php
Normal file
10
app/Models/Language.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Language extends Model
|
||||
{
|
||||
protected $fillable = [ 'name', 'slug' ];
|
||||
}
|
||||
10
app/Models/Modification.php
Normal file
10
app/Models/Modification.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Modification extends Model
|
||||
{
|
||||
protected $fillable = [ 'name', 'slug' ];
|
||||
}
|
||||
17
app/Models/Platform.php
Normal file
17
app/Models/Platform.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Platform extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'slug', 'short_name'
|
||||
];
|
||||
|
||||
}
|
||||
10
app/Models/Status.php
Normal file
10
app/Models/Status.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Status extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'slug'];
|
||||
}
|
||||
32
app/Models/User.php
Normal file
32
app/Models/User.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
#[Fillable(['name', 'email', 'password'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
27
app/Providers/AppServiceProvider.php
Normal file
27
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Auth\XenForoGuard;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
\Auth::extend('xenforo', function ($app, $name, array $config) {
|
||||
return new XenForoGuard($app['request']);
|
||||
});
|
||||
}
|
||||
}
|
||||
59
app/Providers/Filament/ManagePanelProvider.php
Normal file
59
app/Providers/Filament/ManagePanelProvider.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\AuthenticateSession;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages\Dashboard;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Widgets\AccountWidget;
|
||||
use Filament\Widgets\FilamentInfoWidget;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class ManagePanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('manage')
|
||||
->path('manage')
|
||||
->login()
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
|
||||
->pages([
|
||||
Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
|
||||
->widgets([
|
||||
AccountWidget::class,
|
||||
FilamentInfoWidget::class,
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
PreventRequestForgery::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
23
app/Rules/PublicFileExists.php
Normal file
23
app/Rules/PublicFileExists.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Translation\PotentiallyTranslatedString;
|
||||
|
||||
class PublicFileExists implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param Closure(string, ?string=): PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if(!Storage::disk('public')->exists($value) ) {
|
||||
$fail("The file {$value} does not exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
132
app/Services/FileServersService.php
Normal file
132
app/Services/FileServersService.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\EntryFile;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class FileServersService {
|
||||
|
||||
public const FAVORITE_SERVER_TIME = 3600; // In seconds.
|
||||
private const ZEUS_TOKEN_EXPIRATION = 600; // In seconds.
|
||||
|
||||
private string $apiKey;
|
||||
private array $servers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apiKey = config('fileservers.secret_key');
|
||||
$this->servers = config('fileservers.servers');
|
||||
}
|
||||
|
||||
public function generateZeusToken( int $user_id, string $to, string $action ){
|
||||
|
||||
$info = [
|
||||
'user_id' => $user_id,
|
||||
'to' => $to,
|
||||
'action' => $action,
|
||||
'generated_at' => time(),
|
||||
'expires_at' => time() + self::ZEUS_TOKEN_EXPIRATION,
|
||||
'romhackplaza' => bin2hex(random_bytes(16)),
|
||||
];
|
||||
|
||||
$json = json_encode( $info );
|
||||
$sig = hash_hmac( 'sha256', $json, $this->apiKey );
|
||||
|
||||
$end = base64_encode( $json ) . "|" . $sig;
|
||||
return $end;
|
||||
}
|
||||
|
||||
public function getRandomServerKey(): string
|
||||
{
|
||||
$keys = array_keys($this->servers);
|
||||
return $keys[array_rand($keys)];
|
||||
}
|
||||
|
||||
public function getEntryFileServerKey( EntryFile $file ): string
|
||||
{
|
||||
$serverKey = null;
|
||||
|
||||
if( $file->favorite_server !== null && $file->favorite_at !== null ) {
|
||||
if( time() < $file->favorite_at + static::FAVORITE_SERVER_TIME ) {
|
||||
$serverKey = $file->favorite_server;
|
||||
}
|
||||
}
|
||||
if( $serverKey === null || !isset( $this->servers[$serverKey] ) ) {
|
||||
$serverKey = $this->getRandomServerKey();
|
||||
}
|
||||
|
||||
return $serverKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download URL requires 'filepath' and 'filename'.
|
||||
*
|
||||
* @param EntryFile $file
|
||||
* @return string
|
||||
*/
|
||||
public function getDownloadFileUrl( EntryFile $file ): string
|
||||
{
|
||||
$serverKey = $this->getEntryFileServerKey( $file );
|
||||
$url = $this->servers[$serverKey]['download'] ?? "#";
|
||||
if( $url === "#" )
|
||||
return $url;
|
||||
|
||||
return $url . "&" . http_build_query( [ 'filename' => $file->filename, 'filepath' => $file->filepath ] );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConnectionException
|
||||
*/
|
||||
public function uploadChunk(
|
||||
UploadedFile $chunk,
|
||||
string $fileUUID,
|
||||
int $currentChunk,
|
||||
int $totalChunks,
|
||||
string $filename,
|
||||
string $type
|
||||
){
|
||||
|
||||
// Define or get favorite server.
|
||||
if( $currentChunk === 0 ){
|
||||
$serverKey = $this->getRandomServerKey();
|
||||
Cache::put("favorite_server_{$fileUUID}", $serverKey, now()->addHours(2) );
|
||||
} else {
|
||||
$serverKey = Cache::get( "favorite_server_{$fileUUID}" );
|
||||
abort_if( !$serverKey, 422, "File upload expired, please retry." );
|
||||
}
|
||||
|
||||
$server = $this->servers[$serverKey];
|
||||
$filepath = $type . '/' . $fileUUID;
|
||||
|
||||
$response = Http::withHeaders([])
|
||||
->attach( 'file', file_get_contents( $chunk->getRealPath() ), $fileUUID )
|
||||
->post( $server['upload_chunk'], [
|
||||
'filepath' => $filepath,
|
||||
'filename' => $filename,
|
||||
'current_chunk' => $currentChunk,
|
||||
'total_chunks' => $totalChunks,
|
||||
// TODO : Must replace User ID
|
||||
'zeus' => $this->generateZeusToken( 0, $server['base_url'], "Uploadchunk" ),
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
throw new \RuntimeException( $response->body() );
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
if( isset( $data['file'] ) && $data['file'] !== false ){
|
||||
Cache::forget( "favorite_server_{$fileUUID}" );
|
||||
}
|
||||
$data['favorite_server'] = $serverKey;
|
||||
$data['file_uuid'] = $fileUUID;
|
||||
$data['file_path'] = $filepath;
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
388
app/Services/SubmissionsService.php
Normal file
388
app/Services/SubmissionsService.php
Normal file
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Exceptions\SubmissionException;
|
||||
use App\Helpers\EntryHelpers;
|
||||
use App\Http\Requests\StoreEntryRequest;
|
||||
use App\Models\Author;
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryFile;
|
||||
use App\Models\EntryGallery;
|
||||
use App\Models\EntryHash;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Language;
|
||||
use App\Models\Modification;
|
||||
use App\Models\Platform;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
/**
|
||||
* @phpstan-import-type FSFileData from \App\Types\FSTypes
|
||||
*/
|
||||
class SubmissionsService {
|
||||
|
||||
/**
|
||||
* Request for store/edit.
|
||||
* @var StoreEntryRequest|null
|
||||
*/
|
||||
private ?StoreEntryRequest $request = null;
|
||||
|
||||
/**
|
||||
* Section for store/edit.
|
||||
* @var string|null
|
||||
*/
|
||||
private ?string $section = null;
|
||||
|
||||
/**
|
||||
* @return list<FSFileData>
|
||||
*/
|
||||
public function prepareOldFiles( ?Entry $entry = null ): array
|
||||
{
|
||||
if( $entry === null ){
|
||||
$files = old( 'files_uuid', [] );
|
||||
} else {
|
||||
$files = old( 'files_uuid', $entry->files->pluck('file_uuid')->toArray() );
|
||||
}
|
||||
|
||||
if( $files === [] )
|
||||
return [];
|
||||
|
||||
return array_map(
|
||||
function( string $uuid ) {
|
||||
$file = EntryFile::where('file_uuid', $uuid)->first();
|
||||
|
||||
if( $file )
|
||||
return [
|
||||
'name' => $file->filename,
|
||||
'totalChunks' => 0, // Already uploaded.
|
||||
'rawFile' => null,
|
||||
'progressValue' => 0,
|
||||
'currentChunk' => 0,
|
||||
'done' => true,
|
||||
'error' => null,
|
||||
'uuid' => $uuid
|
||||
];
|
||||
|
||||
$file = Cache::get("uploaded_file_{$uuid}");
|
||||
if( $file )
|
||||
return [
|
||||
'name' => $file['filename'],
|
||||
'totalChunks' => 0, // Already uploaded.
|
||||
'rawFile' => null,
|
||||
'progressValue' => 0,
|
||||
'currentChunk' => 0,
|
||||
'done' => true,
|
||||
'error' => null,
|
||||
'uuid' => $uuid
|
||||
];
|
||||
|
||||
return null;
|
||||
},
|
||||
$files );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StoreEntryRequest $request
|
||||
* @param string $section
|
||||
*
|
||||
* @return Entry
|
||||
* @throws SubmissionException
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function storeEntry( StoreEntryRequest $request, string $section ){
|
||||
|
||||
// STEP 1 : Prepare basic fields.
|
||||
|
||||
$this->request = $request;
|
||||
$this->section = $section;
|
||||
$user_id = 0; // TODO: Replace that.
|
||||
|
||||
$entry = DB::transaction(function () use ( $user_id ) {
|
||||
|
||||
// STEP 2 : Create game.
|
||||
|
||||
$gameId = null;
|
||||
if( section_must_be( ['romhacks', 'translations'], $this->section ) ){
|
||||
$gameId = $this->Step2_CreateAndReturnGameId();
|
||||
}
|
||||
|
||||
// STEP 3 : Create Complete title.
|
||||
$completeTitle = $this->Step3_BuildCompleteTitle( $gameId );
|
||||
|
||||
// STEP 4 : Generate slug and entry title.
|
||||
$entrySlug = EntryHelpers::uniqueSlug( $completeTitle, Entry::class );
|
||||
|
||||
if( section_must_be( 'translations', $this->section ) &&
|
||||
!$this->request->input('entry_title') ){
|
||||
$entryTitle = Game::find($gameId)->name;
|
||||
} else {
|
||||
$entryTitle = $this->request->input('entry_title');
|
||||
}
|
||||
|
||||
// STEP 5 : Removed / Delayed.
|
||||
// $mainImage = $this->Step5_MoveMainImage();
|
||||
|
||||
// STEP 6 : Prepare entry fields and save entry.
|
||||
$fields = [
|
||||
'type' => $this->section,
|
||||
'title' => $entryTitle,
|
||||
'slug' => $entrySlug,
|
||||
'description' => $this->request->input('description'),
|
||||
'main_image' => $this->request->input('main-image'),
|
||||
'state' => $this->request->input('submit-state'),
|
||||
'game_id' => $gameId,
|
||||
'status_id' => $this->request->input('status'),
|
||||
'version' => $this->request->input('version'),
|
||||
'release_date' => $this->request->input('release-date'),
|
||||
'staff_credits' => $this->request->input('staff_credits'),
|
||||
'relevant_link' => $this->request->input('release_site'),
|
||||
'youtube_link' => $this->request->input('youtube_video'),
|
||||
'user_id' => $user_id,
|
||||
'complete_title' => $completeTitle,
|
||||
];
|
||||
|
||||
$entry = Entry::create( $fields );
|
||||
|
||||
// STEP 7 : Save entry fields.
|
||||
$this->Step7_SaveEntryFiles( $entry->id );
|
||||
|
||||
// STEP 8 : Save hashes.
|
||||
$this->Step8_SaveHashes( $entry->id );
|
||||
|
||||
// STEP 9 : Save Authors.
|
||||
$this->Step9_SaveAuthors( $entry );
|
||||
|
||||
// STEP 10 : Save Modifications.
|
||||
if( section_must_be( 'romhacks', $this->section ) ){
|
||||
$this->Step10_SaveRomhacksModifications( $entry );
|
||||
}
|
||||
|
||||
// STEP 11 : Save Languages
|
||||
$this->Step11_SaveLanguages( $entry );
|
||||
|
||||
// STEP 12 : Prepare Gallery images.
|
||||
$this->Step12a_PrepareGalleryImages( $entry );
|
||||
|
||||
return $entry;
|
||||
|
||||
});
|
||||
|
||||
// Step 12, Move main image and gallery.
|
||||
$this->Step12b_MoveMainImage( $entry );
|
||||
$this->Step12c_SaveGalleryImages( $entry );
|
||||
|
||||
return $entry;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step2_CreateAndReturnGameId(): int {
|
||||
|
||||
// Already existing game.
|
||||
if( $this->request->input('game_id') )
|
||||
return $this->request->input('game_id');
|
||||
|
||||
// Need to create a game.
|
||||
if( !$this->request->input('new-game-title') || !$this->request->input('new-game-platform') || !$this->request->input('new-game-genre') )
|
||||
throw new SubmissionException( "New game informations is missing" );
|
||||
|
||||
$platform = Platform::find( $this->request->input('new-game-platform') );
|
||||
$genre = Genre::find( $this->request->input('new-game-genre') );
|
||||
|
||||
if( !$platform || !$genre )
|
||||
throw new SubmissionException( "Incorrect game platform id" );
|
||||
|
||||
$gameSlug = EntryHelpers::uniqueSlug( $this->request->input('new-game-title'), Game::class );
|
||||
|
||||
$game = Game::create([
|
||||
'name' => trim( $this->request->input('new-game-title') ),
|
||||
'slug' => $gameSlug,
|
||||
'platform_id' => $platform->id,
|
||||
'genre_id' => $genre->id,
|
||||
]);
|
||||
|
||||
return $game->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and build complete title.
|
||||
*
|
||||
* @param int|null $gameId
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function Step3_BuildCompleteTitle( ?int $gameId = null ): string {
|
||||
$fields = [];
|
||||
|
||||
$fields['entry_title'] = $this->request->input('entry_title') ?? null;
|
||||
if( section_must_be( [ 'homebrew', 'translations' ], $this->section ) && $gameId ){
|
||||
$fields['game_name'] = Game::find( $gameId )->name;
|
||||
}
|
||||
if( section_must_be( 'translations', $this->section ) ) {
|
||||
$fields['languages_string'] = Language::whereIn('id', $this->request->input('languages', []))->pluck('name')->implode(', ');
|
||||
}
|
||||
if( section_must_be(['romhacks', 'homebrew', 'lua-scripts', 'tutorials'], $this->section ) ) {
|
||||
// TODO: Add single platform ID compatibility.
|
||||
$fields['platform_name'] = Game::find( $gameId )->platform->name;
|
||||
}
|
||||
|
||||
return EntryHelpers::buildCompleteTitle( $this->section, $fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step7_SaveEntryFiles( int $entryId ): void
|
||||
{
|
||||
foreach ( $this->request->input('files_uuid', [] ) as $uuid ) {
|
||||
$fileData = Cache::pull("uploaded_file_{$uuid}");
|
||||
if( !$fileData )
|
||||
throw new SubmissionException( "File {$uuid} has expired. Please delete all your files and retry." );
|
||||
|
||||
EntryFile::create([
|
||||
'entry_id' => $entryId,
|
||||
'file_uuid' => $uuid,
|
||||
'filename' => $fileData['filename'],
|
||||
'filepath' => $fileData['filepath'],
|
||||
'favorite_server' => $fileData['favorite_server'],
|
||||
'favorite_at' => \DateTimeImmutable::createFromTimestamp( $fileData['favorite_at'] ),
|
||||
'filesize' => $fileData['filesize'],
|
||||
'state' => 'public'
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $entryId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function Step8_SaveHashes( int $entryId ): void
|
||||
{
|
||||
foreach ( $this->request->input('hashes', [] ) as $hash ) {
|
||||
if( !isset($hash['filename'], $hash['hash_crc32'], $hash['hash_sha1'], $hash['verified']) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EntryHash::create([
|
||||
'entry_id' => $entryId,
|
||||
'filename' => $hash['filename'],
|
||||
'hash_crc32' => $hash['hash_crc32'],
|
||||
'hash_sha1' => $hash['hash_sha1'],
|
||||
'verified' => $hash['verified'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step9_SaveAuthors( Entry $entry ): void
|
||||
{
|
||||
// Existing authors.
|
||||
foreach ( $this->request->input('authors', [] ) as $authorId ) {
|
||||
$author = Author::find( $authorId );
|
||||
if( !$author )
|
||||
throw new SubmissionException( "Author {$authorId} does not exist." );
|
||||
$entry->authors()->attach( $author->id );
|
||||
}
|
||||
|
||||
// New Authors
|
||||
foreach ( $this->request->input('new-authors', [] ) as $authorName ) {
|
||||
$authorName = trim( $authorName );
|
||||
if( $authorName === '' )
|
||||
continue;
|
||||
|
||||
$author = Author::firstOrCreate(
|
||||
['slug' => EntryHelpers::uniqueSlug( $authorName, Author::class )],
|
||||
['name' => $authorName]
|
||||
);
|
||||
$entry->authors()->attach( $author->id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step10_SaveRomhacksModifications( Entry $entry ): void
|
||||
{
|
||||
foreach ( $this->request->input('modifications', [] ) as $modificationId ) {
|
||||
$modification = Modification::find( $modificationId );
|
||||
if( !$modification )
|
||||
throw new SubmissionException( "Modification {$modificationId} does not exist." );
|
||||
$entry->modifications()->attach( $modification->id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
* @throws SubmissionException
|
||||
*/
|
||||
private function Step11_SaveLanguages( Entry $entry ): void
|
||||
{
|
||||
foreach ( $this->request->input('languages', [] ) as $languageId ) {
|
||||
$language = Language::find( $languageId );
|
||||
if( !$language )
|
||||
throw new SubmissionException( "Language {$languageId} does not exist." );
|
||||
$entry->languages()->attach( $language->id );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function Step12a_PrepareGalleryImages( Entry $entry ): void
|
||||
{
|
||||
foreach ( $this->request->input('gallery', [] ) as $imagePath ) {
|
||||
EntryGallery::create([
|
||||
'entry_id' => $entry->id,
|
||||
'image' => $imagePath,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entry $entry
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function Step12b_MoveMainImage( Entry $entry ): void {
|
||||
$mainImage = $entry->main_image;
|
||||
$newPath = 'entries/main-images/' . basename($mainImage);
|
||||
|
||||
if( !Storage::disk('public')->move($mainImage, $newPath) )
|
||||
return;
|
||||
|
||||
$entry->update(['main_image' => $newPath]);
|
||||
}
|
||||
|
||||
private function Step12c_SaveGalleryImages( Entry $entry ): void
|
||||
{
|
||||
foreach ( $entry->gallery as $galleryItem ) {
|
||||
$newPath = 'entries/gallery-images/' . $entry->id . '/' . basename($galleryItem->image);
|
||||
|
||||
if( !Storage::disk('public')->move($galleryItem->image, $newPath) )
|
||||
continue;
|
||||
|
||||
$galleryItem->update(['image' => $newPath]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
app/Services/TemporaryFileService.php
Normal file
25
app/Services/TemporaryFileService.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class TemporaryFileService {
|
||||
|
||||
public const int NB_HOURS_FILES_KEPT = 6;
|
||||
|
||||
/**
|
||||
* Upload a file in the temporary path.
|
||||
*
|
||||
* @param UploadedFile|null $file
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function uploadFile(?UploadedFile $file ): string|bool {
|
||||
|
||||
if( !$file )
|
||||
return false;
|
||||
|
||||
return $file->store( 'temp', 'public' );
|
||||
}
|
||||
}
|
||||
36
app/Services/XenforoService.php
Normal file
36
app/Services/XenforoService.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class XenforoService {
|
||||
|
||||
private const array PERMISSIONS_KEPT = [ 'general', 'romhackplaza' ];
|
||||
private const int TTL_PERMISSIONS = 300;
|
||||
|
||||
public function getPermissions(int $userId, int $permissionCombinationId): array {
|
||||
|
||||
return Cache::remember("xf_permissions_{$userId}", self::TTL_PERMISSIONS, function() use($permissionCombinationId) {
|
||||
$row = \DB::connection('xenforo')
|
||||
->table('permission_combination')
|
||||
->where('permission_combination_id', $permissionCombinationId)
|
||||
->value('cache_value');
|
||||
|
||||
if( !$row )
|
||||
return [];
|
||||
|
||||
$data = json_decode($row, true);
|
||||
$data = array_intersect_key($data, array_flip(self::PERMISSIONS_KEPT));
|
||||
|
||||
return $data ?: [];
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public function clearUserData(int $userId): void
|
||||
{
|
||||
Cache::forget("xf_permissions_{$userId}");
|
||||
}
|
||||
}
|
||||
19
app/Types/FSTypes.php
Normal file
19
app/Types/FSTypes.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Types;
|
||||
|
||||
/**
|
||||
* @phpstan-type FSFileData array{
|
||||
* name:string,
|
||||
* totalChunks:int,
|
||||
* rawFile?:mixed, // Must be used only in JS.
|
||||
* progressValue:int,
|
||||
* currentChunk:int,
|
||||
* done:bool,
|
||||
* error:mixed,
|
||||
* uuid:string
|
||||
* }
|
||||
*/
|
||||
interface FSTypes
|
||||
{
|
||||
}
|
||||
14
app/Types/SubmissionTypes.php
Normal file
14
app/Types/SubmissionTypes.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Types;
|
||||
|
||||
/**
|
||||
* @phpstan-type HashObject array{
|
||||
* filename:string,
|
||||
* hash_crc32:string,
|
||||
* hash_sha1:string,
|
||||
* verified:string
|
||||
* }
|
||||
*/
|
||||
interface SubmissionTypes
|
||||
{}
|
||||
30
app/View/Components/EntryMetaItem.php
Normal file
30
app/View/Components/EntryMetaItem.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class EntryMetaItem extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public string $value,
|
||||
public string $route = "#"
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.entry-meta-item');
|
||||
}
|
||||
}
|
||||
29
app/View/Components/EntrySectionTitle.php
Normal file
29
app/View/Components/EntrySectionTitle.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class EntrySectionTitle extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public string $icon = ''
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.entry-section-title');
|
||||
}
|
||||
}
|
||||
43
app/View/Components/ErrorBlock.php
Normal file
43
app/View/Components/ErrorBlock.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ErrorBlock extends Component
|
||||
{
|
||||
|
||||
private const array ERROR_TYPES = [
|
||||
'custom' => [
|
||||
'icon' => 'ban',
|
||||
'message' => '%s'
|
||||
],
|
||||
'page-not-allowed' => [
|
||||
'icon' => 'shield-ban',
|
||||
'message' => "You do not have permission to access this page.\nRequired permission: %s"
|
||||
]
|
||||
];
|
||||
|
||||
public array $errorArray;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $errorType,
|
||||
public string $message = ""
|
||||
)
|
||||
{
|
||||
$this->errorArray = self::ERROR_TYPES[$errorType] ?? self::ERROR_TYPES['custom'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.error-block');
|
||||
}
|
||||
}
|
||||
29
app/View/Components/FormErrorText.php
Normal file
29
app/View/Components/FormErrorText.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class FormErrorText extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $message,
|
||||
public bool $icon = true
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.form-error-text');
|
||||
}
|
||||
}
|
||||
30
app/View/Components/FormFieldTitle.php
Normal file
30
app/View/Components/FormFieldTitle.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class FormFieldTitle extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $helper = "",
|
||||
public bool $required = false,
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.form-field-title');
|
||||
}
|
||||
}
|
||||
29
app/View/Components/FormGroupTitle.php
Normal file
29
app/View/Components/FormGroupTitle.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class FormGroupTitle extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $label,
|
||||
public string $icon = ""
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.form-group-title');
|
||||
}
|
||||
}
|
||||
29
app/View/Components/GalleryField.php
Normal file
29
app/View/Components/GalleryField.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class GalleryField extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public array $oldPaths = [],
|
||||
public bool $required = true,
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.gallery-field');
|
||||
}
|
||||
}
|
||||
33
app/View/Components/LanguagesSelector.php
Normal file
33
app/View/Components/LanguagesSelector.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use App\Models\Language;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class LanguagesSelector extends Component
|
||||
{
|
||||
|
||||
public $languages;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public array $selected = [],
|
||||
public bool $required = true
|
||||
)
|
||||
{
|
||||
$this->languages = Language::orderBy('name')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.languages-selector');
|
||||
}
|
||||
}
|
||||
29
app/View/Components/MainImageField.php
Normal file
29
app/View/Components/MainImageField.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class MainImageField extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $oldPath = "",
|
||||
public bool $required = true
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.main-image-field');
|
||||
}
|
||||
}
|
||||
35
app/View/Components/MarkdownTextarea.php
Normal file
35
app/View/Components/MarkdownTextarea.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class MarkdownTextarea extends Component
|
||||
{
|
||||
|
||||
public array $toolbar;
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $value = "",
|
||||
public string $minHeight = "200px",
|
||||
public bool $required = true
|
||||
)
|
||||
{
|
||||
$this->toolbar = [
|
||||
'bold', 'italic', 'strikethrough', 'heading', '|', 'quote', 'unordered-list', 'ordered-list', 'check-list', '|', 'link', 'image', '|', 'preview', 'guide'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.markdown-textarea');
|
||||
}
|
||||
}
|
||||
28
app/View/Components/StaffCreditsField.php
Normal file
28
app/View/Components/StaffCreditsField.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class StaffCreditsField extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $oldStaffCredits = null // JSON.
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.staff-credits-field');
|
||||
}
|
||||
}
|
||||
44
app/View/Components/SubmitEntryStatus.php
Normal file
44
app/View/Components/SubmitEntryStatus.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class SubmitEntryStatus extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $section
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function availableStates(): array
|
||||
{
|
||||
// TODO: Change for permissions.
|
||||
return [
|
||||
'draft' => "Save into my drafts",
|
||||
'pending' => "Add to submissions queue",
|
||||
'published' => "Publish it now"
|
||||
];
|
||||
}
|
||||
|
||||
public function defaultState(): string
|
||||
{
|
||||
$availableStates = array_keys( $this->availableStates() );
|
||||
return in_array('published', $availableStates) ? 'published' : ( in_array('pending', $availableStates) ? 'pending' : 'draft' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.submit-entry-status', [ 'states' => $this->availableStates(), 'defaultState' => $this->defaultState(), 'section' => $this->section ]);
|
||||
}
|
||||
}
|
||||
39
app/View/Components/SuccessBlock.php
Normal file
39
app/View/Components/SuccessBlock.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class SuccessBlock extends Component
|
||||
{
|
||||
|
||||
private const array SUCCESS_TYPES = [
|
||||
'custom' => [
|
||||
'icon' => 'check',
|
||||
'message' => '%s'
|
||||
]
|
||||
];
|
||||
|
||||
public array $successArray;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $successType,
|
||||
public string $message = "",
|
||||
)
|
||||
{
|
||||
$this->successArray = self::SUCCESS_TYPES[$this->successType] ?? self::SUCCESS_TYPES['custom'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.success-block');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user