*/ protected $casts = [ 'published' => 'boolean', 'signup_open_at' => 'datetime', 'signup_close_at' => 'datetime', 'branding_json' => 'array', 'settings' => 'array', 'scoring_rules_json' => 'array', ]; public function tracks(): HasMany { return $this->hasMany(CompetitionTrack::class)->orderBy('sort'); } public function signupChannels(): HasMany { return $this->hasMany(SignupChannel::class)->orderByDesc('id'); } public function audienceSessions(): HasMany { return $this->hasMany(AudienceSession::class)->orderBy('sort')->orderBy('id'); } public function competitionAdmins(): HasMany { return $this->hasMany(CompetitionAdmin::class); } /** 嵌套路由 competitions.admins 的绑定关系名 */ public function admins(): HasMany { return $this->competitionAdmins(); } public function audienceRegistrations(): HasMany { return $this->hasMany(AudienceRegistration::class); } public function formSchemaDefinitions(): HasMany { return $this->hasMany(FormSchemaDefinition::class, 'competition_id'); } /** * 嵌套路由参数 `{form_schema}` 的隐式绑定约定关系名为 formSchemas(见 Model::childRouteBindingRelationshipName)。 */ public function formSchemas(): HasMany { return $this->formSchemaDefinitions(); } public function formSchema(): BelongsTo { return $this->belongsTo(FormSchemaDefinition::class, 'form_schema_id'); } public function reviewFormSchema(): BelongsTo { return $this->belongsTo(FormSchemaDefinition::class, 'review_form_schema_id'); } public function audienceFormSchema(): BelongsTo { return $this->belongsTo(FormSchemaDefinition::class, 'audience_form_schema_id'); } /** * 评审端是否开放登录/访问。settings.review_portal.enabled 缺省或非 false 时视为开启。 */ public function isReviewPortalEnabled(): bool { $portal = $this->reviewPortalSettings(); if ($portal === null || ! array_key_exists('enabled', $portal)) { return true; } return $portal['enabled'] !== false && $portal['enabled'] !== 0 && $portal['enabled'] !== '0' && $portal['enabled'] !== 'false'; } /** * 关闭评审端时展示给评审员的说明(登录弹窗 / 接口提示)。 */ public function reviewPortalClosedMessage(): string { $portal = $this->reviewPortalSettings(); $message = is_array($portal) ? trim((string) ($portal['message'] ?? '')) : ''; if ($message === '') { return '评审通道暂未开放,请稍后再试'; } return mb_substr($message, 0, 2000); } /** * 公开接口用的评审端开关摘要。 * * @return array{enabled: bool, message: string} */ public function reviewPortalPublicPayload(): array { $enabled = $this->isReviewPortalEnabled(); $portal = $this->reviewPortalSettings(); $stored = is_array($portal) ? trim((string) ($portal['message'] ?? '')) : ''; return [ 'enabled' => $enabled, 'message' => $enabled ? mb_substr($stored, 0, 2000) : $this->reviewPortalClosedMessage(), ]; } /** * @return array|null */ private function reviewPortalSettings(): ?array { $settings = is_array($this->settings) ? $this->settings : []; $portal = $settings['review_portal'] ?? null; return is_array($portal) ? $portal : null; } /** * 报名截止后是否开启「指定用户仅可补传/删除附件」。 */ public function isPostCloseFileEditEnabled(): bool { $cfg = $this->postCloseFileEditSettings(); if ($cfg === null || ! array_key_exists('enabled', $cfg)) { return false; } return $cfg['enabled'] === true || $cfg['enabled'] === 1 || $cfg['enabled'] === '1' || $cfg['enabled'] === 'true'; } /** * @return list 规范化后的登录手机号白名单 */ public function postCloseFileEditMobiles(): array { $cfg = $this->postCloseFileEditSettings(); $raw = is_array($cfg) ? ($cfg['user_mobiles'] ?? null) : null; if (! is_array($raw)) { return []; } $out = []; foreach ($raw as $item) { $mobile = self::normalizeAllowlistMobile($item); // 勿用手机号做关联数组 key:PHP 会把纯数字字符串键转成 int,导致严格比较失败 if ($mobile !== '' && ! in_array($mobile, $out, true)) { $out[] = $mobile; } } return $out; } public function allowsPostCloseFileEditForMobile(?string $mobile): bool { if (! $this->isPostCloseFileEditEnabled()) { return false; } $normalized = self::normalizeAllowlistMobile($mobile); if ($normalized === '') { return false; } return in_array($normalized, $this->postCloseFileEditMobiles(), true); } public static function normalizeAllowlistMobile(mixed $mobile): string { $s = preg_replace('/\s+/', '', trim((string) $mobile)) ?? ''; return $s; } /** * @return array|null */ private function postCloseFileEditSettings(): ?array { $settings = is_array($this->settings) ? $this->settings : []; $cfg = $settings['post_close_file_edit'] ?? null; return is_array($cfg) ? $cfg : null; } }