From badc2b2ed1489ea9ae44395850c8f603fe06c8d0 Mon Sep 17 00:00:00 2001 From: TheRON Date: Fri, 5 Jun 2026 15:40:09 -0400 Subject: [PATCH] Initial push --- hubzilla/addon/scn01/Widget/Scn01.php | 175 ++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 hubzilla/addon/scn01/Widget/Scn01.php diff --git a/hubzilla/addon/scn01/Widget/Scn01.php b/hubzilla/addon/scn01/Widget/Scn01.php new file mode 100644 index 0000000..1635822 --- /dev/null +++ b/hubzilla/addon/scn01/Widget/Scn01.php @@ -0,0 +1,175 @@ +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 .= ''; + } 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'); + } +}