'; } // ---------------------------------------------------------------------------- // CONTENT ROUTER // ---------------------------------------------------------------------------- function vs01_content() { if (function_exists('head_add_css')) { head_add_css('/addon/vs01/view/css/vs01.css'); } if (function_exists('head_add_js')) { head_add_js('/addon/vs01/view/js/vs01.js'); } $association_slug = argv(1) ?? ''; $vs_code = strtoupper(argv(2) ?? ''); // Index — list registered associations if (!$association_slug) { return vs01_render_index(); } $config = vs01_load_config(); if (!isset($config['associations'][$association_slug])) { return vs01_render_not_found(); } $access = vs01_access_state($association_slug); if (vs01_is_professional()) { $access = 'professional'; } // Manage route — operator only if ($vs_code === 'MANAGE') { if ($access !== 'operator') { return vs01_access_wall($association_slug); } return vs01_render_manage($association_slug); } // VS form route if ($vs_code && preg_match('/^VS-\d{2}$/', $vs_code)) { if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($access === 'public') { return vs01_access_wall($association_slug); } if (!vs01_verify_csrf()) { return '
Invalid form token. Please reload and try again.
'; } return vs01_handle_post($association_slug, $vs_code, $access); } return vs01_render_vs_form($association_slug, $vs_code, $access); } // Association landing return vs01_render_landing($association_slug, $access); } // ---------------------------------------------------------------------------- // RENDER — INDEX // ---------------------------------------------------------------------------- function vs01_render_index() { $config = vs01_load_config(); $associations = $config['associations'] ?? []; if (empty($associations)) { return '

No associations registered.

'; } $out = '
'; $out .= '

Vital Signs

'; $out .= '

Select an association to view or submit its diagnostic record.

'; $out .= '
'; return $out; } // ---------------------------------------------------------------------------- // RENDER — ASSOCIATION LANDING // ---------------------------------------------------------------------------- function vs01_render_landing($association_slug, $access) { $config = vs01_load_config(); $assoc = $config['associations'][$association_slug]; $name = vs01_h($assoc['name'] ?? $association_slug); $schemas = vs01_load_schemas(); $out = '
'; $out .= '
'; $out .= '

' . $name . '

'; $out .= '

Vital Signs diagnostic record.

'; $out .= '
'; // VS navigation $out .= ''; if ($access === 'operator') { $manage_url = z_root() . '/vs01/' . vs01_h($association_slug) . '/manage'; $out .= '
Manage TMP
'; } $out .= '
'; return $out; } // ---------------------------------------------------------------------------- // RENDER — VS FORM // ---------------------------------------------------------------------------- function vs01_render_vs_form($association_slug, $vs_code, $access) { $schemas = vs01_load_schemas(); if (!isset($schemas[$vs_code])) { return '
VS code ' . vs01_h($vs_code) . ' not found.
'; } $schema = $schemas[$vs_code]; $config = vs01_load_config(); $assoc = $config['associations'][$association_slug]; $name = vs01_h($assoc['name'] ?? $association_slug); // Determine perspective from access state $perspective_map = [ 'participant' => 'homeowner', 'professional' => 'professional', 'operator' => 'public_record', 'public' => 'homeowner', ]; $perspective_key = $perspective_map[$access] ?? 'homeowner'; $perspective = $schema['perspectives'][$perspective_key] ?? null; if (!$perspective) { return '
No perspective defined for access state: ' . vs01_h($access) . '
'; } $form_url = z_root() . '/vs01/' . vs01_h($association_slug) . '/' . vs01_h($vs_code); $meta = $schema['_meta']; $out = '
'; // Breadcrumb $out .= ''; // VS header $out .= '
'; $out .= '

' . vs01_h($vs_code) . ' — ' . vs01_h($meta['title'] ?? '') . '

'; $out .= '

' . vs01_h($meta['diagnostic_question'] ?? '') . '

