868 lines
28 KiB
PHP
868 lines
28 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Laravel\Scout\Searchable;
|
|
|
|
class Group extends Model
|
|
{
|
|
use HasFactory;
|
|
use SoftDeletes;
|
|
use Searchable;
|
|
|
|
public const VISIBILITY_PUBLIC = 'public';
|
|
public const VISIBILITY_PRIVATE = 'private';
|
|
public const VISIBILITY_UNLISTED = 'unlisted';
|
|
|
|
public const LIFECYCLE_ACTIVE = 'active';
|
|
public const LIFECYCLE_ARCHIVED = 'archived';
|
|
public const LIFECYCLE_SUSPENDED = 'suspended';
|
|
|
|
public const MEMBERSHIP_INVITE_ONLY = 'invite_only';
|
|
public const MEMBERSHIP_REQUEST_TO_JOIN = 'request_to_join';
|
|
public const MEMBERSHIP_OPEN = 'open';
|
|
|
|
public const ROLE_OWNER = 'owner';
|
|
public const ROLE_ADMIN = 'admin';
|
|
public const ROLE_EDITOR = 'editor';
|
|
public const ROLE_MEMBER = 'member';
|
|
public const ROLE_CONTRIBUTOR = 'contributor';
|
|
|
|
public const PERMISSION_REVIEW_JOIN_REQUESTS = 'review_join_requests';
|
|
public const PERMISSION_REVIEW_SUBMISSIONS = 'review_submissions';
|
|
public const PERMISSION_MANAGE_RECRUITMENT = 'manage_recruitment';
|
|
public const PERMISSION_MANAGE_POSTS = 'manage_posts';
|
|
public const PERMISSION_PUBLISH_POSTS = 'publish_posts';
|
|
public const PERMISSION_PIN_POSTS = 'pin_posts';
|
|
public const PERMISSION_MANAGE_MEMBER_PERMISSIONS = 'manage_member_permissions';
|
|
public const PERMISSION_MANAGE_EVENTS = 'manage_events';
|
|
public const PERMISSION_MANAGE_CHALLENGES = 'manage_challenges';
|
|
public const PERMISSION_MANAGE_PROJECTS = 'manage_projects';
|
|
public const PERMISSION_MANAGE_RELEASES = 'manage_releases';
|
|
public const PERMISSION_PUBLISH_RELEASES = 'publish_releases';
|
|
public const PERMISSION_MANAGE_MILESTONES = 'manage_milestones';
|
|
public const PERMISSION_VIEW_REPUTATION_DASHBOARD = 'view_reputation_dashboard';
|
|
public const PERMISSION_MANAGE_BADGES = 'manage_badges';
|
|
public const PERMISSION_VIEW_INTERNAL_TRUST_METRICS = 'view_internal_trust_metrics';
|
|
public const PERMISSION_FEATURE_RELEASES = 'feature_releases';
|
|
public const PERMISSION_ASSIGN_RELEASE_LEAD = 'assign_release_lead';
|
|
public const PERMISSION_MANAGE_ASSETS = 'manage_assets';
|
|
public const PERMISSION_FEATURE_CHALLENGE_ENTRIES = 'feature_challenge_entries';
|
|
public const PERMISSION_PUBLISH_EVENT_UPDATES = 'publish_event_updates';
|
|
public const PERMISSION_ATTACH_ASSETS_TO_PROJECTS = 'attach_assets_to_projects';
|
|
public const PERMISSION_VIEW_INTERNAL_ASSETS = 'view_internal_assets';
|
|
public const PERMISSION_MANAGE_ACTIVITY_PINS = 'manage_activity_pins';
|
|
|
|
public const STATUS_PENDING = 'pending';
|
|
public const STATUS_ACTIVE = 'active';
|
|
public const STATUS_REVOKED = 'revoked';
|
|
|
|
protected $fillable = [
|
|
'owner_user_id',
|
|
'featured_artwork_id',
|
|
'is_verified',
|
|
'founded_at',
|
|
'name',
|
|
'slug',
|
|
'headline',
|
|
'bio',
|
|
'type',
|
|
'visibility',
|
|
'status',
|
|
'membership_policy',
|
|
'website_url',
|
|
'links_json',
|
|
'avatar_path',
|
|
'banner_path',
|
|
'artworks_count',
|
|
'collections_count',
|
|
'followers_count',
|
|
'last_activity_at',
|
|
];
|
|
|
|
protected $casts = [
|
|
'links_json' => 'array',
|
|
'is_verified' => 'boolean',
|
|
'artworks_count' => 'integer',
|
|
'collections_count' => 'integer',
|
|
'followers_count' => 'integer',
|
|
'founded_at' => 'datetime',
|
|
'last_activity_at' => 'datetime',
|
|
];
|
|
|
|
public function owner(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'owner_user_id');
|
|
}
|
|
|
|
public function getRouteKeyName(): string
|
|
{
|
|
return 'slug';
|
|
}
|
|
|
|
public function members(): HasMany
|
|
{
|
|
return $this->hasMany(GroupMember::class);
|
|
}
|
|
|
|
public function invitations(): HasMany
|
|
{
|
|
return $this->hasMany(GroupInvitation::class);
|
|
}
|
|
|
|
public function follows(): HasMany
|
|
{
|
|
return $this->hasMany(GroupFollow::class);
|
|
}
|
|
|
|
public function artworks(): HasMany
|
|
{
|
|
return $this->hasMany(Artwork::class);
|
|
}
|
|
|
|
public function collections(): HasMany
|
|
{
|
|
return $this->hasMany(Collection::class);
|
|
}
|
|
|
|
public function joinRequests(): HasMany
|
|
{
|
|
return $this->hasMany(GroupJoinRequest::class);
|
|
}
|
|
|
|
public function posts(): HasMany
|
|
{
|
|
return $this->hasMany(GroupPost::class);
|
|
}
|
|
|
|
public function recruitmentProfile(): HasOne
|
|
{
|
|
return $this->hasOne(GroupRecruitmentProfile::class);
|
|
}
|
|
|
|
public function projects(): HasMany
|
|
{
|
|
return $this->hasMany(GroupProject::class);
|
|
}
|
|
|
|
public function releases(): HasMany
|
|
{
|
|
return $this->hasMany(GroupRelease::class);
|
|
}
|
|
|
|
public function challenges(): HasMany
|
|
{
|
|
return $this->hasMany(GroupChallenge::class);
|
|
}
|
|
|
|
public function events(): HasMany
|
|
{
|
|
return $this->hasMany(GroupEvent::class);
|
|
}
|
|
|
|
public function assets(): HasMany
|
|
{
|
|
return $this->hasMany(GroupAsset::class);
|
|
}
|
|
|
|
public function activityItems(): HasMany
|
|
{
|
|
return $this->hasMany(GroupActivityItem::class);
|
|
}
|
|
|
|
public function contributorStats(): HasMany
|
|
{
|
|
return $this->hasMany(GroupContributorStat::class);
|
|
}
|
|
|
|
public function badges(): HasMany
|
|
{
|
|
return $this->hasMany(GroupBadge::class);
|
|
}
|
|
|
|
public function memberBadges(): HasMany
|
|
{
|
|
return $this->hasMany(GroupMemberBadge::class);
|
|
}
|
|
|
|
public function discoveryMetric(): HasOne
|
|
{
|
|
return $this->hasOne(GroupDiscoveryMetric::class);
|
|
}
|
|
|
|
public function historyEntries(): HasMany
|
|
{
|
|
return $this->hasMany(GroupHistory::class);
|
|
}
|
|
|
|
public function scopePublic(Builder $query): Builder
|
|
{
|
|
return $query
|
|
->where('visibility', self::VISIBILITY_PUBLIC)
|
|
->where('status', self::LIFECYCLE_ACTIVE);
|
|
}
|
|
|
|
public static function acceptedVisibilityValues(): array
|
|
{
|
|
return [self::VISIBILITY_PUBLIC, self::VISIBILITY_PRIVATE, self::VISIBILITY_UNLISTED];
|
|
}
|
|
|
|
public static function acceptedMembershipPolicies(): array
|
|
{
|
|
return [self::MEMBERSHIP_INVITE_ONLY, self::MEMBERSHIP_REQUEST_TO_JOIN, self::MEMBERSHIP_OPEN];
|
|
}
|
|
|
|
public static function normalizeMemberRole(string $role): string
|
|
{
|
|
$normalized = strtolower(trim($role));
|
|
|
|
return $normalized === self::ROLE_CONTRIBUTOR ? self::ROLE_MEMBER : $normalized;
|
|
}
|
|
|
|
public static function displayRole(?string $role): ?string
|
|
{
|
|
if ($role === null) {
|
|
return null;
|
|
}
|
|
|
|
return self::normalizeMemberRole($role) === self::ROLE_MEMBER ? self::ROLE_CONTRIBUTOR : self::normalizeMemberRole($role);
|
|
}
|
|
|
|
public function isOwnedBy(User|int|null $user): bool
|
|
{
|
|
$userId = $user instanceof User ? $user->id : $user;
|
|
|
|
return $userId !== null && (int) $userId === (int) $this->owner_user_id;
|
|
}
|
|
|
|
public function isPubliclyVisible(): bool
|
|
{
|
|
return in_array($this->visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED], true)
|
|
&& $this->status !== self::LIFECYCLE_SUSPENDED;
|
|
}
|
|
|
|
public function isOperational(): bool
|
|
{
|
|
return $this->status === self::LIFECYCLE_ACTIVE;
|
|
}
|
|
|
|
public function canArchive(User $user): bool
|
|
{
|
|
return $this->isOwnedBy($user);
|
|
}
|
|
|
|
public function canViewStudio(User $user): bool
|
|
{
|
|
if ($this->status === self::LIFECYCLE_SUSPENDED) {
|
|
return false;
|
|
}
|
|
|
|
return $this->hasActiveMember($user);
|
|
}
|
|
|
|
public function activeRoleFor(User|int|null $user): ?string
|
|
{
|
|
$userId = $user instanceof User ? $user->id : $user;
|
|
|
|
if ($userId === null) {
|
|
return null;
|
|
}
|
|
|
|
if ($this->isOwnedBy($userId)) {
|
|
return self::ROLE_OWNER;
|
|
}
|
|
|
|
$members = $this->relationLoaded('members')
|
|
? $this->members
|
|
: $this->members()->where('status', self::STATUS_ACTIVE)->get();
|
|
|
|
return $members
|
|
->first(fn (GroupMember $member): bool => (int) $member->user_id === (int) $userId && $member->status === self::STATUS_ACTIVE)
|
|
?->role;
|
|
}
|
|
|
|
public function hasActiveMember(User|int|null $user): bool
|
|
{
|
|
return $this->activeRoleFor($user) !== null;
|
|
}
|
|
|
|
public function activeMembershipFor(User|int|null $user): ?GroupMember
|
|
{
|
|
$userId = $user instanceof User ? $user->id : $user;
|
|
|
|
if ($userId === null || $this->isOwnedBy($userId)) {
|
|
return null;
|
|
}
|
|
|
|
$members = $this->relationLoaded('members')
|
|
? $this->members
|
|
: $this->members()->where('status', self::STATUS_ACTIVE)->get();
|
|
|
|
return $members->first(
|
|
fn (GroupMember $member): bool => (int) $member->user_id === (int) $userId && $member->status === self::STATUS_ACTIVE
|
|
);
|
|
}
|
|
|
|
public function permissionOverridesFor(User|int|null $user): array
|
|
{
|
|
if ($this->isOwnedBy($user)) {
|
|
return collect(self::allowedPermissionOverrides())
|
|
->mapWithKeys(fn (string $permission): array => [$permission => true])
|
|
->all();
|
|
}
|
|
|
|
$member = $this->activeMembershipFor($user);
|
|
if (! $member) {
|
|
return [];
|
|
}
|
|
|
|
return collect($member->permission_overrides_json ?? [])
|
|
->mapWithKeys(function ($override): array {
|
|
if (is_array($override)) {
|
|
$key = trim((string) ($override['key'] ?? ''));
|
|
if ($key === '' || ! in_array($key, self::allowedPermissionOverrides(), true)) {
|
|
return [];
|
|
}
|
|
|
|
return [$key => (bool) ($override['is_allowed'] ?? false)];
|
|
}
|
|
|
|
$key = trim((string) $override);
|
|
if ($key === '' || ! in_array($key, self::allowedPermissionOverrides(), true)) {
|
|
return [];
|
|
}
|
|
|
|
return [$key => true];
|
|
})
|
|
->all();
|
|
}
|
|
|
|
public function hasPermission(User|int|null $user, string $permission): bool
|
|
{
|
|
return $this->permissionOverridesFor($user)[$permission] ?? false;
|
|
}
|
|
|
|
public function hasDeniedPermission(User|int|null $user, string $permission): bool
|
|
{
|
|
$overrides = $this->permissionOverridesFor($user);
|
|
|
|
return array_key_exists($permission, $overrides) && $overrides[$permission] === false;
|
|
}
|
|
|
|
public static function permissionKeys(): array
|
|
{
|
|
return self::allowedPermissionOverrides();
|
|
}
|
|
|
|
public function canBeViewedBy(?User $user): bool
|
|
{
|
|
if ($this->isPubliclyVisible()) {
|
|
return true;
|
|
}
|
|
|
|
return $user !== null && $this->hasActiveMember($user);
|
|
}
|
|
|
|
public function canManage(User $user): bool
|
|
{
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true);
|
|
}
|
|
|
|
public function canManageMembers(User $user): bool
|
|
{
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true);
|
|
}
|
|
|
|
public function canPublishArtworks(User $user): bool
|
|
{
|
|
return $this->isOperational()
|
|
&& in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true);
|
|
}
|
|
|
|
public function canCreateArtworkDrafts(User $user): bool
|
|
{
|
|
return $this->isOperational() && $this->hasActiveMember($user);
|
|
}
|
|
|
|
public function canSubmitArtworkForReview(User $user): bool
|
|
{
|
|
return $this->isOperational() && $this->hasActiveMember($user);
|
|
}
|
|
|
|
public function canReviewSubmissions(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_REVIEW_SUBMISSIONS)) {
|
|
return false;
|
|
}
|
|
|
|
$role = $this->activeRoleFor($user);
|
|
|
|
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_REVIEW_SUBMISSIONS);
|
|
}
|
|
|
|
public function canRequestJoin(?User $user): bool
|
|
{
|
|
if (! $this->isOperational() || $user === null || $this->hasActiveMember($user)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->membership_policy, [self::MEMBERSHIP_REQUEST_TO_JOIN, self::MEMBERSHIP_OPEN], true);
|
|
}
|
|
|
|
public function canReviewJoinRequests(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_REVIEW_JOIN_REQUESTS)) {
|
|
return false;
|
|
}
|
|
|
|
$role = $this->activeRoleFor($user);
|
|
|
|
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_REVIEW_JOIN_REQUESTS);
|
|
}
|
|
|
|
public function canManageRecruitment(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_RECRUITMENT)) {
|
|
return false;
|
|
}
|
|
|
|
$role = $this->activeRoleFor($user);
|
|
|
|
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_RECRUITMENT);
|
|
}
|
|
|
|
public function canManagePosts(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_POSTS)) {
|
|
return false;
|
|
}
|
|
|
|
$role = $this->activeRoleFor($user);
|
|
|
|
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_POSTS);
|
|
}
|
|
|
|
public function canPublishPosts(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_PUBLISH_POSTS)) {
|
|
return false;
|
|
}
|
|
|
|
$role = $this->activeRoleFor($user);
|
|
|
|
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_PUBLISH_POSTS)
|
|
|| $this->canManagePosts($user);
|
|
}
|
|
|
|
public function canPinPosts(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_PIN_POSTS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_PIN_POSTS);
|
|
}
|
|
|
|
public function canManageMemberPermissions(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_MEMBER_PERMISSIONS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_MEMBER_PERMISSIONS);
|
|
}
|
|
|
|
public function canManageEvents(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_EVENTS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_EVENTS);
|
|
}
|
|
|
|
public function canManageChallenges(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_CHALLENGES)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_CHALLENGES);
|
|
}
|
|
|
|
public function canManageProjects(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_PROJECTS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_PROJECTS);
|
|
}
|
|
|
|
public function canManageReleases(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_RELEASES)) {
|
|
return false;
|
|
}
|
|
|
|
return $this->canManageProjects($user)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_RELEASES);
|
|
}
|
|
|
|
public function canPublishReleases(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_PUBLISH_RELEASES)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_PUBLISH_RELEASES)
|
|
|| $this->canManageReleases($user);
|
|
}
|
|
|
|
public function canManageMilestones(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_MILESTONES)) {
|
|
return false;
|
|
}
|
|
|
|
return $this->canManageProjects($user)
|
|
|| $this->canManageReleases($user)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_MILESTONES);
|
|
}
|
|
|
|
public function canViewReputationDashboard(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_VIEW_REPUTATION_DASHBOARD)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_VIEW_REPUTATION_DASHBOARD);
|
|
}
|
|
|
|
public function canManageBadges(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_BADGES)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_BADGES);
|
|
}
|
|
|
|
public function canViewInternalTrustMetrics(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_VIEW_INTERNAL_TRUST_METRICS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_VIEW_INTERNAL_TRUST_METRICS);
|
|
}
|
|
|
|
public function canFeatureReleases(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_FEATURE_RELEASES)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_FEATURE_RELEASES);
|
|
}
|
|
|
|
public function canAssignReleaseLead(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_ASSIGN_RELEASE_LEAD)) {
|
|
return false;
|
|
}
|
|
|
|
return $this->canManageReleases($user)
|
|
|| $this->hasPermission($user, self::PERMISSION_ASSIGN_RELEASE_LEAD);
|
|
}
|
|
|
|
public function canManageAssets(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_ASSETS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_ASSETS);
|
|
}
|
|
|
|
public function canFeatureChallengeEntries(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_FEATURE_CHALLENGE_ENTRIES)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_FEATURE_CHALLENGE_ENTRIES);
|
|
}
|
|
|
|
public function canPublishEventUpdates(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_PUBLISH_EVENT_UPDATES)) {
|
|
return false;
|
|
}
|
|
|
|
return $this->canManageEvents($user)
|
|
|| $this->hasPermission($user, self::PERMISSION_PUBLISH_EVENT_UPDATES);
|
|
}
|
|
|
|
public function canAttachAssetsToProjects(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_ATTACH_ASSETS_TO_PROJECTS)) {
|
|
return false;
|
|
}
|
|
|
|
return $this->canManageProjects($user)
|
|
|| $this->hasPermission($user, self::PERMISSION_ATTACH_ASSETS_TO_PROJECTS);
|
|
}
|
|
|
|
public function canViewInternalAssets(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_VIEW_INTERNAL_ASSETS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_VIEW_INTERNAL_ASSETS);
|
|
}
|
|
|
|
public function canPinActivity(User $user): bool
|
|
{
|
|
if (! $this->isOperational()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_ACTIVITY_PINS)) {
|
|
return false;
|
|
}
|
|
|
|
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
|
|| $this->hasPermission($user, self::PERMISSION_MANAGE_ACTIVITY_PINS);
|
|
}
|
|
|
|
public static function allowedPermissionOverrides(): array
|
|
{
|
|
return [
|
|
self::PERMISSION_REVIEW_JOIN_REQUESTS,
|
|
self::PERMISSION_REVIEW_SUBMISSIONS,
|
|
self::PERMISSION_MANAGE_RECRUITMENT,
|
|
self::PERMISSION_MANAGE_POSTS,
|
|
self::PERMISSION_PUBLISH_POSTS,
|
|
self::PERMISSION_PIN_POSTS,
|
|
self::PERMISSION_MANAGE_MEMBER_PERMISSIONS,
|
|
self::PERMISSION_MANAGE_EVENTS,
|
|
self::PERMISSION_MANAGE_CHALLENGES,
|
|
self::PERMISSION_MANAGE_PROJECTS,
|
|
self::PERMISSION_MANAGE_RELEASES,
|
|
self::PERMISSION_PUBLISH_RELEASES,
|
|
self::PERMISSION_MANAGE_MILESTONES,
|
|
self::PERMISSION_VIEW_REPUTATION_DASHBOARD,
|
|
self::PERMISSION_MANAGE_BADGES,
|
|
self::PERMISSION_VIEW_INTERNAL_TRUST_METRICS,
|
|
self::PERMISSION_FEATURE_RELEASES,
|
|
self::PERMISSION_ASSIGN_RELEASE_LEAD,
|
|
self::PERMISSION_MANAGE_ASSETS,
|
|
self::PERMISSION_FEATURE_CHALLENGE_ENTRIES,
|
|
self::PERMISSION_PUBLISH_EVENT_UPDATES,
|
|
self::PERMISSION_ATTACH_ASSETS_TO_PROJECTS,
|
|
self::PERMISSION_VIEW_INTERNAL_ASSETS,
|
|
self::PERMISSION_MANAGE_ACTIVITY_PINS,
|
|
];
|
|
}
|
|
|
|
public function canManageCollections(User $user): bool
|
|
{
|
|
return $this->isOperational() && $this->canPublishArtworks($user);
|
|
}
|
|
|
|
public function avatarUrl(): ?string
|
|
{
|
|
return $this->assetUrl($this->avatar_path);
|
|
}
|
|
|
|
public function bannerUrl(): ?string
|
|
{
|
|
return $this->assetUrl($this->banner_path);
|
|
}
|
|
|
|
public function publicUrl(): string
|
|
{
|
|
return route('groups.show', ['group' => $this->slug]);
|
|
}
|
|
|
|
public function shouldBeSearchable(): bool
|
|
{
|
|
return $this->visibility === self::VISIBILITY_PUBLIC && $this->status === self::LIFECYCLE_ACTIVE;
|
|
}
|
|
|
|
public function toSearchableArray(): array
|
|
{
|
|
$recruitment = $this->relationLoaded('recruitmentProfile')
|
|
? $this->recruitmentProfile
|
|
: $this->recruitmentProfile()->first();
|
|
|
|
$memberNames = $this->members()
|
|
->with('user:id,name,username')
|
|
->where('status', self::STATUS_ACTIVE)
|
|
->limit(12)
|
|
->get()
|
|
->map(fn (GroupMember $member): string => (string) ($member->user?->name ?: $member->user?->username ?: ''))
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
return [
|
|
'id' => (int) $this->id,
|
|
'name' => (string) $this->name,
|
|
'slug' => (string) $this->slug,
|
|
'headline' => (string) ($this->headline ?? ''),
|
|
'bio' => (string) ($this->bio ?? ''),
|
|
'type' => (string) ($this->type ?? ''),
|
|
'visibility' => (string) $this->visibility,
|
|
'status' => (string) ($this->status ?? self::LIFECYCLE_ACTIVE),
|
|
'artworks_count' => (int) ($this->artworks_count ?? 0),
|
|
'followers_count' => (int) ($this->followers_count ?? 0),
|
|
'is_recruiting' => (bool) ($recruitment?->is_recruiting ?? false),
|
|
'recruitment_headline' => (string) ($recruitment?->headline ?? ''),
|
|
'recruitment_roles' => array_values(array_filter($recruitment?->roles_json ?? [])),
|
|
'recruitment_skills' => array_values(array_filter($recruitment?->skills_json ?? [])),
|
|
'release_titles' => $this->releases()->where('visibility', GroupRelease::VISIBILITY_PUBLIC)->latest('published_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
|
'project_titles' => $this->projects()->where('visibility', GroupProject::VISIBILITY_PUBLIC)->latest('updated_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
|
'challenge_titles' => $this->challenges()->where('visibility', GroupChallenge::VISIBILITY_PUBLIC)->latest('updated_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
|
'event_titles' => $this->events()->where('visibility', GroupEvent::VISIBILITY_PUBLIC)->latest('start_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
|
'badge_keys' => $this->badges()->latest('awarded_at')->limit(6)->pluck('badge_key')->filter()->values()->all(),
|
|
'member_names' => $memberNames,
|
|
];
|
|
}
|
|
|
|
private function assetUrl(?string $path): ?string
|
|
{
|
|
$trimmed = trim((string) $path);
|
|
|
|
if ($trimmed === '') {
|
|
return null;
|
|
}
|
|
|
|
if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) {
|
|
return $trimmed;
|
|
}
|
|
|
|
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($trimmed, '/');
|
|
}
|
|
} |