52
.gitea/workflows/deploy-staging.yml
Normal file
52
.gitea/workflows/deploy-staging.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Deploy Laravel Staging
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [staging]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
|
||||
steps:
|
||||
- name: Configure Git credentials
|
||||
run: |
|
||||
git config --global credential.helper store
|
||||
echo "https://staging-runner:${{ env.GITEA_TOKEN }}@code.romhackplaza.org" \
|
||||
> /root/.git-credentials
|
||||
|
||||
- name: Mark Git Folder as safe
|
||||
run: git config --global --add safe.directory /deploy/laravel
|
||||
|
||||
- name: Code pull
|
||||
run: |
|
||||
git -C /deploy/laravel fetch origin staging
|
||||
git -C /deploy/laravel reset --hard origin/staging
|
||||
|
||||
- name: Storage Link
|
||||
run: |
|
||||
docker exec staging_rhpz_web \
|
||||
php /sites/laravel/artisan storage:link --force
|
||||
|
||||
- name: Composer Dependencies
|
||||
run: |
|
||||
docker exec staging_rhpz_web composer install \
|
||||
--no-dev \
|
||||
--optimize-autoloader \
|
||||
--working-dir=/sites/laravel
|
||||
|
||||
- name: JS Dependencies
|
||||
run: cd /deploy/laravel && npm ci
|
||||
|
||||
- name: Build JS/CSS
|
||||
run: cd /deploy/laravel && npm run build
|
||||
|
||||
- name: Migrations
|
||||
run: |
|
||||
docker exec staging_rhpz_web \
|
||||
php /sites/laravel/artisan migrate --force
|
||||
|
||||
- name: Opti Laravel
|
||||
run: |
|
||||
docker exec staging_rhpz_web \
|
||||
php /sites/laravel/artisan optimize
|
||||
9
_rhpz_ide_helper.php
Normal file
9
_rhpz_ide_helper.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace {
|
||||
|
||||
use App\Proxy\VisitorProxy;
|
||||
|
||||
/** @var VisitorProxy $VISITOR */
|
||||
$VISITOR = null;
|
||||
}
|
||||
@@ -5,32 +5,60 @@ namespace App\Auth;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Xenforo authentification bridge.
|
||||
*/
|
||||
class XenForoGuard implements Guard
|
||||
{
|
||||
|
||||
/**
|
||||
* Authenticated user.
|
||||
* @var XenForoUser|null
|
||||
*/
|
||||
private ?XenForoUser $user = null;
|
||||
|
||||
public function __construct(private readonly Request $request) {}
|
||||
|
||||
/**
|
||||
* Check if user is logged in.
|
||||
* @return bool
|
||||
*/
|
||||
public function check(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is a guest.
|
||||
* @return bool
|
||||
*/
|
||||
public function guest(): bool
|
||||
{
|
||||
return ! $this->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user ID.
|
||||
* @return mixed
|
||||
*/
|
||||
public function id(): mixed
|
||||
{
|
||||
return $this->user()?->getAuthIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* If user is defined.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUser(): bool
|
||||
{
|
||||
return $this->user !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login user.
|
||||
* @return XenForoUser|null
|
||||
*/
|
||||
public function user(): ?XenForoUser
|
||||
{
|
||||
if ($this->hasUser())
|
||||
@@ -64,6 +92,13 @@ class XenForoGuard implements Guard
|
||||
return $this->user = new XenForoUser($xfUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused.
|
||||
*
|
||||
* @param array $credentials
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validate(array $credentials = []): bool
|
||||
{
|
||||
return false;
|
||||
@@ -74,6 +109,10 @@ class XenForoGuard implements Guard
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unused.
|
||||
* @return void
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
redirect('/');
|
||||
|
||||
@@ -10,11 +10,67 @@ use Filament\Panel;
|
||||
use Illuminate\Contracts\Auth\Access\Authorizable;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
/**
|
||||
* Xenforo user custom model used for authentification.
|
||||
*
|
||||
* @property-read int $user_id
|
||||
* @property-read string $username
|
||||
* @property-read int $username_date
|
||||
* @property-read int $username_date_visible
|
||||
* @property-read string $email
|
||||
* @property-read string $custom_title
|
||||
* @property-read int $language_id
|
||||
* @property-read int $style_id
|
||||
* @property-read string $style_variation
|
||||
* @property-read string $timezone
|
||||
* @property-read int $visible
|
||||
* @property-read int $activity_visible
|
||||
* @property-read int $user_group_id
|
||||
* @property-read array $secondary_group_ids
|
||||
* @property-read int $display_style_group_id
|
||||
* @property-read int $permission_combination_id
|
||||
* @property-read int $message_count
|
||||
* @property-read int $question_solution_count
|
||||
* @property-read int $conversations_unread
|
||||
* @property-read int $register_date
|
||||
* @property-read int $last_activity
|
||||
* @property-read int $last_summary_email_date
|
||||
* @property-read int $trophy_points
|
||||
* @property-read int $alerts_unviewed
|
||||
* @property-read int $alerts_unread
|
||||
* @property-read int $avatar_date
|
||||
* @property-read int $avatar_width
|
||||
* @property-read int $avatar_height
|
||||
* @property-read int $avatar_highdpi
|
||||
* @property-read int $avatar_optimized
|
||||
* @property-read string $gravatar
|
||||
* @property-read string $user_state
|
||||
* @property-read string $security_lock
|
||||
* @property-read int $is_moderator
|
||||
* @property-read int $is_admin
|
||||
* @property-read int $is_banned
|
||||
* @property-read int $reaction_score
|
||||
* @property-read int $vote_score
|
||||
* @property-read int $warning_points
|
||||
* @property-read int $is_staff
|
||||
* @property-read string $secret_key
|
||||
* @property-read int $privacy_policy_accepted
|
||||
* @property-read int $terms_accepted
|
||||
*
|
||||
* Custom properties.
|
||||
*
|
||||
* @property-read int $rhpz_entry_count
|
||||
*/
|
||||
class XenForoUser extends XenForoData implements Authenticatable, Authorizable, FilamentUser, HasName {
|
||||
|
||||
use \Illuminate\Foundation\Auth\Access\Authorizable;
|
||||
|
||||
/**
|
||||
* Permissions identifier array.
|
||||
* @var array|null
|
||||
*/
|
||||
public ?array $permissions = null;
|
||||
|
||||
public function getAuthIdentifierName(): string
|
||||
{
|
||||
return 'user_id';
|
||||
@@ -51,9 +107,9 @@ class XenForoUser extends XenForoData implements Authenticatable, Authorizable,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get XenForo avatar if it exist.
|
||||
* Get XenForo avatar if it exists.
|
||||
*
|
||||
* @param string $xfSize
|
||||
* @param string $xfSize s/m/...
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
@@ -70,6 +126,14 @@ class XenForoUser extends XenForoData implements Authenticatable, Authorizable,
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom can function. Check XF user permissions.
|
||||
*
|
||||
* @param string $permissionGroup
|
||||
* @param string $permissionName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function _can(string $permissionGroup, string $permissionName): bool
|
||||
{
|
||||
if( !$this->permissions ){
|
||||
@@ -105,4 +169,9 @@ class XenForoUser extends XenForoData implements Authenticatable, Authorizable,
|
||||
{
|
||||
return 'user_id';
|
||||
}
|
||||
|
||||
public function validState(): bool
|
||||
{
|
||||
return $this->user_state === 'valid';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\News;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -20,7 +21,11 @@ class DeleteRejectedEntries extends Command
|
||||
$count = Entry::where('state', 'rejected')
|
||||
->where('rejected_at', '<', now()->subDays($days))
|
||||
->delete();
|
||||
$count += News::where('state', 'rejected')
|
||||
->where('rejected_at', '<', now()->subDays($days))
|
||||
->delete();
|
||||
|
||||
$this->info("Deleted {$count} entries");
|
||||
$this->info("Deleted {$count} entries/news");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
40
app/Console/Commands/FixEncodedSlugs.php
Normal file
40
app/Console/Commands/FixEncodedSlugs.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('fix:encoded-slugs')]
|
||||
class FixEncodedSlugs extends Command
|
||||
{
|
||||
private const array TABLES = [
|
||||
'entries', 'games', 'platforms', 'genres', 'languages', 'modifications',
|
||||
'levels', 'statuses', 'authors', 'categories', 'systems',
|
||||
];
|
||||
|
||||
public function handle()
|
||||
{
|
||||
foreach (self::TABLES as $table) {
|
||||
$rows = DB::table($table)->where('slug', 'like', '%\\%%')->get(['id', 'slug']);
|
||||
$fixed = 0;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$decoded = rawurldecode($row->slug);
|
||||
if ($decoded === $row->slug) continue;
|
||||
|
||||
try {
|
||||
DB::table($table)->where('id', $row->id)->update(['slug' => $decoded]);
|
||||
$fixed++;
|
||||
} catch (\Throwable $e) {
|
||||
$this->warn("{$table}#{$row->id} : collision '{$decoded}', ignored ({$e->getMessage()}).");
|
||||
}
|
||||
}
|
||||
|
||||
if ($fixed > 0) {
|
||||
$this->info("{$table}: {$fixed} fixed slugs.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
app/Console/Commands/FixEntriesDescription.php
Normal file
47
app/Console/Commands/FixEntriesDescription.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
#[Signature('fix:entries-description')]
|
||||
#[Description('Reconvert HTML descriptions to Markdown')]
|
||||
class FixEntriesDescription extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$converter = new HtmlConverter(['hard_break' => true]);
|
||||
|
||||
$rows = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts')
|
||||
->where('target_table', 'entries')
|
||||
->get(['source_id', 'target_id']);
|
||||
|
||||
$this->info("{$rows->count()} entries touched.");
|
||||
|
||||
$this->withProgressBar($rows, function ($row) use ($converter) {
|
||||
$rawHtml = DB::connection('old_wp')->table('posts')
|
||||
->where('ID', $row->source_id)
|
||||
->value('post_content');
|
||||
|
||||
if ($rawHtml === null) return;
|
||||
|
||||
$markdown = trim($rawHtml) === '' ? $rawHtml : $converter->convert(MigrationHelpers::wpAutoP($rawHtml));
|
||||
|
||||
DB::table('entries')->where('id', $row->target_id)->update([
|
||||
'description' => $markdown,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info('Process finished.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
44
app/Console/Commands/MigrateCategoriesConfigure.php
Normal file
44
app/Console/Commands/MigrateCategoriesConfigure.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:categories:configure')]
|
||||
#[Description('Configure categories table.')]
|
||||
class MigrateCategoriesConfigure extends Command
|
||||
{
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$taxonomyMap = [];
|
||||
$taxonomy = "a";
|
||||
$section = "";
|
||||
|
||||
while( $taxonomy !== "" ){
|
||||
$taxonomy = "";
|
||||
$section = "";
|
||||
|
||||
$taxonomy = $this->ask("Write WP taxonomy name. Write nothing if you want to save changes.", "" );
|
||||
if( $taxonomy == "" )
|
||||
break;
|
||||
|
||||
$section = $this->ask("Write entry section equivalent.");
|
||||
|
||||
$taxonomyMap[$taxonomy] = $section;
|
||||
}
|
||||
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'wp_categories_to_entry_sections'
|
||||
],[
|
||||
'value' => json_encode($taxonomyMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info('WP Categories to entry sections has been configured.');
|
||||
}
|
||||
}
|
||||
92
app/Console/Commands/MigrateCategoriesExecute.php
Normal file
92
app/Console/Commands/MigrateCategoriesExecute.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:categories:execute')]
|
||||
#[Description('Migrate categories, make sure you have configured the migration before that.')]
|
||||
class MigrateCategoriesExecute extends Command
|
||||
{
|
||||
|
||||
private const string TABLE = 'categories';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$taxMap = json_decode(DB::table('migration_settings')->where('key', 'wp_categories_to_entry_sections')->value('value'), true);
|
||||
|
||||
if( !$taxMap ){
|
||||
$this->error("No WP taxonomies need to be transferred.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if( $this->ask("Are you sure you want launch that migration? Write ok if you want to launch it.", "") !== 'ok' ){
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$table = self::TABLE;
|
||||
|
||||
foreach ( $taxMap as $wpTax => $restrictedTo )
|
||||
{
|
||||
$this->info("Migrate: {$wpTax} restricted to {$table}");
|
||||
|
||||
$terms = DB::connection('old_wp')
|
||||
->table('term_taxonomy')
|
||||
->join('terms', 'term_taxonomy.term_id', '=', 'terms.term_id')
|
||||
->where('term_taxonomy.taxonomy', $wpTax)
|
||||
->select('term_taxonomy.term_taxonomy_id', 'terms.name', 'terms.slug')
|
||||
->get();
|
||||
|
||||
$this->withProgressBar($terms, function ($term) use ($table, $restrictedTo) {
|
||||
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_term_taxonomy')
|
||||
->where('source_id', $term->term_taxonomy_id )
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$existing = DB::table( $table )->where('slug', $term->slug)->first();
|
||||
|
||||
if( $existing === null) {
|
||||
$Id = DB::table($table)
|
||||
->insertGetId([
|
||||
'name' => $term->name, 'slug' => $term->slug,
|
||||
'restricted_to' => json_encode([$restrictedTo]),
|
||||
'created_at' => now(), 'updated_at' => now()
|
||||
]);
|
||||
} else {
|
||||
$Id = $existing->id;
|
||||
$restrictedToField = json_decode($existing->restricted_to, true) ?? [];
|
||||
if( !in_array( $restrictedTo, $restrictedToField, true ) ){
|
||||
$restrictedToField[] = $restrictedTo;
|
||||
DB::table($table)
|
||||
->where('id', $Id)
|
||||
->update(['restricted_to' => json_encode($restrictedToField), 'updated_at' => now()]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_term_taxonomy',
|
||||
'source_id' => $term->term_taxonomy_id,
|
||||
'target_table' => $table,
|
||||
'target_id' => $Id,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
});
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info("Migration done");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
80
app/Console/Commands/MigrateEntriesComments.php
Normal file
80
app/Console/Commands/MigrateEntriesComments.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:entries:comments {--import-log-table=} {--limit=}')]
|
||||
class MigrateEntriesComments extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$logTable = $this->option('import-log-table');
|
||||
if( !$logTable ){
|
||||
$this->error( 'XenForo import log table required' );
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->commentsThread( 'entries', 'wp_posts', $logTable );
|
||||
$this->commentsThread( 'news', 'wp_posts__news', $logTable );
|
||||
|
||||
$this->info( "Done" );
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function commentsThread( string $table, string $sourceTable, string $logTable ): void
|
||||
{
|
||||
$query = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp' )
|
||||
->where('source_table', $sourceTable )
|
||||
->where('target_table', $table );
|
||||
|
||||
if( $limit = $this->option('limit') ){
|
||||
$query->limit((int)$limit);
|
||||
}
|
||||
|
||||
$rows = $query->get(['source_id', 'target_id']);
|
||||
$this->info( "{$rows->count()} need migration logs" );
|
||||
|
||||
$stats = [ 'updated' => 0, 'no_meta' => 0, 'no_new_thread' => 0 ];
|
||||
|
||||
$this->withProgressBar( $rows, function ( $row ) use( $table, $logTable, &$stats ) {
|
||||
|
||||
$oldThreadId = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $row->source_id )
|
||||
->where('meta_key', 'xf_thread_id')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$oldThreadId ){
|
||||
$stats['no_meta']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$newThreadId = DB::connection('xenforo')
|
||||
->withoutTablePrefix( function( Connection $db ) use( $logTable, $oldThreadId ){
|
||||
return $db->table( $logTable )
|
||||
->where('content_type', 'thread')
|
||||
->where('old_id', (string) $oldThreadId )
|
||||
->value('new_id');
|
||||
});
|
||||
|
||||
if( !$newThreadId ){
|
||||
$stats['no_new_thread']++;
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table( $table )->where('id', $row->target_id )->update([
|
||||
'comments_thread_id' => (int) $newThreadId
|
||||
]);
|
||||
|
||||
$stats['updated']++;
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info( "Updated: {$stats['updated']}, No new thread: {$stats['no_new_thread']}, No meta: {$stats['no_meta']}" );
|
||||
}
|
||||
}
|
||||
295
app/Console/Commands/MigrateEntriesExecute.php
Normal file
295
app/Console/Commands/MigrateEntriesExecute.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:entries:execute {--limit=}')]
|
||||
#[Description('Migrate WP entries')]
|
||||
class MigrateEntriesExecute extends Command
|
||||
{
|
||||
private const array WP_CPTS = ['translations', 'romhacks', 'homebrew', 'utilities', 'documents' ];
|
||||
|
||||
private const array STATE_MAP = [
|
||||
'draft' => 'draft',
|
||||
'pending' => 'pending',
|
||||
'publish' => 'published',
|
||||
'private' => 'hidden',
|
||||
'locked' => 'locked',
|
||||
];
|
||||
|
||||
private const array ACF_FIELDS = [
|
||||
'entry_title', 'version_number', 'release_date', 'release_site',
|
||||
'youtube_video', 'staff', 'hashes',
|
||||
];
|
||||
|
||||
private const array MULTI_TAXONOMIES = [
|
||||
'language' => 'languages',
|
||||
'modifications' => 'modifications',
|
||||
'author-name' => 'authors',
|
||||
'document-category' => 'categories',
|
||||
'utility-category' => 'categories',
|
||||
'utility-os' => 'systems',
|
||||
];
|
||||
|
||||
private array $stats = [];
|
||||
|
||||
private function getSingle( ?int $term_id, string $targetTable ): ?int
|
||||
{
|
||||
if (!$term_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('target_table', $targetTable )
|
||||
->where('source_id', $term_id)
|
||||
->value('target_id');
|
||||
}
|
||||
|
||||
private function uniqueSlug(string $baseSlug, string $table, ?int $ignoreId = null): string
|
||||
{
|
||||
$slug = $baseSlug;
|
||||
$i = 1;
|
||||
while (
|
||||
DB::table($table)->where('slug', $slug)
|
||||
->when($ignoreId, fn ($q) => $q->where('id', '!=', $ignoreId))
|
||||
->exists() && $i < 100
|
||||
) {
|
||||
$slug = $baseSlug . '-' . $i++;
|
||||
}
|
||||
if ($i >= 100) {
|
||||
$slug = (string) \Str::uuid();
|
||||
}
|
||||
return $slug;
|
||||
}
|
||||
|
||||
private function parseStaffCredits(?string $raw): array
|
||||
{
|
||||
if (!$raw || trim($raw) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$credits = [];
|
||||
foreach (preg_split('/\r\n|\r|\n/', $raw) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') continue;
|
||||
|
||||
if (preg_match('/^(.+?):\s*(.*)$/', $line, $m)) {
|
||||
$credits[] = ['name' => trim($m[1]), 'description' => trim($m[2])];
|
||||
} elseif (!empty($credits)) {
|
||||
$last = array_key_last($credits);
|
||||
$credits[$last]['description'] = trim($credits[$last]['description'] . ' ' . $line);
|
||||
} else {
|
||||
$credits[] = ['name' => $line, 'description' => ''];
|
||||
}
|
||||
}
|
||||
|
||||
return $credits;
|
||||
}
|
||||
|
||||
private function parseHashes(?string $raw): array
|
||||
{
|
||||
if (!$raw || trim($raw) === '') return [];
|
||||
|
||||
$results = [];
|
||||
foreach (preg_split('/\n\s*\n/', trim($raw)) as $block) {
|
||||
$fields = [];
|
||||
foreach (preg_split('/\r\n|\r|\n/', trim($block)) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || !str_contains($line, ':')) continue;
|
||||
[$key, $value] = array_map('trim', explode(':', $line, 2));
|
||||
$fields[strtolower(preg_replace('/[^a-z0-9]/i', '', $key))] = $value;
|
||||
}
|
||||
if (empty($fields)) continue;
|
||||
|
||||
$results[] = [
|
||||
'filename' => $fields['filename'] ?? '',
|
||||
'hash_crc32' => $fields['crc32'] ?? '',
|
||||
'hash_sha1' => $fields['sha1'] ?? '',
|
||||
];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function attachMany(int $entryId, string $pivotTable, string $foreignKey, array $ttids, string $targetTable): void
|
||||
{
|
||||
if (empty($ttids)) return;
|
||||
|
||||
$ids = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')->where('target_table', $targetTable)
|
||||
->whereIn('source_id', $ttids)->pluck('target_id');
|
||||
|
||||
foreach ($ids as $id) {
|
||||
DB::table($pivotTable)->insertOrIgnore(['entry_id' => $entryId, $foreignKey => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \StdClass $post
|
||||
* @param string $cpt
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function migratePost( $post, string $cpt ): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts')
|
||||
->where('source_id', $post->ID)
|
||||
->exists();
|
||||
|
||||
if ($exists)
|
||||
return;
|
||||
|
||||
$meta = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $post->ID)
|
||||
->whereIn('meta_key', self::ACF_FIELDS)
|
||||
->pluck('meta_value', 'meta_key');
|
||||
|
||||
$terms = DB::connection('old_wp')
|
||||
->table('term_relationships as tr')
|
||||
->join('term_taxonomy as tt', 'tr.term_taxonomy_id', '=', 'tt.term_taxonomy_id')
|
||||
->where('tr.object_id', $post->ID)
|
||||
->whereIn('tt.taxonomy', array_merge(['game', 'platform', 'hack-status', 'experience-level'], array_keys(self::MULTI_TAXONOMIES)) )
|
||||
->select('tt.taxonomy', 'tt.term_taxonomy_id')
|
||||
->get();
|
||||
|
||||
$byTax = [];
|
||||
foreach ($terms as $term) {
|
||||
$byTax[$term->taxonomy][] = $term->term_taxonomy_id;
|
||||
}
|
||||
|
||||
$gameId = null;
|
||||
if( !empty( $byTax['game'][0] ) && !empty( $byTax['platform'][0] ) )
|
||||
{
|
||||
$gameId = DB::table('migration_game_plan')
|
||||
->where('wp_game_id', $byTax['game'][0])
|
||||
->where('wp_platform_id', $byTax['platform'][0])
|
||||
->value('game_id');
|
||||
|
||||
if( !$gameId )
|
||||
$this->stats['missing_game_plan']++;
|
||||
}
|
||||
|
||||
$statusId = $this->getSingle( $byTax['hack-status'][0] ?? null, 'statuses' );
|
||||
$levelId = $this->getSingle( $byTax['experience-level'][0] ?? null, 'levels' );
|
||||
|
||||
$userId = DB::table('migration_user_plan')
|
||||
->where('wp_user_id', $post->post_author )
|
||||
->value('user_id');
|
||||
|
||||
if( !$userId ) {
|
||||
$this->stats['missing_author']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$title = $meta['entry_title'] ?? null;
|
||||
if( $cpt === 'translations' && !$title && $gameId )
|
||||
{
|
||||
$title = DB::table('games')->where('id', $gameId)->value('name');
|
||||
}
|
||||
|
||||
$slug = $this->uniqueSlug( rawurldecode($post->post_name), 'entries' );
|
||||
|
||||
$entryId = DB::table('entries')->insertGetId([
|
||||
'type' => $cpt,
|
||||
'title' => $title,
|
||||
'complete_title' => $post->post_title ?? null,
|
||||
'slug' => $slug,
|
||||
'description' => MigrationHelpers::htmlToMarkdown($post->post_content),
|
||||
'state' => self::STATE_MAP[$post->post_status],
|
||||
'game_id' => $gameId,
|
||||
'platform_id' => null,
|
||||
'status_id' => $statusId,
|
||||
'level_id' => $levelId,
|
||||
'version' => $meta['version_number'] ?? null,
|
||||
'release_date' => $meta['release_date'] ?? null,
|
||||
'staff_credits' => json_encode( $this->parseStaffCredits($meta['staff'] ?? null)),
|
||||
'relevant_link' => $meta['release_site'] ?? null,
|
||||
'youtube_link' => $meta['youtube_video'] ?? null,
|
||||
'user_id' => $userId,
|
||||
'created_at' => $post->post_date,
|
||||
'updated_at' => $post->post_modified,
|
||||
]);
|
||||
|
||||
$this->attachMany($entryId, 'entry_authors', 'author_id', $byTax['author-name'] ?? [], 'authors');
|
||||
$this->attachMany($entryId, 'entry_languages', 'language_id', $byTax['language'] ?? [], 'languages');
|
||||
|
||||
if( $cpt === 'romhacks')
|
||||
$this->attachMany($entryId, 'entry_modifications', 'modification_id', $byTax['modifications'] ?? [], 'modifications');
|
||||
|
||||
if ($cpt === 'utilities') {
|
||||
$this->attachMany($entryId, 'entry_categories', 'category_id', $byTax['utility-category'] ?? [], 'categories');
|
||||
$this->attachMany($entryId, 'entry_systems', 'system_id', $byTax['utility-os'] ?? [], 'systems');
|
||||
}
|
||||
if ($cpt === 'documents') {
|
||||
$this->attachMany($entryId, 'entry_categories', 'category_id', $byTax['document-category'] ?? [], 'categories');
|
||||
}
|
||||
|
||||
if( $cpt === 'translations' || $cpt === 'romhacks' ){
|
||||
foreach ( $this->parseHashes( $meta['hashes'] ?? null ) as $hash ) {
|
||||
DB::table('entry_hashes')->insert([
|
||||
'entry_id' => $entryId,
|
||||
'filename' => $hash['filename'],
|
||||
'hash_crc32' => $hash['hash_crc32'],
|
||||
'hash_sha1' => $hash['hash_sha1'],
|
||||
'verified' => 'TBD',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts',
|
||||
'source_id' => $post->ID,
|
||||
'target_table' => 'entries',
|
||||
'target_id' => $entryId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$this->stats['created']++;
|
||||
|
||||
}
|
||||
|
||||
private function migrateCpt( string $cpt ): void
|
||||
{
|
||||
$this->info( "Current migration of : $cpt ");
|
||||
$this->stats = [ 'missing_author' => 0, 'missing_game_plan' => 0, 'created' => 0 ];
|
||||
|
||||
$query = DB::connection('old_wp')
|
||||
->table('posts')
|
||||
->where('post_type', $cpt)
|
||||
->whereIn('post_status', array_keys(self::STATE_MAP))
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ) {
|
||||
$query->limit((int) $limit);
|
||||
}
|
||||
|
||||
$posts = $query->select('ID', 'post_title', 'post_name', 'post_content', 'post_status', 'post_author', 'post_date', 'post_modified')
|
||||
->get();
|
||||
|
||||
$this->withProgressBar($posts, fn( $post ) => $this->migratePost( $post, $cpt ) );
|
||||
|
||||
$this->newLine();
|
||||
$this->info( "Created {$this->stats['created']} entries'. No authors {$this->stats['missing_author']}. Missing game {$this->stats['missing_game_plan']} entries" );
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
foreach ( self::WP_CPTS as $cpt ) {
|
||||
$this->migrateCpt( $cpt );
|
||||
}
|
||||
}
|
||||
}
|
||||
146
app/Console/Commands/MigrateEntriesImages.php
Normal file
146
app/Console/Commands/MigrateEntriesImages.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:entries:images {--wp-uploads-path=} {--limit=}')]
|
||||
#[Description("Migrate WP Main images and galeries for entries.")]
|
||||
class MigrateEntriesImages extends Command
|
||||
{
|
||||
private const string GALLERYABLE_TYPE = 'App\\Models\\Entry';
|
||||
|
||||
private function copyAttachment( int $attachmentId, string $wpUploadsPath, string $destinationPath ): ?string
|
||||
{
|
||||
$relativePath = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $attachmentId)
|
||||
->where('meta_key', '_wp_attached_file')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$relativePath )
|
||||
return null;
|
||||
|
||||
$sourcePath = rtrim($wpUploadsPath, '/') . '/' . $relativePath;
|
||||
if( !is_file($sourcePath) )
|
||||
return null;
|
||||
|
||||
$extension = pathinfo($sourcePath, PATHINFO_EXTENSION);
|
||||
$filename = Str::random(40) . ($extension ? '.' . $extension : '');
|
||||
$destinationRelative = $destinationPath . '/' . $filename;
|
||||
|
||||
$stream = fopen($sourcePath, 'r');
|
||||
Storage::disk('public')->put($destinationRelative, $stream);
|
||||
if( is_resource($stream) )
|
||||
fclose($stream);
|
||||
|
||||
return $destinationRelative;
|
||||
}
|
||||
|
||||
private function processEntry( int $wpPostId, int $entryId, string $wpUploadsPath, array &$stats ): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__attachments')
|
||||
->where('source_id', $wpPostId)
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$thumbnailId = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $wpPostId)
|
||||
->where('meta_key', '_thumbnail_id')
|
||||
->value('meta_value');
|
||||
|
||||
if( $thumbnailId ){
|
||||
$newPath = $this->copyAttachment( (int) $thumbnailId, $wpUploadsPath, 'entries/main-images' );
|
||||
if( $newPath ){
|
||||
DB::table('entries')
|
||||
->where('id', $entryId)
|
||||
->update(['main_image' => $newPath]);
|
||||
$stats['main_image']++;
|
||||
} else {
|
||||
$stats['missing_files']++;
|
||||
}
|
||||
}
|
||||
|
||||
$galleryRaw = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $wpPostId)
|
||||
->where('meta_key', 'my_gallery')
|
||||
->value('meta_value');
|
||||
|
||||
$attachmentIds = $galleryRaw ? (@unserialize($galleryRaw) ?: []) : [];
|
||||
|
||||
foreach ( array_values( $attachmentIds ) as $order => $attachmentId ) {
|
||||
$newPath = $this->copyAttachment( (int) $attachmentId, $wpUploadsPath, "entries/gallery-images/{$entryId}" );
|
||||
if( !$newPath ){
|
||||
$stats['missing_files']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('galleries')
|
||||
->insert([
|
||||
'galleryable_type' => self::GALLERYABLE_TYPE,
|
||||
'galleryable_id' => $entryId,
|
||||
'image' => $newPath,
|
||||
'order' => $order,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$stats['gallery_images']++;
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')
|
||||
->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__attachments',
|
||||
'source_id' => $wpPostId,
|
||||
'target_table' => 'entries',
|
||||
'target_id' => $entryId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpUploadsPath = $this->option('wp-uploads-path');
|
||||
if( !$wpUploadsPath || !is_dir($wpUploadsPath) ){
|
||||
$this->error('Missing WP Uploads Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$query = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts')
|
||||
->where('target_table', 'entries')
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ){
|
||||
$query->limit((int)$limit);
|
||||
}
|
||||
|
||||
$rows = $query->get(['source_id', 'target_id']);
|
||||
$this->info("{$rows->count()} entries need to be migrated");
|
||||
|
||||
$stats = ['main_image' => 0, 'gallery_images' => 0, 'missing_files' => 0 ];
|
||||
|
||||
$this->withProgressBar($rows, function($row) use($wpUploadsPath, &$stats) {
|
||||
$this->processEntry( $row->source_id, $row->target_id, $wpUploadsPath, $stats );
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info("Migrated attachments. Main images: {$stats['main_image']}, Galleries: {$stats['gallery_images']}, Missing files: {$stats['missing_files']}");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
169
app/Console/Commands/MigrateGamesExecute.php
Normal file
169
app/Console/Commands/MigrateGamesExecute.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Platform;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:games:execute')]
|
||||
#[Description("Migrate all games from WP posts.")]
|
||||
class MigrateGamesExecute extends Command
|
||||
{
|
||||
private const array WP_CPTS = [ 'translations', 'romhacks', 'homebrew', 'utilities', 'documents', 'lua-scripts' ];
|
||||
private const array RELATED_TAXS = ['game', 'platform', 'genre'];
|
||||
private const int DEFAULT_GENRE_ID = 1;
|
||||
private const int NO_GENRE_SENTINEL = 0;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$rows = DB::connection('old_wp')
|
||||
->table('term_relationships as tr')
|
||||
->join('term_taxonomy as tt', 'tr.term_taxonomy_id', '=', 'tt.term_taxonomy_id')
|
||||
->join('posts as p', 'tr.object_id', '=', 'p.ID')
|
||||
->whereIn('tt.taxonomy', self::RELATED_TAXS )
|
||||
->whereIn('p.post_type', self::WP_CPTS )
|
||||
->where('p.post_status', '!=', 'trash')
|
||||
->select('p.ID as post_id', 'tt.taxonomy', 'tt.term_taxonomy_id' )
|
||||
->get();
|
||||
|
||||
$byPost = [];
|
||||
$ignored = 0;
|
||||
foreach( $rows as $row ){
|
||||
if( isset( $byPost[ $row->post_id ][$row->taxonomy] ) ){
|
||||
$ignored++;
|
||||
continue;
|
||||
}
|
||||
$byPost[ $row->post_id ][$row->taxonomy] = $row->term_taxonomy_id;
|
||||
}
|
||||
if( $ignored ){
|
||||
$this->warn("$ignored posts with multiple taxs will be ignored.");
|
||||
}
|
||||
|
||||
$structure = [];
|
||||
$ignoredNoPlatform = 0;
|
||||
|
||||
foreach ( $byPost as $data )
|
||||
{
|
||||
if( empty( $data['game'] ) )
|
||||
continue;
|
||||
|
||||
if( empty( $data['platform'] ) ){
|
||||
$ignoredNoPlatform++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$gameId = $data['game'];
|
||||
$platformId = $data['platform'];
|
||||
$genreId = $data['genre'] ?? self::NO_GENRE_SENTINEL;
|
||||
|
||||
$structure[$gameId][$platformId]['count'] = ( $structure[$gameId][$platformId]['count'] ?? 0 ) + 1;
|
||||
$structure[$gameId][$platformId]['genres'][$genreId] = ( $structure[$gameId][$platformId]['genres'][$genreId] ?? 0 ) + 1;
|
||||
}
|
||||
if( $ignoredNoPlatform ){
|
||||
$this->warn("$ignoredNoPlatform posts with no platforms will be ignored.");
|
||||
}
|
||||
|
||||
$games = DB::connection('old_wp')
|
||||
->table('term_taxonomy')
|
||||
->join('terms', 'term_taxonomy.term_id', '=', 'terms.term_id')
|
||||
->where('term_taxonomy.taxonomy', 'game' )
|
||||
->select('term_taxonomy.term_taxonomy_id', 'terms.name', 'terms.slug' )
|
||||
->get()
|
||||
->keyBy('term_taxonomy_id');
|
||||
|
||||
$platformMap = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp' )
|
||||
->where('target_table', 'platforms')
|
||||
->pluck('target_id', 'source_id');
|
||||
|
||||
$genreMap = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp' )
|
||||
->where('target_table', 'genres')
|
||||
->pluck('target_id', 'source_id');
|
||||
|
||||
if( $this->ask("Are you sure you want launch that migration? Write ok if you want to launch it.", "") !== 'ok' ){
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$genreConflicts = 0;
|
||||
|
||||
foreach( $structure as $gameId => $platforms )
|
||||
{
|
||||
$game = $games->get( $gameId );
|
||||
if( !$game )
|
||||
continue;
|
||||
|
||||
foreach( $platforms as $platformId => $info )
|
||||
{
|
||||
$alreadyExists = DB::table('migration_game_plan')
|
||||
->where('wp_game_id', $gameId )
|
||||
->where('wp_platform_id', $platformId )
|
||||
->exists();
|
||||
if( $alreadyExists )
|
||||
continue;
|
||||
|
||||
$newPlatformId = $platformMap[$platformId] ?? null;
|
||||
if( !$newPlatformId ){
|
||||
$this->warn("$gameId ignored because platform $platformId does not exist in Laravel.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$genres = $info['genres'];
|
||||
$newGenreId = null;
|
||||
$topId = null;
|
||||
$genreConflict = false;
|
||||
|
||||
arsort( $genres );
|
||||
$topId = array_key_first( $genres );
|
||||
$topCount = $genres[$topId];
|
||||
$tied = count(array_filter(
|
||||
$genres, fn( $c ) => $c === $topCount
|
||||
));
|
||||
if( $tied > 1 ){
|
||||
$genreConflict = true;
|
||||
$genreConflicts++;
|
||||
}
|
||||
$newGenreId = $topId === self::NO_GENRE_SENTINEL
|
||||
? self::DEFAULT_GENRE_ID
|
||||
: ($genreMap[$topId] ?? self::DEFAULT_GENRE_ID);
|
||||
|
||||
$gameSlug = $game->slug;
|
||||
if (count($platforms) > 1) {
|
||||
$platformSlug = DB::table('platforms')->where('id', $newPlatformId)->value('slug');
|
||||
$gameSlug = $game->slug . '-' . $platformSlug;
|
||||
}
|
||||
|
||||
$newGameId = DB::table('games')
|
||||
->insertGetId([
|
||||
'name' => $game->name,
|
||||
'slug' => $gameSlug,
|
||||
'platform_id' => $newPlatformId,
|
||||
'genre_id' => $newGenreId,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('migration_game_plan')->insert([
|
||||
'wp_game_id' => $gameId,
|
||||
'wp_platform_id' => $platformId,
|
||||
'game_id' => $newGameId,
|
||||
'wp_genre_id' => $topId === self::NO_GENRE_SENTINEL ? null : $topId,
|
||||
'post_count' => $info['count'],
|
||||
'genre_conflict' => $genreConflict,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$created++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info("$created games created. $genreConflicts genre conflicts.");
|
||||
}
|
||||
}
|
||||
149
app/Console/Commands/MigrateNewsExecute.php
Normal file
149
app/Console/Commands/MigrateNewsExecute.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:news:execute {--limit=}')]
|
||||
#[Description("Migrate WP news to news table")]
|
||||
class MigrateNewsExecute extends Command
|
||||
{
|
||||
private const array STATE_MAP = [
|
||||
'draft' => 'draft',
|
||||
'pending' => 'pending',
|
||||
'publish' => 'published',
|
||||
'private' => 'hidden',
|
||||
'locked' => 'locked',
|
||||
];
|
||||
|
||||
private array $stats = [];
|
||||
|
||||
private function uniqueSlug(string $baseSlug, string $table, ?int $ignoreId = null): string
|
||||
{
|
||||
$slug = $baseSlug;
|
||||
$i = 1;
|
||||
while (
|
||||
DB::table($table)->where('slug', $slug)
|
||||
->when($ignoreId, fn ($q) => $q->where('id', '!=', $ignoreId))
|
||||
->exists() && $i < 100
|
||||
) {
|
||||
$slug = $baseSlug . '-' . $i++;
|
||||
}
|
||||
if ($i >= 100) {
|
||||
$slug = (string) Str::uuid();
|
||||
}
|
||||
return $slug;
|
||||
}
|
||||
|
||||
private function migrateNews($post): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__news')
|
||||
->where('source_id', $post->ID)
|
||||
->exists();
|
||||
if ($exists)
|
||||
return;
|
||||
|
||||
$meta = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $post->ID)
|
||||
->whereIn('meta_key', ['release_site', 'romhacks_page', 'youtube_video' ])
|
||||
->pluck('meta_value', 'meta_key');
|
||||
|
||||
$categoryTtId = DB::connection('old_wp')
|
||||
->table('term_relationships as tr')
|
||||
->join('term_taxonomy as tt', 'tr.term_taxonomy_id', '=', 'tt.term_taxonomy_id')
|
||||
->where('tr.object_id', $post->ID)
|
||||
->where('tt.taxonomy', 'news-category')
|
||||
->value('tt.term_taxonomy_id');
|
||||
|
||||
$categoryId = null;
|
||||
if( $categoryTtId ){
|
||||
$categoryId = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('target_table', 'categories' )
|
||||
->where('source_id', $categoryTtId)
|
||||
->value('target_id');
|
||||
if( !$categoryId ){
|
||||
$this->stats['missing_category']++;
|
||||
}
|
||||
}
|
||||
|
||||
$userId = DB::table('migration_user_plan')
|
||||
->where('wp_user_id', $post->post_author )
|
||||
->value('user_id');
|
||||
|
||||
if( !$userId ){
|
||||
$this->stats['missing_author']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$slug = $this->uniqueSlug(rawurldecode($post->post_name), 'news');
|
||||
$description = trim($post->post_content) === '' ? '' : MigrationHelpers::htmlToMarkdown($post->post_content);
|
||||
|
||||
if( isset( $meta['romhacks_page'] ) && $meta['romhacks_page'] !== null && $meta['romhacks_page'] !== '' ){
|
||||
$description .= "\n\nLink to: " . $meta['romhacks_page'];
|
||||
}
|
||||
|
||||
$newsId = DB::table('news')
|
||||
->insertGetId([
|
||||
'title' => $post->post_title,
|
||||
'slug' => $slug,
|
||||
'category_id' => $categoryId,
|
||||
'description' => $description,
|
||||
'state' => self::STATE_MAP[$post->post_status],
|
||||
'entry_id' => null,
|
||||
'relevant_link' => $meta['release_site'] ?? null,
|
||||
'youtube_link' => $meta['youtube_video'] ?? null,
|
||||
'user_id' => $userId,
|
||||
'created_at' => $post->post_date,
|
||||
'updated_at' => $post->post_modified,
|
||||
]);
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__news',
|
||||
'source_id' => $post->ID,
|
||||
'target_table' => 'news',
|
||||
'target_id' => $newsId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->stats['created']++;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->stats = [ 'created' => 0, 'missing_author' => 0, 'missing_category' => 0 ];
|
||||
|
||||
$query = DB::connection('old_wp')
|
||||
->table('posts')
|
||||
->where('post_type', 'news')
|
||||
->whereIn('post_status', array_keys(self::STATE_MAP))
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ) {
|
||||
$query->limit((int)$limit);
|
||||
}
|
||||
|
||||
$posts = $query->select('ID', 'post_title', 'post_name', 'post_content', 'post_author', 'post_status', 'post_date', 'post_modified')->get();
|
||||
|
||||
$this->info( "{$posts->count()} posts found" );
|
||||
|
||||
$this->withProgressBar($posts, fn($post) => $this->migrateNews($post) );
|
||||
|
||||
$this->newLine();
|
||||
$this->info( "Created {$this->stats['created']} posts. Missing authors: {$this->stats['missing_author']}. Missing category: {$this->stats['missing_category']}" );
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
128
app/Console/Commands/MigrateNewsImages.php
Normal file
128
app/Console/Commands/MigrateNewsImages.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:news:images {--wp-uploads-path=} {--limit=}')]
|
||||
#[Description("Migrate WP galeries for news.")]
|
||||
class MigrateNewsImages extends Command
|
||||
{
|
||||
private const string GALLERYABLE_TYPE = 'App\\Models\\News';
|
||||
|
||||
private function copyAttachment( int $attachmentId, string $wpUploadsPath, string $destinationPath ): ?string
|
||||
{
|
||||
$relativePath = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $attachmentId)
|
||||
->where('meta_key', '_wp_attached_file')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$relativePath )
|
||||
return null;
|
||||
|
||||
$sourcePath = rtrim($wpUploadsPath, '/') . '/' . $relativePath;
|
||||
if( !is_file($sourcePath) )
|
||||
return null;
|
||||
|
||||
$extension = pathinfo($sourcePath, PATHINFO_EXTENSION);
|
||||
$filename = Str::random(40) . ($extension ? '.' . $extension : '');
|
||||
$destinationRelative = $destinationPath . '/' . $filename;
|
||||
|
||||
$stream = fopen($sourcePath, 'r');
|
||||
Storage::disk('public')->put($destinationRelative, $stream);
|
||||
if( is_resource($stream) )
|
||||
fclose($stream);
|
||||
|
||||
return $destinationRelative;
|
||||
}
|
||||
|
||||
private function processEntry( int $wpPostId, int $entryId, string $wpUploadsPath, array &$stats ): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__attachments')
|
||||
->where('source_id', $wpPostId)
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$galleryRaw = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $wpPostId)
|
||||
->where('meta_key', 'my_gallery')
|
||||
->value('meta_value');
|
||||
|
||||
$attachmentIds = $galleryRaw ? (@unserialize($galleryRaw) ?: []) : [];
|
||||
|
||||
foreach ( array_values( $attachmentIds ) as $order => $attachmentId ) {
|
||||
$newPath = $this->copyAttachment( (int) $attachmentId, $wpUploadsPath, "news/gallery-images/{$entryId}" );
|
||||
if( !$newPath ){
|
||||
$stats['missing_files']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('galleries')
|
||||
->insert([
|
||||
'galleryable_type' => self::GALLERYABLE_TYPE,
|
||||
'galleryable_id' => $entryId,
|
||||
'image' => $newPath,
|
||||
'order' => $order,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
$stats['gallery_images']++;
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')
|
||||
->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__attachments',
|
||||
'source_id' => $wpPostId,
|
||||
'target_table' => 'news',
|
||||
'target_id' => $entryId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpUploadsPath = $this->option('wp-uploads-path');
|
||||
if( !$wpUploadsPath || !is_dir($wpUploadsPath) ){
|
||||
$this->error('Missing WP Uploads Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$query = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__news')
|
||||
->where('target_table', 'news')
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ){
|
||||
$query->limit((int)$limit);
|
||||
}
|
||||
|
||||
$rows = $query->get(['source_id', 'target_id']);
|
||||
$this->info("{$rows->count()} news need to be migrated");
|
||||
|
||||
$stats = ['gallery_images' => 0, 'missing_files' => 0 ];
|
||||
|
||||
$this->withProgressBar($rows, function($row) use($wpUploadsPath, &$stats) {
|
||||
$this->processEntry( $row->source_id, $row->target_id, $wpUploadsPath, $stats );
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info("Migrated attachments Galleries: {$stats['gallery_images']}, Missing files: {$stats['missing_files']}");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
117
app/Console/Commands/MigrateReviewsExecute.php
Normal file
117
app/Console/Commands/MigrateReviewsExecute.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Helpers\MigrationHelpers;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:reviews:execute {--limit=}')]
|
||||
class MigrateReviewsExecute extends Command
|
||||
{
|
||||
|
||||
private function migrateReview( $post, array &$stats ): void
|
||||
{
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts__reviews')
|
||||
->where('source_id', $post->ID )
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$meta = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $post->ID)
|
||||
->whereIn('meta_key', ['reviews_post_link', 'review_rating'])
|
||||
->pluck('meta_value', 'meta_key')
|
||||
;
|
||||
|
||||
$linkedWpPostId = $meta['reviews_post_link'] ?? null;
|
||||
if( !$linkedWpPostId ){
|
||||
$stats['missing_entry']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$entryId = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_posts')
|
||||
->where('source_id', (int) $linkedWpPostId )
|
||||
->where('target_table', 'entries')
|
||||
->value('target_id');
|
||||
|
||||
if( !$entryId ){
|
||||
$stats['missing_entry']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$userId = DB::table('migration_user_plan')
|
||||
->where('wp_user_id', $post->post_author )
|
||||
->value('user_id');
|
||||
|
||||
if( !$userId ){
|
||||
$stats['missing_author']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$rating = $meta['review_rating'] ?? null;
|
||||
if( $rating === null || $rating === '' ){
|
||||
$stats['missing_rating']++;
|
||||
return;
|
||||
}
|
||||
|
||||
$description = trim($post->post_content) === '' ? '' : MigrationHelpers::htmlToMarkdown($post->post_content);
|
||||
|
||||
$reviewId = DB::table('entry_reviews')->insertGetId([
|
||||
'entry_id' => $entryId,
|
||||
'title' => $post->post_title,
|
||||
'rating' => (int) $rating,
|
||||
'description' => $description,
|
||||
'user_id' => $userId,
|
||||
'created_at' => $post->post_date,
|
||||
'updated_at' => $post->post_modified,
|
||||
]);
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_posts__reviews',
|
||||
'source_id' => $post->ID,
|
||||
'target_table' => 'entry_reviews',
|
||||
'target_id' => $reviewId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$stats['created']++;
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$query = DB::connection('old_wp')
|
||||
->table('posts')
|
||||
->where('post_type', 'reviews')
|
||||
->where('post_status', 'publish')
|
||||
;
|
||||
|
||||
if( $limit = $this->option('limit') ) {
|
||||
$query->limit((int) $limit);
|
||||
}
|
||||
|
||||
$posts = $query->select('ID', 'post_title', 'post_content', 'post_author', 'post_date', 'post_modified' )->get();
|
||||
$this->info("{$posts->count()} reviews found.");
|
||||
|
||||
$stats = ['created' => 0, 'missing_entry' => 0, 'missing_author' => 0, 'missing_rating' => 0 ];
|
||||
|
||||
$this->withProgressBar($posts, function($post) use (&$stats){
|
||||
$this->migrateReview($post, $stats);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info("{$stats['created']} reviews created. {$stats['missing_entry']} missing entry. {$stats['missing_author']} missing author. {$stats['missing_rating']} missing rating.");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
44
app/Console/Commands/MigrateTaxonomiesConfigure.php
Normal file
44
app/Console/Commands/MigrateTaxonomiesConfigure.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:taxonomies:configure')]
|
||||
#[Description('Configure taxonomies table.')]
|
||||
class MigrateTaxonomiesConfigure extends Command
|
||||
{
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$taxonomyMap = [];
|
||||
$taxonomy = "a";
|
||||
$tableName = "";
|
||||
|
||||
while( $taxonomy !== "" ){
|
||||
$taxonomy = "";
|
||||
$tableName = "";
|
||||
|
||||
$taxonomy = $this->ask("Write WP taxonomy name. Write nothing if you want to save changes.", "" );
|
||||
if( $taxonomy == "" )
|
||||
break;
|
||||
|
||||
$tableName = $this->ask("Write equivalent Laravel table name.");
|
||||
|
||||
$taxonomyMap[$taxonomy] = $tableName;
|
||||
}
|
||||
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'wp_taxonomies_to_laravel_tables'
|
||||
],[
|
||||
'value' => json_encode($taxonomyMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info('WP Taxonomies to Laravel tables have been configured.');
|
||||
}
|
||||
}
|
||||
77
app/Console/Commands/MigrateTaxonomiesExecute.php
Normal file
77
app/Console/Commands/MigrateTaxonomiesExecute.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:taxonomies:execute')]
|
||||
#[Description('Migrate taxonomies, make sure you have configured the migration before that.')]
|
||||
class MigrateTaxonomiesExecute extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$taxMap = json_decode(DB::table('migration_settings')->where('key', 'wp_taxonomies_to_laravel_tables')->value('value'), true);
|
||||
|
||||
if( !$taxMap ){
|
||||
$this->error("No WP taxonomies need to be transferred.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if( $this->ask("Are you sure you want launch that migration? Write ok if you want to launch it.", "") !== 'ok' ){
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
foreach ( $taxMap as $wpTax => $table )
|
||||
{
|
||||
$this->info("Migrate: {$wpTax} => {$table}");
|
||||
|
||||
$terms = DB::connection('old_wp')
|
||||
->table('term_taxonomy')
|
||||
->join('terms', 'term_taxonomy.term_id', '=', 'terms.term_id')
|
||||
->where('term_taxonomy.taxonomy', $wpTax)
|
||||
->select('term_taxonomy.term_taxonomy_id', 'terms.name', 'terms.slug')
|
||||
->get();
|
||||
|
||||
$this->withProgressBar($terms, function ($term) use ($table) {
|
||||
|
||||
$exists = DB::table('migrations_logs')
|
||||
->where('source_system', 'wp')
|
||||
->where('source_table', 'wp_term_taxonomy')
|
||||
->where('source_id', $term->term_taxonomy_id )
|
||||
->exists();
|
||||
|
||||
if( $exists )
|
||||
return;
|
||||
|
||||
$Id = DB::table( $table )->where('slug', $term->slug)->value('id');
|
||||
|
||||
if( $Id === null) {
|
||||
$Id = DB::table($table)
|
||||
->insertGetId([
|
||||
'name' => $term->name, 'slug' => $term->slug,
|
||||
'created_at' => now(), 'updated_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => 'wp',
|
||||
'source_table' => 'wp_term_taxonomy',
|
||||
'source_id' => $term->term_taxonomy_id,
|
||||
'target_table' => $table,
|
||||
'target_id' => $Id,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
});
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$this->info("Migration done");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
57
app/Console/Commands/MigrateUsersConfigure.php
Normal file
57
app/Console/Commands/MigrateUsersConfigure.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:users:configure')]
|
||||
#[Description('Configure users migrations settings like roles.')]
|
||||
class MigrateUsersConfigure extends Command
|
||||
{
|
||||
|
||||
private function getWpRoles(): array
|
||||
{
|
||||
$wpRoles = ['administrator', 'editor', 'author', 'contributor', 'subscriber'];
|
||||
$customWpRoles = ['bot'];
|
||||
|
||||
return array_merge($wpRoles, $customWpRoles);
|
||||
}
|
||||
|
||||
private function getOldXfGroups(): array|Collection
|
||||
{
|
||||
return DB::connection('old_xf')->table('user_group')->pluck('title', 'user_group_id');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpRoles = $this->getWpRoles();
|
||||
$roleMap = [];
|
||||
foreach ($wpRoles as $role) {
|
||||
$roleMap[$role] = (int) $this->ask("XenForo group ID linked to role {$role}");
|
||||
}
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'wp_role_to_xf_group'
|
||||
], [
|
||||
'value' => json_encode($roleMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$oldXfGroups = $this->getOldXfGroups();
|
||||
$groupMap = [];
|
||||
foreach ($oldXfGroups as $id => $title) {
|
||||
$groupMap[$id] = (int) $this->ask("Xenforo group ID linked to old XF group {$title}|{$id}");
|
||||
}
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert([
|
||||
'key' => 'old_xf_group_to_xf_group'
|
||||
],[
|
||||
'value' => json_encode($groupMap), 'updated_at' => now()
|
||||
]);
|
||||
|
||||
$this->info('XF groups updated.');
|
||||
}
|
||||
}
|
||||
229
app/Console/Commands/MigrateUsersExecute.php
Normal file
229
app/Console/Commands/MigrateUsersExecute.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\MigrationUserPlan;
|
||||
use App\Services\XenforoApiService;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
#[Signature('migrate:users:execute {--limit=} {--wp-uploads-path=} {--xf-data-path=}')]
|
||||
#[Description("Migrate all users in migration table to XenForo.")]
|
||||
class MigrateUsersExecute extends Command
|
||||
{
|
||||
|
||||
private function extractWpRole(?string $serialized): string
|
||||
{
|
||||
$caps = $serialized ? @unserialize($serialized) : null;
|
||||
return is_array($caps) ? (array_key_first($caps) ?: 'contributor') : 'contributor';
|
||||
}
|
||||
|
||||
private function extractWpAvatarPath(?string $serialized, string $wpUploadsPath ): ?string
|
||||
{
|
||||
$data = $serialized ? @unserialize($serialized) : null;
|
||||
$mediaId = $data['media_id'] ?? null;
|
||||
if( !$mediaId )
|
||||
return null;
|
||||
|
||||
$relative = DB::connection('old_wp')
|
||||
->table('postmeta')
|
||||
->where('post_id', $mediaId)
|
||||
->where('meta_key', '_wp_attached_file')
|
||||
->value('meta_value');
|
||||
|
||||
if( !$relative )
|
||||
return null;
|
||||
|
||||
$path = rtrim($wpUploadsPath, '/') . '/' . $relative;
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function extractXfAvatarPath(int $userId, string $xfDataPath, string $size = 'o' ): ?string
|
||||
{
|
||||
$path = sprintf('%s/avatars/%s/%d/%d.jpg',
|
||||
rtrim($xfDataPath, '/'), $size, (int) floor($userId / 1000), $userId
|
||||
);
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function extractXfBannerPath(int $userId, string $xfDataPath, string $size = 'l' ): ?string
|
||||
{
|
||||
$path = sprintf('%s/profile_banners/%s/%d/%d.jpg',
|
||||
rtrim($xfDataPath, '/'), $size, (int) floor($userId / 1000), $userId
|
||||
);
|
||||
return is_file($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function buildUserInfos( MigrationUserPlan $plan, array $roleMap, array $groupMap, string $wpUploadsPath, string $xfDataPath ): array
|
||||
{
|
||||
$infos = [
|
||||
'username' => $plan->xf_username ?: $plan->wp_username,
|
||||
'email' => $plan->email,
|
||||
'user_group_id' => null,
|
||||
'password' => Str::uuid(), // Used for API verifications.
|
||||
'xf_user_id' => 0,
|
||||
'avatar_path' => null,
|
||||
'banner_path' => null,
|
||||
'register_date' => null,
|
||||
'profile' => [
|
||||
'about' => null,
|
||||
'website' => null,
|
||||
],
|
||||
'source_real_password' => null,
|
||||
'wp_password' => null,
|
||||
'xf_scheme_class' => null,
|
||||
'xf_password_data' => null,
|
||||
];
|
||||
|
||||
if( $plan->wp_user_id )
|
||||
{
|
||||
$wp = DB::connection('old_wp')
|
||||
->table('users')
|
||||
->leftJoin('usermeta as m1', fn( $j ) => $j->on('users.ID', '=', 'm1.user_id')->where('m1.meta_key', '=', 'description') )
|
||||
->leftJoin('usermeta as m2', fn( $j ) => $j->on('users.ID', '=', 'm2.user_id')->where('m2.meta_key', '=', 'wp_capabilities') )
|
||||
->leftJoin('usermeta as m3', fn( $j ) => $j->on('users.ID', '=', 'm3.user_id')->where('m3.meta_key', '=', 'simple_local_avatar') )
|
||||
->where('users.ID', '=', $plan->wp_user_id )
|
||||
->select('m1.meta_value as description', 'm2.meta_value as capabilities', 'm3.meta_value as avatar_meta', 'users.user_url as website', 'users.user_pass as password', 'users.user_registred' )
|
||||
->first();
|
||||
|
||||
$infos['register_date'] = $wp->user_registred ? strtotime($wp->user_registred) : null;
|
||||
$infos['profile']['about'] = $wp->description;
|
||||
$infos['profile']['website'] = $wp->website;
|
||||
$role = $this->extractWpRole($wp->capabilities);
|
||||
$infos['user_group_id'] = $roleMap[$role] ?? $roleMap['contributor'];
|
||||
|
||||
if( $url = $this->extractWpAvatarPath($wp->avatar_meta, $wpUploadsPath)){
|
||||
$infos['avatar_path'] = $url;
|
||||
}
|
||||
|
||||
$infos['source_real_password'] = 'wp';
|
||||
$infos['wp_password'] = $wp->password;
|
||||
}
|
||||
|
||||
if( $plan->xf_user_id )
|
||||
{
|
||||
$xf = DB::connection('old_xf')
|
||||
->table('user')
|
||||
->leftJoin('user_profile', 'user.user_id', '=', 'user_profile.user_id')
|
||||
->leftJoin('user_authenticate', 'user.user_id', '=', 'user_authenticate.user_id')
|
||||
->where('user.user_id', '=', $plan->xf_user_id)
|
||||
->select('user.avatar_date', 'user.user_group_id', 'user.register_date', 'user_profile.about', 'user_profile.website', 'user_profile.banner_date', 'user_authenticate.scheme_class', 'user_authenticate.data')
|
||||
->first();
|
||||
|
||||
if( !$infos['register_date'] && $xf )
|
||||
$infos['register_date'] = $xf->register_date ?: null;
|
||||
if( !$infos['profile']['about'] && $xf )
|
||||
$infos['profile']['about'] = $xf->about ?: null;
|
||||
if( !$infos['profile']['website'] && $xf )
|
||||
$infos['profile']['website'] = $xf->website ?: null;
|
||||
|
||||
if( !$plan->wp_user_id ){
|
||||
$infos['user_group_id'] = $groupMap[$xf->user_group_id] ?? reset($groupMap);
|
||||
}
|
||||
$infos['xf_user_id'] = $plan->xf_user_id;
|
||||
|
||||
if( $infos['avatar_path'] === null && (int) $xf->avatar_date > 0){
|
||||
if( $path = $this->extractXfAvatarPath($plan->xf_user_id, $xfDataPath)){
|
||||
$infos['avatar_path'] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
if( $infos['banner_path'] === null && (int) $xf->banner_date > 0 ){
|
||||
if( $path = $this->extractXfBannerPath($plan->xf_user_id, $xfDataPath)){
|
||||
$infos['banner_path'] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
if( $infos['source_real_password'] === null && $xf->scheme_class && $xf->data ){
|
||||
$infos['source_real_password'] = 'xf';
|
||||
$infos['xf_scheme_class'] = $xf->scheme_class;
|
||||
$infos['xf_password_data'] = $xf->data;
|
||||
}
|
||||
}
|
||||
|
||||
return $infos;
|
||||
}
|
||||
|
||||
private function logMap( string $sourceSystem, string $sourceTable, int $sourceId, int $targetId )
|
||||
{
|
||||
DB::table('migrations_logs')->insert([
|
||||
'source_system' => $sourceSystem,
|
||||
'source_table' => $sourceTable,
|
||||
'source_id' => $sourceId,
|
||||
'target_table' => 'xf_user',
|
||||
'target_id' => $targetId,
|
||||
'status' => 'done',
|
||||
'migrated_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$wpUploadsPath = $this->option('wp-uploads-path');
|
||||
$xfDataPath = $this->option('xf-data-path');
|
||||
|
||||
if( !$wpUploadsPath || !is_dir($wpUploadsPath) ){
|
||||
$this->error('Missing WP Uploads Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if( !$xfDataPath || !is_dir($xfDataPath) ){
|
||||
$this->error('Missing XF Data Path');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$roleMap = json_decode(DB::table('migration_settings')->where('key', 'wp_role_to_xf_group')->value('value'), true);
|
||||
$groupMap = json_decode(DB::table('migration_settings')->where('key', 'old_xf_group_to_xf_group')->value('value'), true);
|
||||
|
||||
if( !$roleMap || !$groupMap ) {
|
||||
$this->error('Role map and group map are required.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$query = MigrationUserPlan::where('status', 'approved')->whereNull('user_id');
|
||||
if( $limit = $this->option('limit') ) {
|
||||
$query->limit((int) $limit);
|
||||
}
|
||||
|
||||
$rows = $query->get();
|
||||
$this->info("{$rows->count()} accounts will be created on XenForo database !!!.");
|
||||
$ok = $this->ask("Write 'ok' if you want to start the migration. Everything else to quit the migration.");
|
||||
if( $ok !== 'ok' ) {
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$service = app(XenforoApiService::class);
|
||||
|
||||
$this->withProgressBar($rows, function($plan) use( $roleMap, $groupMap, $wpUploadsPath, $xfDataPath, $service ) {
|
||||
try {
|
||||
$infos = $this->buildUserInfos( $plan, $roleMap, $groupMap, $wpUploadsPath, $xfDataPath );
|
||||
|
||||
[ $userId, $passwordSet ] = $service->_migrateUser( $infos );
|
||||
if( !$userId ){
|
||||
throw new \RuntimeException("Error when user creation.");
|
||||
}
|
||||
|
||||
MigrationUserPlan::where('id', $plan->id )->update([ 'user_id' => $userId ]);
|
||||
|
||||
$this->logMap( $plan->wp_user_id ? 'wp' : 'xf', $plan->wp_user_id ? 'wp_users' : 'xf_user', $plan->wp_user_id ?? $plan->xf_user_id, $userId );
|
||||
if( $plan->wp_user_id && $plan->xf_user_id ){
|
||||
$this->logMap('xf', 'xf_user', $plan->xf_user_id, $userId );
|
||||
}
|
||||
|
||||
|
||||
} catch ( \Throwable $e ) {
|
||||
Log::error("Unable to create Plan#{$plan->id} user : {$e->getMessage()}");
|
||||
}
|
||||
});
|
||||
|
||||
$this->newLine(2);
|
||||
$this->info("Process finished.");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
143
app/Console/Commands/MigrateUsersPlan.php
Normal file
143
app/Console/Commands/MigrateUsersPlan.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\MigrationUserPlan;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:users:plan {--fresh}')]
|
||||
#[Description('Construct migration plan for WP/XF accounts.')]
|
||||
class MigrateUsersPlan extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
if( $this->option('fresh') ) {
|
||||
MigrationUserPlan::truncate();
|
||||
}
|
||||
|
||||
$this->info("Loading old XF accounts...");
|
||||
|
||||
$xfUsers = DB::connection('old_xf')->table('user')
|
||||
->select('user_id','username','email')
|
||||
->get()
|
||||
->keyBy('user_id')
|
||||
;
|
||||
|
||||
$xfUsersByEmail = $xfUsers->groupBy(fn($u) => strtolower($u->email));
|
||||
|
||||
$this->info("Loading old WP accounts...");
|
||||
|
||||
$wpUsers = DB::connection('old_wp')->table('users')
|
||||
->leftJoin('usermeta', function ($join) {
|
||||
$join->on('users.ID', '=', 'usermeta.user_id')->where('usermeta.meta_key', '=', 'xf_user_id');
|
||||
})
|
||||
->select('users.ID as wp_id', 'users.user_email as email', 'users.user_login as username', 'usermeta.meta_value as linked_xf_id')
|
||||
->get();
|
||||
|
||||
$linkedXfIds = [];
|
||||
|
||||
$this->withProgressBar( $wpUsers, function ($wp) use ($xfUsers, $xfUsersByEmail, &$linkedXfIds) {
|
||||
$email = strtolower($wp->email);
|
||||
$linkedId = $wp->linked_xf_id ? (int) $wp->linked_xf_id : null;
|
||||
|
||||
if( $linkedId && $xfUsers->has($linkedId) ) {
|
||||
|
||||
$xf = $xfUsers->get($linkedId);
|
||||
$matchType = strtolower($xf->email) === $email ? 'explicit' : 'conflict';
|
||||
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id],
|
||||
[
|
||||
'xf_user_id' => $linkedId,
|
||||
'match_type' => $matchType,
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'xf_username' => $xf->username,
|
||||
'note' => $matchType === 'conflict' ? "E-Mail différent: {$xf->email}" : null,
|
||||
'status' => $matchType === 'explicit' ? 'approved' : 'pending',
|
||||
]
|
||||
);
|
||||
$linkedXfIds[$linkedId] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if( $linkedId && !$xfUsers->has($linkedId) ) {
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id ],
|
||||
[
|
||||
'match_type' => 'conflict',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'note' => "xf_user_id={$linkedId} introuvable",
|
||||
'status' => 'approved'
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$candidates = $xfUsersByEmail->get($email,collect());
|
||||
|
||||
if( $candidates->count() === 1 ){
|
||||
$xf = $candidates->first();
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id],
|
||||
[
|
||||
'xf_user_id' => $xf->user_id,
|
||||
'match_type' => 'email',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'xf_username' => $xf->username,
|
||||
'status' => 'pending'
|
||||
]
|
||||
);
|
||||
$linkedXfIds[$xf->user_id] = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if( $candidates->count() > 1 ){
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id ],
|
||||
[
|
||||
'match_type' => 'conflict',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'note' => "E-mail identique sur plusieurs comptes XF.",
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['wp_user_id' => $wp->wp_id ],
|
||||
[
|
||||
'match_type' => 'wp_only',
|
||||
'email' => $email,
|
||||
'wp_username' => $wp->username,
|
||||
'status' => 'approved'
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
$this->info("Listing old XF accounts...");
|
||||
foreach( $xfUsers as $id => $xf ) {
|
||||
if( isset($linkedXfIds[$id]) ) {
|
||||
continue;
|
||||
}
|
||||
MigrationUserPlan::updateOrCreate(
|
||||
['xf_user_id' => $xf->user_id, 'wp_user_id' => null],
|
||||
[
|
||||
'match_type' => 'xf_only',
|
||||
'email' => strtolower($xf->email),
|
||||
'xf_username' => $xf->username,
|
||||
'status' => 'approved'
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$pending = MigrationUserPlan::where('status', 'pending')->count();
|
||||
$this->newLine(2);
|
||||
$this->info("Plan generated. {$pending} pending cases.");
|
||||
}
|
||||
}
|
||||
41
app/Console/Commands/MigrateXFConfigure.php
Normal file
41
app/Console/Commands/MigrateXFConfigure.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
#[Signature('migrate:xf:configure')]
|
||||
#[Description("Configure forums map for migrating threads.")]
|
||||
class MigrateXFConfigure extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$forums = DB::connection('old_xf')
|
||||
->table('node')
|
||||
->where('node_type_id', 'Forum')
|
||||
->select('node_id', 'title')
|
||||
->get()
|
||||
;
|
||||
|
||||
$this->info("{$forums->count()} forums found.");
|
||||
|
||||
$forumMap = [];
|
||||
foreach ($forums as $forum) {
|
||||
$newId = $this->ask("New ID for {$forum->title}");
|
||||
if( $newId !== null && $newId !== '' ){
|
||||
$map[$forum->node_id] = (int) $newId;
|
||||
}
|
||||
}
|
||||
|
||||
DB::table('migration_settings')
|
||||
->updateOrInsert(
|
||||
[ 'key' => 'old_xf_node_to_new_xf_node'],
|
||||
[ 'value' => json_encode($map), 'updated_at' => now() ],
|
||||
);
|
||||
|
||||
$this->info("Config saved.");
|
||||
}
|
||||
}
|
||||
31
app/Console/Commands/PurgeFeaturedEntries.php
Normal file
31
app/Console/Commands/PurgeFeaturedEntries.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Entry;
|
||||
|
||||
#[Signature('entries:purge-featured {--days=15}')]
|
||||
#[Description('Remove Featured from entries higher than X days')]
|
||||
class PurgeFeaturedEntries extends Command
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
$days = $this->option('days');
|
||||
|
||||
$cutoff = now()->subDays($days);
|
||||
|
||||
$count = Entry::query()
|
||||
->where('featured', true)
|
||||
->where('featured_at', '<=', $cutoff)
|
||||
->update([
|
||||
'featured' => false,
|
||||
'featured_at' => null,
|
||||
]);
|
||||
|
||||
$this->info("$count entr" . ($count > 1 ? 'ies' : 'y') . " unfeatured.");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
48
app/Filament/Resources/Categories/CategoryResource.php
Normal file
48
app/Filament/Resources/Categories/CategoryResource.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categories;
|
||||
|
||||
use App\Filament\Resources\Categories\Pages\CreateCategory;
|
||||
use App\Filament\Resources\Categories\Pages\EditCategory;
|
||||
use App\Filament\Resources\Categories\Pages\ListCategories;
|
||||
use App\Filament\Resources\Categories\Schemas\CategoryForm;
|
||||
use App\Filament\Resources\Categories\Tables\CategoriesTable;
|
||||
use App\Models\Category;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CategoryResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Category::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return CategoryForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return CategoriesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListCategories::route('/'),
|
||||
'create' => CreateCategory::route('/create'),
|
||||
'edit' => EditCategory::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Categories/Pages/CreateCategory.php
Normal file
11
app/Filament/Resources/Categories/Pages/CreateCategory.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categories\Pages;
|
||||
|
||||
use App\Filament\Resources\Categories\CategoryResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCategory extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Categories/Pages/EditCategory.php
Normal file
19
app/Filament/Resources/Categories/Pages/EditCategory.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categories\Pages;
|
||||
|
||||
use App\Filament\Resources\Categories\CategoryResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCategory extends EditRecord
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Categories/Pages/ListCategories.php
Normal file
19
app/Filament/Resources/Categories/Pages/ListCategories.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categories\Pages;
|
||||
|
||||
use App\Filament\Resources\Categories\CategoryResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListCategories extends ListRecords
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
24
app/Filament/Resources/Categories/Schemas/CategoryForm.php
Normal file
24
app/Filament/Resources/Categories/Schemas/CategoryForm.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categories\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class CategoryForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->required(),
|
||||
Textarea::make('restricted_to')
|
||||
->default(null)
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
app/Filament/Resources/Categories/Tables/CategoriesTable.php
Normal file
42
app/Filament/Resources/Categories/Tables/CategoriesTable.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Categories\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CategoriesTable
|
||||
{
|
||||
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(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
app/Filament/Resources/Levels/LevelResource.php
Normal file
48
app/Filament/Resources/Levels/LevelResource.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels;
|
||||
|
||||
use App\Filament\Resources\Levels\Pages\CreateLevel;
|
||||
use App\Filament\Resources\Levels\Pages\EditLevel;
|
||||
use App\Filament\Resources\Levels\Pages\ListLevels;
|
||||
use App\Filament\Resources\Levels\Schemas\LevelForm;
|
||||
use App\Filament\Resources\Levels\Tables\LevelsTable;
|
||||
use App\Models\Level;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LevelResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Level::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return LevelForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return LevelsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListLevels::route('/'),
|
||||
'create' => CreateLevel::route('/create'),
|
||||
'edit' => EditLevel::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Filament/Resources/Levels/Pages/CreateLevel.php
Normal file
11
app/Filament/Resources/Levels/Pages/CreateLevel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Pages;
|
||||
|
||||
use App\Filament\Resources\Levels\LevelResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateLevel extends CreateRecord
|
||||
{
|
||||
protected static string $resource = LevelResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Levels/Pages/EditLevel.php
Normal file
19
app/Filament/Resources/Levels/Pages/EditLevel.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Pages;
|
||||
|
||||
use App\Filament\Resources\Levels\LevelResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditLevel extends EditRecord
|
||||
{
|
||||
protected static string $resource = LevelResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Levels/Pages/ListLevels.php
Normal file
19
app/Filament/Resources/Levels/Pages/ListLevels.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Pages;
|
||||
|
||||
use App\Filament\Resources\Levels\LevelResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListLevels extends ListRecords
|
||||
{
|
||||
protected static string $resource = LevelResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Filament/Resources/Levels/Schemas/LevelForm.php
Normal file
20
app/Filament/Resources/Levels/Schemas/LevelForm.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class LevelForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
42
app/Filament/Resources/Levels/Tables/LevelsTable.php
Normal file
42
app/Filament/Resources/Levels/Tables/LevelsTable.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Levels\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class LevelsTable
|
||||
{
|
||||
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/Systems/Pages/CreateSystem.php
Normal file
11
app/Filament/Resources/Systems/Pages/CreateSystem.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Systems\Pages;
|
||||
|
||||
use App\Filament\Resources\Systems\SystemResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateSystem extends CreateRecord
|
||||
{
|
||||
protected static string $resource = SystemResource::class;
|
||||
}
|
||||
19
app/Filament/Resources/Systems/Pages/EditSystem.php
Normal file
19
app/Filament/Resources/Systems/Pages/EditSystem.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Systems\Pages;
|
||||
|
||||
use App\Filament\Resources\Systems\SystemResource;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditSystem extends EditRecord
|
||||
{
|
||||
protected static string $resource = SystemResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
19
app/Filament/Resources/Systems/Pages/ListSystems.php
Normal file
19
app/Filament/Resources/Systems/Pages/ListSystems.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Systems\Pages;
|
||||
|
||||
use App\Filament\Resources\Systems\SystemResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListSystems extends ListRecords
|
||||
{
|
||||
protected static string $resource = SystemResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
20
app/Filament/Resources/Systems/Schemas/SystemForm.php
Normal file
20
app/Filament/Resources/Systems/Schemas/SystemForm.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Systems\Schemas;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class SystemForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->required(),
|
||||
TextInput::make('slug')
|
||||
->required(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
app/Filament/Resources/Systems/SystemResource.php
Normal file
48
app/Filament/Resources/Systems/SystemResource.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Systems;
|
||||
|
||||
use App\Filament\Resources\Systems\Pages\CreateSystem;
|
||||
use App\Filament\Resources\Systems\Pages\EditSystem;
|
||||
use App\Filament\Resources\Systems\Pages\ListSystems;
|
||||
use App\Filament\Resources\Systems\Schemas\SystemForm;
|
||||
use App\Filament\Resources\Systems\Tables\SystemsTable;
|
||||
use App\Models\System;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class SystemResource extends Resource
|
||||
{
|
||||
protected static ?string $model = System::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return SystemForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return SystemsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListSystems::route('/'),
|
||||
'create' => CreateSystem::route('/create'),
|
||||
'edit' => EditSystem::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
42
app/Filament/Resources/Systems/Tables/SystemsTable.php
Normal file
42
app/Filament/Resources/Systems/Tables/SystemsTable.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Systems\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class SystemsTable
|
||||
{
|
||||
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(),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryFile;
|
||||
use App\Models\News;
|
||||
use App\Services\XenforoApiService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -62,13 +64,16 @@ class EntryHelpers {
|
||||
};
|
||||
}
|
||||
|
||||
public static function getLatestComments(Entry $entry, int $limit = 20): array {
|
||||
public static function getLatestComments(Entry|News $entry, int $limit = 20): array {
|
||||
|
||||
if( !$entry->comments_thread_id ){
|
||||
return [];
|
||||
}
|
||||
|
||||
$cacheKey = "entry_comments_{$entry->id}";
|
||||
if( is_a( $entry, News::class ) )
|
||||
$cacheKey = "news_comments_{$entry->id}";
|
||||
else
|
||||
$cacheKey = "entry_comments_{$entry->id}";
|
||||
return Cache::remember($cacheKey, now()->addDays(1), function () use ($entry, $limit) {
|
||||
|
||||
$service = app(XenforoApiService::class);
|
||||
@@ -90,4 +95,28 @@ class EntryHelpers {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public static function enableOnlinePatcherBasedOnExtension(string $filename): bool
|
||||
{
|
||||
return Str::endsWith(Str::lower($filename), ['.ips', '.bps', '.xdelta', '.ups', '.aps', '.ppf', '.zip' ]);
|
||||
}
|
||||
|
||||
public static function getYoutubeVideoId(string $url): ?string
|
||||
{
|
||||
$pattern = '%(?:https?://)?(?:www\.|m\.)?(?:youtu\.be/|youtube(?:-nocookie)?\.com/(?:watch\?.*v=|embed/|v/|shorts/|live/))([\w-]{11})%i';
|
||||
|
||||
preg_match($pattern, $url, $matches);
|
||||
return $matches[1] ?? null;
|
||||
}
|
||||
|
||||
public static function fileAlreadyDownloaded(EntryFile $entryFile): bool
|
||||
{
|
||||
return session("downloaded_file_{$entryFile->file_uuid}", null ) !== null;
|
||||
}
|
||||
|
||||
public static function markFileAsDownloaded(EntryFile $entryFile): bool
|
||||
{
|
||||
session(["downloaded_file_{$entryFile->file_uuid}" => 1]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,79 @@ class FormHelpers {
|
||||
'release_site_helper' => "Project entry on site/blog/forum/Github.",
|
||||
'youtube_video' => "YouTube video",
|
||||
],
|
||||
'homebrew' => [
|
||||
'page_title' => "Submit an homebrew",
|
||||
'about_the' => "About the homebrew",
|
||||
'version' => "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",
|
||||
],
|
||||
'utilities' => [
|
||||
'page_title' => "Submit a utility",
|
||||
'entry_title' => "Title",
|
||||
'about_the' => "About the utility",
|
||||
'version' => "Version",
|
||||
'status' => "Status",
|
||||
'system' => "OS",
|
||||
'categories' => "Categories",
|
||||
'level' => "Experience Level",
|
||||
'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",
|
||||
],
|
||||
'documents' => [
|
||||
'page_title' => "Submit a document",
|
||||
'entry_title' => "Title",
|
||||
'about_the' => "About the document",
|
||||
'version' => "Version",
|
||||
'status' => "Status",
|
||||
'categories' => "Categories",
|
||||
'level' => "Experience Level",
|
||||
'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",
|
||||
],
|
||||
'lua-scripts' => [
|
||||
'page_title' => "Submit a LUA Script",
|
||||
'about_the' => "About the script",
|
||||
'entry_title' => "Title",
|
||||
'type_of_hack' => "Modifications",
|
||||
'version' => "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 ){
|
||||
|
||||
47
app/Helpers/MigrationHelpers.php
Normal file
47
app/Helpers/MigrationHelpers.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
|
||||
class MigrationHelpers
|
||||
{
|
||||
|
||||
public static function wpAutoP(string $content): string
|
||||
{
|
||||
$content = trim($content);
|
||||
if ($content === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$content = str_replace(["\r\n", "\r"], "\n", $content);
|
||||
$blocks = preg_split('/\n\s*\n/', $content);
|
||||
|
||||
$blocks = array_map(function ($block) {
|
||||
$block = trim($block);
|
||||
if ($block === '') {
|
||||
return '';
|
||||
}
|
||||
if (preg_match('/^<(p|div|table|ul|ol|blockquote|h[1-6]|pre|figure)[\s>]/i', $block)) {
|
||||
return $block;
|
||||
}
|
||||
return '<p>' . nl2br($block, false) . '</p>';
|
||||
}, $blocks);
|
||||
|
||||
return implode("\n\n", array_filter($blocks, fn ($b) => $b !== ''));
|
||||
}
|
||||
|
||||
public static function htmlToMarkdown( ?string $html ): string
|
||||
{
|
||||
if (!$html || trim($html) === '') {
|
||||
return $html;
|
||||
}
|
||||
|
||||
static $converter = null;
|
||||
if ($converter === null) {
|
||||
$converter = new HtmlConverter(['hard_break' => true]);
|
||||
}
|
||||
|
||||
return $converter->convert(self::wpAutoP($html));
|
||||
}
|
||||
}
|
||||
57
app/Helpers/PlayOnlineHelpers.php
Normal file
57
app/Helpers/PlayOnlineHelpers.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
class PlayOnlineHelpers
|
||||
{
|
||||
public static function getCoreLists(): array
|
||||
{
|
||||
return [
|
||||
'nes',
|
||||
'fceumm',
|
||||
'nestopia',
|
||||
'snes',
|
||||
'snes9x',
|
||||
'bsnes',
|
||||
'gb',
|
||||
'gambatte',
|
||||
'gba',
|
||||
'mgba',
|
||||
'nds',
|
||||
'melonds',
|
||||
'desmume2015',
|
||||
'desmume',
|
||||
'a5200',
|
||||
'mame2003',
|
||||
'mame2003_plus',
|
||||
'fbneo',
|
||||
'psx',
|
||||
'pcsx_rearmed',
|
||||
'mednafen_psx_hw',
|
||||
'segaSaturn',
|
||||
'yabuase',
|
||||
'segaMD',
|
||||
'segaGG',
|
||||
'segaCD',
|
||||
'genesis_plus_gx',
|
||||
'n64',
|
||||
'mupen64plus_next',
|
||||
'parallel-n64',
|
||||
'atari7800',
|
||||
'prosystem',
|
||||
'atari2600',
|
||||
'stella2014',
|
||||
'sega32x',
|
||||
'picodrive',
|
||||
'segaMS',
|
||||
'smsplus',
|
||||
'c64',
|
||||
'vice_x64sc',
|
||||
'same_cdi',
|
||||
'psp',
|
||||
'ppsspp',
|
||||
'3ds',
|
||||
'azahar'
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Helpers;
|
||||
|
||||
use App\Auth\XenForoUser;
|
||||
use App\Models\Entry;
|
||||
use App\Models\News;
|
||||
use App\Services\XenforoApiService;
|
||||
|
||||
class XenForoHelpers {
|
||||
@@ -44,7 +45,7 @@ class XenForoHelpers {
|
||||
$service->updateEntriesCount( $count, $userId );
|
||||
}
|
||||
|
||||
public static function entryApproved( Entry $entry ): void
|
||||
public static function entryApproved( Entry|News $entry ): void
|
||||
{
|
||||
// 1. Update XF Entry count.
|
||||
self::updateEntriesCount( $entry->user_id );
|
||||
@@ -58,6 +59,29 @@ class XenForoHelpers {
|
||||
$title = "Entry approved : {$entry->title}";
|
||||
$message = "Your entry {$entry->title} has been approved by {$moderator}.";
|
||||
|
||||
$service = app(XenForoApiService::class);
|
||||
|
||||
$service->createConversation([ $entry->user_id ], $title, $message, false, false);
|
||||
}
|
||||
|
||||
public static function entryRejected( Entry|News $entry ): void
|
||||
{
|
||||
// 1. Update XF Entry count.
|
||||
self::updateEntriesCount( $entry->user_id );
|
||||
|
||||
// 2. Send a private message
|
||||
/*
|
||||
if( \Auth::user()->user_id === $entry->user_id ) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
$moderator = \Auth::user()->username;
|
||||
$title = "Entry rejected : {$entry->title}";
|
||||
$message = "Your entry {$entry->title} has been rejected by {$moderator}.\nReason: {$entry->staff_comment}\n\nYou have 7 days to edit your entry before it is permanently deleted.";
|
||||
|
||||
$service = app(XenForoApiService::class);
|
||||
|
||||
$service->createConversation([ $entry->user_id ], $title, $message, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\XenForoHelpers;
|
||||
use App\Services\ActivityService;
|
||||
use App\Services\XenforoApiService;
|
||||
use App\Services\XenforoService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
@@ -63,4 +65,24 @@ class DynamicLoadController extends Controller
|
||||
|
||||
return response()->json( $data );
|
||||
}
|
||||
|
||||
public function activityFeed(Request $request): JsonResponse
|
||||
{
|
||||
$availableFilters = ['entries', 'news', 'messages', 'threads', 'clubs', 'reviews'];
|
||||
|
||||
$requested = $request->query('filters')
|
||||
? explode(',', $request->query('filters'))
|
||||
: [];
|
||||
|
||||
$activeFilters = !empty($requested)
|
||||
? array_intersect($requested, $availableFilters)
|
||||
: $availableFilters;
|
||||
|
||||
$service = app(ActivityService::class);
|
||||
$items = $service->getActivities(array_values($activeFilters));
|
||||
|
||||
return response()->json([
|
||||
'html' => view('activity.timeline', compact('items'))->render(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\EntryHelpers;
|
||||
use App\Models\Entry;
|
||||
use App\Models\News;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -11,7 +12,7 @@ use Illuminate\View\View;
|
||||
class EntryController extends Controller
|
||||
{
|
||||
|
||||
private const SECTION_TYPES = ['translations', 'romhacks', 'homebrew', 'utilities', 'documents', 'lua-scripts', 'tutorials'];
|
||||
private const SECTION_TYPES = ['translations', 'romhacks', 'homebrew', 'utilities', 'documents', 'lua-scripts'];
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
@@ -31,7 +32,8 @@ class EntryController extends Controller
|
||||
if ($entry->type !== $section)
|
||||
abort(404);
|
||||
|
||||
Gate::authorize('viewAny', $entry);
|
||||
if( !\Auth::guest() )
|
||||
Gate::authorize('viewAny', $entry);
|
||||
|
||||
// Permissions.
|
||||
$entryPolicy = match ($entry->state) {
|
||||
@@ -48,8 +50,9 @@ class EntryController extends Controller
|
||||
Gate::authorize($entryPolicy, $entry);
|
||||
|
||||
$comments = EntryHelpers::getLatestComments($entry);
|
||||
$reviews = $entry->reviews()->orderBy('created_at', 'desc')->limit(10)->get();
|
||||
|
||||
return view('entries.show', compact('entry', 'section', 'comments'));
|
||||
return view('entries.show', compact('entry', 'section', 'comments', 'reviews'));
|
||||
|
||||
}
|
||||
|
||||
@@ -61,7 +64,12 @@ class EntryController extends Controller
|
||||
->orderBy('updated_at', 'desc')
|
||||
->paginate(20);
|
||||
|
||||
return view('entries.drafts', compact('drafts'));
|
||||
$newsDrafts = News::where('user_id', \Auth::user()->user_id )
|
||||
->where('state', 'draft')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->paginate(20);
|
||||
|
||||
return view('entries.drafts', compact('drafts', 'newsDrafts'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
23
app/Http/Controllers/EntryFeaturedRequestController.php
Normal file
23
app/Http/Controllers/EntryFeaturedRequestController.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Services\XenforoApiService;
|
||||
use App\Services\XenforoService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EntryFeaturedRequestController extends Controller
|
||||
{
|
||||
|
||||
public function featuredRequest(Request $request, Entry $entry)
|
||||
{
|
||||
$service = app(XenforoApiService::class);
|
||||
$response = $service->featuredRequest($entry);
|
||||
return response()->json([
|
||||
'success' => $response,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\EntryHelpers;
|
||||
use App\Models\EntryFile;
|
||||
use App\Models\LogXfUser;
|
||||
use App\Services\FileServersService;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -44,7 +46,7 @@ class FileServerController extends Controller {
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
\Cache::put("uploaded_file_{$fileUuid}", [
|
||||
$fileData = [
|
||||
'uuid' => $fileUuid,
|
||||
'type' => $type,
|
||||
'filename' => $filename,
|
||||
@@ -52,8 +54,17 @@ class FileServerController extends Controller {
|
||||
'filesize' => $data['file']['size'],
|
||||
'favorite_server' => $data['favorite_server'],
|
||||
'favorite_at' => time(),
|
||||
'state' => 'public'
|
||||
], now()->addHours(2) );
|
||||
'state' => 'public',
|
||||
];
|
||||
|
||||
activity('entry-file')
|
||||
->causedBy(LogXfUser::find(\Auth::user()->getAuthIdentifier()))
|
||||
->withProperties($fileData)
|
||||
->event('file_upload')
|
||||
->log("File uploaded")
|
||||
;
|
||||
|
||||
\Cache::put("uploaded_file_{$fileUuid}", $fileData, now()->addHours(2) );
|
||||
|
||||
$data['finished'] = true;
|
||||
return response()->json($data);
|
||||
@@ -66,7 +77,13 @@ class FileServerController extends Controller {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
// TODO: DL Count.
|
||||
if($request->input('count_download', true)) {
|
||||
if (!EntryHelpers::fileAlreadyDownloaded($file)) {
|
||||
EntryHelpers::markFileAsDownloaded($file);
|
||||
$file->increaseDownloadCount();
|
||||
}
|
||||
}
|
||||
|
||||
return redirect( $this->fs->getDownloadFileUrl( $file) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,43 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Services\ActivityService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use App\Models\News;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
|
||||
public function index(): View {
|
||||
return view('home');
|
||||
public function __construct( private ActivityService $service ) {}
|
||||
|
||||
public function index( Request $request ): View {
|
||||
|
||||
$filters = [ 'entries', 'news', 'messages', 'threads', 'clubs', 'reviews' ];
|
||||
|
||||
$cookie = $request->cookie('activity_filters');
|
||||
$activeFilters = $cookie ? array_intersect( json_decode( $cookie, true ) ?? [], $filters ) : $filters;
|
||||
|
||||
if( empty( $activeFilters ) ) {
|
||||
$activeFilters = $filters;
|
||||
}
|
||||
|
||||
$items = $this->service->getActivities( array_values( $activeFilters ) );
|
||||
|
||||
$viewFilters = [
|
||||
'entries' => ['label' => 'Entries', 'icon' => 'database'],
|
||||
'news' => ['label' => 'News', 'icon' => 'newspaper'],
|
||||
'messages' => ['label' => 'Posts', 'icon' => 'message-square'],
|
||||
'threads' => ['label' => 'Threads', 'icon' => 'messages-square'],
|
||||
'clubs' => ['label' => 'Clubs', 'icon' => 'balloon'],
|
||||
'reviews' => ['label' => 'Reviews', 'icon' => 'star'],
|
||||
];
|
||||
|
||||
$latestNews = News::published()->latest('created_at')->limit(5)->get();
|
||||
$featuredEntries = Entry::published()->where('featured', true)->latest('featured_at')->get();
|
||||
|
||||
return view('home', compact('items', 'activeFilters', 'viewFilters', 'latestNews', 'featuredEntries'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -75,4 +75,9 @@ class ModCPController extends Controller
|
||||
$entry->forceDelete();
|
||||
return back()->with('success', "Entry permanently deleted");
|
||||
}
|
||||
|
||||
public function logs()
|
||||
{
|
||||
return view('modcp.logs');
|
||||
}
|
||||
}
|
||||
|
||||
124
app/Http/Controllers/NewsController.php
Normal file
124
app/Http/Controllers/NewsController.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\SubmissionException;
|
||||
use App\Helpers\EntryHelpers;
|
||||
use App\Http\Requests\StoreDraftRequest;
|
||||
use App\Http\Requests\StoreEntryRequest;
|
||||
use App\Http\Requests\StoreNewsDraftRequest;
|
||||
use App\Http\Requests\StoreNewsRequest;
|
||||
use App\Jobs\DeleteXenForoCommentsThread;
|
||||
use App\Models\News;
|
||||
use App\Services\NewsService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class NewsController extends Controller
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
return view('news.index');
|
||||
}
|
||||
|
||||
public function show(Request $request, News $news)
|
||||
{
|
||||
if( !\Auth::guest() )
|
||||
Gate::authorize('viewAny', $news);
|
||||
|
||||
// Permissions.
|
||||
$entryPolicy = match ($news->state) {
|
||||
'pending' => 'viewPending',
|
||||
'draft' => 'viewDraft',
|
||||
'rejected' => 'viewRejected',
|
||||
'hidden' => 'viewHidden',
|
||||
'locked' => 'viewLocked',
|
||||
'published' => null,
|
||||
'default' => null
|
||||
};
|
||||
|
||||
if ($entryPolicy)
|
||||
Gate::authorize($entryPolicy, $news);
|
||||
|
||||
$comments = EntryHelpers::getLatestComments($news);
|
||||
|
||||
return view('news.show', compact('news', 'comments'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$data = [
|
||||
'news' => new News(),
|
||||
'isEdit' => false,
|
||||
'oldCategory' => old('category') ? [ old('category') ] : []
|
||||
];
|
||||
|
||||
return view ('news.create', $data);
|
||||
}
|
||||
|
||||
public function edit(Request $request, News $news)
|
||||
{
|
||||
$data = [
|
||||
'news' => $news,
|
||||
'isEdit' => true,
|
||||
'oldCategory' => old('category', $news->category_id) ? [ old('category', $news->category_id) ] : []
|
||||
];
|
||||
|
||||
return view ('news.edit', $data);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request = $request->input('submit-state') === 'draft' ? app(StoreNewsDraftRequest::class) : app(StoreNewsRequest::class);
|
||||
$request->validateResolved();
|
||||
|
||||
$service = app(NewsService::class);
|
||||
|
||||
try {
|
||||
$entry = $service->storeNews($request);
|
||||
|
||||
return match ($entry->state) {
|
||||
'published' => redirect()->route('news.show', ['news' => $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(Request $request, News $news)
|
||||
{
|
||||
$request = $request->input('submit-state') === 'draft' ? app(StoreNewsDraftRequest::class) : app(StoreNewsRequest::class);
|
||||
$request->validateResolved();
|
||||
|
||||
$service = app(NewsService::class);
|
||||
|
||||
try {
|
||||
$news = $service->editNews($request, $news);
|
||||
|
||||
return match ($news->state) {
|
||||
'published' => redirect()->route('news.show', ['news' => $news->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 destroy(Request $request, News $news)
|
||||
{
|
||||
if( $news->comments_thread_id )
|
||||
DeleteXenForoCommentsThread::dispatch( $news->comments_thread_id );
|
||||
|
||||
$news->delete();
|
||||
return redirect( route('news.index') )->with('success', "Entry successfully deleted.");
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers\XenForoHelpers;
|
||||
use App\Models\Entry;
|
||||
use App\Models\News;
|
||||
use App\Services\XenforoService;
|
||||
use Illuminate\Http\Request;
|
||||
class QueueController extends Controller
|
||||
@@ -14,7 +15,25 @@ class QueueController extends Controller
|
||||
->with(['authors', 'game.platform'])
|
||||
->orderByRaw("CASE WHEN state = 'pending' THEN 1 ELSE 0 END")
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
->get()
|
||||
->map(fn($item) => $item->setAttribute('queue_type', 'entry'));
|
||||
|
||||
$news = News::inQueue()
|
||||
->orderByRaw("CASE WHEN state = 'pending' THEN 1 ELSE 0 END")
|
||||
->orderBy('created_at', 'asc')
|
||||
->get()
|
||||
->map(fn($item) => $item->setAttribute('queue_type', 'news'));
|
||||
|
||||
$entries = $entries->concat($news)->sort(function($a, $b) {
|
||||
$aPending = $a->state === 'pending' ? 0 : 1;
|
||||
$bPending = $b->state === 'pending' ? 0 : 1;
|
||||
|
||||
if($aPending !== $bPending) {
|
||||
return $aPending <=> $bPending;
|
||||
}
|
||||
|
||||
return $a->created_at <=> $b->created_at;
|
||||
})->values();
|
||||
|
||||
return view('queue.index', compact('entries'));
|
||||
}
|
||||
@@ -25,24 +44,53 @@ class QueueController extends Controller
|
||||
|
||||
$entry->update(['staff_comment' => $request->input('comment')]);
|
||||
|
||||
return back()->with('success', 'Comment supdated');
|
||||
}
|
||||
|
||||
public function updateComment_news(Request $request, News $news)
|
||||
{
|
||||
$request->validate(['comment' => 'nullable|string|max:2000']);
|
||||
|
||||
$news->update(['staff_comment' => $request->input('comment')]);
|
||||
|
||||
return back()->with('success', 'Comment updated');
|
||||
}
|
||||
|
||||
public function approve(Request $request, Entry $entry)
|
||||
{
|
||||
// $entry->update(['state' => 'published']);
|
||||
$entry->update(['state' => 'published', 'created_at' => now()]);
|
||||
|
||||
XenForoHelpers::entryApproved($entry);
|
||||
|
||||
return back()->with('success', 'Entry approved');
|
||||
}
|
||||
|
||||
public function approve_news(Request $request, News $news)
|
||||
{
|
||||
$news->update(['state' => 'published', 'created_at' => now()]);
|
||||
|
||||
XenForoHelpers::entryApproved($news);
|
||||
|
||||
return back()->with('success', 'Entry approved');
|
||||
}
|
||||
|
||||
public function reject(Request $request, Entry $entry)
|
||||
{
|
||||
$request->validate(['reason' => 'nullable|string|max:2000']);
|
||||
|
||||
$entry->update(['state' => 'rejected', 'staff_comment' => $request->input('reason'), 'rejected_at' => now() ]);
|
||||
|
||||
XenForoHelpers::entryRejected($entry);
|
||||
return back()->with('success', 'Entry rejected');
|
||||
}
|
||||
|
||||
public function reject_news(Request $request, News $news)
|
||||
{
|
||||
$request->validate(['reason' => 'nullable|string|max:2000']);
|
||||
|
||||
$news->update(['state' => 'rejected', 'staff_comment' => $request->input('reason'), 'rejected_at' => now() ]);
|
||||
|
||||
XenForoHelpers::entryRejected($news);
|
||||
return back()->with('success', 'Entry rejected');
|
||||
}
|
||||
|
||||
|
||||
@@ -14,4 +14,13 @@ class RedirectController extends Controller
|
||||
|
||||
return redirect()->route('entries.show', ['section' => $entry->type, 'entry' => $entry])->with('success', "Your report has been sent.");
|
||||
}
|
||||
|
||||
public function newsReportRedirect( Request $request )
|
||||
{
|
||||
$id = $request->input('id');
|
||||
$entry = News::findOrFail($id);
|
||||
|
||||
return redirect()->route('news.show', ['news' => $entry])->with('success', "Your report has been sent.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
33
app/Http/Controllers/ReviewController.php
Normal file
33
app/Http/Controllers/ReviewController.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exceptions\SubmissionException;
|
||||
use App\Http\Requests\StoreReviewRequest;
|
||||
use App\Models\Entry;
|
||||
use App\Services\ReviewsService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ReviewController extends Controller
|
||||
{
|
||||
|
||||
public function __construct(private ReviewsService $services){}
|
||||
|
||||
public function index(){
|
||||
|
||||
return view('reviews.index');
|
||||
}
|
||||
|
||||
|
||||
public function store(StoreReviewRequest $request, Entry $entry)
|
||||
{
|
||||
try {
|
||||
$this->services->storeReview( $request, $entry );
|
||||
return redirect()->route('entries.show', [ 'section' => $entry->type, 'entry' => $entry ])->with('success', "Your review has been published.");
|
||||
} catch ( SubmissionException $e ) {
|
||||
return back()->withInput()->withErrors(['error' => $e->getMessage()]);
|
||||
} catch ( \Exception $e ) {
|
||||
return back()->withInput()->withErrors(['error' => 'Unknown error: '.$e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,16 @@ use App\Jobs\DeleteXenForoCommentsThread;
|
||||
use App\Models\Author;
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryFile;
|
||||
use App\Models\EntryGallery;
|
||||
use App\Models\Gallery;
|
||||
use App\Models\EntryHash;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Language;
|
||||
use App\Models\Level;
|
||||
use App\Models\Modification;
|
||||
use App\Models\Platform;
|
||||
use App\Models\Status;
|
||||
use App\Models\System;
|
||||
use App\Services\SubmissionsService;
|
||||
use App\Services\XenforoApiService;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -31,6 +33,62 @@ class SubmissionController extends Controller
|
||||
|
||||
public function __construct(private SubmissionsService $services){}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
|
||||
$entryTypes = [
|
||||
'romhack' => [
|
||||
'slug' => 'romhacks',
|
||||
'label' => 'Romhack',
|
||||
'icon' => 'gamepad-2',
|
||||
'color' => '#4a6fc2',
|
||||
'bg' => '#d5e1fc1a',
|
||||
'border' => '#d5e1fc40',
|
||||
],
|
||||
'translation' => [
|
||||
'slug' => 'translations',
|
||||
'label' => 'Translation',
|
||||
'icon' => 'languages',
|
||||
'color' => '#4a8a2a',
|
||||
'bg' => '#e7f4d91a',
|
||||
'border' => '#e7f4d940',
|
||||
],
|
||||
'homebrew' => [
|
||||
'slug' => 'homebrew',
|
||||
'label' => 'Homebrew',
|
||||
'icon' => 'cpu',
|
||||
'color' => '#c23060',
|
||||
'bg' => '#ffeaf01a',
|
||||
'border' => '#ffeaf040',
|
||||
],
|
||||
'utility' => [
|
||||
'slug' => 'utilities',
|
||||
'label' => 'Utility',
|
||||
'icon' => 'wrench',
|
||||
'color' => '#8a6600',
|
||||
'bg' => '#fff8d51a',
|
||||
'border' => '#fff8d540',
|
||||
],
|
||||
'document' => [
|
||||
'slug' => 'documents',
|
||||
'label' => 'Document',
|
||||
'icon' => 'file-text',
|
||||
'color' => '#7a35c2',
|
||||
'bg' => '#f3eaff1a',
|
||||
'border' => '#f3eaff40',
|
||||
],
|
||||
'lua-script' => [
|
||||
'slug' => 'lua-script',
|
||||
'label' => 'Lua script',
|
||||
'icon' => 'terminal',
|
||||
'color' => '#a04515',
|
||||
'bg' => '#eed6c51a',
|
||||
'border' => '#eed6c540',
|
||||
] ];
|
||||
|
||||
return view('submissions.index', compact('entryTypes'));
|
||||
}
|
||||
|
||||
public function create(Request $request, string $section)
|
||||
{
|
||||
$data = [
|
||||
@@ -39,19 +97,27 @@ class SubmissionController extends Controller
|
||||
'words' => FormHelpers::getEntryFormWords($section),
|
||||
'isEdit' => false,
|
||||
'oldModifications' => old( 'modifications', [] ),
|
||||
'oldSystems' => old( 'systems', [] ),
|
||||
'oldLanguages' => old( 'languages', [] ),
|
||||
'oldCategories' => old( 'categories', [] ),
|
||||
'oldFilesArray' => $this->services->prepareOldFiles( null )
|
||||
];
|
||||
|
||||
if( $data['words'] === [] )
|
||||
abort(500);
|
||||
|
||||
if( section_must_be( 'romhacks', $section ) ){
|
||||
if( section_must_be( ['romhacks', 'lua-scripts'], $section ) ){
|
||||
$data['modifications'] = Modification::orderBy('name')->get();
|
||||
}
|
||||
if( section_must_be( [ 'romhacks', 'translations' ], $section ) ){
|
||||
if( section_must_be( [ 'romhacks', 'translations', 'homebrew', 'lua-scripts' ], $section ) ){
|
||||
$data['statuses'] = Status::orderBy('id')->get();
|
||||
}
|
||||
if( section_must_be( 'utilities' , $section ) ){
|
||||
$data['systems'] = System::orderBy('name')->get();
|
||||
}
|
||||
if( section_must_be( [ 'utilities', 'documents' ], $section ) ) {
|
||||
$data['levels'] = Level::orderBy('id')->get();
|
||||
}
|
||||
|
||||
return view('submissions.create', $data);
|
||||
}
|
||||
@@ -68,19 +134,27 @@ class SubmissionController extends Controller
|
||||
'words' => FormHelpers::getEntryFormWords($section),
|
||||
'isEdit' => true,
|
||||
'oldModifications' => old('modifications', $entry->modifications->pluck('id')->toArray() ?? [] ),
|
||||
'oldSystems' => old( 'systems', $entry->systems->pluck('id')->toArray() ?? [] ),
|
||||
'oldLanguages' => old('languages', $entry->languages->pluck('id')->toArray() ?? [] ),
|
||||
'oldCategories' => old('categories', $entry->categories->pluck('id')->toArray() ?? [] ),
|
||||
'oldFilesArray' => $this->services->prepareOldFiles( $entry )
|
||||
];
|
||||
|
||||
if( $data['words'] === [] )
|
||||
abort(500);
|
||||
|
||||
if( section_must_be( 'romhacks', $section ) ){
|
||||
if( section_must_be( [ 'romhacks', 'lua-scripts' ], $section ) ){
|
||||
$data['modifications'] = Modification::orderBy('name')->get();
|
||||
}
|
||||
if( section_must_be( [ 'romhacks', 'translations' ], $section ) ){
|
||||
if( section_must_be( [ 'romhacks', 'translations', 'homebrew', 'lua-scripts' ], $section ) ){
|
||||
$data['statuses'] = Status::orderBy('id')->get();
|
||||
}
|
||||
if( section_must_be( 'utilities' , $section ) ){
|
||||
$data['systems'] = System::orderBy('name')->get();
|
||||
}
|
||||
if( section_must_be( [ 'utilities', 'documents' ], $section ) ) {
|
||||
$data['levels'] = Level::orderBy('id')->get();
|
||||
}
|
||||
|
||||
return view('submissions.edit', $data);
|
||||
}
|
||||
@@ -90,7 +164,7 @@ class SubmissionController extends Controller
|
||||
if( $entry->type !== $section )
|
||||
abort(404);
|
||||
|
||||
if( $entry->comments_thread_id)
|
||||
if( $entry->comments_thread_id )
|
||||
DeleteXenForoCommentsThread::dispatch( $entry->comments_thread_id );
|
||||
|
||||
$entry->delete();
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryFile;
|
||||
use App\Services\FileServersService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ToolsController extends Controller
|
||||
@@ -9,14 +12,59 @@ class ToolsController extends Controller
|
||||
|
||||
public function patcher()
|
||||
{
|
||||
return view('tools.patcher');
|
||||
}
|
||||
|
||||
public function directPatch( Request $request, int $entryId, EntryFile $file )
|
||||
{
|
||||
if( $file->entry_id != $entryId || $file->state === 'private' ) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$service = app(FileServersService::class);
|
||||
|
||||
|
||||
$patches = [
|
||||
'file' => 'ZELDA.ips',
|
||||
'name' => "Meltin",
|
||||
'description' => 'Blablabla',
|
||||
'outputName' => 'Game...'
|
||||
'file' => $service->getDownloadFileUrl( $file ),
|
||||
'name' => $file->entry->title,
|
||||
'outputName' => $file->filename
|
||||
];
|
||||
|
||||
|
||||
|
||||
return view('tools.patcher', compact('patches'));
|
||||
}
|
||||
|
||||
public function play( Request $request, int $entryId, EntryFile $file )
|
||||
{
|
||||
if( $file->entry_id != $entryId || $file->state === 'private' ) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$service = app(FileServersService::class);
|
||||
|
||||
$emuConfig = [
|
||||
'core' => $file->playOnlineSetting?->core,
|
||||
'threads' => $file->playOnlineSetting?->threads,
|
||||
];
|
||||
|
||||
if( $file->entry->type === 'homebrew' ){
|
||||
$filePath = $service->getDownloadFileUrl( $file );
|
||||
|
||||
return view('tools.play-homebrew', compact('filePath', 'emuConfig'));
|
||||
}
|
||||
$patches = [
|
||||
'file' => $service->getDownloadFileUrl( $file ),
|
||||
'name' => $file->entry->title,
|
||||
'outputName' => $file->filename
|
||||
];
|
||||
|
||||
return view('tools.play', compact('patches', 'emuConfig'));
|
||||
}
|
||||
|
||||
public function hasher( Request $request )
|
||||
{
|
||||
return view('tools.hasher');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
50
app/Http/Middleware/CheckXenForoUserState.php
Normal file
50
app/Http/Middleware/CheckXenForoUserState.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CheckXenForoUserState
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): (Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if( \Auth::guest() )
|
||||
return $next($request);
|
||||
|
||||
if( \Auth::user()->security_lock === 'change' )
|
||||
return $this->deny( $request, "Password must be changed." );
|
||||
else if( \Auth::user()->security_lock === 'reset' )
|
||||
return $this->deny( $request, "Password must be reset.");
|
||||
|
||||
if( \Auth::user()->user_state === 'valid' )
|
||||
return $next($request);
|
||||
|
||||
else if( \Auth::user()->user_state === 'email_confirm' || \Auth::user()->user_state === 'email_confirm_edit' )
|
||||
return $this->deny( $request, "You must verify your email address." );
|
||||
else if( \Auth::user()->user_state === 'email_bounce' )
|
||||
return $this->deny( $request, "Invalid email address." );
|
||||
else if( \Auth::user()->user_state === 'rejected' )
|
||||
return $this->deny( $request, "Your account is currently rejected." );
|
||||
else if( \Auth::user()->user_state === 'disabled' )
|
||||
return $this->deny( $request, "Your account is currently disabled." );
|
||||
|
||||
return $this->deny($request, "Invalid user state.");
|
||||
}
|
||||
|
||||
private function deny(Request $request, string $reason): Response
|
||||
{
|
||||
if($request->expectsJson())
|
||||
return \response()->json(['error' => 'forbidden', 'reason' => $reason], 403);
|
||||
|
||||
return response()->view('pages.user_state', [
|
||||
'reason' => $reason,
|
||||
], 403 );
|
||||
}
|
||||
}
|
||||
@@ -64,34 +64,55 @@ class StoreEntryRequest extends FormRequest
|
||||
$rules['files_state.*'] = 'string|in:public,private,archived';
|
||||
}
|
||||
|
||||
if( section_must_not_be( 'translations', $section ) ){
|
||||
if( section_must_not_be( ['translations','homebrew'], $section ) ){
|
||||
$rules['entry_title'] = "required|string|max:255";
|
||||
} else {
|
||||
$rules['entry_title'] = "nullable|string|max:255";
|
||||
}
|
||||
|
||||
if( section_must_be( 'romhacks', $section ) ){
|
||||
if( section_must_be( ['romhacks', 'lua-scripts'], $section ) ){
|
||||
$rules['modifications'] = 'array|required|min:1';
|
||||
$rules['modifications.*'] = 'integer|exists:modifications,id';
|
||||
} else if( section_must_be( ['utilities','documents'], $section ) ){
|
||||
$rules['categories'] = 'array|required|min:1';
|
||||
$rules['categories.*'] = 'integer|exists:categories,id';
|
||||
}
|
||||
|
||||
if( section_must_be( 'utilities', $section ) ){
|
||||
$rules['systems'] = 'array|required|min:1';
|
||||
$rules['systems.*'] = 'integer|exists:systems,id';
|
||||
}
|
||||
|
||||
$rules['version'] = 'required|string|max:50';
|
||||
$rules['release-date'] = 'required|date';
|
||||
$rules['status'] = 'required|integer|exists:statuses,id';
|
||||
if( section_must_not_be( ['utilities', 'documents'], $section ) ){
|
||||
$rules['status'] = 'required|integer|exists:statuses,id';
|
||||
} else {
|
||||
$rules['level'] = 'required|integer|exists:levels,id';
|
||||
}
|
||||
$rules['description'] = 'required|string';
|
||||
|
||||
if( section_must_be( ['romhacks', 'translations' ], $section ) ){
|
||||
$rules['game_selection_mode'] = 'required|string|in:game,platform,none';
|
||||
$gameSelectionMode = $this->input('game_selection_mode') !== '' ? $this->input('game_selection_mode') : 'game';
|
||||
|
||||
if( $gameSelectionMode === 'none' ){
|
||||
// ...
|
||||
} else if( $gameSelectionMode === 'platform' ){
|
||||
$rules['platform_only_id'] = 'required|integer|exists:platforms,id';
|
||||
} else {
|
||||
$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';
|
||||
if( section_must_be( ['translations', 'romhacks'], $section ) ){
|
||||
$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';
|
||||
@@ -124,11 +145,21 @@ class StoreEntryRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
|
||||
if( $isEdit ){
|
||||
$rules['files_metadata'] = 'array|nullable';
|
||||
$rules['files_metadata.*.online_patcher'] = 'nullable|boolean';
|
||||
$rules['files_metadata.*.secondary_online_patcher'] = 'nullable|boolean|required_with:files_metadata.*.online_patcher';
|
||||
$rules['files_metadata.*.play_online'] = 'nullable|boolean';
|
||||
$rules['files_metadata.*.play_online_core'] = 'nullable|string';
|
||||
$rules['files_metadata.*.play_online_threads'] = 'nullable|boolean';
|
||||
}
|
||||
|
||||
if( $isEdit && $this->user()->can('moderate', $this->route('entry') ) ){
|
||||
$rules['staff_comment'] = 'nullable|string';
|
||||
$rules['owner_user_id'] = [ 'required', 'integer', new XfUserExists ];
|
||||
$rules['comments_thread_id'] = 'nullable|integer';
|
||||
$rules['featured'] = 'nullable|boolean';
|
||||
$rules['refresh_created_at'] = 'nullable|boolean';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
@@ -137,16 +168,31 @@ class StoreEntryRequest extends FormRequest
|
||||
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.',
|
||||
'files_uuid.required' => 'Please upload at least a file.',
|
||||
'files_state.required' => 'A file may be corrupted, please reupload it.',
|
||||
'files_state.*.in' => 'A file state doesn\'t have a standard value.',
|
||||
'entry_title.required' => 'Please enter a title.',
|
||||
'modifications.required' => 'Please select at least one modification.',
|
||||
'categories.required' => 'Please select at least one category.',
|
||||
'system.required' => 'Please select at least one system.',
|
||||
'version.required' => 'Please enter a version number.',
|
||||
'release-date.required' => 'Please enter a valid release date.',
|
||||
'status.required' => 'Please select a status.',
|
||||
'level.required' => 'Please select an experience level.',
|
||||
'description.required' => 'Please enter a description.',
|
||||
'game_selection_mode.required' => 'Please select a game selection mode.',
|
||||
'platform_only_id.required' => 'Please select a platform.',
|
||||
'game_id.required' => 'Please select a game.',
|
||||
'new-game-title.required' => 'Please enter a game title.',
|
||||
'new-game-platform.required' => 'Please select a game platform.',
|
||||
'new-game-genre.required' => 'Please select a game genre.',
|
||||
'hashes.required' => 'Please send at least one hash.',
|
||||
'languages.required' => 'Please select at least one language.',
|
||||
'main-image.required' => 'Please upload a main image.',
|
||||
'gallery.required' => 'Please upload at least one image for the gallery.',
|
||||
'authors.required' => 'Please select at least one author.',
|
||||
'new-authors.required' => 'Please select at least one author.',
|
||||
'submit-state.required' => 'Please select a submit state.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
26
app/Http/Requests/StoreNewsDraftRequest.php
Normal file
26
app/Http/Requests/StoreNewsDraftRequest.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
class StoreNewsDraftRequest extends StoreNewsRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = parent::rules();
|
||||
$rules['submit-state'] = 'required|string|in:draft';
|
||||
|
||||
$rules = array_map(function($rule){
|
||||
if( is_array($rule) ){
|
||||
return array_map( fn($r) => $r === 'required' ? 'nullable' : $r, $rule);
|
||||
}
|
||||
|
||||
return preg_replace(
|
||||
['/\brequired_without\S*/', '/required_with\S*/', '/\brequired\b/'],
|
||||
['nullable', 'nullable', 'nullable'],
|
||||
$rule
|
||||
);
|
||||
}, $rules );
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
78
app/Http/Requests/StoreNewsRequest.php
Normal file
78
app/Http/Requests/StoreNewsRequest.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\News;
|
||||
use App\Rules\PublicFileExists;
|
||||
use App\Rules\XfUserExists;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreNewsRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$news = $this->route('news');
|
||||
if( $news )
|
||||
return $this->user()->can('update', $news);
|
||||
|
||||
return $this->user()->can('create', News::class);
|
||||
}
|
||||
|
||||
public function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'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
|
||||
{
|
||||
$isEdit = (bool) $this->route('news');
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['title'] = 'required|string|max:255';
|
||||
$rules['category'] = 'required|integer|exists:categories,id';
|
||||
|
||||
$rules['description'] = 'required|string';
|
||||
$rules['gallery'] = 'array|required|min:1';
|
||||
$rules['gallery.*'] = [ 'string', new PublicFileExists ];
|
||||
|
||||
$rules['entry_id'] = 'nullable|integer|exists:entries,id';
|
||||
$rules['release_site'] = 'nullable|url|max:500';
|
||||
$rules['youtube_video'] = 'nullable|url|max:500';
|
||||
|
||||
if( $isEdit ){
|
||||
$ss = 'draft,pending,published';
|
||||
if( \Auth::user()->can('moderate', $this->route('news')) && \Auth::user()->can('view-hidden', $this->route('news')) )
|
||||
$ss .= ',hidden';
|
||||
if(\Auth::user()->can('moderate', $this->route('news')) && \Auth::user()->can('view-locked', $this->route('news')) )
|
||||
$ss .= ',locked';
|
||||
$rules['submit-state'] = 'required|in:' . $ss;
|
||||
} else {
|
||||
if( $this->user()->can('skip-queue', '\App\Models\News') ){
|
||||
$rules['submit-state'] = 'required|string|in:draft,pending,published';
|
||||
} else {
|
||||
$rules['submit-state'] = 'required|string|in:draft,pending';
|
||||
}
|
||||
}
|
||||
|
||||
if( $isEdit && $this->user()->can('moderate', $this->route('news') ) ){
|
||||
$rules['staff_comment'] = 'nullable|string';
|
||||
$rules['owner_user_id'] = [ 'required', 'integer', new XfUserExists ];
|
||||
$rules['comments_thread_id'] = 'nullable|integer';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
|
||||
}
|
||||
}
|
||||
41
app/Http/Requests/StoreReviewRequest.php
Normal file
41
app/Http/Requests/StoreReviewRequest.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreReviewRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$review = $this->route('review');
|
||||
if( $review )
|
||||
return $this->user()->can('update', $review);
|
||||
|
||||
return $this->user()->can('create', '\App\Models\EntryReview');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
||||
$isEdit = (bool) $this->route('review');
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['rating'] = 'required|numeric|min:1|max:5';
|
||||
$rules['title'] = 'required|string|max:255';
|
||||
$rules['description'] = 'required|string';
|
||||
|
||||
return $rules;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\News;
|
||||
use App\Services\XenforoApiService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
@@ -18,7 +19,7 @@ class CreateXenForoCommentsThread implements ShouldQueue
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(
|
||||
protected Entry $entry
|
||||
protected Entry|News $entry
|
||||
)
|
||||
{
|
||||
//
|
||||
|
||||
36
app/Jobs/DeleteFile.php
Normal file
36
app/Jobs/DeleteFile.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\EntryFile;
|
||||
use App\Services\FileServersService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class DeleteFile implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 3;
|
||||
public $backoff = 10;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $filePath,
|
||||
public string $fileName,
|
||||
public int $userId
|
||||
)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(FileServersService $service): void
|
||||
{
|
||||
$service->deleteFile($this->filePath, $this->fileName, $this->userId);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class DeleteXenForoCommentsThread implements ShouldQueue
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(
|
||||
protected int $threadId,
|
||||
protected ?int $threadId,
|
||||
)
|
||||
{
|
||||
//
|
||||
@@ -29,6 +29,7 @@ class DeleteXenForoCommentsThread implements ShouldQueue
|
||||
*/
|
||||
public function handle(XenforoApiService $service): void
|
||||
{
|
||||
$service->deleteThreadWithEntry($this->threadId);
|
||||
if( $this->threadId )
|
||||
$service->deleteThreadWithEntry($this->threadId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class RestoreXenForoCommentsThread implements ShouldQueue
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(
|
||||
protected int $threadId,
|
||||
protected ?int $threadId,
|
||||
)
|
||||
{
|
||||
//
|
||||
@@ -29,6 +29,7 @@ class RestoreXenForoCommentsThread implements ShouldQueue
|
||||
*/
|
||||
public function handle(XenforoApiService $service): void
|
||||
{
|
||||
$service->restoreThreadWithEntry($this->threadId);
|
||||
if( $this->threadId )
|
||||
$service->restoreThreadWithEntry($this->threadId);
|
||||
}
|
||||
}
|
||||
|
||||
64
app/Livewire/ActivityLogs.php
Normal file
64
app/Livewire/ActivityLogs.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class ActivityLogs extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
#[Url(except: '')]
|
||||
public string $search = '';
|
||||
|
||||
#[Url(except: '')]
|
||||
public string $logName = '';
|
||||
|
||||
#[Url(except: '')]
|
||||
public string $event = '';
|
||||
|
||||
#[Url(except: '')]
|
||||
public string $dateFrom = '';
|
||||
|
||||
#[Url(except: '')]
|
||||
public string $dateTo = '';
|
||||
|
||||
#[Url(except: '')]
|
||||
public string $causerId = '';
|
||||
|
||||
public function updating(): void { $this->resetPage(); }
|
||||
|
||||
public function clearFilters(): void
|
||||
{
|
||||
$this->reset('search', 'logName', 'event', 'dateFrom', 'dateTo', 'causerId');
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$logs = Activity::query()
|
||||
->with(['causer'])
|
||||
->when($this->search, fn($q) => $q
|
||||
->where('description', 'like', "%{$this->search}%")
|
||||
->orWhere('subject_type', 'like', "%{$this->search}%")
|
||||
->orWhere('log_name', 'like', "%{$this->search}%")
|
||||
)
|
||||
->when($this->logName, fn($q) => $q->where('log_name', $this->logName))
|
||||
->when($this->event, fn($q) => $q->where('event', $this->event))
|
||||
->when($this->causerId, fn($q) => $q->where('causer_id', $this->causerId))
|
||||
->when($this->dateFrom, fn($q) => $q->whereDate('created_at', '>=', $this->dateFrom))
|
||||
->when($this->dateTo, fn($q) => $q->whereDate('created_at', '<=', $this->dateTo))
|
||||
->latest()
|
||||
->paginate(50);
|
||||
|
||||
$logNames = Activity::distinct()->orderBy('log_name')->pluck('log_name')->filter()->values();
|
||||
$events = Activity::distinct()->orderBy('event')->pluck('event')->filter()->values();
|
||||
|
||||
$hasFilters = $this->logName || $this->event || $this->dateFrom || $this->dateTo || $this->causerId;
|
||||
|
||||
return view('livewire.activity-logs', compact('logs', 'logNames', 'events', 'hasFilters'));
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,16 @@
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Author;
|
||||
use App\Models\Category;
|
||||
use App\Models\Entry;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Language;
|
||||
use App\Models\Level;
|
||||
use App\Models\Modification;
|
||||
use App\Models\Platform;
|
||||
use App\Models\Status;
|
||||
use App\Models\System;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
@@ -114,6 +117,44 @@ class Database extends Component
|
||||
#[Url(except:'or')]
|
||||
public string $modificationsMode = 'or';
|
||||
|
||||
/**
|
||||
* Categories IDs filter.
|
||||
* @var array
|
||||
*/
|
||||
#[Url(except:[])]
|
||||
public array $categories = [];
|
||||
|
||||
/**
|
||||
* Categories mode and/or
|
||||
* @var string
|
||||
*/
|
||||
#[Url(except:'or')]
|
||||
public string $categoriesMode = 'or';
|
||||
|
||||
/**
|
||||
* Systems IDs filter.
|
||||
* @var array
|
||||
*/
|
||||
#[Url(except:[])]
|
||||
public array $systems = [];
|
||||
|
||||
/**
|
||||
* Systems mode and/or
|
||||
* @var string
|
||||
*/
|
||||
#[Url(except:'or')]
|
||||
public string $systemsMode = 'or';
|
||||
|
||||
/**
|
||||
* Levels IDs filter.
|
||||
* @var array
|
||||
*/
|
||||
#[Url(except:[])]
|
||||
public array $levels = [];
|
||||
|
||||
#[Url(except:null)]
|
||||
public ?int $userId = null;
|
||||
|
||||
/**
|
||||
* Sort by field.
|
||||
* @var string
|
||||
@@ -147,7 +188,6 @@ class Database extends Component
|
||||
'utilities' => 'Utilities',
|
||||
'documents' => 'Documents',
|
||||
'lua-scripts' => 'Lua Scripts',
|
||||
'tutorials' => 'Tutorials',
|
||||
];
|
||||
|
||||
public const int PAGINATION = 30;
|
||||
@@ -164,12 +204,19 @@ class Database extends Component
|
||||
public function updatedLanguagesMode(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedModifications(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedModificationsMode(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedCategories(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedCategoriesMode(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedSystems(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedSystemsMode(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedLevels(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedUserId(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
|
||||
public function clearFilters(): void
|
||||
{
|
||||
$this->reset([
|
||||
'search', 'types', 'platforms', 'genres', 'statuses', 'authors', 'authorsMode', 'languages', 'languagesMode', 'modifications', 'modificationsMode'
|
||||
'search', 'types', 'platforms', 'genres', 'statuses', 'authors', 'authorsMode', 'languages', 'languagesMode', 'modifications', 'modificationsMode', 'categories', 'categoriesMode', 'systems', 'systemsMode', 'levels', 'userId'
|
||||
]);
|
||||
$this->dispatch('filters-updated');
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
@@ -188,7 +235,7 @@ class Database extends Component
|
||||
private function buildQuery()
|
||||
{
|
||||
$query = Entry::query()->published()->with([
|
||||
'game.platform', 'game.genre', 'status', 'authors', 'languages'
|
||||
'game.platform', 'game.genre', 'status', 'authors', 'languages', 'level', 'systems', 'categories', 'modifications'
|
||||
]);
|
||||
|
||||
if( $this->search ) {
|
||||
@@ -253,20 +300,88 @@ class Database extends Component
|
||||
}
|
||||
}
|
||||
|
||||
if( $this->levels ) {
|
||||
$query->whereIn('level_id', $this->levels);
|
||||
}
|
||||
|
||||
if( $this->categories ) {
|
||||
if( $this->categoriesMode === 'and' ) {
|
||||
foreach ( $this->categories as $categoryId ) {
|
||||
$query->whereHas('categories', fn($q) => $q->where('categories.id', $categoryId));
|
||||
}
|
||||
} else {
|
||||
$query->whereHas('categories', fn($q) => $q->whereIn('categories.id', $this->categories));
|
||||
}
|
||||
}
|
||||
|
||||
if( $this->systems ) {
|
||||
if( $this->systemsMode === 'and' ) {
|
||||
foreach ( $this->systems as $systemId ) {
|
||||
$query->whereHas('systems', fn($q) => $q->where('systems.id', $systemId));
|
||||
}
|
||||
} else {
|
||||
$query->whereHas('systems', fn($q) => $q->whereIn('systems.id', $this->systems));
|
||||
}
|
||||
}
|
||||
|
||||
if( $this->userId ){
|
||||
$query->where('user_id', $this->userId);
|
||||
}
|
||||
|
||||
return $query->orderBy($this->sortBy, $this->sortDir);
|
||||
}
|
||||
|
||||
private function searchFilter( string $modelClass, string $search )
|
||||
{
|
||||
|
||||
$this->dispatch('filters-updated');
|
||||
|
||||
if( mb_strlen( $search ) < 3 ) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $modelClass::where('name', 'like', "%{$search}%")
|
||||
->orderBy('name')
|
||||
->limit(50)
|
||||
->get();
|
||||
}
|
||||
|
||||
private function searchGameFilter()
|
||||
{
|
||||
|
||||
$search = $this->gameSearch;
|
||||
|
||||
$this->dispatch('filters-updated');
|
||||
|
||||
if( mb_strlen( $search ) < 3 ) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$collect = Game::where('name', 'like', "%{$search}%")
|
||||
->orderBy('name')
|
||||
->limit(50)
|
||||
->get();
|
||||
|
||||
return $collect->map(function($item){
|
||||
$item->name = $item->name . ' (' . ($item->platform?->short_name ?? $item->platform->name) . ')';
|
||||
return $item;
|
||||
} );
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.database', [
|
||||
'entries' => $this->buildQuery()->paginate(self::PAGINATION),
|
||||
'allGames' => Game::orderBy('name')->get(),
|
||||
'allGames' => $this->searchGameFilter(),
|
||||
'allPlatforms' => Platform::orderBy('name')->get(),
|
||||
'allGenres' => Genre::orderBy('name')->get(),
|
||||
'allStatuses' => Status::orderBy('name')->get(),
|
||||
'allAuthors' => Author::orderBy('name')->get(),
|
||||
'allAuthors' => $this->searchFilter(Author::class, $this->authorSearch),
|
||||
'allLanguages' => Language::orderBy('name')->get(),
|
||||
'allModifications' => Modification::orderBy('name')->get(),
|
||||
'allCategories' => Category::orderBy('name')->get(),
|
||||
'allLevels' => Level::orderBy('name')->get(),
|
||||
'allSystems' => System::orderBy('name')->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
72
app/Livewire/EntrySelector.php
Normal file
72
app/Livewire/EntrySelector.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Entry;
|
||||
use Livewire\Component;
|
||||
|
||||
class EntrySelector extends Component
|
||||
{
|
||||
public string $search = '';
|
||||
public ?int $selectedEntryId = null;
|
||||
|
||||
public ?string $entryName = null;
|
||||
|
||||
public bool $dropdown = false;
|
||||
|
||||
public function mount( ?int $oldEntryId = null ): void
|
||||
{
|
||||
if( $oldEntryId ) {
|
||||
$entry = Entry::find($oldEntryId);
|
||||
if( $entry ) {
|
||||
$this->selectedEntryId = $oldEntryId;
|
||||
$this->entryName = $entry->complete_title ?? $entry->title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
if( $this->selectedEntryId ) {
|
||||
$this->selectedEntryId = null;
|
||||
$this->entryName = null;
|
||||
}
|
||||
$this->dropdown = strlen($this->search) > 2;
|
||||
}
|
||||
|
||||
public function selectEntry( int $id ){
|
||||
$entry = Entry::find($id);
|
||||
if( $entry ) {
|
||||
$this->selectedEntryId = $id;
|
||||
$this->entryName = $entry->complete_title ?? $entry->title;
|
||||
$this->search = $entry->complete_title ?? $entry->title;
|
||||
$this->dropdown = false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function clearEntry(): void
|
||||
{
|
||||
$this->selectedEntryId = null;
|
||||
$this->entryName = null;
|
||||
$this->search = '';
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$entries = collect();
|
||||
|
||||
if( $this->dropdown && strlen($this->search) > 2 ) {
|
||||
$entries = Entry::where('complete_title', 'like', '%' . $this->search . '%')
|
||||
->orWhere('title', 'like', '%' . $this->search . '%')
|
||||
->orderBy('complete_title', 'asc')
|
||||
->orderBy('title', 'asc')
|
||||
->limit(20)
|
||||
->get();
|
||||
}
|
||||
|
||||
$data = [ 'entries' => $entries, 'required_chars' => 3 ];
|
||||
|
||||
return view('livewire.entry-selector', $data);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,17 @@ class GameSelector extends Component
|
||||
|
||||
public const int REQUIRED_CHARS = 3;
|
||||
|
||||
/**
|
||||
* Which section we can change selection mode.
|
||||
*/
|
||||
public const array CHANGE_SECTION_MODE = [ 'utilities', 'documents' ];
|
||||
|
||||
/**
|
||||
* Selection mode between game|platform|none.
|
||||
* @var string
|
||||
*/
|
||||
public string $selectionMode = 'game';
|
||||
|
||||
/**
|
||||
* If we are in new game mode.
|
||||
* @var bool
|
||||
@@ -70,9 +81,25 @@ class GameSelector extends Component
|
||||
*/
|
||||
public bool $dropdown = false;
|
||||
|
||||
public function mount( ?int $gameId = null, ?string $newGameTitle = null, ?int $newGamePlatform = null, ?int $newGameGenre = null ): void
|
||||
/**
|
||||
* In platform mode.
|
||||
* @var int|null
|
||||
*/
|
||||
public ?int $platformModeId = null;
|
||||
|
||||
/**
|
||||
* In platform mode.
|
||||
* @var string|null
|
||||
*/
|
||||
public ?string $platformModeName = null;
|
||||
|
||||
public ?string $section = null;
|
||||
|
||||
public function mount( ?int $gameId = null, ?string $newGameTitle = null, ?int $newGamePlatform = null, ?int $newGameGenre = null, ?string $section, ?int $platformOnlyId ): void
|
||||
{
|
||||
|
||||
$this->section = $section;
|
||||
|
||||
// If we selected an existent game.
|
||||
if( $gameId ){
|
||||
$game = Game::with(['platform','genre'])->find($gameId);
|
||||
@@ -93,6 +120,36 @@ class GameSelector extends Component
|
||||
$this->gamePlatformId = is_numeric($newGamePlatform) ? (int) $newGamePlatform : null;
|
||||
$this->gameGenreId = is_numeric($newGameGenre) ? (int) $newGameGenre : null;
|
||||
}
|
||||
|
||||
if( in_array( $section, self::CHANGE_SECTION_MODE ) ) {
|
||||
if ($platformOnlyId) {
|
||||
$this->selectionMode = 'platform';
|
||||
$this->platformModeId = $platformOnlyId;
|
||||
$platform = Platform::find($platformOnlyId);
|
||||
if ($platform) {
|
||||
$this->platformModeName = $platform->name;
|
||||
} else {
|
||||
$this->platformModeId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setSelectionMode(string $mode): void
|
||||
{
|
||||
if( !in_array( $this->section, self::CHANGE_SECTION_MODE ) )
|
||||
return;
|
||||
|
||||
$this->selectionMode = $mode;
|
||||
|
||||
if( $mode !== 'game' ){
|
||||
$this->clearGame();
|
||||
$this->newGame = false;
|
||||
}
|
||||
if( $mode !== 'platform' ){
|
||||
$this->platformModeId = null;
|
||||
$this->platformModeName = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,6 +191,15 @@ class GameSelector extends Component
|
||||
|
||||
}
|
||||
|
||||
public function selectPlatformOnly( int $id ): void
|
||||
{
|
||||
$platform = Platform::find($id);
|
||||
if( $platform ){
|
||||
$this->platformModeId = $platform->id;
|
||||
$this->platformModeName = $platform->name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear existent game selection.
|
||||
* @return void
|
||||
@@ -179,6 +245,10 @@ class GameSelector extends Component
|
||||
}
|
||||
$data['hasOldNewGame'] = old('new-game-title') || old('new-game-platform') || old('new-game-genre');
|
||||
|
||||
$data['canChangeSelection'] = in_array( $this->section, self::CHANGE_SECTION_MODE );
|
||||
if( in_array( $this->section, self::CHANGE_SECTION_MODE ) )
|
||||
$data['platforms'] = Platform::orderBy('name')->get();
|
||||
|
||||
return view('livewire.game-selector', $data );
|
||||
}
|
||||
}
|
||||
|
||||
81
app/Livewire/HashesChecker.php
Normal file
81
app/Livewire/HashesChecker.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Helpers\HashesHelpers;
|
||||
use App\Models\EntryHash;
|
||||
use App\Models\Game;
|
||||
use App\Models\Genre;
|
||||
use App\Models\Platform;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* @phpstan-import-type HashObject from \App\Types\SubmissionTypes
|
||||
*/
|
||||
class HashesChecker extends Component
|
||||
{
|
||||
|
||||
/**
|
||||
* Current hash.
|
||||
* @var null|HashObject $hash
|
||||
*/
|
||||
public ?array $hash = null;
|
||||
|
||||
/**
|
||||
* Add a hash.
|
||||
*
|
||||
* @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
|
||||
{
|
||||
|
||||
if( $verified !== null && $verified !== "No" )
|
||||
$this->hash = [
|
||||
'filename' => $filename,
|
||||
'hash_crc32' => $crc32,
|
||||
'hash_sha1' => $sha1,
|
||||
'verified' => $verified
|
||||
];
|
||||
else if( ( $hash = HashesHelpers::findHashes( $sha1 ) ) !== null )
|
||||
$this->hash = [
|
||||
'filename' => $hash->filename,
|
||||
'hash_crc32' => $hash->crc32,
|
||||
'hash_sha1' => $hash->sha1,
|
||||
'verified' => HashesHelpers::getReferenceName( $hash->dat_reference_id )
|
||||
];
|
||||
else
|
||||
$this->hash = [
|
||||
'filename' => $filename,
|
||||
'hash_crc32' => $crc32,
|
||||
'hash_sha1' => $sha1,
|
||||
'verified' => "No"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the hash.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeHash(): void
|
||||
{
|
||||
$this->hash = null;
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$entries = collect();
|
||||
if( $this->hash !== null ){
|
||||
$entries = EntryHash::where('hash_sha1', $this->hash['hash_sha1'] )->get()->pluck('entry')->filter();
|
||||
}
|
||||
return view('livewire.hashes-checker', compact('entries'));
|
||||
}
|
||||
}
|
||||
104
app/Livewire/NewsDatabase.php
Normal file
104
app/Livewire/NewsDatabase.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\News;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class NewsDatabase extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
/**
|
||||
* News title search
|
||||
* @var string
|
||||
*/
|
||||
#[Url(as: 's', except: '')]
|
||||
public string $search = '';
|
||||
|
||||
/**
|
||||
* Categories IDs filter.
|
||||
* @var array
|
||||
*/
|
||||
#[Url(except:[])]
|
||||
public array $categories = [];
|
||||
|
||||
/**
|
||||
* Sort by field.
|
||||
* @var string
|
||||
*/
|
||||
#[Url(as: 'sort',except: 'created_at')]
|
||||
public string $sortBy = 'created_at';
|
||||
|
||||
/**
|
||||
* asc/desc
|
||||
* @var string
|
||||
*/
|
||||
#[Url(as: 'dir',except: 'desc')]
|
||||
public string $sortDir = 'desc';
|
||||
|
||||
/**
|
||||
* Translation of sort options key.
|
||||
*/
|
||||
public const array SORT_OPTIONS = [
|
||||
'created_at' => 'Date added',
|
||||
'title' => 'Title'
|
||||
];
|
||||
|
||||
public const int PAGINATION = 30;
|
||||
|
||||
public function updatedSearch(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedCategories(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedCategoriesMode(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
|
||||
public function clearFilters(): void
|
||||
{
|
||||
$this->reset([
|
||||
'search', 'categories',
|
||||
]);
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function setSort(string $field): void
|
||||
{
|
||||
if( $this->sortBy === $field ) {
|
||||
$this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortBy = $field;
|
||||
$this->sortDir = 'asc';
|
||||
}
|
||||
$this->resetPage();
|
||||
$this->dispatch('filters-updated');
|
||||
}
|
||||
|
||||
private function buildQuery()
|
||||
{
|
||||
$query = News::query()->published();
|
||||
|
||||
if( $this->search ){
|
||||
$query->where(function($q){
|
||||
$q->where('title', 'like', '%'.$this->search.'%');
|
||||
});
|
||||
}
|
||||
|
||||
if( $this->categories ) {
|
||||
$query->whereIn('category_id', $this->categories);
|
||||
}
|
||||
|
||||
return $query->orderBy($this->sortBy, $this->sortDir);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.news-database', [
|
||||
'news' => $this->buildQuery()->paginate(self::PAGINATION),
|
||||
'allCategories' => Category::where(function ($query) {
|
||||
$query->whereJsonContains('restricted_to', "news")
|
||||
->orWhereNull('restricted_to');
|
||||
})->orderBy('name')->get()
|
||||
]);
|
||||
}
|
||||
}
|
||||
92
app/Livewire/Reviews.php
Normal file
92
app/Livewire/Reviews.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\EntryReview;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class Reviews extends Component
|
||||
{
|
||||
|
||||
use WithPagination;
|
||||
|
||||
#[Url(except:null)]
|
||||
public ?int $entryId = null;
|
||||
|
||||
#[Url(except:null)]
|
||||
public ?int $rating = null;
|
||||
|
||||
/**
|
||||
* Sort by field.
|
||||
* @var string
|
||||
*/
|
||||
#[Url(as: 'sort',except: 'created_at')]
|
||||
public string $sortBy = 'created_at';
|
||||
|
||||
/**
|
||||
* asc/desc
|
||||
* @var string
|
||||
*/
|
||||
#[Url(as: 'dir',except: 'desc')]
|
||||
public string $sortDir = 'desc';
|
||||
|
||||
/**
|
||||
* Translation of sort options key.
|
||||
*/
|
||||
public const array SORT_OPTIONS = [
|
||||
'created_at' => 'Date added',
|
||||
'rating' => 'Rating',
|
||||
'title' => 'Title'
|
||||
];
|
||||
|
||||
public const int PAGINATION = 30;
|
||||
|
||||
public function updatedEntryId(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
public function updatedRating(): void { $this->resetPage(); $this->dispatch('filters-updated'); }
|
||||
|
||||
public function clearFilters(): void
|
||||
{
|
||||
$this->reset([
|
||||
'entryId', 'rating'
|
||||
]);
|
||||
$this->dispatch('filters-updated');
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function setSort(string $field): void
|
||||
{
|
||||
if( $this->sortBy === $field ) {
|
||||
$this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortBy = $field;
|
||||
$this->sortDir = 'asc';
|
||||
}
|
||||
$this->resetPage();
|
||||
$this->dispatch('filters-updated');
|
||||
}
|
||||
|
||||
private function buildQuery()
|
||||
{
|
||||
$query = EntryReview::query()->with([
|
||||
'entry'
|
||||
]);
|
||||
|
||||
if( $this->entryId ) {
|
||||
$query->where('entry_id', $this->entryId);
|
||||
}
|
||||
if( $this->rating ){
|
||||
$query->where('rating', $this->rating);
|
||||
}
|
||||
|
||||
return $query->orderBy($this->sortBy, $this->sortDir);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.reviews', [
|
||||
'reviews' => $this->buildQuery()->paginate(self::PAGINATION),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,28 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string|null $website
|
||||
* @property int|null $user_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Entry> $entries
|
||||
* @property-read int|null $entries_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author whereUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Author whereWebsite($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Author extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
|
||||
33
app/Models/Category.php
Normal file
33
app/Models/Category.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property array<array-key, mixed>|null $restricted_to
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category whereRestrictedTo($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Category whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Category extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'slug', 'restricted_to'];
|
||||
|
||||
protected $casts = [
|
||||
'restricted_to' => 'array',
|
||||
];
|
||||
|
||||
}
|
||||
@@ -2,17 +2,114 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\EntryHelpers;
|
||||
use App\Traits\HasGallery;
|
||||
use App\Traits\HasXenforoUserId;
|
||||
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;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
use Monolog\Level;
|
||||
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||
use Spatie\Activitylog\Support\LogOptions;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $type
|
||||
* @property string|null $title
|
||||
* @property string|null $slug
|
||||
* @property string|null $description
|
||||
* @property string|null $main_image
|
||||
* @property string $state
|
||||
* @property string|null $staff_comment
|
||||
* @property \Illuminate\Support\Carbon|null $rejected_at
|
||||
* @property bool $featured
|
||||
* @property \Illuminate\Support\Carbon|null $featured_at
|
||||
* @property int|null $game_id
|
||||
* @property int|null $platform_id
|
||||
* @property int|null $status_id
|
||||
* @property string|null $version
|
||||
* @property \Illuminate\Support\Carbon|null $release_date
|
||||
* @property string|null $staff_credits
|
||||
* @property string|null $relevant_link
|
||||
* @property string|null $youtube_link
|
||||
* @property int $user_id
|
||||
* @property int|null $comments_thread_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property string|null $complete_title
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property int|null $level_id
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Author> $authors
|
||||
* @property-read int|null $authors_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Category> $categories
|
||||
* @property-read int|null $categories_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\EntryFile> $files
|
||||
* @property-read int|null $files_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Gallery> $gallery
|
||||
* @property-read int|null $gallery_count
|
||||
* @property-read \App\Models\Game|null $game
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\EntryHash> $hashes
|
||||
* @property-read int|null $hashes_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Language> $languages
|
||||
* @property-read int|null $languages_count
|
||||
* @property-read \App\Models\Level|null $level
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Modification> $modifications
|
||||
* @property-read int|null $modifications_count
|
||||
* @property-read \App\Models\Platform|null $platform
|
||||
* @property-read \App\Models\Status|null $status
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\System> $systems
|
||||
* @property-read int|null $systems_count
|
||||
* @method static Builder<static>|Entry inQueue(int $daysRejected = 7)
|
||||
* @method static Builder<static>|Entry newModelQuery()
|
||||
* @method static Builder<static>|Entry newQuery()
|
||||
* @method static Builder<static>|Entry onlyTrashed()
|
||||
* @method static Builder<static>|Entry published()
|
||||
* @method static Builder<static>|Entry query()
|
||||
* @method static Builder<static>|Entry whereCommentsThreadId($value)
|
||||
* @method static Builder<static>|Entry whereCompleteTitle($value)
|
||||
* @method static Builder<static>|Entry whereCreatedAt($value)
|
||||
* @method static Builder<static>|Entry whereDeletedAt($value)
|
||||
* @method static Builder<static>|Entry whereDescription($value)
|
||||
* @method static Builder<static>|Entry whereFeatured($value)
|
||||
* @method static Builder<static>|Entry whereFeaturedAt($value)
|
||||
* @method static Builder<static>|Entry whereGameId($value)
|
||||
* @method static Builder<static>|Entry whereId($value)
|
||||
* @method static Builder<static>|Entry whereLevelId($value)
|
||||
* @method static Builder<static>|Entry whereMainImage($value)
|
||||
* @method static Builder<static>|Entry wherePlatformId($value)
|
||||
* @method static Builder<static>|Entry whereRejectedAt($value)
|
||||
* @method static Builder<static>|Entry whereReleaseDate($value)
|
||||
* @method static Builder<static>|Entry whereRelevantLink($value)
|
||||
* @method static Builder<static>|Entry whereSlug($value)
|
||||
* @method static Builder<static>|Entry whereStaffComment($value)
|
||||
* @method static Builder<static>|Entry whereStaffCredits($value)
|
||||
* @method static Builder<static>|Entry whereState($value)
|
||||
* @method static Builder<static>|Entry whereStatusId($value)
|
||||
* @method static Builder<static>|Entry whereTitle($value)
|
||||
* @method static Builder<static>|Entry whereType($value)
|
||||
* @method static Builder<static>|Entry whereUpdatedAt($value)
|
||||
* @method static Builder<static>|Entry whereUserId($value)
|
||||
* @method static Builder<static>|Entry whereVersion($value)
|
||||
* @method static Builder<static>|Entry whereYoutubeLink($value)
|
||||
* @method static Builder<static>|Entry withTrashed(bool $withTrashed = true)
|
||||
* @method static Builder<static>|Entry withoutTrashed()
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Spatie\Activitylog\Models\Activity> $activitiesAsSubject
|
||||
* @property-read int|null $activities_as_subject_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\EntryReview> $reviews
|
||||
* @property-read int|null $reviews_count
|
||||
* @property-read float $average_rating
|
||||
* @property-read int $reviews_count_cached
|
||||
* @property-read string $description_html
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Entry extends Model
|
||||
{
|
||||
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, HasGallery, LogsActivity, HasXenforoUserId;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
@@ -25,6 +122,7 @@ class Entry extends Model
|
||||
'main_image',
|
||||
'state',
|
||||
'featured',
|
||||
'featured_at',
|
||||
'game_id',
|
||||
'platform_id',
|
||||
'status_id',
|
||||
@@ -38,6 +136,8 @@ class Entry extends Model
|
||||
'comments_thread_id',
|
||||
'staff_comment',
|
||||
'rejected_at',
|
||||
'level_id',
|
||||
'created_at'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -47,8 +147,18 @@ class Entry extends Model
|
||||
'featured' => 'boolean',
|
||||
'release_date' => 'date',
|
||||
'rejected_at' => 'datetime',
|
||||
'featured_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected static function booted(): void {
|
||||
static::saving( function( $entry ) {
|
||||
if( $entry->isDirty('version') ) {
|
||||
$entry->created_at = now();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function scopePublished( Builder $query ): Builder {
|
||||
return $query->where( 'state', 'published' );
|
||||
}
|
||||
@@ -81,6 +191,10 @@ class Entry extends Model
|
||||
return $this->belongsTo(Status::class );
|
||||
}
|
||||
|
||||
public function level(): BelongsTo {
|
||||
return $this->belongsTo(\App\Models\Level::class);
|
||||
}
|
||||
|
||||
public function authors(): BelongsToMany {
|
||||
return $this->belongsToMany(Author::class, 'entry_authors');
|
||||
}
|
||||
@@ -93,19 +207,47 @@ class Entry extends Model
|
||||
return $this->belongsToMany( Modification::class, 'entry_modifications');
|
||||
}
|
||||
|
||||
public function files(): HasMany {
|
||||
return $this->hasMany(EntryFile::class)->orderBy('filename');
|
||||
public function categories(): BelongsToMany {
|
||||
return $this->belongsToMany(Category::class, 'entry_categories');
|
||||
}
|
||||
|
||||
public function gallery(): HasMany {
|
||||
return $this->hasMany(EntryGallery::class)->orderBy('id');
|
||||
public function systems(): BelongsToMany {
|
||||
return $this->belongsToMany(System::class, 'entry_systems');
|
||||
}
|
||||
|
||||
public function files(): HasMany {
|
||||
return $this->hasMany(EntryFile::class)->orderBy('filename');
|
||||
}
|
||||
|
||||
public function hashes(): HasMany {
|
||||
return $this->hasMany(EntryHash::class);
|
||||
}
|
||||
|
||||
public function parseStaffCredits(): array {
|
||||
public function reviews(): HasMany {
|
||||
return $this->hasMany(EntryReview::class);
|
||||
}
|
||||
|
||||
public function getAverageRatingAttribute(): float
|
||||
{
|
||||
return round( $this->reviews->avg('rating') ?? 0, 1 );
|
||||
}
|
||||
|
||||
public function getReviewsCountCachedAttribute(): int
|
||||
{
|
||||
return $this->reviews->count();
|
||||
}
|
||||
|
||||
public function getDescriptionHtmlAttribute(): string
|
||||
{
|
||||
$converter = new GithubFlavoredMarkdownConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
return $converter->convert($this->description)->getContent();
|
||||
}
|
||||
|
||||
public function parseStaffCredits(): ?array {
|
||||
return json_decode( $this->staff_credits ?? "", true );
|
||||
}
|
||||
|
||||
@@ -113,10 +255,17 @@ class Entry extends Model
|
||||
if( !$this->youtube_link )
|
||||
return null;
|
||||
|
||||
$pattern = '%(?:https?://)?(?:www\.|m\.)?(?:youtu\.be/|youtube(?:-nocookie)?\.com/(?:watch\?.*v=|embed/|v/|shorts/|live/))([\w-]{11})%i';
|
||||
return EntryHelpers::getYoutubeVideoId( $this->youtube_link );
|
||||
}
|
||||
|
||||
preg_match($pattern, $this->youtube_link, $matches);
|
||||
return $matches[1] ?? null;
|
||||
public function getActivitylogOptions(): LogOptions
|
||||
{
|
||||
return LogOptions::defaults()
|
||||
->useLogName('entry')
|
||||
->logAll()
|
||||
->logOnlyDirty()
|
||||
->dontLogEmptyChanges()
|
||||
->setDescriptionForEvent(fn(string $eventName) => "Entry {$eventName}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,44 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $entry_id
|
||||
* @property string $filename
|
||||
* @property string $filepath
|
||||
* @property string $favorite_server
|
||||
* @property int $favorite_at
|
||||
* @property int|null $filesize
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property string $file_uuid
|
||||
* @property string $state
|
||||
* @property int $online_patcher
|
||||
* @property int $secondary_online_patcher
|
||||
* @property-read \App\Models\Entry|null $entry
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereEntryId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereFavoriteAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereFavoriteServer($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereFileUuid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereFilename($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereFilepath($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereFilesize($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereOnlinePatcher($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereSecondaryOnlinePatcher($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereState($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereUpdatedAt($value)
|
||||
* @property-read \App\Models\PlayOnlineSetting|null $playOnlineSetting
|
||||
* @property int $download_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryFile whereDownloadCount($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class EntryFile extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
@@ -20,4 +57,32 @@ class EntryFile extends Model
|
||||
return $this->belongsTo(Entry::class);
|
||||
}
|
||||
|
||||
public function playOnlineSetting(): HasOne
|
||||
{
|
||||
return $this->hasOne(PlayOnlineSetting::class,'file_id');
|
||||
}
|
||||
|
||||
public function prettyFileSize(): string
|
||||
{
|
||||
$bytes = $this->filesize;
|
||||
|
||||
if ($bytes >= 1073741824) {
|
||||
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
|
||||
} elseif ($bytes >= 1048576) {
|
||||
$bytes = number_format($bytes / 1048576, 2) . ' MB';
|
||||
} elseif ($bytes >= 1024) {
|
||||
$bytes = number_format($bytes / 1024, 2) . ' KB';
|
||||
} else {
|
||||
$bytes = ($bytes == 0) ? '0 bytes' : $bytes . ' bytes';
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
public function increaseDownloadCount(): void
|
||||
{
|
||||
$this->download_count++;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,10 +2,26 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EntryGallery extends Model
|
||||
{
|
||||
protected $fillable = ['entry_id','image'];
|
||||
|
||||
}
|
||||
/**
|
||||
* @deprecated Use Gallery instead.
|
||||
* @property int $id
|
||||
* @property string $galleryable_type
|
||||
* @property int $galleryable_id
|
||||
* @property string $image
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $galleryable
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery whereGalleryableId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery whereGalleryableType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery whereImage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery whereUpdatedAt($value)
|
||||
* @property int $order
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryGallery whereOrder($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class EntryGallery extends Gallery {}
|
||||
|
||||
@@ -5,6 +5,29 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $entry_id
|
||||
* @property string $filename
|
||||
* @property string $hash_crc32
|
||||
* @property string $hash_sha1
|
||||
* @property string $verified
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Entry|null $entry
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereEntryId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereFilename($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereHashCrc32($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereHashSha1($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryHash whereVerified($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class EntryHash extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
|
||||
63
app/Models/EntryReview.php
Normal file
63
app/Models/EntryReview.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasXenforoUserId;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Str;
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $entry_id
|
||||
* @property string $title
|
||||
* @property int $rating
|
||||
* @property string $description
|
||||
* @property string|null $deleted_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Entry|null $entry
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereEntryId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereRating($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereTitle($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereUpdatedAt($value)
|
||||
* @property int $user_id
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview whereUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview withTrashed(bool $withTrashed = true)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|EntryReview withoutTrashed()
|
||||
* @property-read string $description_html
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class EntryReview extends Model
|
||||
{
|
||||
|
||||
use HasXenforoUserId, SoftDeletes;
|
||||
|
||||
protected $fillable = [ 'entry_id', 'title', 'rating', 'description', 'user_id' ];
|
||||
|
||||
public function entry(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Entry::class);
|
||||
}
|
||||
|
||||
public function getDescriptionHtmlAttribute(): string
|
||||
{
|
||||
$converter = new GithubFlavoredMarkdownConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
return $converter->convert($this->description)->getContent();
|
||||
}
|
||||
|
||||
}
|
||||
41
app/Models/Gallery.php
Normal file
41
app/Models/Gallery.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $entry_id
|
||||
* @property string $image
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereEntryId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereImage($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereUpdatedAt($value)
|
||||
* @property string $galleryable_type
|
||||
* @property int $galleryable_id
|
||||
* @property-read Model|\Eloquent $galleryable
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereGalleryableId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereGalleryableType($value)
|
||||
* @property int $order
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Gallery whereOrder($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Gallery extends Model
|
||||
{
|
||||
protected $table = 'galleries';
|
||||
protected $fillable = ['image', 'galleryable_id', 'galleryable_type'];
|
||||
|
||||
public function galleryable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,30 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property int $platform_id
|
||||
* @property int $genre_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Entry> $entries
|
||||
* @property-read int|null $entries_count
|
||||
* @property-read \App\Models\Genre $genre
|
||||
* @property-read \App\Models\Platform $platform
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game whereGenreId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game wherePlatformId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Game whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Game extends Model
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,24 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Game> $games
|
||||
* @property-read int|null $games_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Genre whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Genre extends Model
|
||||
{
|
||||
protected $fillable = [ 'name', 'slug' ];
|
||||
|
||||
@@ -6,6 +6,24 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Entry> $entries
|
||||
* @property-read int|null $entries_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Language whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Language extends Model
|
||||
{
|
||||
protected $fillable = [ 'name', 'slug' ];
|
||||
|
||||
26
app/Models/Level.php
Normal file
26
app/Models/Level.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Level whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Level extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'slug'];
|
||||
}
|
||||
129
app/Models/LogXfUser.php
Normal file
129
app/Models/LogXfUser.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Used only for logging details on XenForo user.
|
||||
*
|
||||
* Read-Only, must not be used in other things than logging.
|
||||
*
|
||||
* @property int $user_id
|
||||
* @property string $username
|
||||
* @property int $username_date
|
||||
* @property int $username_date_visible
|
||||
* @property string $email
|
||||
* @property string $custom_title
|
||||
* @property int $language_id
|
||||
* @property int $style_id 0 = use system default
|
||||
* @property string $style_variation
|
||||
* @property string $timezone Example: 'Europe/London'
|
||||
* @property int $visible Show browsing activity to others
|
||||
* @property int $activity_visible
|
||||
* @property int $user_group_id
|
||||
* @property string $secondary_group_ids
|
||||
* @property int $display_style_group_id User group ID that provides user styling
|
||||
* @property int $permission_combination_id
|
||||
* @property int $message_count
|
||||
* @property int $question_solution_count
|
||||
* @property int $conversations_unread
|
||||
* @property int $register_date
|
||||
* @property int $last_activity
|
||||
* @property int|null $last_summary_email_date
|
||||
* @property int $trophy_points
|
||||
* @property int $alerts_unviewed
|
||||
* @property int $alerts_unread
|
||||
* @property int $avatar_date
|
||||
* @property int $avatar_width
|
||||
* @property int $avatar_height
|
||||
* @property int $avatar_highdpi
|
||||
* @property int $avatar_optimized
|
||||
* @property string $gravatar If specified, this is an email address corresponding to the user's 'Gravatar'
|
||||
* @property string $user_state
|
||||
* @property string $security_lock
|
||||
* @property int $is_moderator
|
||||
* @property int $is_admin
|
||||
* @property int $is_banned
|
||||
* @property int $reaction_score
|
||||
* @property int $vote_score
|
||||
* @property int $warning_points
|
||||
* @property int $is_staff
|
||||
* @property string $secret_key
|
||||
* @property int $privacy_policy_accepted
|
||||
* @property int $terms_accepted
|
||||
* @property int $rhpz_entry_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereActivityVisible($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereAlertsUnread($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereAlertsUnviewed($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereAvatarDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereAvatarHeight($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereAvatarHighdpi($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereAvatarOptimized($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereAvatarWidth($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereConversationsUnread($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereCustomTitle($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereDisplayStyleGroupId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereGravatar($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereIsAdmin($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereIsBanned($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereIsModerator($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereIsStaff($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereLanguageId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereLastActivity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereLastSummaryEmailDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereMessageCount($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser wherePermissionCombinationId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser wherePrivacyPolicyAccepted($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereQuestionSolutionCount($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereReactionScore($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereRegisterDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereRhpzEntryCount($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereSecondaryGroupIds($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereSecretKey($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereSecurityLock($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereStyleId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereStyleVariation($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereTermsAccepted($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereTimezone($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereTrophyPoints($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereUserGroupId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereUserState($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereUsername($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereUsernameDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereUsernameDateVisible($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereVisible($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereVoteScore($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|LogXfUser whereWarningPoints($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class LogXfUser extends Model
|
||||
{
|
||||
|
||||
protected $connection = 'xenforo';
|
||||
protected $table = 'user';
|
||||
protected $primaryKey = 'user_id';
|
||||
public $timestamps = false;
|
||||
public $incrementing = true;
|
||||
|
||||
public function save(array $options = [])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function update(array $attributes = [], array $options = [])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
53
app/Models/MigrationUserPlan.php
Normal file
53
app/Models/MigrationUserPlan.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan query()
|
||||
* @property int $id
|
||||
* @property int|null $wp_user_id
|
||||
* @property int|null $xf_user_id
|
||||
* @property string $match_type
|
||||
* @property string|null $email
|
||||
* @property string|null $wp_username
|
||||
* @property string|null $xf_username
|
||||
* @property string|null $note
|
||||
* @property string $status
|
||||
* @property int|null $user_id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereMatchType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereNote($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereWpUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereWpUsername($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereXfUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereXfUsername($value)
|
||||
* @property int $wp_password_bridge
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|MigrationUserPlan whereWpPasswordBridge($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class MigrationUserPlan extends Model
|
||||
{
|
||||
protected $table = 'migration_user_plan';
|
||||
|
||||
protected $fillable = [
|
||||
'wp_user_id',
|
||||
'xf_user_id',
|
||||
'match_type',
|
||||
'email',
|
||||
'wp_username',
|
||||
'xf_username',
|
||||
'note',
|
||||
'status',
|
||||
];
|
||||
}
|
||||
@@ -4,6 +4,22 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Modification whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Modification extends Model
|
||||
{
|
||||
protected $fillable = [ 'name', 'slug' ];
|
||||
|
||||
130
app/Models/News.php
Normal file
130
app/Models/News.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\EntryHelpers;
|
||||
use App\Traits\HasGallery;
|
||||
use App\Traits\HasXenforoUserId;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $title
|
||||
* @property string $slug
|
||||
* @property int|null $category_id
|
||||
* @property string $description
|
||||
* @property string $state
|
||||
* @property string|null $staff_comment
|
||||
* @property \Illuminate\Support\Carbon|null $rejected_at
|
||||
* @property int|null $entry_id
|
||||
* @property string|null $relevant_link
|
||||
* @property string|null $youtube_link
|
||||
* @property int $user_id
|
||||
* @property int|null $comments_thread_id
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Category|null $category
|
||||
* @property-read \App\Models\Entry|null $entry
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Gallery> $gallery
|
||||
* @property-read int|null $gallery_count
|
||||
* @method static Builder<static>|News inQueue(int $daysRejected = 7)
|
||||
* @method static Builder<static>|News newModelQuery()
|
||||
* @method static Builder<static>|News newQuery()
|
||||
* @method static Builder<static>|News onlyTrashed()
|
||||
* @method static Builder<static>|News published()
|
||||
* @method static Builder<static>|News query()
|
||||
* @method static Builder<static>|News whereCategoryId($value)
|
||||
* @method static Builder<static>|News whereCommentsThreadId($value)
|
||||
* @method static Builder<static>|News whereCreatedAt($value)
|
||||
* @method static Builder<static>|News whereDeletedAt($value)
|
||||
* @method static Builder<static>|News whereDescription($value)
|
||||
* @method static Builder<static>|News whereEntryId($value)
|
||||
* @method static Builder<static>|News whereId($value)
|
||||
* @method static Builder<static>|News whereRejectedAt($value)
|
||||
* @method static Builder<static>|News whereRelevantLink($value)
|
||||
* @method static Builder<static>|News whereSlug($value)
|
||||
* @method static Builder<static>|News whereStaffComment($value)
|
||||
* @method static Builder<static>|News whereState($value)
|
||||
* @method static Builder<static>|News whereTitle($value)
|
||||
* @method static Builder<static>|News whereUpdatedAt($value)
|
||||
* @method static Builder<static>|News whereUserId($value)
|
||||
* @method static Builder<static>|News whereYoutubeLink($value)
|
||||
* @method static Builder<static>|News withTrashed(bool $withTrashed = true)
|
||||
* @method static Builder<static>|News withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class News extends Model
|
||||
{
|
||||
|
||||
use SoftDeletes, HasGallery, HasXenforoUserId;
|
||||
|
||||
protected $table = 'news';
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'slug',
|
||||
'description',
|
||||
'state',
|
||||
'category_id',
|
||||
'entry_id',
|
||||
'relevant_link',
|
||||
'youtube_link',
|
||||
'user_id',
|
||||
'comments_thread_id',
|
||||
'staff_comment',
|
||||
'rejected_at',
|
||||
'created_at'
|
||||
];
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'rejected_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function scopePublished(Builder $query): Builder
|
||||
{
|
||||
return $query->where('state', 'published' );
|
||||
}
|
||||
|
||||
public function scopeInQueue( Builder $query, int $daysRejected = 7 ): Builder
|
||||
{
|
||||
return $query->withTrashed()->where(function($q) use($daysRejected) {
|
||||
$q->where('state', 'pending')->whereNull('deleted_at');
|
||||
})->orWhere(function($q) use($daysRejected) {
|
||||
$q->where('state', 'rejected')->whereNotNull('rejected_at')->where('rejected_at', '>=', now()->subDays($daysRejected) );
|
||||
});
|
||||
}
|
||||
|
||||
public function entry(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Entry::class);
|
||||
}
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
public function getDescriptionHtmlAttribute(): string
|
||||
{
|
||||
$converter = new GithubFlavoredMarkdownConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
return $converter->convert($this->description)->getContent();
|
||||
}
|
||||
|
||||
public function getYoutubeVideoId(): ?string {
|
||||
if( !$this->youtube_link )
|
||||
return null;
|
||||
|
||||
return EntryHelpers::getYoutubeVideoId( $this->youtube_link );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,30 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string|null $short_name
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Entry> $entries
|
||||
* @property-read int|null $entries_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Game> $games
|
||||
* @property-read int|null $games_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform whereShortName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform whereUpdatedAt($value)
|
||||
* @property string|null $play_online_core
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Platform wherePlayOnlineCore($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Platform extends Model
|
||||
{
|
||||
|
||||
|
||||
45
app/Models/PlayOnlineSetting.php
Normal file
45
app/Models/PlayOnlineSetting.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $file_id
|
||||
* @property string $core
|
||||
* @property bool $threads
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\EntryFile $file
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting whereCore($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting whereFileId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting whereThreads($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|PlayOnlineSetting whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class PlayOnlineSetting extends Model
|
||||
{
|
||||
protected $primaryKey = 'file_id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'int';
|
||||
|
||||
protected $fillable = [
|
||||
'file_id',
|
||||
'core',
|
||||
'threads'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'threads' => 'boolean',
|
||||
];
|
||||
|
||||
public function file(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(EntryFile::class, 'file_id');
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,22 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Status whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Status extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'slug'];
|
||||
|
||||
26
app/Models/System.php
Normal file
26
app/Models/System.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System whereSlug($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|System whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class System extends Model
|
||||
{
|
||||
protected $fillable = ['name', 'slug'];
|
||||
}
|
||||
@@ -10,6 +10,31 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property \Illuminate\Support\Carbon|null $email_verified_at
|
||||
* @property string $password
|
||||
* @property string|null $remember_token
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
|
||||
* @property-read int|null $notifications_count
|
||||
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerifiedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePassword($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRememberToken($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
#[Fillable(['name', 'email', 'password'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
|
||||
36
app/Policies/EntryReviewPolicy.php
Normal file
36
app/Policies/EntryReviewPolicy.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryReview;
|
||||
use App\Models\User;
|
||||
|
||||
class EntryReviewPolicy
|
||||
{
|
||||
public function viewAny(\App\Auth\XenForoUser $user): bool
|
||||
{
|
||||
if( $user->_can( 'romhackplaza', 'view' ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function create(\App\Auth\XenForoUser $user, ?EntryReview $review = null ): bool
|
||||
{
|
||||
return $user->_can( 'romhackplaza', 'canSubmitEntry' );
|
||||
}
|
||||
|
||||
public function update(\App\Auth\XenForoUser $user, ?EntryReview $review = null ): bool
|
||||
{
|
||||
// Staff editors
|
||||
if( $user->_can('romhackplaza', 'canEditOthersEntries') )
|
||||
return true;
|
||||
|
||||
// Author.
|
||||
if( $user->_can( 'romhackplaza', 'canEditMyEntries' ) && $review->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
165
app/Policies/NewsPolicy.php
Normal file
165
app/Policies/NewsPolicy.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\News;
|
||||
use App\Auth\XenForoUser as User;
|
||||
|
||||
class NewsPolicy
|
||||
{
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
if( $user->_can( 'romhackplaza', 'view' ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function viewPending(User $user, News $news): bool
|
||||
{
|
||||
// Author.
|
||||
if( $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
return $user->_can( 'romhackplaza', 'canModerateEntries' );
|
||||
}
|
||||
|
||||
public function viewDraft(User $user, News $news): bool {
|
||||
// Author.
|
||||
if( $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
return $user->_can( 'romhackplaza', 'canSeeOthersDrafts' );
|
||||
}
|
||||
|
||||
public function viewRejected(User $user, News $news): bool
|
||||
{
|
||||
// Author.
|
||||
if( $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
return $user->_can( 'romhackplaza', 'canSeeRejectedEntries' );
|
||||
}
|
||||
|
||||
public function viewHidden(User $user, News $news): bool
|
||||
{
|
||||
return $user->_can('romhackplaza', 'canSeeHiddenEntries' );
|
||||
}
|
||||
|
||||
public function viewLocked(User $user, News $news): bool
|
||||
{
|
||||
// Author.
|
||||
if( $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
return $user->_can('romhackplaza', 'canSeeLockedEntries' );
|
||||
}
|
||||
|
||||
public function create(User $user, ?News $news = null ): bool
|
||||
{
|
||||
return $user->_can( 'romhackplaza', 'canSubmitEntry' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, News $news): bool
|
||||
{
|
||||
if( $news->state === 'published' ){
|
||||
|
||||
// Staff editors
|
||||
if( $user->_can('romhackplaza', 'canEditOthersEntries') )
|
||||
return true;
|
||||
|
||||
// Author.
|
||||
if( $user->_can( 'romhackplaza', 'canEditMyEntries' ) && $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
} else if( $news->state === 'pending' ){
|
||||
|
||||
// Staff moderation.
|
||||
if( $user->_can('romhackplaza', 'canEditOthersEntries') && $user->_can('romhackplaza', 'canModerateEntries') )
|
||||
return true;
|
||||
|
||||
// Author.
|
||||
if( $user->_can( 'romhackplaza', 'canEditMyEntries' ) && $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
} else if( $news->state === 'draft' ){
|
||||
|
||||
// Staff.
|
||||
if( $user->_can('romhackplaza', 'canEditOthersEntries') && $user->_can( 'romhackplaza', 'canSeeOthersDrafts' ) )
|
||||
return true;
|
||||
|
||||
// Author.
|
||||
if( $user->_can( 'romhackplaza', 'canEditMyEntries' ) && $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
} else if( $news->state === 'rejected' ){
|
||||
|
||||
// Staff.
|
||||
if( $user->_can('romhackplaza', 'canEditOthersEntries') && $user->_can( 'romhackplaza', 'canSeeRejectedEntries' ) )
|
||||
return true;
|
||||
|
||||
// Author.
|
||||
if( $user->_can( 'romhackplaza', 'canEditMyEntries' ) && $news->user_id === $user->user_id )
|
||||
return true;
|
||||
|
||||
} else if( $news->state === 'locked' ){
|
||||
|
||||
// Staff.
|
||||
if( $user->_can('romhackplaza', 'canEditOthersEntries') && $user->_can( 'romhackplaza', 'canSeeLockedEntries' ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
} else if( $news->state === 'hidden' ){
|
||||
|
||||
// Staff.
|
||||
if( $user->_can('romhackplaza', 'canEditOthersEntries') && $user->_can( 'romhackplaza', 'canSeeHiddenEntries' ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function skipQueue(User $user, ?News $news = null): bool
|
||||
{
|
||||
return $user->_can( 'romhackplaza', 'canSubmitEntryInPublished' );
|
||||
}
|
||||
|
||||
public function updateComment(User $user, News $news): bool
|
||||
{
|
||||
return $user->_can('romhackplaza', 'canModerateEntries' );
|
||||
}
|
||||
|
||||
public function manageButtonsInQueue(User $user, News $news): bool
|
||||
{
|
||||
if( $news->state === 'rejected' ){
|
||||
return $this->viewRejected( $user, $news );
|
||||
}
|
||||
|
||||
return $user->_can('romhackplaza', 'canModerateEntries' );
|
||||
}
|
||||
|
||||
public function approve(User $user, News $news): bool
|
||||
{
|
||||
return $user->_can('romhackplaza', 'canModerateEntries' );
|
||||
}
|
||||
|
||||
public function reject(User $user, News $news): bool
|
||||
{
|
||||
return $user->_can('romhackplaza', 'canModerateEntries' );
|
||||
}
|
||||
|
||||
public function moderate(User $user, News $news): bool
|
||||
{
|
||||
return $user->_can('romhackplaza', 'canModerateEntries' );
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,12 @@ namespace App\Providers;
|
||||
use App\Auth\XenForoGuard;
|
||||
use App\Auth\XenForoUser;
|
||||
use App\Policies\TempFilePolicy;
|
||||
use App\Proxy\VisitorProxy;
|
||||
use App\Services\TemporaryFileService;
|
||||
use App\Support\XenForoCauserResolver;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Spatie\Activitylog\Support\CauserResolver;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -16,7 +19,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
$this->app->bind(CauserResolver::class, XenForoCauserResolver::class );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,5 +40,10 @@ class AppServiceProvider extends ServiceProvider
|
||||
Gate::define('is-mod', function (XenForoUser $user) {
|
||||
return $user->is_moderator === 1;
|
||||
});
|
||||
|
||||
\View::composer('*', function ($view) {
|
||||
$view->with('VISITOR', new VisitorProxy( \Auth::user() ) );
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
43
app/Proxy/VisitorProxy.php
Normal file
43
app/Proxy/VisitorProxy.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Proxy;
|
||||
|
||||
use App\Auth\XenForoUser;
|
||||
use App\Services\XenforoService;
|
||||
|
||||
/**
|
||||
* @mixin XenForoUser
|
||||
*/
|
||||
class VisitorProxy
|
||||
{
|
||||
private ?XenForoUser $currentVisitor;
|
||||
private array $users = [];
|
||||
|
||||
public function __construct(?XenForoUser $user)
|
||||
{
|
||||
$this->currentVisitor = $user;
|
||||
}
|
||||
|
||||
public function __get( string $name ): mixed
|
||||
{
|
||||
return $this->currentVisitor?->$name;
|
||||
}
|
||||
|
||||
public function __invoke( int $userId ): ?XenForoUser
|
||||
{
|
||||
if( !isset( $this->users[$userId] ) ){
|
||||
$this->users[$userId] = app(XenforoService::class)->getXfUser($userId);
|
||||
}
|
||||
return $this->users[$userId];
|
||||
}
|
||||
|
||||
public function loggedIn(): bool
|
||||
{
|
||||
return $this->currentVisitor !== null;
|
||||
}
|
||||
|
||||
public function guest(): bool
|
||||
{
|
||||
return $this->currentVisitor === null;
|
||||
}
|
||||
}
|
||||
257
app/Services/ActivityService.php
Normal file
257
app/Services/ActivityService.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Entry;
|
||||
use App\Models\EntryReview;
|
||||
use App\Models\News;
|
||||
use App\View\Components\EntryCard;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ActivityService
|
||||
{
|
||||
private const CACHE_ENTRIES = 300; // seconds.
|
||||
private const CACHE_NEWS = 300; // seconds.
|
||||
private const CACHE_MESSAGES = 300; // seconds.
|
||||
private const CACHE_THREADS = 300; // seconds.
|
||||
private const CACHE_CLUBS = 300; // seconds.
|
||||
private const CACHE_REVIEWS = 300; // seconds.
|
||||
private const ITEMS_PER_TYPE = 15;
|
||||
|
||||
public function getActivities( array $activities = [ 'entries', 'news', 'messages', 'threads', 'clubs', 'reviews' ] ): Collection
|
||||
{
|
||||
$c = collect();
|
||||
if( in_array( 'entries', $activities ) ) {
|
||||
$c = $c->merge($this->getEntries());
|
||||
}
|
||||
if( in_array( 'news', $activities ) ) {
|
||||
$c = $c->merge($this->getNews());
|
||||
}
|
||||
if( in_array( 'messages', $activities ) ) {
|
||||
$c = $c->merge($this->getMessages());
|
||||
}
|
||||
if( in_array( 'threads', $activities ) ) {
|
||||
$c = $c->merge($this->getThreads());
|
||||
}
|
||||
if( in_array( 'clubs', $activities ) ) {
|
||||
$c = $c->merge($this->getClubs());
|
||||
}
|
||||
if( in_array( 'reviews', $activities ) ) {
|
||||
$c = $c->merge($this->getReviews());
|
||||
}
|
||||
|
||||
return $c->sortByDesc('date')
|
||||
->values()
|
||||
->take(30)
|
||||
->map(function(array $item){
|
||||
$obj = (object) $item;
|
||||
$obj->date = Carbon::createFromTimestamp($obj->date);
|
||||
return $obj;
|
||||
});
|
||||
}
|
||||
|
||||
private function formatEntry( Entry $entry ): array
|
||||
{
|
||||
return [
|
||||
'type' => 'entry',
|
||||
'title' => $entry->complete_title ?? $entry->title,
|
||||
'url' => route('entries.show', ['section' => $entry->type, 'entry' => $entry]),
|
||||
'image' => $entry->main_image ? \Storage::url($entry->main_image) : null,
|
||||
'date' => $entry->created_at->timestamp,
|
||||
'author' => $entry->authors->pluck('name')->implode(', '),
|
||||
'user_id' => $entry->user_id,
|
||||
'badge' => EntryCard::ENTRY_TYPES_BADGE[$entry->type],
|
||||
'badge_class' => $entry->type,
|
||||
'excerpt' => $entry->description ? \Str::limit(strip_tags($entry->description), 80) : null,
|
||||
'meta' => $entry->getRealPlatform()?->name
|
||||
];
|
||||
}
|
||||
|
||||
private function formatNews( News $news ): array
|
||||
{
|
||||
return [
|
||||
'type' => 'news',
|
||||
'title' => $news->title,
|
||||
'url' => route('news.show', ['news' => $news]),
|
||||
'image' => $news->gallery()->first() ? \Storage::url($news->gallery()->first()->image) : null,
|
||||
'date' => $news->created_at->timestamp,
|
||||
'author' => null,
|
||||
'user_id' => $news->user_id,
|
||||
'badge' => 'News',
|
||||
'badge_class' => 'news',
|
||||
'excerpt' => $news->description ? \Str::limit(strip_tags($news->description), 80) : null,
|
||||
'meta' => $news->category?->name
|
||||
];
|
||||
}
|
||||
|
||||
private function formatMessage( object $message ): array
|
||||
{
|
||||
return [
|
||||
'type' => 'message',
|
||||
'title' => $message->title,
|
||||
'url' => xfRoute('threads/.' ) . $message->thread_id . '/post-' . $message->post_id,
|
||||
'image' => null,
|
||||
'date' => $message->post_date,
|
||||
'author' => null,
|
||||
'user_id' => $message->user_id,
|
||||
'badge' => 'Post',
|
||||
'badge_class' => 'message',
|
||||
'excerpt' => $message->message ? \Str::limit(strip_tags($message->message), 80) : null,
|
||||
'meta' => null
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
private function formatThread( object $thread ): array
|
||||
{
|
||||
return [
|
||||
'type' => 'thread',
|
||||
'title' => $thread->title,
|
||||
'url' => xfRoute('threads/.' ) . $thread->thread_id,
|
||||
'image' => null,
|
||||
'date' => $thread->post_date,
|
||||
'author' => null,
|
||||
'user_id' => $thread->user_id,
|
||||
'badge' => 'Thread',
|
||||
'badge_class' => 'thread',
|
||||
'excerpt' => $thread->message ? \Str::limit(strip_tags($thread->message), 80) : null,
|
||||
'meta' => null
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
private function formatClub( object $club ): array
|
||||
{
|
||||
return [
|
||||
'type' => 'club',
|
||||
'title' => $club->title,
|
||||
'url' => xfRoute('forums/.' ) . $club->node_id,
|
||||
'image' => null, // TODO: Remplacer par banner_date
|
||||
'date' => $club->club_creation_date,
|
||||
'author' => null,
|
||||
'user_id' => $club->user_id,
|
||||
'badge' => 'Club',
|
||||
'badge_class' => 'club',
|
||||
'excerpt' => $club->description ? \Str::limit(strip_tags($club->description), 80) : null,
|
||||
'meta' => null
|
||||
];
|
||||
}
|
||||
|
||||
private function formatReview( EntryReview $review ): array
|
||||
{
|
||||
return [
|
||||
'type' => 'review',
|
||||
'title' => $review->title,
|
||||
'url' => $review->entry()->exists() ? route('entries.show', ['section' => $review->entry->type, 'entry' => $review->entry]) : '',
|
||||
'image' => null,
|
||||
'date' => $review->created_at->timestamp,
|
||||
'author' => null,
|
||||
'user_id' => $review->user_id,
|
||||
'badge' => 'Review',
|
||||
'badge_class' => 'review',
|
||||
'excerpt' => $review->description ? \Str::limit(strip_tags($review->description), 80) : null,
|
||||
'meta' => $review->entry()->exists() ? ( $review->entry->complete_title ?? $review->entry->title ) : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function getEntries(): array
|
||||
{
|
||||
return Cache::remember('activity_entries', self::CACHE_ENTRIES, function() {
|
||||
return Entry::published()
|
||||
->with(['authors', 'game.platform'])
|
||||
->latest('created_at')
|
||||
->limit(self::ITEMS_PER_TYPE)
|
||||
->get()
|
||||
->map($this->formatEntry(...))
|
||||
->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
private function getNews(): array
|
||||
{
|
||||
return Cache::remember('activity_news', self::CACHE_NEWS, function() {
|
||||
return News::published()
|
||||
->with('gallery')
|
||||
->latest('created_at')
|
||||
->limit(self::ITEMS_PER_TYPE)
|
||||
->get()
|
||||
->map($this->formatNews(...))
|
||||
->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
private function getMessages(): array
|
||||
{
|
||||
return Cache::remember('activity_messages', self::CACHE_MESSAGES, function() {
|
||||
return DB::connection('xenforo')
|
||||
->table('post')
|
||||
->join('user', 'post.user_id', '=', 'user.user_id')
|
||||
->join('thread', 'post.thread_id', '=', 'thread.thread_id')
|
||||
->where('post.message_state', 'visible')
|
||||
->where('thread.first_post_id', '!=', 'post.post_id')
|
||||
->orderByDesc('post.post_date')
|
||||
->limit(self::ITEMS_PER_TYPE)
|
||||
->select([
|
||||
'thread.title', 'thread.thread_id', 'post.post_id', 'post.post_date',
|
||||
'post.user_id', 'post.message'
|
||||
])
|
||||
->get()
|
||||
->map($this->formatMessage(...))
|
||||
->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
private function getThreads(): array
|
||||
{
|
||||
return Cache::remember('activity_threads', self::CACHE_THREADS, function() {
|
||||
return DB::connection('xenforo')
|
||||
->table('thread')
|
||||
->join('user', 'thread.user_id', '=', 'user.user_id')
|
||||
->join('post', 'thread.first_post_id', '=', 'post.post_id')
|
||||
->where('thread.discussion_state', 'visible')
|
||||
->where('thread.discussion_type', '!=', 'redirect' )
|
||||
->orderByDesc('thread.post_date')
|
||||
->limit(self::ITEMS_PER_TYPE)
|
||||
->select([
|
||||
'thread.title', 'thread.thread_id', 'thread.post_date', 'thread.user_id',
|
||||
'post.message'
|
||||
])
|
||||
->get()
|
||||
->map($this->formatThread(...))
|
||||
->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
private function getClubs(): array
|
||||
{
|
||||
return Cache::remember('activity_clubs', self::CACHE_CLUBS, function() {
|
||||
return DB::connection('xenforo')
|
||||
->table('club')
|
||||
->where('club_state', 'visible')
|
||||
->orderByDesc('club_creation_date')
|
||||
->limit(self::ITEMS_PER_TYPE)
|
||||
->select([
|
||||
'club.title', 'club.description', 'club.node_id',
|
||||
'club.club_creation_date', 'club.user_id'
|
||||
])
|
||||
->get()
|
||||
->map($this->formatClub(...))
|
||||
->toArray();
|
||||
});
|
||||
}
|
||||
|
||||
private function getReviews(): array
|
||||
{
|
||||
return Cache::remember('activity_reviews', self::CACHE_REVIEWS, function() {
|
||||
return EntryReview::with(['entry'])
|
||||
->latest('created_at')
|
||||
->limit(self::ITEMS_PER_TYPE)
|
||||
->get()
|
||||
->map($this->formatReview(...))
|
||||
->toArray();
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user