'; $out .= '
'; // Compound condition banners $out .= vs01_render_compound_banners($association_slug); // Verification sources $out .= vs01_render_verification_sources($meta); // Perspective note for VS-07 and VS-09 homeowner slot $out .= vs01_render_perspective_note($schema, $perspective_key); // Public access — show read-only content, no form if ($access === 'public') { $out .= '
'; $out .= 'Vital Signs are public. '; $out .= 'Complete the SASE process '; $out .= 'to submit a record for this association.'; $out .= '
'; $out .= '
'; return $out; } // Immutable check for public record perspective if ($perspective_key === 'public_record' && ($perspective['immutable_after_submit'] ?? false) && vs01_public_record_exists($vs_code, $association_slug)) { $out .= '
'; $out .= 'Public record — immutable. Contact the operator to correct spelling or citation errors.'; $out .= '
'; $out .= vs01_render_public_record_readonly($perspective, []); $out .= ''; return $out; } // Form $out .= '

' . vs01_h($perspective['instruction'] ?? '') . '

'; $out .= '
'; $out .= vs01_csrf_token(); $out .= ''; $out .= ''; foreach ($perspective['fields'] ?? [] as $field) { $field_html = vs01_render_field($field, []); if (!empty($field['depends_on'])) { $field_html = vs01_wrap_depends_on($field, $field_html); } $out .= $field_html; } // THROWAWAY — bare minimum confirmation target; replace with full TMP workflow $out .= '
'; $out .= ''; $out .= '
'; $out .= '
'; // VS navigation — previous / next $out .= vs01_render_vs_nav($association_slug, $vs_code); $out .= ''; return $out; } // ---------------------------------------------------------------------------- // RENDER — VS NAVIGATION // ---------------------------------------------------------------------------- function vs01_render_vs_nav($association_slug, $current_code) { $schemas = vs01_load_schemas(); $codes = array_keys($schemas); $idx = array_search($current_code, $codes, true); $base = z_root() . '/vs01/' . vs01_h($association_slug) . '/'; $out = ''; return $out; } // ---------------------------------------------------------------------------- // RENDER — MANAGE (TMP REVIEW) // ---------------------------------------------------------------------------- function vs01_render_manage($association_slug) { $config = vs01_load_config(); $assoc = $config['associations'][$association_slug]; $name = vs01_h($assoc['name'] ?? $association_slug); $out = '
'; $out .= '

Manage — ' . $name . '

'; // TODO: load TMP submissions from orchestrator and render review interface $out .= '

TMP review forthcoming.

'; $out .= '
'; return $out; } // ---------------------------------------------------------------------------- // RENDER — NOT FOUND // ---------------------------------------------------------------------------- function vs01_render_not_found() { return '
Association not found.
'; } // ---------------------------------------------------------------------------- // CONFIG // ---------------------------------------------------------------------------- function vs01_load_config() { $path = 'addon/vs01/config.json'; $raw = @file_get_contents($path); if ($raw === false) return []; $data = json_decode($raw, true); return (json_last_error() === JSON_ERROR_NONE) ? $data : []; } // ---------------------------------------------------------------------------- // LISTINGS // ---------------------------------------------------------------------------- function vs01_load_listings() { $config = vs01_load_config(); $path = $config['listings_file'] ?? 'addon/vs01/listings.json'; $raw = @file_get_contents($path); if ($raw === false) { return ['core' => [], 'tier1' => [], 'tier2' => [], 'other' => []]; } $data = json_decode($raw, true); if (json_last_error() !== JSON_ERROR_NONE) { return ['core' => [], 'tier1' => [], 'tier2' => [], 'other' => []]; } return $data; } // ---------------------------------------------------------------------------- // CSRF // ---------------------------------------------------------------------------- function vs01_csrf_token() { if (empty($_SESSION['vs01_csrf'])) { $_SESSION['vs01_csrf'] = bin2hex(random_bytes(16)); } return ''; } function vs01_verify_csrf() { return isset($_POST['vs01_csrf'], $_SESSION['vs01_csrf']) && hash_equals($_SESSION['vs01_csrf'], $_POST['vs01_csrf']); }