This commit is contained in:
2026-06-06 19:59:32 -04:00
parent 00b56600a8
commit 6189d79cfd

View File

@@ -3,7 +3,7 @@
/**
* Name: VS-01 Vital Signs
* Description: Public civic diagnostic — the ten structural preconditions of an HOA association.
* Version: 0.2.0
* Version: 0.3.0
* MinVersion: 11.0
* MaxVersion: 12.0
*/
@@ -48,50 +48,73 @@ function vs01_h($value) {
// ----------------------------------------------------------------------------
function vs01_access_state($association_slug = '') {
if (!local_channel()) {
return 'public';
}
$config = vs01_load_config();
// Operator check — site owner, not association channel owner
$channel = App::get_channel();
if (local_channel() === intval($channel['channel_id'])) {
return 'operator';
// Operator check — association channel owner (local channel only)
if (local_channel()) {
$channel = App::get_channel();
if (local_channel() === intval($channel['channel_id'])) {
return 'operator';
}
}
if (!$association_slug) {
return 'public';
}
$assoc = $config['associations'][$association_slug] ?? null;
$assoc = $config['associations'][$association_slug] ?? null;
if (!$assoc) return 'public';
// get_observer_hash() works for both local channels and guest token visitors
$observer = get_observer_hash();
$groups = $assoc['groups'] ?? [];
if (!$observer) return 'public';
$groups = $assoc['groups'] ?? [];
// Direct pgrp_member query — works for guest tokens because it does not
// call local_channel(). The group IDs come from config.json per association.
// Corpus Builder — highest participant tier
$cb_gid = intval($groups['corpus_builder'] ?? 0);
if ($cb_gid && in_array($observer, group_get_members_xchan($cb_gid))) {
return 'participant';
if ($cb_gid) {
$r = q("SELECT xchain FROM pgrp_member WHERE gid = %d AND xchain = '%s' LIMIT 1",
intval($cb_gid),
dbesc($observer)
);
if ($r) return 'participant';
}
// SASE Participant
$sase_gid = intval($groups['sase_participant'] ?? 0);
if ($sase_gid && in_array($observer, group_get_members_xchan($sase_gid))) {
return 'participant';
if ($sase_gid) {
$r = q("SELECT xchain FROM pgrp_member WHERE gid = %d AND xchain = '%s' LIMIT 1",
intval($sase_gid),
dbesc($observer)
);
if ($r) return 'participant';
}
// Civic Professional
$prof_gid = intval($groups['civic_professional'] ?? 0);
if ($prof_gid) {
$r = q("SELECT xchain FROM pgrp_member WHERE gid = %d AND xchain = '%s' LIMIT 1",
intval($prof_gid),
dbesc($observer)
);
if ($r) return 'participant';
}
return 'public';
}
function vs01_is_professional() {
if (!local_channel()) return false;
// Works for both local channels and guest token visitors via get_observer_hash()
$observer = get_observer_hash();
if (!$observer) return false;
$listings = vs01_load_listings();
foreach (['tier1', 'tier2'] as $tier) {
foreach ($listings[$tier] ?? [] as $entry) {
if (($entry['xchan'] ?? '') === $observer
if (($entry['xchain'] ?? '') === $observer
&& ($entry['status'] ?? '') === 'active') {
return true;
}
@@ -211,9 +234,9 @@ function vs01_render_index() {
// ----------------------------------------------------------------------------
function vs01_render_landing($association_slug, $access) {
$config = vs01_load_config();
$assoc = $config['associations'][$association_slug];
$name = vs01_h($assoc['name'] ?? $association_slug);
$config = vs01_load_config();
$assoc = $config['associations'][$association_slug];
$name = vs01_h($assoc['name'] ?? $association_slug);
$schemas = vs01_load_schemas();
$out = '<div class="vs01-content">';