From df8878889074bd232034c76390850381a2b3065e Mon Sep 17 00:00:00 2001 From: TheRON Date: Sat, 6 Jun 2026 11:53:11 -0400 Subject: [PATCH] Updated --- .../addon/assoc_profile/assoc_profile.php | 401 +----------------- .../assoc_profile/assoc_profile_data.php | 121 ++++++ .../assoc_profile/assoc_profile_post.php | 200 +++++++++ .../assoc_profile/assoc_profile_view.php | 81 ++++ 4 files changed, 422 insertions(+), 381 deletions(-) create mode 100644 hubzilla/addon/assoc_profile/assoc_profile_data.php create mode 100644 hubzilla/addon/assoc_profile/assoc_profile_post.php create mode 100644 hubzilla/addon/assoc_profile/assoc_profile_view.php diff --git a/hubzilla/addon/assoc_profile/assoc_profile.php b/hubzilla/addon/assoc_profile/assoc_profile.php index 33f20e4..b3ec655 100644 --- a/hubzilla/addon/assoc_profile/assoc_profile.php +++ b/hubzilla/addon/assoc_profile/assoc_profile.php @@ -3,13 +3,16 @@ /** * Name: Association Profile * Description: Structured diagnostic identity fields for HOA association channels. - * Version: 0.2.0 + * Version: 0.3.0 * MinVersion: 11.0 * MaxVersion: 12.0 */ function assoc_profile_module() {} +require_once 'addon/assoc_profile/assoc_profile_data.php'; +require_once 'addon/assoc_profile/assoc_profile_post.php'; +require_once 'addon/assoc_profile/assoc_profile_view.php'; require_once 'addon/assoc_profile/assoc_profile_manage.php'; function assoc_profile_load() { @@ -25,6 +28,7 @@ function assoc_profile_unload() { } function assoc_profile_load_pdl(&$b) { + // Load PDL layout only when this module is active. if (!is_array($b) || empty($b['module']) || $b['module'] !== 'assoc_profile') { return; } @@ -35,123 +39,42 @@ function assoc_profile_load_pdl(&$b) { } // ---------------------------------------------------------------------------- -// HELPERS +// HELPERS — used by all files in this addon // ---------------------------------------------------------------------------- function assoc_h($v) { + // HTML-escape a value for safe output. return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); } function assoc_slug_from_address($addr) { + // Extract the local part of a channel@node address. return explode('@', $addr)[0] ?? ''; } function assoc_is_operator() { + // True only when the logged-in channel is the current page channel. if (!local_channel()) return false; $channel = App::get_channel(); return local_channel() === intval($channel['channel_id']); } // ---------------------------------------------------------------------------- -// FILE I/O +// CSRF — used by manage and post handlers // ---------------------------------------------------------------------------- -function assoc_load_config() { - $raw = @file_get_contents('addon/assoc_profile/config.json'); - if (!$raw) return []; - $d = json_decode($raw, true); - return json_last_error() === JSON_ERROR_NONE ? $d : []; -} - -function assoc_registry_path() { - $c = assoc_load_config(); - return $c['registry_file'] ?? 'addon/assoc_profile/assoc_registry.json'; -} - -function assoc_fields_path() { - $c = assoc_load_config(); - return $c['fields_file'] ?? 'addon/assoc_profile/assoc_fields.json'; -} - -function assoc_load_registry() { - $raw = @file_get_contents(assoc_registry_path()); - if (!$raw) { logger('assoc_profile: registry not found'); return []; } - $d = json_decode($raw, true); - if (json_last_error() !== JSON_ERROR_NONE) { logger('assoc_profile: registry malformed'); return []; } - foreach (['_note','_version','_slug_format','_select_values'] as $k) unset($d[$k]); - return $d; -} - -function assoc_write_registry($data) { - $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); - if (!$json) { logger('assoc_profile: encode failed'); return false; } - if (file_put_contents(assoc_registry_path(), $json, LOCK_EX) === false) { - logger('assoc_profile: write failed'); return false; +function assoc_csrf_token() { + // Generate or return the session CSRF token as a hidden input. + if (empty($_SESSION['assoc_profile_csrf'])) { + $_SESSION['assoc_profile_csrf'] = bin2hex(random_bytes(16)); } - return true; + return ''; } -function assoc_load_fields() { - $raw = @file_get_contents(assoc_fields_path()); - if (!$raw) { logger('assoc_profile: fields file not found'); return ['groups'=>[],'fields'=>[]]; } - $d = json_decode($raw, true); - if (json_last_error() !== JSON_ERROR_NONE) { logger('assoc_profile: fields file malformed'); return ['groups'=>[],'fields'=>[]]; } - return $d; -} - -function assoc_write_fields($data) { - $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); - if (!$json) return false; - return file_put_contents(assoc_fields_path(), $json, LOCK_EX) !== false; -} - -function assoc_get($slug) { - $r = assoc_load_registry(); - return $r[$slug] ?? null; -} - -// ---------------------------------------------------------------------------- -// FIELD HELPERS -// ---------------------------------------------------------------------------- - -function assoc_field_map() { - $def = assoc_load_fields(); - $map = []; - foreach ($def['fields'] ?? [] as $f) { - $map[$f['nickname']] = $f; - } - return $map; -} - -function assoc_blank_entry() { - $map = assoc_field_map(); - $entry = []; - foreach ($map as $nick => $f) { - $entry[$nick] = $f['default'] ?? ''; - } - return $entry; -} - -function assoc_backfill_all($new_field_def) { - $registry = assoc_load_registry(); - $nick = $new_field_def['nickname']; - $default = $new_field_def['default'] ?? ''; - foreach ($registry as $slug => &$entry) { - if (!isset($entry[$nick])) { - $entry[$nick] = $default; - } - } - unset($entry); - return $registry; -} - -function assoc_remove_field_from_all($nickname) { - $registry = assoc_load_registry(); - foreach ($registry as $slug => &$entry) { - unset($entry[$nickname]); - } - unset($entry); - return $registry; +function assoc_verify_csrf() { + // Return true if the POST CSRF token matches the session token. + return isset($_POST['assoc_profile_csrf'], $_SESSION['assoc_profile_csrf']) + && hash_equals($_SESSION['assoc_profile_csrf'], $_POST['assoc_profile_csrf']); } // ---------------------------------------------------------------------------- @@ -159,6 +82,7 @@ function assoc_remove_field_from_all($nickname) { // ---------------------------------------------------------------------------- function assoc_profile_content() { + // Load assets and route /assoc_profile/manage/* requests. if (function_exists('head_add_css')) { head_add_css('/addon/assoc_profile/view/css/assoc_profile.css'); } @@ -196,288 +120,3 @@ function assoc_profile_content() { return '
Unknown management action.
'; } - -// ---------------------------------------------------------------------------- -// POST HANDLER -// ---------------------------------------------------------------------------- - -function assoc_handle_post() { - if (!assoc_verify_csrf()) { - return '
Invalid form token.
'; - } - - $action = $_POST['assoc_action'] ?? ''; - - switch ($action) { - case 'save_association': return assoc_save_association(); - case 'add_association': return assoc_add_association(); - case 'delete_association':return assoc_delete_association(); - case 'save_fields': return assoc_save_fields(); - case 'add_field': return assoc_add_field(); - case 'remove_field': return assoc_remove_field(); - case 'export_selected': return assoc_handle_export($_POST['export_slugs'] ?? ''); - case 'import_upload': return assoc_handle_import_upload(); - case 'import_confirm': return assoc_handle_import_confirm(); - default: - return '
Unknown action.
'; - } -} - -// ---------------------------------------------------------------------------- -// SAVE ASSOCIATION -// ---------------------------------------------------------------------------- - -function assoc_save_association() { - $slug = substr(strip_tags($_POST['assoc_slug'] ?? ''), 0, 128); - if (!$slug) return '
Missing slug.
'; - - $registry = assoc_load_registry(); - if (!isset($registry[$slug])) { - return '
Association not found.
'; - } - - $field_map = assoc_field_map(); - foreach ($field_map as $nick => $fdef) { - if (($fdef['type'] ?? '') === 'readonly') continue; - if (!isset($_POST[$nick])) continue; - $val = substr(strip_tags((string)$_POST[$nick]), 0, 1024); - if (isset($fdef['options']) && !in_array($val, $fdef['options'], true)) continue; - $registry[$slug][$nick] = $val; - } - $registry[$slug]['assoc_updated'] = date('Y-m-d'); - - if (assoc_write_registry($registry)) { - goaway(z_root() . '/assoc_profile/manage/assoc/' . $slug); - } - return '
Save failed. Check server logs.
'; -} - -// ---------------------------------------------------------------------------- -// ADD ASSOCIATION -// ---------------------------------------------------------------------------- - -function assoc_add_association() { - $slug = strtolower(trim($_POST['new_slug'] ?? '')); - $slug = preg_replace('/[^a-z0-9\-]/', '', $slug); - if (!$slug) return '
Invalid slug.
'; - - $registry = assoc_load_registry(); - if (isset($registry[$slug])) { - return '
Slug already exists: ' . assoc_h($slug) . '
'; - } - - $entry = assoc_blank_entry(); - $entry['assoc_registered'] = date('Y-m-d'); - $entry['assoc_updated'] = date('Y-m-d'); - $registry[$slug] = $entry; - - if (assoc_write_registry($registry)) { - goaway(z_root() . '/assoc_profile/manage/assoc/' . $slug); - } - return '
Failed to create association.
'; -} - -// ---------------------------------------------------------------------------- -// DELETE ASSOCIATION -// ---------------------------------------------------------------------------- - -function assoc_delete_association() { - $slug = substr(strip_tags($_POST['assoc_slug'] ?? ''), 0, 128); - if (!$slug) return '
Missing slug.
'; - - $registry = assoc_load_registry(); - if (!isset($registry[$slug])) { - return '
Association not found.
'; - } - - $name = $registry[$slug]['assoc_legal_name'] ?? $slug; - unset($registry[$slug]); - - if (assoc_write_registry($registry)) { - goaway(z_root() . '/assoc_profile/manage'); - } - return '
Delete failed. Check server logs.
'; -} - -// ---------------------------------------------------------------------------- -// FIELD MANAGEMENT -// ---------------------------------------------------------------------------- - -function assoc_save_fields() { - // Save label and group changes to existing fields - $def = assoc_load_fields(); - $fields = &$def['fields']; - - foreach ($fields as &$f) { - $nick = $f['nickname']; - if (isset($_POST['label_' . $nick])) { - $f['label'] = substr(strip_tags($_POST['label_' . $nick]), 0, 128); - } - if (isset($_POST['group_' . $nick])) { - $f['group'] = substr(strip_tags($_POST['group_' . $nick]), 0, 64); - } - if (isset($_POST['help_' . $nick])) { - $f['help'] = substr(strip_tags($_POST['help_' . $nick]), 0, 256); - } - } - unset($f); - - // Save group order - if (!empty($_POST['groups'])) { - $groups = array_map(function($g) { return substr(strip_tags($g), 0, 64); }, - explode("\n", $_POST['groups'])); - $def['groups'] = array_values(array_filter(array_map('trim', $groups))); - } - - if (assoc_write_fields($def)) { - goaway(z_root() . '/assoc_profile/manage/fields'); - } - return '
Save failed.
'; -} - -function assoc_add_field() { - $nick = strtolower(preg_replace('/[^a-z0-9_]/', '', $_POST['new_nickname'] ?? '')); - $label = substr(strip_tags($_POST['new_label'] ?? ''), 0, 128); - $type = in_array($_POST['new_type'] ?? '', ['text','select','readonly']) ? $_POST['new_type'] : 'text'; - $group = substr(strip_tags($_POST['new_group'] ?? ''), 0, 64); - $default = substr(strip_tags($_POST['new_default'] ?? ''), 0, 128); - $help = substr(strip_tags($_POST['new_help'] ?? ''), 0, 256); - - if (!$nick || !$label) { - return '
Nickname and label are required.
'; - } - - $def = assoc_load_fields(); - foreach ($def['fields'] as $f) { - if ($f['nickname'] === $nick) { - return '
Field already exists: ' . assoc_h($nick) . '
'; - } - } - - $new_field = [ - 'nickname' => $nick, - 'label' => $label, - 'type' => $type, - 'group' => $group, - 'default' => $default, - 'required' => false, - 'help' => $help, - ]; - - if ($type === 'select' && !empty($_POST['new_options'])) { - $opts = array_map('trim', explode("\n", $_POST['new_options'])); - $new_field['options'] = array_values(array_filter($opts)); - } - - $def['fields'][] = $new_field; - assoc_write_fields($def); - - // Backfill all existing associations - $registry = assoc_backfill_all($new_field); - assoc_write_registry($registry); - - goaway(z_root() . '/assoc_profile/manage/fields'); -} - -function assoc_remove_field() { - $nick = preg_replace('/[^a-z0-9_]/', '', $_POST['remove_nickname'] ?? ''); - if (!$nick) return '
Missing nickname.
'; - - $def = assoc_load_fields(); - $def['fields'] = array_values(array_filter($def['fields'], function($f) use ($nick) { - return $f['nickname'] !== $nick; - })); - assoc_write_fields($def); - - $registry = assoc_remove_field_from_all($nick); - assoc_write_registry($registry); - - goaway(z_root() . '/assoc_profile/manage/fields'); -} - -// ---------------------------------------------------------------------------- -// PROFILE VIEW HOOK -// ---------------------------------------------------------------------------- - -function assoc_profile_view_hook(&$o) { - $uid = intval(App::$profile['profile_uid'] ?? 0); - if (!$uid) return; - $r = q("SELECT channel_address FROM channel WHERE channel_id = %d LIMIT 1", intval($uid)); - if (!$r) return; - $slug = assoc_slug_from_address($r[0]['channel_address']); - $entry = assoc_get($slug); - if (!$entry) return; - if (function_exists('head_add_css')) head_add_css('/addon/assoc_profile/view/css/assoc_profile.css'); - $o .= assoc_render_view($entry); -} - -// ---------------------------------------------------------------------------- -// PROFILE EDIT HOOK -// ---------------------------------------------------------------------------- - -function assoc_profile_edit_hook(&$b) { - if (!assoc_is_operator()) return; - $channel = App::get_channel(); - $slug = assoc_slug_from_address($channel['channel_address'] ?? ''); - $entry = assoc_get($slug); - if (!$entry) return; - if (function_exists('head_add_css')) head_add_css('/addon/assoc_profile/view/css/assoc_profile.css'); - $b['html'] .= ''; -} - -// ---------------------------------------------------------------------------- -// RENDER — PROFILE VIEW -// ---------------------------------------------------------------------------- - -function assoc_render_view($entry) { - $def = assoc_load_fields(); - $groups = $def['groups'] ?? []; - $fields = $def['fields'] ?? []; - - $by_group = []; - foreach ($fields as $f) { - $by_group[$f['group']][] = $f; - } - - $out = '
'; - $out .= '

Association Diagnostic Profile

'; - - foreach ($groups as $group) { - $group_fields = $by_group[$group] ?? []; - if (empty($group_fields)) continue; - $out .= '
'; - $out .= '
' . assoc_h($group) . '
'; - foreach ($group_fields as $f) { - $nick = $f['nickname']; - $val = $entry[$nick] ?? ''; - if ($val === '') $val = '—'; - if ($val === 'UNK') $val = 'Unknown — not yet verified'; - $out .= '
'; - $out .= '' . assoc_h($f['label'] ?? $nick) . ': '; - $out .= '' . assoc_h($val) . ''; - $out .= '
'; - } - $out .= '
'; - } - $out .= '
'; - return $out; -} - -// ---------------------------------------------------------------------------- -// CSRF -// ---------------------------------------------------------------------------- - -function assoc_csrf_token() { - if (empty($_SESSION['assoc_profile_csrf'])) { - $_SESSION['assoc_profile_csrf'] = bin2hex(random_bytes(16)); - } - return ''; -} - -function assoc_verify_csrf() { - return isset($_POST['assoc_profile_csrf'], $_SESSION['assoc_profile_csrf']) - && hash_equals($_SESSION['assoc_profile_csrf'], $_POST['assoc_profile_csrf']); -} diff --git a/hubzilla/addon/assoc_profile/assoc_profile_data.php b/hubzilla/addon/assoc_profile/assoc_profile_data.php new file mode 100644 index 0000000..3c79c7f --- /dev/null +++ b/hubzilla/addon/assoc_profile/assoc_profile_data.php @@ -0,0 +1,121 @@ +[],'fields'=>[]]; } + $d = json_decode($raw, true); + if (json_last_error() !== JSON_ERROR_NONE) { logger('assoc_profile: fields file malformed'); return ['groups'=>[],'fields'=>[]]; } + return $d; +} + +function assoc_write_fields($data) { + // Write the fields definition array to disk; return true on success. + $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + if (!$json) return false; + return file_put_contents(assoc_fields_path(), $json, LOCK_EX) !== false; +} + +function assoc_get($slug) { + // Return a single registry entry by slug, or null if not found. + $r = assoc_load_registry(); + return $r[$slug] ?? null; +} + +// ---------------------------------------------------------------------------- +// FIELD HELPERS +// ---------------------------------------------------------------------------- + +function assoc_field_map() { + // Return fields array keyed by nickname. + $def = assoc_load_fields(); + $map = []; + foreach ($def['fields'] ?? [] as $f) { + $map[$f['nickname']] = $f; + } + return $map; +} + +function assoc_blank_entry() { + // Return a new registry entry with all fields set to their defaults. + $map = assoc_field_map(); + $entry = []; + foreach ($map as $nick => $f) { + $entry[$nick] = $f['default'] ?? ''; + } + return $entry; +} + +function assoc_backfill_all($new_field_def) { + // Add a new field with its default to every existing registry entry. + $registry = assoc_load_registry(); + $nick = $new_field_def['nickname']; + $default = $new_field_def['default'] ?? ''; + foreach ($registry as $slug => &$entry) { + if (!isset($entry[$nick])) { + $entry[$nick] = $default; + } + } + unset($entry); + return $registry; +} + +function assoc_remove_field_from_all($nickname) { + // Remove a field key from every registry entry. + $registry = assoc_load_registry(); + foreach ($registry as $slug => &$entry) { + unset($entry[$nickname]); + } + unset($entry); + return $registry; +} diff --git a/hubzilla/addon/assoc_profile/assoc_profile_post.php b/hubzilla/addon/assoc_profile/assoc_profile_post.php new file mode 100644 index 0000000..fdfe815 --- /dev/null +++ b/hubzilla/addon/assoc_profile/assoc_profile_post.php @@ -0,0 +1,200 @@ +Invalid form token.'; + } + + $action = $_POST['assoc_action'] ?? ''; + + switch ($action) { + case 'save_association': return assoc_save_association(); + case 'add_association': return assoc_add_association(); + case 'delete_association':return assoc_delete_association(); + case 'save_fields': return assoc_save_fields(); + case 'add_field': return assoc_add_field(); + case 'remove_field': return assoc_remove_field(); + case 'export_selected': return assoc_handle_export($_POST['export_slugs'] ?? ''); + case 'import_upload': return assoc_handle_import_upload(); + case 'import_confirm': return assoc_handle_import_confirm(); + default: + return '
Unknown action.
'; + } +} + +// ---------------------------------------------------------------------------- +// ASSOCIATION HANDLERS +// ---------------------------------------------------------------------------- + +function assoc_save_association() { + // Save edited fields for an existing association. + $slug = substr(strip_tags($_POST['assoc_slug'] ?? ''), 0, 128); + if (!$slug) return '
Missing slug.
'; + + $registry = assoc_load_registry(); + if (!isset($registry[$slug])) { + return '
Association not found.
'; + } + + $field_map = assoc_field_map(); + foreach ($field_map as $nick => $fdef) { + if (($fdef['type'] ?? '') === 'readonly') continue; + if (!isset($_POST[$nick])) continue; + $val = substr(strip_tags((string)$_POST[$nick]), 0, 1024); + if (isset($fdef['options']) && !in_array($val, $fdef['options'], true)) continue; + $registry[$slug][$nick] = $val; + } + $registry[$slug]['assoc_updated'] = date('Y-m-d'); + + if (assoc_write_registry($registry)) { + goaway(z_root() . '/assoc_profile/manage/assoc/' . $slug); + } + return '
Save failed. Check server logs.
'; +} + +function assoc_add_association() { + // Create a new blank association entry with the given slug. + $slug = strtolower(trim($_POST['new_slug'] ?? '')); + $slug = preg_replace('/[^a-z0-9\-]/', '', $slug); + if (!$slug) return '
Invalid slug.
'; + + $registry = assoc_load_registry(); + if (isset($registry[$slug])) { + return '
Slug already exists: ' . assoc_h($slug) . '
'; + } + + $entry = assoc_blank_entry(); + $entry['assoc_registered'] = date('Y-m-d'); + $entry['assoc_updated'] = date('Y-m-d'); + $registry[$slug] = $entry; + + if (assoc_write_registry($registry)) { + goaway(z_root() . '/assoc_profile/manage/assoc/' . $slug); + } + return '
Failed to create association.
'; +} + +function assoc_delete_association() { + // Permanently remove an association from the registry. + $slug = substr(strip_tags($_POST['assoc_slug'] ?? ''), 0, 128); + if (!$slug) return '
Missing slug.
'; + + $registry = assoc_load_registry(); + if (!isset($registry[$slug])) { + return '
Association not found.
'; + } + + unset($registry[$slug]); + + if (assoc_write_registry($registry)) { + goaway(z_root() . '/assoc_profile/manage'); + } + return '
Delete failed. Check server logs.
'; +} + +// ---------------------------------------------------------------------------- +// FIELD HANDLERS +// ---------------------------------------------------------------------------- + +function assoc_save_fields() { + // Save label, group, and help text changes; optionally reorder groups. + $def = assoc_load_fields(); + $fields = &$def['fields']; + + foreach ($fields as &$f) { + $nick = $f['nickname']; + if (isset($_POST['label_' . $nick])) { + $f['label'] = substr(strip_tags($_POST['label_' . $nick]), 0, 128); + } + if (isset($_POST['group_' . $nick])) { + $f['group'] = substr(strip_tags($_POST['group_' . $nick]), 0, 64); + } + if (isset($_POST['help_' . $nick])) { + $f['help'] = substr(strip_tags($_POST['help_' . $nick]), 0, 256); + } + } + unset($f); + + if (!empty($_POST['groups'])) { + $groups = array_map(function($g) { return substr(strip_tags($g), 0, 64); }, + explode("\n", $_POST['groups'])); + $def['groups'] = array_values(array_filter(array_map('trim', $groups))); + } + + if (assoc_write_fields($def)) { + goaway(z_root() . '/assoc_profile/manage/fields'); + } + return '
Save failed.
'; +} + +function assoc_add_field() { + // Add a new field definition and backfill all existing associations. + $nick = strtolower(preg_replace('/[^a-z0-9_]/', '', $_POST['new_nickname'] ?? '')); + $label = substr(strip_tags($_POST['new_label'] ?? ''), 0, 128); + $type = in_array($_POST['new_type'] ?? '', ['text','select','readonly']) ? $_POST['new_type'] : 'text'; + $group = substr(strip_tags($_POST['new_group'] ?? ''), 0, 64); + $default = substr(strip_tags($_POST['new_default'] ?? ''), 0, 128); + $help = substr(strip_tags($_POST['new_help'] ?? ''), 0, 256); + + if (!$nick || !$label) { + return '
Nickname and label are required.
'; + } + + $def = assoc_load_fields(); + foreach ($def['fields'] as $f) { + if ($f['nickname'] === $nick) { + return '
Field already exists: ' . assoc_h($nick) . '
'; + } + } + + $new_field = [ + 'nickname' => $nick, + 'label' => $label, + 'type' => $type, + 'group' => $group, + 'default' => $default, + 'required' => false, + 'help' => $help, + ]; + + if ($type === 'select' && !empty($_POST['new_options'])) { + $opts = array_map('trim', explode("\n", $_POST['new_options'])); + $new_field['options'] = array_values(array_filter($opts)); + } + + $def['fields'][] = $new_field; + assoc_write_fields($def); + + $registry = assoc_backfill_all($new_field); + assoc_write_registry($registry); + + goaway(z_root() . '/assoc_profile/manage/fields'); +} + +function assoc_remove_field() { + // Remove a field from the definition and from all registry entries. + $nick = preg_replace('/[^a-z0-9_]/', '', $_POST['remove_nickname'] ?? ''); + if (!$nick) return '
Missing nickname.
'; + + $def = assoc_load_fields(); + $def['fields'] = array_values(array_filter($def['fields'], function($f) use ($nick) { + return $f['nickname'] !== $nick; + })); + assoc_write_fields($def); + + $registry = assoc_remove_field_from_all($nick); + assoc_write_registry($registry); + + goaway(z_root() . '/assoc_profile/manage/fields'); +} diff --git a/hubzilla/addon/assoc_profile/assoc_profile_view.php b/hubzilla/addon/assoc_profile/assoc_profile_view.php new file mode 100644 index 0000000..6d90475 --- /dev/null +++ b/hubzilla/addon/assoc_profile/assoc_profile_view.php @@ -0,0 +1,81 @@ + + + Edit Association Diagnostic Profile + '; +} + +// ---------------------------------------------------------------------------- +// PROFILE VIEW RENDERER +// ---------------------------------------------------------------------------- + +function assoc_render_view($entry) { + // Render all field groups as a read-only diagnostic profile block. + $def = assoc_load_fields(); + $groups = $def['groups'] ?? []; + $fields = $def['fields'] ?? []; + + $by_group = []; + foreach ($fields as $f) { + $by_group[$f['group']][] = $f; + } + + $out = '
'; + $out .= '

Association Diagnostic Profile

'; + + foreach ($groups as $group) { + $group_fields = $by_group[$group] ?? []; + if (empty($group_fields)) continue; + $out .= '
'; + $out .= '
' . assoc_h($group) . '
'; + foreach ($group_fields as $f) { + $nick = $f['nickname']; + $val = $entry[$nick] ?? ''; + if ($val === '') $val = '—'; + if ($val === 'UNK') $val = 'Unknown — not yet verified'; + $out .= '
'; + $out .= '' . assoc_h($f['label'] ?? $nick) . ': '; + $out .= '' . assoc_h($val) . ''; + $out .= '
'; + } + $out .= '
'; + } + $out .= '
'; + return $out; +}