'boolean', 'is_secret' => 'boolean', 'views' => 'integer', ]; protected $attributes = [ 'is_notice' => false, 'is_secret' => false, 'views' => 0, 'status' => 'published', 'editor_type' => 'wysiwyg', ]; // ========================================================================= // Scopes // ========================================================================= /** * 특정 게시판의 글 */ public function scopeOfBoard(Builder $query, int $boardId): Builder { return $query->where('board_id', $boardId); } /** * 공지사항만 */ public function scopeNotices(Builder $query): Builder { return $query->where('is_notice', true); } /** * 일반 글만 (공지 제외) */ public function scopeRegular(Builder $query): Builder { return $query->where('is_notice', false); } /** * 게시된 글만 */ public function scopePublished(Builder $query): Builder { return $query->where('status', 'published'); } /** * 비밀글이 아닌 것만 */ public function scopePublic(Builder $query): Builder { return $query->where('is_secret', false); } // ========================================================================= // Relationships // ========================================================================= public function board(): BelongsTo { return $this->belongsTo(Board::class, 'board_id'); } public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class, 'tenant_id'); } public function author(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } public function customFieldValues(): HasMany { return $this->hasMany(PostCustomFieldValue::class, 'post_id'); } /** * 첨부파일 (document_id + document_type 기반) */ public function files(): HasMany { return $this->hasMany(File::class, 'document_id') ->where('document_type', 'post'); } /** * 댓글 (활성 상태만) */ public function comments(): HasMany { return $this->hasMany(BoardComment::class, 'post_id') ->where('status', 'active'); } /** * 모든 댓글 (상태 무관) */ public function allComments(): HasMany { return $this->hasMany(BoardComment::class, 'post_id'); } // ========================================================================= // Helper Methods // ========================================================================= /** * 조회수 증가 */ public function incrementViews(): void { $this->increment('views'); } /** * 커스텀 필드 값 가져오기 */ public function getCustomFieldValue(string $fieldKey): ?string { $fieldValue = $this->customFieldValues() ->whereHas('field', fn ($q) => $q->where('field_key', $fieldKey)) ->first(); return $fieldValue?->value; } /** * 커스텀 필드 값들을 배열로 가져오기 */ public function getCustomFieldsArray(): array { return $this->customFieldValues() ->with('field') ->get() ->mapWithKeys(fn ($v) => [$v->field->field_key => $v->value]) ->toArray(); } /** * 비밀글 접근 가능 여부 */ public function canAccess(?User $user): bool { // 비밀글이 아니면 모두 접근 가능 if (! $this->is_secret) { return true; } // 비로그인이면 접근 불가 if (! $user) { return false; } // 작성자 본인이면 접근 가능 if ($this->user_id === $user->id) { return true; } // 관리자면 접근 가능 if ($user->hasRole(['admin', 'super-admin'])) { return true; } return false; } /** * 안전한 HTML 콘텐츠 반환 (XSS 방지) * - 허용된 태그만 남기고 나머지 제거 * - 위험한 속성 (onclick, onerror 등) 제거 */ public function getSafeHtmlContent(): string { $content = $this->content ?? ''; // 허용할 HTML 태그 $allowedTags = '


' .'