*/ public const FILE_FIELD_KEYS = ['plan', 'supporting']; /** 无专属类型约束时,标量字段允许的 schema type */ private const GENERIC_SCALAR_TYPES = [ 'text', 'textarea', 'email', 'tel', 'select', 'number', 'date', ]; /** * 可选:为常用字段收窄允许的 schema type;未列出的 fillable 列使用 GENERIC_SCALAR_TYPES。 * * @var array> */ private const SCALAR_TYPE_HINTS = [ 'player_name' => ['text'], 'school' => ['text'], 'degree' => ['select'], 'contact_email' => ['email', 'text'], 'contact_mobile' => ['tel', 'text'], 'company_name' => ['text'], 'project_name' => ['text'], 'track' => ['select'], 'location_country' => ['select'], 'location_province' => ['select', 'text'], 'location_city' => ['select', 'text'], 'oversea_country' => ['text'], 'event_location' => ['select', 'text'], 'team_members' => ['text', 'textarea'], 'intro' => ['textarea', 'text'], 'recommend' => ['text', 'textarea'], ]; /** * @return list */ public static function scalarColumnKeys(): array { return Application::signupScalarFillableKeys(); } /** * @return list */ public static function allSupportedFormKeys(): array { return array_values(array_unique([ ...self::scalarColumnKeys(), ...self::FILE_FIELD_KEYS, self::COMMITMENT_KEY, ])); } /** * 供管理端报名表编辑器拉取:与后端 fillable 实时同步。 * * @return array{ * scalar_field_keys: list, * file_field_keys: list, * commitment_field_key: string, * scalar_type_hints: array>, * generic_scalar_types: list, * all_supported_form_keys: list * } */ public static function manifest(): array { $hints = []; foreach (self::scalarColumnKeys() as $key) { if (isset(self::SCALAR_TYPE_HINTS[$key])) { $hints[$key] = self::SCALAR_TYPE_HINTS[$key]; } } return [ 'scalar_field_keys' => self::scalarColumnKeys(), 'file_field_keys' => self::FILE_FIELD_KEYS, 'commitment_field_key' => self::COMMITMENT_KEY, 'scalar_type_hints' => $hints, 'generic_scalar_types' => self::GENERIC_SCALAR_TYPES, 'all_supported_form_keys' => self::allSupportedFormKeys(), ]; } /** * @param array|null $schemaJson * @return list */ public static function keysFromSchemaJson(?array $schemaJson): array { if (! is_array($schemaJson)) { return []; } $keys = []; foreach ($schemaJson as $row) { if (! is_array($row)) { continue; } $key = trim((string) ($row['key'] ?? '')); if ($key !== '') { $keys[] = $key; } } return array_values(array_unique($keys)); } public static function schemaHasKey(Competition $competition, string $key): bool { $competition->loadMissing('formSchema'); $rows = $competition->formSchema?->schema_json; return in_array($key, self::keysFromSchemaJson(is_array($rows) ? $rows : null), true); } /** * @return list */ public static function allowedTypesForScalarKey(string $key): array { return self::SCALAR_TYPE_HINTS[$key] ?? self::GENERIC_SCALAR_TYPES; } /** * @param array $schemaJson */ public static function validateSignupSchemaJson(array $schemaJson): ?string { $seen = []; $scalarKeys = array_flip(self::scalarColumnKeys()); foreach ($schemaJson as $index => $row) { if (! is_array($row)) { return '报名表字段定义第 '.($index + 1).' 项格式无效'; } $key = trim((string) ($row['key'] ?? '')); $type = trim((string) ($row['type'] ?? '')); $label = trim((string) ($row['label'] ?? '')); if ($key === '') { return '报名表存在未填写字段 key 的项(须与 applications 表列名一致)'; } if ($label === '') { return '字段「'.$key.'」缺少显示标签'; } if (isset($seen[$key])) { return '字段 key「'.$key.'」重复'; } $seen[$key] = true; if ($key === self::COMMITMENT_KEY) { if ($type !== 'checkbox') { return '参赛承诺书字段须为 checkbox 类型(key='.self::COMMITMENT_KEY.')'; } continue; } if (in_array($key, self::FILE_FIELD_KEYS, true)) { if ($type !== 'file') { return '附件字段「'.$key.'」须为 file 类型'; } continue; } if (! isset($scalarKeys[$key])) { if (! in_array($type, self::GENERIC_SCALAR_TYPES, true)) { return '自定义字段「'.$key.'」类型须为:'.implode(' / ', self::GENERIC_SCALAR_TYPES); } continue; } $allowedTypes = self::allowedTypesForScalarKey($key); if ($type === '' || ! in_array($type, $allowedTypes, true)) { return '字段「'.$key.'」类型须为:'.implode(' / ', $allowedTypes); } } return null; } /** * @return array */ public static function scalarAttributesFromApplication(Application $app): array { $out = []; foreach (self::scalarColumnKeys() as $key) { $out[$key] = $app->{$key}; } return $out; } /** * @param array $data * @return array */ public static function filterFillableScalarData(array $data): array { $allowed = array_flip(self::scalarColumnKeys()); $out = []; foreach ($data as $key => $value) { if (isset($allowed[$key])) { $out[$key] = $value; } } return $out; } /** * @param array $data * @return array */ public static function filterAnswersJsonData(array $data): array { $columnKeys = array_flip(self::scalarColumnKeys()); $out = []; foreach ($data as $key => $value) { if (! is_string($key) || $key === '' || isset($columnKeys[$key])) { continue; } if (in_array($key, self::FILE_FIELD_KEYS, true) || $key === self::COMMITMENT_KEY || $key === self::PROMISE_SIGNATURE_KEY) { continue; } $out[$key] = $value; } return $out; } }