You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
10 KiB
282 lines
10 KiB
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\Application;
|
|
use App\Models\AudienceRegistration;
|
|
use App\Models\Competition;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Http\Request;
|
|
|
|
final class SignupSchemaLayout
|
|
{
|
|
/** @var list<string> */
|
|
private const SKIP_KEYS = ['commitment_accepted', 'plan', 'supporting', 'promise_signature'];
|
|
|
|
/** @var list<string> */
|
|
private const SKIP_TYPES = ['file'];
|
|
|
|
/** @var list<string> */
|
|
private const AUDIENCE_COLUMNS = ['name', 'phone', 'company', 'job', 'purpose'];
|
|
|
|
/**
|
|
* @param list<mixed>|null $schemaJson
|
|
* @return list<array{key: string, type: string, label: string, list: bool, filter: bool, options: list<array{value: string, label: string}>}>
|
|
*/
|
|
public static function fields(?array $schemaJson): array
|
|
{
|
|
if (! is_array($schemaJson)) {
|
|
return [];
|
|
}
|
|
$out = [];
|
|
foreach ($schemaJson as $row) {
|
|
if (! is_array($row)) {
|
|
continue;
|
|
}
|
|
$key = trim((string) ($row['key'] ?? ''));
|
|
$type = trim((string) ($row['type'] ?? ''));
|
|
if ($key === '' || in_array($key, self::SKIP_KEYS, true) || in_array($type, self::SKIP_TYPES, true)) {
|
|
continue;
|
|
}
|
|
$label = trim((string) ($row['label'] ?? $key));
|
|
$options = [];
|
|
if (($row['options'] ?? null) && is_array($row['options'])) {
|
|
foreach ($row['options'] as $opt) {
|
|
if (! is_array($opt)) {
|
|
continue;
|
|
}
|
|
$value = trim((string) ($opt['value'] ?? ''));
|
|
if ($value === '') {
|
|
continue;
|
|
}
|
|
$options[] = [
|
|
'value' => $value,
|
|
'label' => trim((string) ($opt['label'] ?? $value)),
|
|
];
|
|
}
|
|
}
|
|
$out[] = [
|
|
'key' => $key,
|
|
'type' => $type !== '' ? $type : 'text',
|
|
'label' => $label !== '' ? $label : $key,
|
|
'list' => ($row['list'] ?? false) === true,
|
|
'filter' => ($row['filter'] ?? false) === true,
|
|
'options' => $options,
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @param list<mixed>|null $schemaJson
|
|
* @return list<array{key: string, type: string, label: string, list: bool, filter: bool, options: list<array{value: string, label: string}>}>
|
|
*/
|
|
public static function listFields(?array $schemaJson): array
|
|
{
|
|
return array_values(array_filter(self::fields($schemaJson), fn (array $f) => $f['list']));
|
|
}
|
|
|
|
/**
|
|
* @param list<mixed>|null $schemaJson
|
|
* @return list<array{key: string, type: string, label: string, list: bool, filter: bool, options: list<array{value: string, label: string}>}>
|
|
*/
|
|
public static function filterFields(?array $schemaJson): array
|
|
{
|
|
return array_values(array_filter(
|
|
self::fields($schemaJson),
|
|
fn (array $f) => $f['filter'] && ($f['type'] === 'select' || in_array($f['key'], ['track', 'purpose'], true))
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 管理端筛选项(中文标签);赛道/场次补全选项。
|
|
*
|
|
* @return list<array{key: string, type: string, label: string, options: list<array{value: string, label: string}>}>
|
|
*/
|
|
public static function adminFilterFields(Competition $competition, string $purpose = 'signup'): array
|
|
{
|
|
$schema = $purpose === 'audience'
|
|
? ($competition->audienceFormSchema?->schema_json)
|
|
: ($competition->formSchema?->schema_json);
|
|
$fields = self::filterFields(is_array($schema) ? $schema : null);
|
|
$out = [];
|
|
foreach ($fields as $field) {
|
|
$options = $field['options'];
|
|
if ($field['key'] === 'track') {
|
|
if ($options === []) {
|
|
$competition->loadMissing('tracks');
|
|
foreach ($competition->tracks as $track) {
|
|
$options[] = [
|
|
'value' => (string) $track->track_code,
|
|
'label' => (string) $track->title,
|
|
];
|
|
}
|
|
}
|
|
$hasOther = false;
|
|
foreach ($options as $opt) {
|
|
if (($opt['value'] ?? '') === SignupDisplay::TRACK_OTHER_CODE) {
|
|
$hasOther = true;
|
|
break;
|
|
}
|
|
}
|
|
if (! $hasOther) {
|
|
$options[] = [
|
|
'value' => SignupDisplay::TRACK_OTHER_CODE,
|
|
'label' => '其他',
|
|
];
|
|
}
|
|
}
|
|
if ($purpose === 'audience' && $field['key'] === 'audience_session_id' && $options === []) {
|
|
$competition->loadMissing('audienceSessions');
|
|
foreach ($competition->audienceSessions as $session) {
|
|
$options[] = [
|
|
'value' => (string) $session->id,
|
|
'label' => (string) $session->name,
|
|
];
|
|
}
|
|
}
|
|
$out[] = [
|
|
'key' => $field['key'],
|
|
'type' => $field['type'],
|
|
'label' => $field['label'],
|
|
'options' => $options,
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @return list<array{key: string, type: string, label: string}>
|
|
*/
|
|
public static function adminListFields(Competition $competition, string $purpose = 'signup'): array
|
|
{
|
|
$schema = $purpose === 'audience'
|
|
? ($competition->audienceFormSchema?->schema_json)
|
|
: ($competition->formSchema?->schema_json);
|
|
$out = [];
|
|
foreach (self::listFields(is_array($schema) ? $schema : null) as $field) {
|
|
$out[] = [
|
|
'key' => $field['key'],
|
|
'type' => $field['type'],
|
|
'label' => $field['label'],
|
|
];
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $filters
|
|
*/
|
|
public static function extractFilterValues(Request $request, Competition $competition, string $purpose = 'signup'): array
|
|
{
|
|
$out = [];
|
|
foreach (self::adminFilterFields($competition, $purpose) as $field) {
|
|
$key = $field['key'];
|
|
if (! $request->exists($key)) {
|
|
continue;
|
|
}
|
|
$value = trim((string) $request->input($key));
|
|
if ($value !== '') {
|
|
$out[$key] = $value;
|
|
}
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @param Builder<\App\Models\Application|\App\Models\AudienceRegistration> $query
|
|
* @param array<string, mixed> $filters
|
|
* @param list<string> $alreadyApplied
|
|
*/
|
|
public static function applyFilters(
|
|
Builder $query,
|
|
Competition $competition,
|
|
array $filters,
|
|
string $purpose = 'signup',
|
|
array $alreadyApplied = ['track', 'event_location', 'purpose'],
|
|
): void {
|
|
$schema = $purpose === 'audience'
|
|
? ($competition->audienceFormSchema?->schema_json)
|
|
: ($competition->formSchema?->schema_json);
|
|
$columnKeys = $purpose === 'audience'
|
|
? array_flip(self::AUDIENCE_COLUMNS)
|
|
: array_flip(SignupApplicationFieldRegistry::scalarColumnKeys());
|
|
$skip = array_flip($alreadyApplied);
|
|
|
|
foreach (self::filterFields(is_array($schema) ? $schema : null) as $field) {
|
|
$key = $field['key'];
|
|
if (isset($skip[$key])) {
|
|
continue;
|
|
}
|
|
$value = trim((string) ($filters[$key] ?? ''));
|
|
if ($value === '') {
|
|
continue;
|
|
}
|
|
if ($purpose === 'signup' && in_array($key, ['degree', 'location_country', 'event_location'], true)) {
|
|
$value = SignupOptionCatalog::normalize($key, $value) ?: $value;
|
|
}
|
|
if ($purpose === 'audience' && $key === 'purpose') {
|
|
$value = SignupOptionCatalog::normalize('purpose', $value) ?: $value;
|
|
}
|
|
if (isset($columnKeys[$key])) {
|
|
$query->where($key, $value);
|
|
} else {
|
|
$query->where('answers_json->'.$key, $value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function applicationDisplayMap(Competition $competition, Application $app): array
|
|
{
|
|
$competition->loadMissing('formSchema');
|
|
$schema = $competition->formSchema?->schema_json;
|
|
$schemaArr = is_array($schema) ? $schema : null;
|
|
$out = [];
|
|
foreach (self::listFields($schemaArr) as $field) {
|
|
$out[$field['key']] = SignupDisplay::applicationFieldLabel($competition, $app, $field['key'], $schemaArr);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function audienceDisplayMap(Competition $competition, AudienceRegistration $row): array
|
|
{
|
|
$competition->loadMissing('audienceFormSchema');
|
|
$schema = $competition->audienceFormSchema?->schema_json;
|
|
$schemaArr = is_array($schema) ? $schema : null;
|
|
$locale = SignupDisplay::audienceLocale((int) $row->user_id, (int) $row->competition_id);
|
|
$out = [];
|
|
foreach (self::listFields($schemaArr) as $field) {
|
|
$key = $field['key'];
|
|
$raw = SignupDisplay::audienceFieldValue($row, $key);
|
|
$value = is_scalar($raw) ? trim((string) $raw) : '';
|
|
if ($value === '') {
|
|
$out[$key] = '';
|
|
continue;
|
|
}
|
|
if ($key === 'purpose') {
|
|
$out[$key] = SignupDisplay::purposeLabel($value, $locale);
|
|
continue;
|
|
}
|
|
$fieldDef = LocalizedSchema::fieldByKey($schemaArr, $key);
|
|
if ($fieldDef !== null && ($fieldDef['type'] ?? '') === 'select') {
|
|
$out[$key] = LocalizedSchema::optionLabelForValue($fieldDef, $value, $locale);
|
|
continue;
|
|
}
|
|
$out[$key] = $value;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
}
|