*/
public function featureFiles(): HasMany
{
return $this->hasMany(FeatureFile::class, 'feature_id', 'id');
}
public function scopeApproved(Builder $builder): Builder
{
return $builder->whereIn('status_id', [\App\Enums\FeatureStatus::Approved])
->orderBy('upvote_count', 'DESC');
}
public function scopeVisible(Builder $builder): Builder
{
$statuses = [
\App\Enums\FeatureStatus::Approved,
\App\Enums\FeatureStatus::Later,
\App\Enums\FeatureStatus::Next,
\App\Enums\FeatureStatus::Now,
\App\Enums\FeatureStatus::Done,
];
return $builder->whereIn('status_id', $statuses)
->orderBy('upvote_count', 'DESC');
}
public function scopeSearch(Builder $builder, string $search): Builder
{
if (empty($search) | mb_strlen($search) < 3) {
return $builder;
}
return $builder->where('name', 'like', '%' . $search . '%');
}
public function isUpvoted(): bool
{
if (auth()->guest()) {
return false;
}
return auth()->user()->upvotes()->forFeature($this)->count() === 1;
}
public function cleanDescription(): string
{
if (Str::startsWith($this->description, '')) {
return $this->description;
}
return '
' . nl2br($this->description) . '
';
}
}
================================================
FILE: app/Models/FeatureCategory.php
================================================
*/
public function features(): HasMany
{
return $this->hasMany(Feature::class, 'category_id', 'id');
}
public function progress(): HasMany
{
return $this->features()
->whereIn('features.status_id', [
FeatureStatus::Later, FeatureStatus::Next, FeatureStatus::Now,
]);
}
public function done(): HasMany
{
return $this->features()->where('status_id', FeatureStatus::Done)->orderBy('updated_at', 'DESC');
}
public function now(): HasMany
{
return $this->features()->where('status_id', FeatureStatus::Now);
}
public function next(): HasMany
{
return $this->features()->where('status_id', FeatureStatus::Next);
}
public function later(): HasMany
{
return $this->features()->where('status_id', FeatureStatus::Later);
}
public function nothingPlanned(): bool
{
return $this->now->count() + $this->later->count() + $this->next->count() === 0;
}
public function nothingDone(): bool
{
return $this->done->count() === 0;
}
}
================================================
FILE: app/Models/FeatureFile.php
================================================
*/
public function feature(): BelongsTo
{
return $this->belongsTo(Feature::class, 'feature_id');
}
}
================================================
FILE: app/Models/FeatureStatus.php
================================================
*/
public function features(): HasMany
{
return $this->hasMany(Feature::class);
}
}
================================================
FILE: app/Models/FeatureUpvote.php
================================================
*/
public function feature(): BelongsTo
{
return $this->belongsTo(Feature::class);
}
}
================================================
FILE: app/Models/FeatureVote.php
================================================
*/
public function feature(): BelongsTo
{
return $this->belongsTo(Feature::class);
}
public function scopeForFeature(Builder $query, Feature $feature): Builder
{
return $query->where('feature_id', $feature->id);
}
}
================================================
FILE: app/Models/GameSystem.php
================================================
*/
public function campaignSystem(): HasMany
{
return $this->hasMany(CampaignSystem::class, 'system_id', 'id');
}
}
================================================
FILE: app/Models/Genre.php
================================================
Visibility::class,
];
protected string $userField = 'created_by';
protected array $sanitizable = [
'name',
];
/**
* @return BelongsTo
*/
public function imageFolder(): BelongsTo
{
return $this->belongsTo(Image::class, 'folder_id', 'id');
}
/**
* @return HasMany
*/
public function images(): HasMany
{
return $this->hasMany(Image::class, 'folder_id', 'id');
}
/**
* @return HasMany
*/
public function folders(): HasMany
{
return $this->hasMany(Image::class, 'folder_id', 'id')
->where('is_folder', true);
}
/**
* @return HasMany
*/
public function entities(): HasMany
{
return $this->hasMany(Entity::class, 'image_uuid', 'id');
}
/**
* @return HasMany
*/
public function mapLayers(): HasMany
{
return $this->hasMany(MapLayer::class, 'image_uuid', 'id');
}
/**
* @return HasMany
*/
public function inventories(): HasMany
{
return $this->hasMany(Inventory::class, 'image_uuid', 'id');
}
/**
* @return HasMany
*/
public function entityAssets(): HasMany
{
return $this->hasMany(EntityAsset::class, 'image_uuid', 'id')
->with('entity')
->has('entity');
}
/**
* @return HasMany
*/
public function headers(): HasMany
{
return $this->hasMany(Entity::class, 'header_uuid', 'id');
}
/**
* @return HasMany
*/
public function mentions(): HasMany
{
return $this->hasMany(ImageMention::class, 'image_id', 'id')
->with('entity')
->with('post')
->has('entity');
}
public function inEntities(): array
{
$entities = [];
foreach ($this->entities as $entity) {
if (isset($entities[$entity->id])) {
continue;
}
$entities[$entity->id] = $entity;
}
foreach ($this->headers as $entity) {
if (isset($entities[$entity->id])) {
continue;
}
$entities[$entity->id] = $entity;
}
foreach ($this->entityAssets as $asset) {
if (isset($entities[$asset->entity->id])) {
continue;
}
$entities[$asset->entity->id] = $asset->entity;
}
return $entities;
}
public function isUsed(): bool
{
$entities = count($this->inEntities());
$mentions = $this->mentions()->count();
$layers = $this->mapLayers()->count();
$inventories = $this->inventories()->count();
return $entities || $mentions || $layers || $inventories;
}
public function inEntitiesCount(): int
{
if (isset($this->_usageCount)) {
return $this->_usageCount;
}
return $this->_usageCount = count($this->inEntities());
}
/**
* @return bool
*/
public function getIncrementing()
{
return false;
}
/**
* @return string
*/
public function getKeyType()
{
return 'string';
}
public function getPathAttribute(): string
{
return $this->folder . '/' . $this->file;
}
public function getFileAttribute(): string
{
return $this->id . '.' . $this->ext;
}
public function getFolderAttribute(): string
{
return 'campaigns/' . $this->campaign_id;
}
public function niceSize(): string
{
if ($this->size > 1000) {
return round($this->size / 1024, 2) . ' MB';
}
return $this->size . ' KB';
}
public function scopeImageFolder(Builder $query, ?string $folder = null): Builder
{
if (empty($folder)) {
return $query->whereNull('folder_id');
}
return $query->where('folder_id', $folder);
}
public function scopeDefaultOrder(Builder $query): Builder
{
return $query
->orderBy('is_folder', 'desc')
->orderBy('updated_at', 'desc')
->orderBy('name', 'asc');
}
public function scopeSortOrder(Builder $query, string $sort = 'asc'): Builder
{
return $query
->orderBy('is_folder', 'desc')
->orderBy('name', $sort)
->orderBy('updated_at', 'desc');
}
public function scopeFolders(Builder $query): Builder
{
return $query
->where('is_folder', true)
->orderBy('name', 'asc');
}
public function scopeAcl(Builder $query, bool $browse): Builder
{
if (! $browse) {
return $query->where('created_by', auth()->user()->id);
}
return $query;
}
public function scopeNamed(Builder $query, ?string $term): Builder
{
if (empty($term)) {
return $query;
}
return $query->where($this->getTable() . '.name', 'like', '%' . $term . '%');
}
public function scopeSearch(Builder $query, ?string $folder, ?string $term): Builder
{
if (empty($term)) {
// @phpstan-ignore-next-line
return $query->imageFolder($folder);
}
// @phpstan-ignore-next-line
return $query->named($term);
}
public function hasNoFolders(): bool
{
return $this->images()->where('is_folder', '1')->count() == 0;
}
public function getImagePath($width = 40, $height = 40): string
{
return Img::resetCrop()->crop($width, $height)->url($this->path);
}
public function isFolder(): bool
{
return (bool) $this->is_folder;
}
public function isFont(): bool
{
return in_array($this->ext, ['woff', 'woff2']);
}
public function hasThumbnail(): bool
{
return in_array($this->ext, ['jpg', 'png', 'jpeg', 'gif', 'webp']);
}
public function getUrl(?int $sizeX = null, ?int $sizeY = null): string
{
if ($this->isSvg()) {
return $this->url();
}
Img::reset();
if (! $sizeY && $sizeX) {
$sizeY = $sizeX;
} elseif (! $sizeX && $sizeY) {
$sizeX = $sizeY;
}
if ($sizeX && $sizeY) {
if (! $this->focus_x && ! $this->focus_y) {
return Img::crop($sizeX, $sizeY)->url($this->path);
}
return Img::focus($this->focus_x, $this->focus_y)->crop($sizeX, $sizeY)->url($this->path);
}
if ($this->focus_x && $this->focus_y) {
return Img::focus($this->focus_x, $this->focus_y)->url($this->path);
}
return Img::url($this->path);
}
public function isSvg(): bool
{
return $this->ext == 'svg';
}
public function url(): string
{
$path = $this->path;
$cdn = config('cdn.ugc');
if ($cdn) {
return $cdn . '/' . $path;
}
return Storage::url($path);
}
}
================================================
FILE: app/Models/ImageMention.php
================================================
*/
public function entity(): BelongsTo
{
return $this->belongsTo('App\Models\Entity', 'entity_id', 'id');
}
/**
* @return BelongsTo
*/
public function post(): BelongsTo
{
return $this->belongsTo('App\Models\Post', 'post_id', 'id');
}
/**
* Determine if the mention goes to a post
*/
public function isPost(): bool
{
return ! empty($this->post_id);
}
/**
* Build the query that will loop on the various mentions to get the total count.
* The AclTrait on entities and posts makes sure only visible things get added to the query.
*/
public function scopePrepareCount(Builder $query): Builder
{
return $query->where(function ($sub) {
return $sub
->where(function ($subEnt) {
// @phpstan-ignore-next-line
return $subEnt
->entity()
->has('entity');
})
->orWhere(function ($subPost) {
// @phpstan-ignore-next-line
return $subPost
->post()
->has('post.entity');
});
});
}
public function scopeEntity(Builder $query): Builder
{
return $query->whereNotNull('image_mentions.entity_id');
}
public function scopePost(Builder $query): Builder
{
return $query->whereNotNull('image_mentions.post_id');
}
}
================================================
FILE: app/Models/Inventory.php
================================================
Visibility::class,
];
protected array $sanitizable = [
'name',
'position',
'description',
];
/**
* @return BelongsTo
*/
public function entity(): BelongsTo
{
return $this->belongsTo('App\Models\Entity');
}
/**
* @return BelongsTo-
*/
public function item(): BelongsTo
{
return $this->belongsTo('App\Models\Item');
}
/**
* @return HasOne
*/
public function image(): HasOne
{
return $this->hasOne('App\Models\Image', 'id', 'image_uuid');
}
/**
* List of recently used positions for the form suggestions
*/
public function scopePositionList(Builder $builder, Campaign $campaign): Builder
{
return $builder->groupBy('position')
->whereNotNull('position')
->leftJoin('entities as e', 'e.id', 'inventories.entity_id')
->where('e.campaign_id', $campaign->id)
->orderBy('position', 'ASC')
->limit(50);
}
/**
* Get the item name, either custom or attached object
*/
public function itemName(): string
{
if (empty($this->name) && ! empty($this->item)) {
return $this->item->name;
}
return (string) $this->name;
}
/**
* Copy an entity inventory to another target
*/
public function copyTo(Entity $target, bool $sameCampaign): bool
{
$without = $sameCampaign ? ['entity_id'] : ['entity_id', 'item_id', 'image_uuid'];
$new = $this->replicate($without);
$new->entity_id = $target->id;
if ($sameCampaign) {
return $new->save();
}
if (empty($new->name)) {
return false;
}
return $new->save();
}
public function isEquipped(): bool
{
return $this->is_equipped;
}
}
================================================
FILE: app/Models/Item.php
================================================
'unsigned',
];
/**
* Nullable values (foreign keys)
*
* @var string[]
*/
public array $nullableForeignKeys = [
'location_id',
];
/**
* Foreign relations to add to export
*/
protected array $foreignExport = [
'itemCreators',
];
protected array $exportFields = [
'base',
'price',
'size',
'weight',
'location_id',
];
/**
* @var string[] Extra relations loaded for the API endpoint
*/
public array $apiWith = ['itemCreators'];
/**
* Tooltip subtitle (item price/size)
*/
public function tooltipSubtitle(): string
{
$extra = [];
if (! empty($this->price)) {
$extra[] = __('items.fields.price') . ': ' . e($this->price);
}
if (! empty($this->size)) {
$extra[] = __('items.fields.size') . ': ' . e($this->size);
}
if (! empty($this->weight)) {
$extra[] = __('items.fields.weight') . ': ' . e($this->weight);
}
if (empty($extra)) {
return '';
}
return implode('
', $extra);
}
/**
* @return HasMany
*/
public function itemCreators(): HasMany
{
return $this->hasMany(ItemCreator::class, 'item_id')
->orderBy('id');
}
/**
* @return BelongsToMany
*/
public function creators(): BelongsToMany
{
return $this->belongsToMany(Entity::class, 'item_creator', 'item_id', 'creator_id')
->orderBy('item_creator.id');
}
/**
* @return HasMany
*/
public function inventories(): HasMany
{
return $this->hasMany('App\Models\Inventory', 'item_id');
}
/**
* @return HasManyThrough
*/
public function entities(): HasManyThrough
{
return $this->hasManyThrough(
'App\Models\Entity',
'App\Models\Inventory',
'item_id',
'id',
'id',
'entity_id'
);
}
/**
* Get the entity_type id from the entity_types table
*/
public function entityTypeId(): int
{
return (int) config('entities.ids.item');
}
/**
* Determine if the model has profile data to be displayed
*/
public function showProfileInfo(): bool
{
if (! empty($this->price) || ! empty($this->size) || ! empty($this->weight)) {
return true;
}
if ($this->itemCreators->isNotEmpty() || $this->location) {
return true;
}
return parent::showProfileInfo();
}
/**
* Define the fields unique to this model that can be used on filters
*
* @return string[]
*/
public function filterableColumns(): array
{
return [
'location_id',
'creators',
'price',
'size',
'weight',
];
}
/**
* Grid mode sortable fields
*/
public function datagridSortableColumns(): array
{
$columns = [
'name' => __('crud.fields.name'),
'type' => __('crud.fields.type'),
'price' => __('items.fields.price'),
'size' => __('items.fields.size'),
'weight' => __('items.fields.weight'),
];
if (auth()->check() && auth()->user()->isAdmin()) {
$columns['is_private'] = __('crud.fields.is_private');
}
return $columns;
}
}
================================================
FILE: app/Models/ItemCreator.php
================================================
*/
public function item(): BelongsTo
{
return $this->belongsTo(Item::class);
}
/**
* @return BelongsTo
*/
public function creator(): BelongsTo
{
return $this->belongsTo(Entity::class, 'creator_id');
}
public function exportFields(): array
{
return [
'item_id',
'creator_id',
];
}
}
================================================
FILE: app/Models/JobLog.php
================================================
entity->id];
foreach ($this->entity->descendants as $descendant) {
$entityIds[] = $descendant->id;
}
return Journal::whereHas('entity', fn ($q) => $q->whereIn('entities.parent_id', $entityIds))
->has('entity')
->with('entity.parent');
}
/**
* @return BelongsTo
*/
public function character(): BelongsTo
{
return $this->belongsTo('App\Models\Character', 'character_id');
}
/**
* @return BelongsTo
*/
public function author(): BelongsTo
{
return $this->belongsTo('App\Models\Entity', 'author_id');
}
/**
* Get the entity_type id from the entity_types table
*/
public function entityTypeId(): int
{
return (int) config('entities.ids.journal');
}
/**
* Determine if the model has profile data to be displayed
*/
public function showProfileInfo(): bool
{
if (! empty($this->date)) {
return true;
}
if (! empty($this->author) || ! empty($this->location)) {
return true;
}
if (! empty($this->entity->calendarReminder())) {
return true;
}
return parent::showProfileInfo();
}
/**
* Define the fields unique to this model that can be used on filters
*
* @return string[]
*/
public function filterableColumns(): array
{
return [
'date',
'character_id',
'location_id',
'author_id',
'date_start',
'date_end',
];
}
/**
* Grid mode sortable fields
*/
public function datagridSortableColumns(): array
{
$columns = [
'name' => __('crud.fields.name'),
'type' => __('crud.fields.type'),
'date' => __('journals.fields.date'),
'calendar_date' => __('crud.fields.calendar_date'),
];
if (auth()->check() && auth()->user()->isAdmin()) {
$columns['is_private'] = __('crud.fields.is_private');
}
return $columns;
}
}
================================================
FILE: app/Models/Location.php
================================================
*/
public function races(): BelongsToMany
{
return $this->belongsToMany('App\Models\Race', 'race_location');
}
/**
* @return BelongsToMany
*/
public function creatures(): BelongsToMany
{
return $this->belongsToMany('App\Models\Creature', 'creature_location');
}
/**
* @return BelongsToMany
*/
public function entities(): BelongsToMany
{
return $this->belongsToMany('App\Models\Entity', 'entity_locations');
}
/**
* @return HasMany-
*/
public function items(): HasMany
{
return $this->hasMany('App\Models\Item', 'location_id', 'id');
}
/**
* @return HasMany