load_listings($path); return $this->render_directory($listings, $config); } private function load_listings($path) { // Loads listings.json from the operator-configured path. if (!$path) return ['core' => [], 'tier1' => [], 'tier2' => [], 'other' => []]; $raw = @file_get_contents($path); if ($raw === false) return ['core' => [], 'tier1' => [], 'tier2' => [], 'other' => []]; $data = json_decode($raw, true); return (json_last_error() === JSON_ERROR_NONE) ? $data : ['core' => [], 'tier1' => [], 'tier2' => [], 'other' => []]; } private function render_directory($listings, $config) { // Renders the four-tab Bootstrap directory widget. $default_tab = $config['directory_default_tab'] ?? 'core'; $uid = 'cry01-dir-' . substr(md5(uniqid()), 0, 6); $tabs = [ 'core' => 'Core', 'tier1' => 'Tier-I', 'tier2' => 'Tier-II', 'other' => 'Other', ]; $out = '
'; $out .= ''; $out .= '
'; foreach ($tabs as $key => $label) { $active = ($key === $default_tab) ? 'show active' : ''; $out .= '
'; $out .= ($key === 'core') ? $this->render_core_tab($listings['core'] ?? []) : $this->render_tier_tab($listings[$key] ?? [], $label); $out .= '
'; } $out .= '
'; return $out; } private function render_core_tab($core) { // Renders the three permanent Core slots. Pending slots show invitation text. $slots = [ 'taxonomy-authority' => ['role' => 'Taxonomy Authority', 'desc' => 'The institutional source of the case law categories this diagnostic record is organized by.'], 'methodology-certifier' => ['role' => 'Methodology Certifier', 'desc' => 'The law firm whose professional association with this project certifies the diagnostic record meets a standard the legal community can use.'], 'peer-operator' => ['role' => 'Peer Operator', 'desc' => 'The cooperating Civic Infrastructure host who can independently verify, support, and if necessary continue this diagnostic record.'], ]; $indexed = []; foreach ($core as $entry) { $indexed[$entry['slot'] ?? ''] = $entry; } $out = ''; foreach ($slots as $slot_id => $slot_def) { $entry = $indexed[$slot_id] ?? []; $status = $entry['status'] ?? 'pending'; $out .= '
'; if ($status === 'pending' || empty($entry['name'])) { $out .= '
' . $this->h($slot_def['role']) . '
'; $out .= '
' . $this->h($slot_def['desc']) . '
'; $out .= '
Invitation pending
'; } else { $url = $entry['url'] ?? ''; $name = $this->h($entry['name']); $out .= '
'; $out .= $url ? '' . $name . '' : $name; $out .= '
'; $out .= '
' . $this->h($slot_def['role']) . '
'; } $out .= '
'; } return $out; } private function render_tier_tab($entries, $label) { // Renders a tier tab. Shows placeholder text if no entries. if (empty($entries)) { return '

No ' . $this->h($label) . ' listings yet.

'; } $out = ''; foreach ($entries as $entry) { $name = $this->h($entry['name'] ?? ''); $role = $this->h($entry['role'] ?? ''); $url = $entry['url'] ?? ''; $desc = $this->h($entry['description'] ?? ''); $out .= '
'; $out .= '
'; $out .= $url ? '' . $name . '' : $name; $out .= '
'; if ($role) $out .= '
' . $role . '
'; if ($desc) $out .= '
' . $desc . '
'; $out .= '
'; } return $out; } private function h($value) { // HTML-escapes a value for safe output. return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } }