Skip to content

Commit

Permalink
feat(characters): add support for multiple subtypes (#870)
Browse files Browse the repository at this point in the history
* Merge branch 'extension/multiple-subtypes' into release/v3.0.0

* replace visible()s

* refactor: fix blade formatting

* fix(search): fix exclusionary search and search url on subtypes

* refactor: fix PHP styling

* feat: use migration over artisan call

* feat(migration): make migration fit-for-purpose as core feature

* refactor: fix PHP styling

* fix(command): check for correct column over table

---------

Co-authored-by: Ne-wt <[email protected]>
Co-authored-by: ScuffedNewt <[email protected]>
  • Loading branch information
3 people authored Aug 28, 2024
1 parent c4bb48e commit 0385914
Show file tree
Hide file tree
Showing 29 changed files with 671 additions and 271 deletions.
102 changes: 102 additions & 0 deletions app/Console/Commands/ConvertCharacterSubtype.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace App\Console\Commands;

use App\Models\Character\CharacterImage;
use App\Models\Character\CharacterImageSubtype;
use Illuminate\Console\Command;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class ConvertCharacterSubtype extends Command {
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'convert-character-subtype';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Converts the subtype_id columns in the character_images table to a new row in character_image_subtype table.';

/**
* Create a new command instance.
*/
public function __construct() {
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle() {
if (Schema::hasColumn('character_images', 'subtype_id')) {
$check = $this->confirm('Do you have the second subtype extension installed?', true);
if ($check) {
$this->info('This command will need minor modifications to run correctly with this extension. Please see the comments in the file.');

return;
}

// DESIGN UPDATES
$updates = DB::table('design_updates')->where('subtype_id', '!=', null)->get();
// make the string into an array
foreach ($updates as $update) {
$update->update([
'subtype_ids' => json_encode([$update->subtype_ids]),
]);
}
Schema::table('design_updates', function (Blueprint $table) {
$table->dropColumn('subtype_id');
});

$characterImages = CharacterImage::whereNotNull('subtype_id')->get();

$this->info('Converting '.count($characterImages).' character images to subtypes...');
$bar = $this->output->createProgressBar(count($characterImages));
foreach ($characterImages as $characterImage) {
/*
* FOR THE SECOND SUBTYPE EXTENSION,
*
* You will need to create two characterImageSubtype records, one for each subtype.
* ex.
*
* CharacterImageSubtype::create([
* 'character_image_id' => $characterImage->id,
* 'subtype_id' => $characterImage->subtype_one_id // or subtype_two_id
* ]);
*/
CharacterImageSubtype::create([
'character_image_id' => $characterImage->id,
'subtype_id' => $characterImage->subtype_id,
]);
$bar->advance();
}

$bar->finish();
$this->info('');

$this->info('Dropping subtype_id column from character_images table...');

/*
* FOR THE SECOND SUBTYPE EXTENSION,
*
* You will need to drop both subtype columns from the character_images table.
*/
Schema::table('character_images', function (Blueprint $table) {
$table->dropColumn('subtype_id');
});

$this->info('Done!');
} else {
$this->info('This command will not execute, as it has already been run.');
}
}
}
10 changes: 5 additions & 5 deletions app/Http/Controllers/Admin/Characters/CharacterController.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function getCreateCharacter() {
'userOptions' => User::query()->orderBy('name')->pluck('name', 'id')->toArray(),
'rarities' => ['0' => 'Select Rarity'] + Rarity::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'specieses' => ['0' => 'Select Species'] + Species::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => ['0' => 'Pick a Species First'],
'subtypes' => [],
'features' => Feature::getDropdownItems(1),
'isMyo' => false,
]);
Expand All @@ -67,7 +67,7 @@ public function getCreateMyo() {
'userOptions' => User::query()->orderBy('name')->pluck('name', 'id')->toArray(),
'rarities' => ['0' => 'Select Rarity'] + Rarity::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'specieses' => ['0' => 'Select Species'] + Species::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => ['0' => 'Pick a Species First'],
'subtypes' => [],
'features' => Feature::getDropdownItems(1),
'isMyo' => true,
]);
Expand All @@ -82,7 +82,7 @@ public function getCreateCharacterMyoSubtype(Request $request) {
$species = $request->input('species');

return view('admin.masterlist._create_character_subtype', [
'subtypes' => ['0' => 'Select Subtype'] + Subtype::where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => Subtype::where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'isMyo' => $request->input('myo'),
]);
}
Expand All @@ -103,7 +103,7 @@ public function postCreateCharacter(Request $request, CharacterManager $service)
'x0', 'x1', 'y0', 'y1',
'designer_id', 'designer_url',
'artist_id', 'artist_url',
'species_id', 'subtype_id', 'rarity_id', 'feature_id', 'feature_data',
'species_id', 'subtype_ids', 'rarity_id', 'feature_id', 'feature_data',
'image', 'thumbnail', 'image_description',
]);
if ($character = $service->createCharacter($data, Auth::user())) {
Expand Down Expand Up @@ -135,7 +135,7 @@ public function postCreateMyo(Request $request, CharacterManager $service) {
'x0', 'x1', 'y0', 'y1',
'designer_id', 'designer_url',
'artist_id', 'artist_url',
'species_id', 'subtype_id', 'rarity_id', 'feature_id', 'feature_data',
'species_id', 'subtype_ids', 'rarity_id', 'feature_id', 'feature_data',
'image', 'thumbnail',
]);
if ($character = $service->createCharacter($data, Auth::user(), true)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function getNewImage($slug) {
'character' => $this->character,
'rarities' => ['0' => 'Select Rarity'] + Rarity::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'specieses' => ['0' => 'Select Species'] + Species::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => ['0' => 'Select Subtype'] + Subtype::where('species_id', '=', $this->character->image->species_id)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => Subtype::where('species_id', '=', $this->character->image->species_id)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'users' => User::query()->orderBy('name')->pluck('name', 'id')->toArray(),
'features' => Feature::getDropdownItems(1),
'isMyo' => false,
Expand All @@ -58,8 +58,8 @@ public function getNewImageSubtype(Request $request) {
$id = $request->input('id');

return view('character.admin._upload_image_subtype', [
'subtypes' => ['0' => 'Select Subtype'] + Subtype::where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtype' => $id,
'subtypes' => Subtype::where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'image' => CharacterImage::find($id),
]);
}

Expand All @@ -73,7 +73,7 @@ public function getNewImageSubtype(Request $request) {
*/
public function postNewImage(Request $request, CharacterManager $service, $slug) {
$request->validate(CharacterImage::$createRules);
$data = $request->only(['image', 'thumbnail', 'x0', 'x1', 'y0', 'y1', 'use_cropper', 'artist_url', 'artist_id', 'designer_url', 'designer_id', 'species_id', 'subtype_id', 'rarity_id', 'feature_id', 'feature_data', 'is_valid', 'is_visible']);
$data = $request->only(['image', 'thumbnail', 'x0', 'x1', 'y0', 'y1', 'use_cropper', 'artist_url', 'artist_id', 'designer_url', 'designer_id', 'species_id', 'subtype_ids', 'rarity_id', 'feature_id', 'feature_data', 'is_valid', 'is_visible']);
$this->character = Character::where('slug', $slug)->first();
if (!$this->character) {
abort(404);
Expand Down Expand Up @@ -105,7 +105,7 @@ public function getEditImageFeatures($id) {
'image' => $image,
'rarities' => ['0' => 'Select Rarity'] + Rarity::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'specieses' => ['0' => 'Select Species'] + Species::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => ['0' => 'Select Subtype'] + Subtype::where('species_id', '=', $image->species_id)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => Subtype::where('species_id', '=', $image->species_id)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'features' => Feature::getDropdownItems(1),
]);
}
Expand All @@ -119,7 +119,7 @@ public function getEditImageFeatures($id) {
* @return \Illuminate\Http\RedirectResponse
*/
public function postEditImageFeatures(Request $request, CharacterManager $service, $id) {
$data = $request->only(['species_id', 'subtype_id', 'rarity_id', 'feature_id', 'feature_data']);
$data = $request->only(['species_id', 'subtype_ids', 'rarity_id', 'feature_id', 'feature_data']);
$image = CharacterImage::find($id);
if (!$image) {
abort(404);
Expand All @@ -146,7 +146,7 @@ public function getEditImageSubtype(Request $request) {

return view('character.admin._edit_features_subtype', [
'image' => CharacterImage::find($id),
'subtypes' => ['0' => 'Select Subtype'] + Subtype::where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => Subtype::where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
]);
}

Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/Admin/Data/ItemController.php
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ public function getEditItemTag(ItemService $service, $id, $tag) {
return view('admin.items.edit_tag', [
'item' => $item,
'tag' => $tag,
] + $tag->getEditData());
] + $tag->getEditData($tag));
}

/**
Expand Down
31 changes: 28 additions & 3 deletions app/Http/Controllers/BrowseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,33 @@ public function getCharacters(Request $request) {
if ($request->get('species_id')) {
$imageQuery->where('species_id', $request->get('species_id'));
}
if ($request->get('subtype_id')) {
$imageQuery->where('subtype_id', $request->get('subtype_id'));
if ($request->get('subtype_ids')) {
// if subtype ids contains "any" search for all subtypes
if (in_array('any', $request->get('subtype_ids')) || in_array('hybrid', $request->get('subtype_ids'))) {
$imageQuery->whereHas('subtypes', function ($query) use ($request) {
$query->havingRaw('COUNT(*) > '.(in_array('hybrid', $request->get('subtype_ids')) ? 1 : 0));
});
} else {
if (config('lorekeeper.extensions.exclusionary_search')) {
$imageQuery->whereHas('subtypes', function ($query) use ($request) {
$subtypeIds = $request->get('subtype_ids');

// Filter to ensure the character has all the specified subtypes
$query->whereIn('character_image_subtypes.subtype_id', $subtypeIds)
->groupBy('character_image_subtypes.character_image_id')
->havingRaw('COUNT(character_image_subtypes.subtype_id) = ?', [count($subtypeIds)]);
})->whereDoesntHave('subtypes', function ($query) use ($request) {
$subtypeIds = $request->get('subtype_ids');

// Ensure that no additional subtypes are present
$query->whereNotIn('character_image_subtypes.subtype_id', $subtypeIds);
});
} else {
$imageQuery->whereHas('subtypes', function ($query) use ($request) {
$query->whereIn('character_image_subtypes.subtype_id', $request->get('subtype_ids'));
});
}
}
}
if ($request->get('feature_ids')) {
$featureIds = $request->get('feature_ids');
Expand Down Expand Up @@ -314,7 +339,7 @@ public function getCharacters(Request $request) {
'characters' => $query->paginate(24)->appends($request->query()),
'categories' => [0 => 'Any Category'] + CharacterCategory::whereNotIn('id', $subCategories)->visible(Auth::user() ?? null)->orderBy('character_categories.sort', 'DESC')->pluck('name', 'id')->toArray(),
'specieses' => [0 => 'Any Species'] + Species::whereNotIn('id', $subSpecies)->visible(Auth::user() ?? null)->orderBy('specieses.sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => [0 => 'Any Subtype'] + Subtype::visible(Auth::user() ?? null)->orderBy('subtypes.sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => ['any' => 'Any Subtype', 'hybrid' => 'Multiple / Hybrid Subtypes'] + Subtype::visible(Auth::user() ?? null)->orderBy('subtypes.sort', 'DESC')->pluck('name', 'id')->toArray(),
'rarities' => [0 => 'Any Rarity'] + Rarity::orderBy('rarities.sort', 'DESC')->pluck('name', 'id')->toArray(),
'features' => Feature::getDropdownItems(),
'sublists' => Sublist::orderBy('sort', 'DESC')->get(),
Expand Down
4 changes: 2 additions & 2 deletions app/Http/Controllers/Characters/DesignController.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public function getFeatures($id) {
return view('character.design.features', [
'request' => $r,
'specieses' => ['0' => 'Select Species'] + Species::visible()->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => ['0' => 'No Subtype'] + Subtype::visible()->where('species_id', '=', $r->species_id)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => Subtype::visible()->where('species_id', '=', $r->species_id)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'rarities' => ['0' => 'Select Rarity'] + Rarity::orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'features' => Feature::getDropdownItems(),
]);
Expand All @@ -240,7 +240,7 @@ public function getFeaturesSubtype(Request $request) {
$id = $request->input('id');

return view('character.design._features_subtype', [
'subtypes' => ['0' => 'Select Subtype'] + Subtype::visible()->where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtypes' => Subtype::visible()->where('species_id', '=', $species)->orderBy('sort', 'DESC')->pluck('name', 'id')->toArray(),
'subtype' => $id,
]);
}
Expand Down
30 changes: 22 additions & 8 deletions app/Models/Character/CharacterDesignUpdate.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class CharacterDesignUpdate extends Model {
'character_id', 'status', 'user_id', 'staff_id',
'comments', 'staff_comments', 'data', 'extension',
'use_cropper', 'x0', 'x1', 'y0', 'y1',
'hash', 'species_id', 'subtype_id', 'rarity_id',
'hash', 'species_id', 'subtype_ids', 'rarity_id',
'has_comments', 'has_image', 'has_addons', 'has_features',
'submitted_at', 'update_type', 'fullsize_hash',
'approval_votes', 'rejection_votes',
Expand All @@ -42,6 +42,7 @@ class CharacterDesignUpdate extends Model {
*/
protected $casts = [
'submitted_at' => 'datetime',
'subtype_ids' => 'array',
];

/**
Expand Down Expand Up @@ -97,13 +98,6 @@ public function species() {
return $this->belongsTo(Species::class, 'species_id');
}

/**
* Get the subtype of the design update.
*/
public function subtype() {
return $this->belongsTo(Subtype::class, 'subtype_id');
}

/**
* Get the rarity of the design update.
*/
Expand Down Expand Up @@ -371,4 +365,24 @@ public function getBank($type) {

return $result;
}

/**
* Get the subtypes of the design update.
*/
public function subtypes() {
return $this->subtype_ids;
}

/**
* Get the subtypes of the design update.
*/
public function displaySubtypes() {
$subtypes = $this->subtypes();
$result = [];
foreach ($subtypes as $subtype) {
$result[] = Subtype::find($subtype)->displayName;
}

return implode(', ', $result);
}
}
27 changes: 24 additions & 3 deletions app/Models/Character/CharacterImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class CharacterImage extends Model {
* @var array
*/
protected $fillable = [
'character_id', 'user_id', 'species_id', 'subtype_id', 'rarity_id', 'url',
'character_id', 'user_id', 'species_id', 'rarity_id', 'url',
'extension', 'use_cropper', 'hash', 'fullsize_hash', 'fullsize_extension', 'sort',
'x0', 'x1', 'y0', 'y1',
'description', 'parsed_description',
Expand Down Expand Up @@ -94,8 +94,8 @@ public function species() {
/**
* Get the subtype of the character image.
*/
public function subtype() {
return $this->belongsTo(Subtype::class, 'subtype_id');
public function subtypes() {
return $this->hasMany(CharacterImageSubtype::class, 'character_image_id');
}

/**
Expand Down Expand Up @@ -264,4 +264,25 @@ public function getThumbnailPathAttribute() {
public function getThumbnailUrlAttribute() {
return asset($this->imageDirectory.'/'.$this->thumbnailFileName);
}

/**********************************************************************************************
OTHER FUNCTIONS
**********************************************************************************************/

/**
* Displays the image's subtypes as an imploded string.
*/
public function displaySubtypes() {
if (!count($this->subtypes)) {
return 'None';
}
$subtypes = [];
foreach ($this->subtypes as $subtype) {
$subtypes[] = $subtype->subtype->displayName;
}

return implode(', ', $subtypes);
}
}
Loading

0 comments on commit 0385914

Please sign in to comment.