load_listings(); return $this->render_directory($listings); } // ---------------------------------------------------------------------------- // DIRECTORY // ---------------------------------------------------------------------------- private function render_directory($listings) { $out = '
'; // Horizontal Bootstrap nav-tabs $out .= ''; // Tab panes $out .= '
'; $out .= '
'; $out .= $this->render_core_tab($listings['core'] ?? []); $out .= '
'; $out .= '
'; $out .= $this->render_tier_tab($listings['tier1'] ?? [], 'Mediators, subject matter experts, advisors, and authors will be listed here.'); $out .= '
'; $out .= '
'; $out .= $this->render_tier_tab($listings['tier2'] ?? [], 'Attorneys who have concluded HOA homeowner cases in Illinois will be listed here.'); $out .= '
'; $out .= '
'; $out .= $this->render_tier_tab($listings['other'] ?? [], 'Civic organizations, consumer protection entities, and oversight agencies will be listed here.'); $out .= '
'; $out .= '
'; // tab-content $out .= '
'; // scn01-directory return $out; } private function render_core_tab($core_entries) { $slots = [ [ 'slot' => 'taxonomy-authority', 'role' => 'Taxonomy Authority', 'description' => 'The institutional source of the case law categories this diagnostic record is organized by.', ], [ 'slot' => 'methodology-certifier', 'role' => 'Methodology Certifier', 'description' => 'The law firm whose professional association with this project certifies the diagnostic record meets a standard the legal community can use.', ], [ 'slot' => 'peer-operator', 'role' => 'Peer Operator', 'description' => 'The cooperating Civic Infrastructure host who can independently verify, support, and if necessary continue this diagnostic record.', ], ]; // Index populated entries by slot $populated = []; foreach ($core_entries as $entry) { if (!empty($entry['slot']) && ($entry['status'] ?? '') === 'active') { $populated[$entry['slot']] = $entry; } } $out = ''; foreach ($slots as $slot) { if (isset($populated[$slot['slot']])) { $e = $populated[$slot['slot']]; $name = $this->h($e['name'] ?? ''); $url = $this->h($e['url'] ?? ''); $out .= '
'; $out .= '
' . $this->h($slot['role']) . '
'; if ($url) { $out .= '
' . $name . '
'; } else { $out .= '
' . $name . '
'; } $out .= '
'; } else { $out .= '
'; $out .= '
' . $this->h($slot['role']) . '
'; $out .= '
' . $this->h($slot['description']) . '
'; $out .= '
Invitation pending
'; $out .= '
'; } } return $out; } private function render_tier_tab($entries, $placeholder) { if (empty($entries)) { return '
' . $this->h($placeholder) . '
'; } $out = ''; foreach ($entries as $entry) { $name = $this->h($entry['name'] ?? ''); $role = $this->h($entry['role'] ?? ''); $desc = $this->h($entry['description'] ?? ''); $url = $this->h($entry['url'] ?? ''); $out .= '
'; $out .= '
' . $role . '
'; if ($url) { $out .= '
' . $name . '
'; } else { $out .= '
' . $name . '
'; } if ($desc) { $out .= '
' . $desc . '
'; } $out .= '
'; } return $out; } // ---------------------------------------------------------------------------- // LISTINGS LOADER // ---------------------------------------------------------------------------- private function load_listings() { $config = $this->load_config(); $path = $config['listings_file'] ?? 'addon/scn01/listings.json'; $raw = @file_get_contents($path); if ($raw === false) { // Fail visibly — return structure with empty tiers, Core slots will show pending return ['core' => [], 'tier1' => [], 'tier2' => [], 'other' => []]; } $data = json_decode($raw, true); if (json_last_error() !== JSON_ERROR_NONE) { return ['core' => [], 'tier1' => [], 'tier2' => [], 'other' => []]; } return $data; } private function load_config() { $raw = @file_get_contents('addon/scn01/config.json'); if ($raw === false) return []; $data = json_decode($raw, true); return (json_last_error() === JSON_ERROR_NONE) ? $data : []; } // ---------------------------------------------------------------------------- // HELPER // ---------------------------------------------------------------------------- private function h($value) { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } }