1,
'name' => '',
'aliases' => array(),
'legal_name' => '',
'type' => '',
'description' => '',
'founded' => '',
'founders' => array(),
'people' => array(), // [{name, role}]
'address' => array('street' => '', 'city' => '', 'postal' => '', 'region' => '', 'country' => ''),
'phone' => '',
'email' => '',
'website' => '',
'services' => array(), // [{name, description, price}]
'numbers' => array(), // [{label, value, as_of}]
'sameas' => array(),
'wikidata' => '', // 2.48 — QID (Q123): from the dashboard's engine check or typed by hand
'areas' => array(),
'languages' => array(),
'ai_notes' => '',
'updated_at' => '',
);
}
function citami_truth_lines_to_list($raw, $max = CITAMI_TRUTH_MAX_LIST) {
if (is_array($raw)) {
$items = $raw;
} else {
$items = preg_split('/\r\n|\r|\n|,/', (string) $raw);
}
$out = array();
foreach ((array) $items as $it) {
$it = mb_substr(sanitize_text_field((string) $it), 0, 200);
if ($it !== '' && !in_array($it, $out, true)) {
$out[] = $it;
}
if (count($out) >= $max) {
break;
}
}
return $out;
}
function citami_truth_sanitize_sheet($in) {
$d = citami_truth_defaults();
$in = is_array($in) ? $in : array();
$out = $d;
foreach (array('name', 'legal_name', 'description', 'phone', 'ai_notes') as $k) {
$out[$k] = mb_substr(sanitize_text_field((string) (isset($in[$k]) ? $in[$k] : '')), 0, $k === 'ai_notes' || $k === 'description' ? 600 : 200);
}
$out['type'] = preg_replace('/[^A-Za-z]/', '', (string) (isset($in['type']) ? $in['type'] : ''));
$founded = trim((string) (isset($in['founded']) ? $in['founded'] : ''));
$out['founded'] = preg_match('/^\d{4}(-\d{2}(-\d{2})?)?$/', $founded) ? $founded : '';
$email = trim((string) (isset($in['email']) ? $in['email'] : ''));
$out['email'] = filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : '';
$web = trim((string) (isset($in['website']) ? $in['website'] : ''));
$out['website'] = preg_match('#^https?://#i', $web) ? esc_url_raw($web) : '';
$addr = isset($in['address']) && is_array($in['address']) ? $in['address'] : array();
foreach (array('street', 'city', 'postal', 'region') as $k) {
$out['address'][$k] = mb_substr(sanitize_text_field((string) (isset($addr[$k]) ? $addr[$k] : '')), 0, 120);
}
$out['address']['country'] = strtoupper(mb_substr(preg_replace('/[^A-Za-z]/', '', (string) (isset($addr['country']) ? $addr['country'] : '')), 0, 2));
foreach (array('aliases', 'founders', 'areas', 'languages') as $k) {
$out[$k] = citami_truth_lines_to_list(isset($in[$k]) ? $in[$k] : array());
}
$same = array();
foreach (citami_truth_lines_to_list(isset($in['sameas']) ? $in['sameas'] : array()) as $u) {
if (preg_match('#^https?://#i', $u)) {
$same[] = esc_url_raw($u);
}
}
$out['sameas'] = $same;
$qid = strtoupper(preg_replace('/[^Qq0-9]/', '', (string) (isset($in['wikidata']) ? $in['wikidata'] : '')));
$out['wikidata'] = preg_match('/^Q\d+$/', $qid) ? $qid : '';
$people = array();
$raw_people = isset($in['people']) ? $in['people'] : array();
if (is_string($raw_people)) {
$raw_people = preg_split('/\r\n|\r|\n/', $raw_people);
}
foreach ((array) $raw_people as $p) {
if (is_string($p)) {
$parts = array_map('trim', explode('|', $p, 2));
$p = array('name' => $parts[0], 'role' => isset($parts[1]) ? $parts[1] : '');
}
if (!is_array($p)) {
continue;
}
$name = mb_substr(sanitize_text_field((string) (isset($p['name']) ? $p['name'] : '')), 0, 120);
if ($name === '') {
continue;
}
$people[] = array('name' => $name, 'role' => mb_substr(sanitize_text_field((string) (isset($p['role']) ? $p['role'] : '')), 0, 120));
if (count($people) >= CITAMI_TRUTH_MAX_LIST) {
break;
}
}
$out['people'] = $people;
$services = array();
$raw_services = isset($in['services']) ? $in['services'] : array();
if (is_string($raw_services)) {
$raw_services = preg_split('/\r\n|\r|\n/', $raw_services);
}
foreach ((array) $raw_services as $s) {
if (is_string($s)) {
$parts = array_map('trim', explode('|', $s, 3));
$s = array('name' => $parts[0], 'description' => isset($parts[1]) ? $parts[1] : '', 'price' => isset($parts[2]) ? $parts[2] : '');
}
if (!is_array($s)) {
continue;
}
$name = mb_substr(sanitize_text_field((string) (isset($s['name']) ? $s['name'] : '')), 0, 120);
if ($name === '') {
continue;
}
$services[] = array(
'name' => $name,
'description' => mb_substr(sanitize_text_field((string) (isset($s['description']) ? $s['description'] : '')), 0, 300),
'price' => mb_substr(sanitize_text_field((string) (isset($s['price']) ? $s['price'] : '')), 0, 60),
);
if (count($services) >= CITAMI_TRUTH_MAX_LIST) {
break;
}
}
$out['services'] = $services;
$numbers = array();
$raw_numbers = isset($in['numbers']) ? $in['numbers'] : array();
if (is_string($raw_numbers)) {
$raw_numbers = preg_split('/\r\n|\r|\n/', $raw_numbers);
}
foreach ((array) $raw_numbers as $n) {
if (is_string($n)) {
$parts = array_map('trim', explode('|', $n, 3));
$n = array('label' => $parts[0], 'value' => isset($parts[1]) ? $parts[1] : '', 'as_of' => isset($parts[2]) ? $parts[2] : '');
}
if (!is_array($n)) {
continue;
}
$label = mb_substr(sanitize_text_field((string) (isset($n['label']) ? $n['label'] : '')), 0, 120);
$value = mb_substr(sanitize_text_field((string) (isset($n['value']) ? $n['value'] : '')), 0, 60);
if ($label === '' || $value === '') {
continue;
}
$numbers[] = array('label' => $label, 'value' => $value, 'as_of' => mb_substr(sanitize_text_field((string) (isset($n['as_of']) ? $n['as_of'] : '')), 0, 20));
if (count($numbers) >= CITAMI_TRUTH_MAX_LIST) {
break;
}
}
$out['numbers'] = $numbers;
$out['updated_at'] = isset($in['updated_at']) && is_string($in['updated_at']) ? mb_substr($in['updated_at'], 0, 40) : '';
return $out;
}
/** The sheet as stored (plus 'source' and 'received_at' bookkeeping). */
function citami_truth_sheet() {
$raw = get_option(CITAMI_TRUTH_OPT, array());
if (!is_array($raw)) {
$raw = array();
}
$sheet = citami_truth_sanitize_sheet(isset($raw['sheet']) ? $raw['sheet'] : $raw);
$sheet['source'] = isset($raw['source']) && $raw['source'] === 'platform' ? 'platform' : 'local';
$sheet['received_at'] = isset($raw['received_at']) ? (int) $raw['received_at'] : 0;
return $sheet;
}
function citami_truth_has_sheet($sheet = null) {
$sheet = $sheet === null ? citami_truth_sheet() : $sheet;
return $sheet['name'] !== '';
}
function citami_truth_store($sheet, $source = 'local') {
$clean = citami_truth_sanitize_sheet($sheet);
update_option(CITAMI_TRUTH_OPT, array('sheet' => $clean, 'source' => $source === 'platform' ? 'platform' : 'local', 'received_at' => time()), false);
delete_transient(CITAMI_TRUTH_SCAN_TRANSIENT);
return $clean;
}
// The dashboard's sheet travels with the sync pull (WP17): it always wins.
add_action('citami_sync_response', function ($data) {
if (!is_array($data) || empty($data['truth_sheet']) || !is_array($data['truth_sheet'])) {
return;
}
$incoming = citami_truth_sanitize_sheet($data['truth_sheet']);
if ($incoming['name'] === '') {
return;
}
$current = citami_truth_sheet();
unset($current['source'], $current['received_at']);
if ($current !== $incoming) {
citami_truth_store($incoming, 'platform');
} else {
$raw = get_option(CITAMI_TRUTH_OPT, array());
if (!is_array($raw) || (isset($raw['source']) ? $raw['source'] : 'local') !== 'platform') {
citami_truth_store($incoming, 'platform');
}
}
});
function citami_truth_settings() {
// 2.62 — /ai-info is opt-in: no engine has announced it reads such a page,
// so it is published only when the owner turns it on (sites that already
// had it keep it, see citami_truth_migrate_ai_info_default()).
$d = array('ai_info' => 'off', 'slug' => 'ai-info', 'footer_link' => 'off');
$s = get_option(CITAMI_TRUTH_SETTINGS_OPT, array());
if (is_array($s)) {
if (isset($s['ai_info']) && $s['ai_info'] === 'on') {
$d['ai_info'] = 'on';
}
if (isset($s['slug']) && is_string($s['slug'])) {
$slug = sanitize_title($s['slug']);
if ($slug !== '') {
$d['slug'] = $slug;
}
}
if (isset($s['footer_link']) && $s['footer_link'] === 'on') {
$d['footer_link'] = 'on';
}
}
return $d;
}
/**
* 2.62 — before 2.62 an unset option meant "on". Sites that already had a
* fact sheet (so /ai-info was live) keep it on, written explicitly; sites
* without a sheet never had the page and get the new opt-in default.
*/
function citami_truth_migrate_ai_info_default() {
if (get_option('citami_ai_info_optin_v262')) {
return;
}
$s = get_option(CITAMI_TRUTH_SETTINGS_OPT, array());
$s = is_array($s) ? $s : array();
if (!isset($s['ai_info']) && function_exists('citami_truth_has_sheet') && citami_truth_has_sheet()) {
$s['ai_info'] = 'on';
if (!isset($s['footer_link'])) {
$s['footer_link'] = 'on';
}
update_option(CITAMI_TRUTH_SETTINGS_OPT, $s, false);
}
update_option('citami_ai_info_optin_v262', 1, false);
}
add_action('init', 'citami_truth_migrate_ai_info_default', 1);
add_action('admin_init', function () {
register_setting('citami_truth', CITAMI_TRUTH_SETTINGS_OPT, array(
'type' => 'array',
'sanitize_callback' => function ($v) {
$in = is_array($v) ? $v : array();
$slug = isset($in['slug']) ? sanitize_title((string) $in['slug']) : '';
return array(
'ai_info' => (isset($in['ai_info']) && $in['ai_info'] === 'on') ? 'on' : 'off',
'slug' => $slug !== '' ? $slug : 'ai-info',
'footer_link' => (isset($in['footer_link']) && $in['footer_link'] === 'on') ? 'on' : 'off',
);
},
'default' => array(),
));
});
/* ---------------------------------------------------------------- */
/* Normalisation helpers (pure) */
/* ---------------------------------------------------------------- */
/** Digits only; the last 9 are what people actually type differently. */
function citami_truth_phone_key($phone) {
$digits = preg_replace('/\D+/', '', (string) $phone);
if (strlen($digits) < 6) {
return '';
}
return substr($digits, -9);
}
/** 2.56.1 — a founder also listed in the team is one person, not two. */
function citami_truth_unique_names(array $names) {
$out = array();
foreach ($names as $n) {
$n = trim((string) $n);
if ($n !== '' && !isset($out[mb_strtolower($n)])) $out[mb_strtolower($n)] = $n;
}
return array_values($out);
}
function citami_truth_norm($text) {
$t = mb_strtolower(trim((string) $text));
$t = preg_replace('/[^\p{L}\p{N}]+/u', ' ', $t);
return trim(preg_replace('/\s+/u', ' ', $t));
}
/** Phone-looking sequences in a text (Italian and international shapes). */
function citami_truth_find_phones($text) {
$out = array();
$text = (string) $text;
if (preg_match_all('/(? 15) {
continue; // dates, prices, years
}
if (preg_match('/^(19|20)\d{2}$/', $digits) || preg_match('/^\d{5}$/', $digits)) {
continue;
}
$key = citami_truth_phone_key($cand);
if ($key !== '' && !isset($out[$key])) {
$out[$key] = trim($cand);
}
}
}
return $out; // key => as written
}
function citami_truth_find_emails($text) {
$out = array();
if (preg_match_all('/[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}/', (string) $text, $m)) {
foreach ($m[0] as $e) {
$out[strtolower($e)] = $e;
}
}
return $out;
}
/** "fondata nel 1998", "dal 2005", "since 2012", "founded in 2019", "est. 1987" */
function citami_truth_find_founding_years($text) {
$out = array();
if (preg_match_all('/\b(?:fondat[oa]\s+(?:nel|il)|dal|since|founded\s+in|est\.?|established\s+in|attiv[oa]\s+dal)\s+((?:19|20)\d{2})\b/iu', (string) $text, $m)) {
foreach ($m[1] as $y) {
$out[$y] = true;
}
}
// PHP turns numeric keys into ints: hand back strings so "!== $year" compares like with like.
return array_map('strval', array_keys($out));
}
/** Postal code + city pairs ("10121 Torino", "Torino 10121"). */
function citami_truth_find_postal_city($text) {
$out = array();
if (preg_match_all('/\b(\d{5})\s+([\p{L}][\p{L}\'’.-]*(?:\s[\p{L}][\p{L}\'’.-]*){0,3})/u', (string) $text, $m, PREG_SET_ORDER)) {
foreach ($m as $x) {
$out[] = array('postal' => $x[1], 'city' => trim($x[2]));
}
}
return $out;
}
/* ---------------------------------------------------------------- */
/* Consistency scan */
/* ---------------------------------------------------------------- */
function citami_truth_finding($kind, $severity, $where, $found, $expected, $url = '', $hint = '') {
return array('kind' => $kind, 'severity' => $severity, 'where' => $where, 'found' => (string) $found, 'expected' => (string) $expected, 'url' => $url, 'hint' => $hint);
}
/** Pages worth reading: about, contact, front page (published only). */
function citami_truth_pages_to_check() {
$out = array();
$seen = array();
$slugs = array_merge(citami_about_page_slugs(), array('contatti', 'contatto', 'contact', 'contact-us', 'contacts', 'dove-siamo', 'sedi', 'kontakt', 'anfahrt', 'standorte', 'so-finden-sie-uns'));
foreach ($slugs as $slug) {
$p = get_page_by_path($slug);
if ($p && isset($p->post_status) && $p->post_status === 'publish' && !isset($seen[$p->ID])) {
$seen[$p->ID] = true;
$is_about = in_array($slug, citami_about_page_slugs(), true);
$out[] = array('post' => $p, 'role' => $is_about ? 'about' : 'contact');
}
if (count($out) >= 6) {
break;
}
}
return $out;
}
/* ---------------------------------------------------------------- */
/* About page check (2.32.1) */
/* The About page is where every engine looks to learn who a */
/* business is. Ten yes/no checks against the sheet: it exists, it */
/* names the entity exactly, says since when, where, who (with a */
/* role), what it does, how to contact, in enough words, with a */
/* sentence of description and the AboutPage schema role. */
/* ---------------------------------------------------------------- */
function citami_truth_about_page() {
foreach (citami_about_page_slugs() as $slug) {
$p = get_page_by_path($slug);
if ($p && isset($p->post_status) && $p->post_status === 'publish') {
return $p;
}
}
return null;
}
function citami_truth_about_check($sheet = null) {
$sheet = $sheet === null ? citami_truth_sheet() : $sheet;
$post = citami_truth_about_page();
$out = array('found' => false, 'url' => '', 'edit' => '', 'title' => '', 'score' => 0, 'checks' => array(), 'missing' => array());
if (!$post) {
$out['checks'][] = array('id' => 'exists', 'ok' => false);
$out['missing'][] = 'exists';
return $out;
}
$out['found'] = true;
$out['url'] = get_permalink($post);
$out['edit'] = get_edit_post_link($post->ID, 'raw');
$out['title'] = (string) $post->post_title;
$text = citami_truth_post_text($post);
$norm = citami_truth_norm($text);
$has = function ($needle) use ($norm) { $n = citami_truth_norm($needle); return $n !== '' && strpos($norm, $n) !== false; };
$words = str_word_count(wp_strip_all_tags($text));
$checks = array();
$checks['exists'] = true;
$checks['name'] = $sheet['name'] !== '' ? $has($sheet['name']) : false;
$checks['founded'] = $sheet['founded'] !== '' ? in_array(substr($sheet['founded'], 0, 4), citami_truth_find_founding_years($text), true) || $has(substr($sheet['founded'], 0, 4)) : false;
$people_ok = false;
foreach (array_merge($sheet['founders'], array_map(function ($p) { return $p['name']; }, $sheet['people'])) as $n) {
if ($has($n)) { $people_ok = true; break; }
}
$checks['people'] = $people_ok;
$checks['city'] = $sheet['address']['city'] !== '' ? $has($sheet['address']['city']) : false;
$svc = 0;
foreach ($sheet['services'] as $s) { if ($has($s['name'])) $svc++; }
$checks['services'] = count($sheet['services']) > 0 ? $svc >= min(2, count($sheet['services'])) : false;
$checks['contacts'] = ($sheet['phone'] !== '' && in_array(citami_truth_phone_key($sheet['phone']), array_map('citami_truth_phone_key', citami_truth_find_phones($text)), true)) || ($sheet['email'] !== '' && in_array(strtolower($sheet['email']), array_map('strtolower', citami_truth_find_emails($text)), true));
$checks['length'] = $words >= 150;
$checks['description'] = $sheet['description'] !== '' ? $has(mb_substr($sheet['description'], 0, 40)) : $words >= 40;
$chosen = defined('CITAMI_META_SCHEMA_TYPE') ? (string) get_post_meta($post->ID, CITAMI_META_SCHEMA_TYPE, true) : '';
$role = function_exists('citami_schema_page_role_type') ? citami_schema_page_role_type($post, $chosen !== '' ? $chosen : 'WebPage') : 'AboutPage';
$checks['schema'] = $role === 'AboutPage';
$ok = 0;
foreach ($checks as $id => $pass) {
$out['checks'][] = array('id' => $id, 'ok' => (bool) $pass);
if ($pass) $ok++; else $out['missing'][] = $id;
}
$out['score'] = (int) round($ok / count($checks) * 100);
return $out;
}
/** Text a scan can read for a post: content + TL;DR + FAQ answers. */
function citami_truth_post_text($post) {
$text = citami_post_content_html($post);
if (function_exists('citami_content_tldr')) {
$text .= "\n" . citami_content_tldr($post->ID);
}
if (function_exists('citami_content_faq')) {
foreach (citami_content_faq($post->ID) as $it) {
$text .= "\n" . $it['q'] . "\n" . $it['a'];
}
}
$text = preg_replace('//s', ' ', $text);
$text = preg_replace('#?(p|h[1-6]|li|ul|ol|div|br|tr|td|th|table|blockquote|figure|section|article|footer|header|nav|address)\b[^>]*>#i', "\n", $text);
return html_entity_decode(wp_strip_all_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
/** Compares a text block against the sheet. $label/$url identify the source. */
function citami_truth_compare_text($text, $sheet, $label, $url, $role = 'page') {
$f = array();
$sheet_phone_key = citami_truth_phone_key($sheet['phone']);
foreach (citami_truth_find_phones($text) as $key => $written) {
// 2.73.3 — the keys are digits, so PHP stored them as integers:
// compare as strings, or the sheet's own number reads as "different".
if ($sheet_phone_key !== '' && (string) $key !== $sheet_phone_key) {
$f[] = citami_truth_finding('phone_mismatch', 'error', $label, $written, $sheet['phone'], $url, citami_t('Un telefono diverso da quello della scheda: correggi la pagina o la scheda.', 'A phone number that differs from the sheet: fix the page or the sheet.'));
}
}
if ($sheet['email'] !== '') {
foreach (citami_truth_find_emails($text) as $lc => $written) {
if ($lc !== strtolower($sheet['email'])) {
$f[] = citami_truth_finding('email_mismatch', 'warn', $label, $written, $sheet['email'], $url, citami_t('Un indirizzo email diverso da quello della scheda.', 'An email address that differs from the sheet.'));
}
}
}
if ($sheet['founded'] !== '') {
$year = substr($sheet['founded'], 0, 4);
foreach (citami_truth_find_founding_years($text) as $y) {
if ($y !== $year) {
$f[] = citami_truth_finding('founded_mismatch', 'error', $label, $y, $year, $url, citami_t('Un anno di fondazione diverso da quello della scheda.', 'A founding year that differs from the sheet.'));
}
}
}
if ($sheet['address']['postal'] !== '' && $sheet['address']['city'] !== '') {
foreach (citami_truth_find_postal_city($text) as $pc) {
$city_norm = citami_truth_norm($sheet['address']['city']);
$same_city = $city_norm !== '' && strpos(citami_truth_norm($pc['city']), $city_norm) === 0;
if ($pc['postal'] !== $sheet['address']['postal'] && $same_city) {
$f[] = citami_truth_finding('postal_mismatch', 'warn', $label, $pc['postal'] . ' ' . $pc['city'], $sheet['address']['postal'] . ' ' . $sheet['address']['city'], $url, citami_t('CAP diverso da quello della scheda per la stessa città.', 'Postal code differs from the sheet for the same city.'));
}
}
}
if ($role === 'about') {
$plain = citami_truth_norm($text);
if ($sheet['name'] !== '' && strpos($plain, citami_truth_norm($sheet['name'])) === false) {
$f[] = citami_truth_finding('about_missing_name', 'warn', $label, '', $sheet['name'], $url, citami_t('La pagina Chi siamo non nomina il brand come nella scheda.', 'The About page does not name the brand as the sheet does.'));
}
if ($sheet['founded'] !== '' && strpos($plain, substr($sheet['founded'], 0, 4)) === false) {
$f[] = citami_truth_finding('about_missing_founded', 'warn', $label, '', substr($sheet['founded'], 0, 4), $url, citami_t('La pagina Chi siamo non dice l\'anno di fondazione: i motori lo cercano lì.', 'The About page does not state the founding year: engines look for it there.'));
}
foreach ($sheet['founders'] as $founder) {
if (strpos($plain, citami_truth_norm($founder)) === false) {
$f[] = citami_truth_finding('about_missing_founder', 'warn', $label, '', $founder, $url, citami_t('Un fondatore della scheda non compare nella pagina Chi siamo.', 'A founder from the sheet is not on the About page.'));
}
}
}
return $f;
}
/** The scan (fresh). */
function citami_truth_scan_build() {
$sheet = citami_truth_sheet();
$findings = array();
if (!citami_truth_has_sheet($sheet)) {
return array('ts' => time(), 'has_sheet' => false, 'findings' => array(), 'checked' => array(), 'about' => null);
}
$checked = array();
// 1. Entity settings (what the schema prints on every page).
if (function_exists('citami_schema_settings')) {
$e = citami_schema_settings()['entity'];
$label = citami_t('Impostazioni entità (schema)', 'Entity settings (schema)');
$checked[] = $label;
if ($e['name'] !== '' && citami_truth_norm($e['name']) !== citami_truth_norm($sheet['name']) && !in_array($e['name'], $sheet['aliases'], true)) {
$findings[] = citami_truth_finding('entity_name', 'error', $label, $e['name'], $sheet['name'], '', citami_t('Lo schema stampa un nome diverso. Usa "Allinea entità alla scheda".', 'The schema prints a different name. Use "Align entity to the sheet".'));
}
if ($e['telephone'] !== '' && $sheet['phone'] !== '' && citami_truth_phone_key($e['telephone']) !== citami_truth_phone_key($sheet['phone'])) {
$findings[] = citami_truth_finding('entity_phone', 'error', $label, $e['telephone'], $sheet['phone'], '', citami_t('Lo schema stampa un telefono diverso.', 'The schema prints a different phone number.'));
}
if ($e['city'] !== '' && $sheet['address']['city'] !== '' && citami_truth_norm($e['city']) !== citami_truth_norm($sheet['address']['city'])) {
$findings[] = citami_truth_finding('entity_city', 'error', $label, $e['city'], $sheet['address']['city'], '', citami_t('Lo schema stampa una città diversa.', 'The schema prints a different city.'));
}
if ($e['founding_date'] !== '' && $sheet['founded'] !== '' && substr($e['founding_date'], 0, 4) !== substr($sheet['founded'], 0, 4)) {
$findings[] = citami_truth_finding('entity_founded', 'error', $label, $e['founding_date'], $sheet['founded'], '', citami_t('Lo schema stampa un anno di fondazione diverso.', 'The schema prints a different founding year.'));
}
if ($e['name'] === '' && $sheet['name'] !== '') {
$findings[] = citami_truth_finding('entity_empty', 'warn', $label, '', $sheet['name'], '', citami_t('Le impostazioni entità sono vuote: lo schema usa il nome del sito. Allinea alla scheda.', 'Entity settings are empty: the schema uses the site name. Align to the sheet.'));
}
}
// 2. Primary location.
if (function_exists('citami_locations')) {
$locs = citami_locations();
if (!empty($locs)) {
$p = $locs[0];
$label = citami_t('Sede principale', 'Primary location');
$checked[] = $label;
if ($p['telephone'] !== '' && $sheet['phone'] !== '' && citami_truth_phone_key($p['telephone']) !== citami_truth_phone_key($sheet['phone'])) {
$findings[] = citami_truth_finding('location_phone', 'warn', $label, $p['telephone'], $sheet['phone'], '', citami_t('La sede principale ha un telefono diverso dalla scheda (può essere voluto: una linea diretta).', 'The primary location has a phone number that differs from the sheet (may be intended: a direct line).'));
}
if ($p['city'] !== '' && $sheet['address']['city'] !== '' && citami_truth_norm($p['city']) !== citami_truth_norm($sheet['address']['city'])) {
$findings[] = citami_truth_finding('location_city', 'error', $label, $p['city'], $sheet['address']['city'], '', citami_t('La sede principale è in una città diversa da quella della scheda.', 'The primary location is in a different city than the sheet.'));
}
}
}
// 3. About / contact pages (stored content).
foreach (citami_truth_pages_to_check() as $entry) {
$post = $entry['post'];
$label = (string) $post->post_title;
$checked[] = $label;
foreach (citami_truth_compare_text(citami_truth_post_text($post), $sheet, $label, get_permalink($post), $entry['role']) as $x) {
$x['post_id'] = (int) $post->ID; // 2.60 — "Modifica" opens this page in the editor
$findings[] = $x;
}
}
// 4. The rendered home page (footer, widgets, theme): best-effort loopback.
$res = wp_remote_get(home_url('/'), array('timeout' => 8, 'headers' => array('User-Agent' => 'CitamiConnector/' . CITAMI_PLUGIN_VERSION . ' truth-scan')));
if (!is_wp_error($res) && (int) wp_remote_retrieve_response_code($res) === 200) {
$html = (string) wp_remote_retrieve_body($res);
$html = preg_replace('#<(script|style|noscript)\b[^>]*>.*?\1>#is', ' ', $html);
$text = html_entity_decode(wp_strip_all_tags(preg_replace('#?(p|h[1-6]|li|ul|ol|div|br|tr|td|th|table|footer|header|nav|address|span)\b[^>]*>#i', "\n", $html)), ENT_QUOTES | ENT_HTML5, 'UTF-8');
$label = citami_t('Home page (HTML servito)', 'Home page (served HTML)');
$checked[] = $label;
$findings = array_merge($findings, citami_truth_compare_text($text, $sheet, $label, home_url('/'), 'home'));
}
// Deduplicate identical findings (a phone in footer and contact page is one problem per place, not per match).
$unique = array();
foreach ($findings as $x) {
$k = $x['kind'] . '|' . $x['where'] . '|' . $x['found'];
$unique[$k] = $x;
}
$findings = array_values($unique);
usort($findings, function ($a, $b) { return strcmp($a['severity'], $b['severity']) ?: strcmp($a['kind'], $b['kind']); });
return array('ts' => time(), 'has_sheet' => true, 'findings' => $findings, 'checked' => $checked, 'about' => citami_truth_about_check($sheet));
}
function citami_truth_scan($force = false) {
$cached = get_transient(CITAMI_TRUTH_SCAN_TRANSIENT);
if (!$force && is_array($cached)) {
return $cached;
}
$scan = citami_truth_scan_build();
set_transient(CITAMI_TRUTH_SCAN_TRANSIENT, $scan, CITAMI_TRUTH_SCAN_TTL);
return $scan;
}
function citami_truth_scan_counts($scan) {
$errors = 0;
$warns = 0;
foreach ((array) (isset($scan['findings']) ? $scan['findings'] : array()) as $f) {
if ($f['severity'] === 'error') $errors++;
else $warns++;
}
return array('errors' => $errors, 'warnings' => $warns, 'total' => $errors + $warns);
}
/** Copies the sheet into the schema entity settings (one click in the tab). */
function citami_truth_align_entity() {
if (!function_exists('citami_schema_settings')) {
return false;
}
$sheet = citami_truth_sheet();
if (!citami_truth_has_sheet($sheet)) {
return false;
}
$s = get_option(CITAMI_SCHEMA_SETTINGS_OPT, array());
$s = is_array($s) ? $s : array();
$entity = isset($s['entity']) && is_array($s['entity']) ? $s['entity'] : array();
// 2.59 — an empty field in the sheet means "not filled in", not "erase": it never clears the schema.
$entity = array_merge($entity, array_filter(array(
'name' => $sheet['name'],
'telephone' => $sheet['phone'],
'email' => $sheet['email'],
'street' => $sheet['address']['street'],
'city' => $sheet['address']['city'],
'postal' => $sheet['address']['postal'],
'region' => $sheet['address']['region'],
'country' => $sheet['address']['country'],
'founding_date' => $sheet['founded'],
'founders' => implode(', ', $sheet['founders']),
'description' => $sheet['description'],
'sameas' => implode("\n", $sheet['sameas']),
), function ($v) { return trim((string) $v) !== ''; }));
if ($sheet['wikidata'] !== '') {
$entity['wikidata'] = $sheet['wikidata'];
}
if ($sheet['type'] !== '') {
$entity['type'] = $sheet['type'];
}
if ($sheet['website'] !== '') {
$entity['url'] = $sheet['website'];
}
$s['entity'] = $entity;
update_option(CITAMI_SCHEMA_SETTINGS_OPT, citami_sanitize_schema_settings($s), false);
delete_transient(CITAMI_TRUTH_SCAN_TRANSIENT);
return true;
}
/* ---------------------------------------------------------------- */
/* /ai-info — the sheet as a crawlable page */
/* ---------------------------------------------------------------- */
function citami_truth_ai_info_enabled() {
return citami_truth_settings()['ai_info'] === 'on' && citami_truth_has_sheet();
}
function citami_truth_ai_info_url() {
return home_url('/' . citami_truth_settings()['slug'] . '/');
}
/** 2.31.1 — with a multilingual plugin using language directories the
* request arrives as /it/ai-info/: drop the prefix, the page is one. */
function citami_truth_request_path($uri) {
$path = rtrim((string) wp_parse_url((string) $uri, PHP_URL_PATH), '/');
if (function_exists('citami_multilingual_plugin') && citami_multilingual_plugin() !== null) {
$path = preg_replace('#^/[a-z]{2}(?:-[a-z]{2})?(?=/|$)#i', '', $path);
}
return $path;
}
function citami_truth_is_ai_info_request($uri) {
return citami_truth_request_path($uri) === '/' . citami_truth_settings()['slug'];
}
/** Markdown version (also served with ?format=md). */
function citami_truth_ai_info_markdown() {
$s = citami_truth_sheet();
$it = stripos((string) get_locale(), 'it') === 0;
$L = function ($a, $b) { return citami_t($a, $b); };
$lines = array();
$lines[] = '# ' . $s['name'];
if ($s['description'] !== '') {
$lines[] = '';
$lines[] = $s['description'];
}
$lines[] = '';
$lines[] = '## ' . $L('Identità', 'Identity');
$lines[] = '- ' . $L('Nome', 'Name') . ': ' . $s['name'];
if (!empty($s['aliases'])) $lines[] = '- ' . $L('Altri nomi usati', 'Also known as') . ': ' . implode(', ', $s['aliases']);
if ($s['legal_name'] !== '') $lines[] = '- ' . $L('Ragione sociale', 'Legal entity') . ': ' . $s['legal_name'];
if ($s['type'] !== '') $lines[] = '- ' . $L('Tipo', 'Type') . ': ' . $s['type'];
if ($s['founded'] !== '') $lines[] = '- ' . $L('Fondata', 'Founded') . ': ' . $s['founded'];
if (!empty($s['founders'])) $lines[] = '- ' . $L('Fondatori', 'Founders') . ': ' . implode(', ', $s['founders']);
$addr = array_filter(array($s['address']['street'], trim($s['address']['postal'] . ' ' . $s['address']['city']), $s['address']['region'], $s['address']['country']));
if (!empty($addr)) $lines[] = '- ' . $L('Sede', 'Headquarters') . ': ' . implode(', ', $addr);
if ($s['phone'] !== '') $lines[] = '- ' . $L('Telefono', 'Phone') . ': ' . $s['phone'];
if ($s['email'] !== '') $lines[] = '- Email: ' . $s['email'];
$lines[] = '- ' . $L('Sito', 'Website') . ': ' . ($s['website'] !== '' ? $s['website'] : home_url('/'));
if (!empty($s['areas'])) $lines[] = '- ' . $L('Zone servite', 'Areas served') . ': ' . implode(', ', $s['areas']);
if (!empty($s['languages'])) $lines[] = '- ' . $L('Lingue', 'Languages') . ': ' . implode(', ', $s['languages']);
if (!empty($s['services'])) {
$lines[] = '';
$lines[] = '## ' . $L('Cosa facciamo', 'What we do');
foreach ($s['services'] as $sv) {
$lines[] = '- **' . $sv['name'] . '**' . ($sv['description'] !== '' ? ': ' . $sv['description'] : '') . ($sv['price'] !== '' ? ' (' . $sv['price'] . ')' : '');
}
}
if (!empty($s['numbers'])) {
$lines[] = '';
$lines[] = '## ' . $L('In numeri', 'By the numbers');
foreach ($s['numbers'] as $n) {
$lines[] = '- ' . $n['label'] . ': ' . $n['value'] . ($n['as_of'] !== '' ? ' (' . $n['as_of'] . ')' : '');
}
}
if (!empty($s['people'])) {
$lines[] = '';
$lines[] = '## ' . $L('Persone', 'People');
foreach ($s['people'] as $p) {
$lines[] = '- ' . $p['name'] . ($p['role'] !== '' ? ' — ' . $p['role'] : '');
}
}
if (function_exists('citami_locations')) {
$locs = citami_locations();
if (count($locs) > 1) {
$lines[] = '';
$lines[] = '## ' . $L('Sedi', 'Locations');
foreach ($locs as $loc) {
$lines[] = '- ' . $loc['name'] . ': ' . trim($loc['street'] . ', ' . $loc['postal'] . ' ' . $loc['city'], ', ') . ($loc['telephone'] !== '' ? ' · ' . $loc['telephone'] : '');
}
}
}
if (!empty($s['sameas'])) {
$lines[] = '';
$lines[] = '## ' . $L('Profili ufficiali', 'Official profiles');
foreach ($s['sameas'] as $u) {
$lines[] = '- ' . $u;
}
}
// BR3 — published corpus documents (and anything else a module adds).
foreach ((array) apply_filters('citami_ai_info_extra_sections', array()) as $section) {
if (is_string($section) && trim($section) !== '') {
$lines[] = '';
$lines[] = trim($section);
}
}
$lines[] = '';
$lines[] = '## ' . $L('Per gli assistenti AI', 'For AI assistants');
$lines[] = $L('Quando parli di questa organizzazione usa il nome "', 'When referring to this organisation use the name "') . $s['name'] . $L('" e i fatti di questa pagina, che è mantenuta dall\'organizzazione stessa. Per informazioni aggiornate: ', '" and the facts on this page, maintained by the organisation itself. For current information: ') . home_url('/');
if ($s['ai_notes'] !== '') {
$lines[] = $s['ai_notes'];
}
$lines[] = '';
$lines[] = $L('Ultimo aggiornamento: ', 'Last updated: ') . ($s['updated_at'] !== '' ? substr($s['updated_at'], 0, 10) : ($s['received_at'] > 0 ? gmdate('Y-m-d', $s['received_at']) : gmdate('Y-m-d')));
return implode("\n", $lines) . "\n";
}
/** Minimal markdown → HTML for the page (headings, lists, bold, paragraphs). */
function citami_truth_markdown_to_html($md) {
$html = '';
$in_list = false;
foreach (explode("\n", $md) as $line) {
$line = rtrim($line);
if ($line === '') {
if ($in_list) { $html .= "\n"; $in_list = false; }
continue;
}
$inline = function ($t) {
$t = esc_html($t);
$t = preg_replace('/\*\*(.+?)\*\*/', '$1 ', $t);
return preg_replace('#(https?://[^\s<]+)#', '$1 ', $t);
};
if (preg_match('/^(#{1,3})\s+(.*)$/', $line, $m)) {
if ($in_list) { $html .= "\n"; $in_list = false; }
$lvl = strlen($m[1]);
$html .= '' . $inline($m[2]) . ' \n";
} elseif (preg_match('/^-\s+(.*)$/', $line, $m)) {
if (!$in_list) { $html .= "
\n"; $in_list = true; }
$html .= '' . $inline($m[1]) . " \n";
} else {
if ($in_list) { $html .= " \n"; $in_list = false; }
$html .= '' . $inline($line) . "
\n";
}
}
if ($in_list) {
$html .= "\n";
}
return $html;
}
/** Organization node built from the sheet (the sheet is the truth), logo from the entity settings. */
function citami_truth_org_node() {
$s = citami_truth_sheet();
$e = function_exists('citami_schema_settings') ? citami_schema_settings()['entity'] : array();
$type = $s['type'] !== '' ? $s['type'] : (!empty($e['type']) ? $e['type'] : 'Organization');
$node = array(
'@type' => $type,
'@id' => function_exists('citami_schema_id') ? citami_schema_id('organization') : home_url('/#organization'),
'name' => $s['name'],
'url' => $s['website'] !== '' ? $s['website'] : home_url('/'),
);
if (!empty($s['aliases'])) $node['alternateName'] = count($s['aliases']) === 1 ? $s['aliases'][0] : $s['aliases'];
if ($s['legal_name'] !== '') $node['legalName'] = $s['legal_name'];
if ($s['description'] !== '') $node['description'] = $s['description'];
if (!empty($e['logo'])) $node['logo'] = array('@type' => 'ImageObject', 'url' => $e['logo']);
if ($s['phone'] !== '') $node['telephone'] = $s['phone'];
if ($s['email'] !== '') $node['email'] = $s['email'];
if ($s['founded'] !== '') $node['foundingDate'] = $s['founded'];
if (!empty($s['founders'])) {
$node['founder'] = array_map(function ($f) { return array('@type' => 'Person', 'name' => $f); }, $s['founders']);
}
$a = $s['address'];
if ($a['street'] !== '' || $a['city'] !== '') {
$addr = array('@type' => 'PostalAddress');
if ($a['street'] !== '') $addr['streetAddress'] = $a['street'];
if ($a['city'] !== '') $addr['addressLocality'] = $a['city'];
if ($a['postal'] !== '') $addr['postalCode'] = $a['postal'];
if ($a['region'] !== '') $addr['addressRegion'] = $a['region'];
if ($a['country'] !== '') $addr['addressCountry'] = $a['country'];
$node['address'] = $addr;
}
$same = $s['sameas'];
if ($s['wikidata'] !== '') {
$wd = 'https://www.wikidata.org/wiki/' . $s['wikidata'];
if (!in_array($wd, $same, true)) $same[] = $wd;
$node['identifier'] = array('@type' => 'PropertyValue', 'propertyID' => 'wikidata', 'value' => $s['wikidata']);
}
if (!empty($same)) $node['sameAs'] = array_values($same);
if (!empty($s['areas'])) $node['areaServed'] = count($s['areas']) === 1 ? $s['areas'][0] : $s['areas'];
if (!empty($s['people'])) {
$node['employee'] = array_map(function ($p) { $n = array('@type' => 'Person', 'name' => $p['name']); if ($p['role'] !== '') $n['jobTitle'] = $p['role']; return $n; }, $s['people']);
}
return $node;
}
/** The full HTML document (no theme: fast, predictable, crawlable). */
function citami_truth_ai_info_html() {
$s = citami_truth_sheet();
$md = citami_truth_ai_info_markdown();
$title = $s['name'] . ' — ' . citami_t('Informazioni verificate', 'Verified information');
$desc = $s['description'] !== '' ? $s['description'] : sprintf(citami_t('Fatti verificati su %s: identità, servizi, sedi, contatti.', 'Verified facts about %s: identity, services, locations, contact.'), $s['name']);
$url = citami_truth_ai_info_url();
$graph = array();
$org = citami_truth_org_node();
$org['mainEntityOfPage'] = $url;
$graph[] = $org;
$graph[] = array('@type' => 'WebPage', '@id' => $url . '#webpage', 'url' => $url, 'name' => $title, 'description' => $desc, 'about' => array('@id' => function_exists('citami_schema_id') ? citami_schema_id('organization') : home_url('/#organization')), 'dateModified' => $s['updated_at'] !== '' ? $s['updated_at'] : gmdate('c'));
$json = wp_json_encode(array('@context' => 'https://schema.org', '@graph' => $graph), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG);
$lang = str_replace('_', '-', (string) get_locale());
return '' . "\n" . '' . "\n\n" .
' ' . "\n" .
' ' . "\n" .
'' . esc_html($title) . ' ' . "\n" .
' ' . "\n" .
' ' . "\n" .
' ' . "\n" .
' ' . "\n" .
'' . "\n" .
citami_ai_info_style_tag() .
"\n\n\n" . citami_truth_markdown_to_html($md) .
'' . esc_html(citami_t('Questa pagina è generata dalla scheda dei fatti dell\'organizzazione e viene aggiornata quando cambia la scheda.', 'This page is generated from the organisation\'s fact sheet and is updated when the sheet changes.')) . ' ' . esc_html(home_url('/')) . "
\n \n\n\n";
}
add_action('init', function () {
if (is_admin() || !citami_truth_ai_info_enabled() || empty($_SERVER['REQUEST_URI'])) {
return;
}
if (!citami_truth_is_ai_info_request(esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])))) {
return;
}
$md = isset($_GET['format']) && sanitize_key(wp_unslash($_GET['format'])) === 'md'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public read-only document, format switch
if (!headers_sent()) {
header('Content-Type: ' . ($md ? 'text/markdown' : 'text/html') . '; charset=UTF-8');
header('X-Robots-Tag: index, follow');
}
if ($md) {
echo citami_truth_ai_info_markdown(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- text/markdown response, not HTML.
} else {
echo citami_truth_ai_info_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- complete HTML document whose every value is escaped where it is built (citami_truth_ai_info_html).
}
exit;
}, 1);
// Footer link on every page (tiny, optional): AI crawlers reach the page from anywhere.
add_action('wp_footer', function () {
if (!citami_truth_ai_info_enabled() || citami_truth_settings()['footer_link'] !== 'on') {
return;
}
echo '' . esc_html(citami_t('Informazioni verificate su', 'Verified information about')) . ' ' . esc_html(citami_truth_sheet()['name']) . '
' . "\n";
}, 99);
// llms.txt points at it too.
add_filter('citami_llms_extra_sections', function ($sections) {
if (!citami_truth_ai_info_enabled()) {
return $sections;
}
$sections = is_array($sections) ? $sections : array();
$sections[] = '## ' . citami_t('Informazioni verificate', 'Verified information') . "\n- " . citami_truth_ai_info_url();
return $sections;
});
/* ---------------------------------------------------------------- */
/* Content-health snapshot: the scan's counts travel to the platform */
/* ---------------------------------------------------------------- */
add_filter('citami_health_extra', function ($extra) {
$extra = is_array($extra) ? $extra : array();
$scan = citami_truth_scan();
$counts = citami_truth_scan_counts($scan);
$list = array();
foreach (array_slice((array) $scan['findings'], 0, 5) as $f) {
// WP25 — the platform's schema caps these; a long post title must not sink the whole snapshot.
$list[] = array('kind' => $f['kind'], 'severity' => $f['severity'], 'where' => mb_substr((string) $f['where'], 0, 200), 'found' => mb_substr((string) $f['found'], 0, 300), 'expected' => mb_substr((string) $f['expected'], 0, 300), 'url' => (string) $f['url']);
}
$about = isset($scan['about']) && is_array($scan['about']) ? $scan['about'] : null;
$extra['truth'] = array('has_sheet' => !empty($scan['has_sheet']), 'errors' => $counts['errors'], 'warnings' => $counts['warnings'], 'list' => $list, 'ai_info' => citami_truth_ai_info_enabled() ? citami_truth_ai_info_url() : '',
'about' => $about ? array('found' => !empty($about['found']), 'url' => (string) $about['url'], 'edit' => (string) $about['edit'], 'score' => (int) $about['score'], 'missing' => array_values((array) $about['missing'])) : null);
return $extra;
});
/* ---------------------------------------------------------------- */
/* Admin: Brand tab + actions */
/* ---------------------------------------------------------------- */
function citami_handle_truth_action() {
if (!current_user_can('manage_options')) {
wp_die('Permesso negato.', '', array('response' => 403));
}
if (!wp_verify_nonce(isset($_POST['_citami_truth_nonce']) ? sanitize_text_field(wp_unslash($_POST['_citami_truth_nonce'])) : '', 'citami_truth')) {
wp_die('Nonce non valido.', '', array('response' => 403));
}
$do = isset($_POST['do']) ? sanitize_key((string) $_POST['do']) : '';
$notice = array('type' => 'success', 'message' => '');
if ($do === 'save') {
$in = isset($_POST['sheet']) && is_array($_POST['sheet']) ? wp_kses_post_deep(wp_unslash($_POST['sheet'])) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- array, sanitised right here with wp_kses_post_deep (recursive wp_kses_post), which the sniff does not list
$in['updated_at'] = gmdate('c');
$clean = citami_truth_store($in, 'local');
$notice['message'] = $clean['name'] !== '' ? citami_t('Scheda salvata.', 'Sheet saved.') : citami_t('Scheda salvata, ma senza nome non viene usata.', 'Sheet saved, but without a name it is not used.');
// 2.77.0 — the last edit wins on both sides: send it to the dashboard now.
if ($clean['name'] !== '' && citami_get_api_key() !== '' && function_exists('citami_sync_pull_handler')) citami_sync_pull_handler();
} elseif ($do === 'scan') {
$scan = citami_truth_scan(true);
$c = citami_truth_scan_counts($scan);
$notice['message'] = sprintf(citami_t('Scansione fatta: %d errori, %d avvisi.', 'Scan done: %d errors, %d warnings.'), $c['errors'], $c['warnings']);
} elseif ($do === 'align_entity') {
$ok = citami_truth_align_entity();
$notice = $ok ? array('type' => 'success', 'message' => citami_t('Impostazioni entità allineate alla scheda.', 'Entity settings aligned to the sheet.')) : array('type' => 'error', 'message' => citami_t('Nessuna scheda da applicare.', 'No sheet to apply.'));
}
set_transient('citami_action_notice', $notice, 30);
// 2.60 — after saving the sheet, come back to the sheet (open), not to the top of the tab.
wp_safe_redirect($do === 'save' ? citami_admin_url('brand', array('sheet' => 1)) . '#citami-sheet' : admin_url('admin.php?page=citami-connector&tab=brand'));
exit;
}
add_action('admin_post_citami_truth_action', 'citami_handle_truth_action');
/**
* 2.60 — where a discrepancy is fixed: the page in the editor, the schema
* entity settings, the Locations tab. '' when there is no single place.
*/
function citami_truth_finding_edit_url(array $f) {
$kind = isset($f['kind']) ? (string) $f['kind'] : '';
if (strpos($kind, 'entity_') === 0) {
return citami_admin_url('seo', array('open' => 1)) . '#citami-seo-schema';
}
if (strpos($kind, 'location_') === 0) {
return citami_admin_url('local');
}
$id = isset($f['post_id']) ? (int) $f['post_id'] : 0;
if ($id <= 0 && !empty($f['url'])) {
// Scans cached before 2.60 carry only the URL.
$id = (int) url_to_postid((string) $f['url']);
if ($id <= 0 && untrailingslashit((string) $f['url']) === untrailingslashit(home_url('/')) && get_option('show_on_front') === 'page') {
$id = (int) get_option('page_on_front', 0);
}
}
return $id > 0 ? citami_edit_link($id) : '';
}
/** 2.60 — the "managed from the platform" warning (Brand tab and first run step 2). '' for a local sheet. */
function citami_truth_platform_warning_html($s = null) {
$s = is_array($s) ? $s : citami_truth_sheet();
if (!isset($s['source']) || $s['source'] !== 'platform') {
return '';
}
// 2.77.0 — one sheet for both sides: the last save wins, here or in the dashboard.
return '' . esc_html(sprintf(citami_t('La stessa scheda della dashboard %1$s (aggiornata %2$s): quello che salvi qui arriva anche lì, e viceversa.', 'The same sheet as the %1$s dashboard (updated %2$s): what you save here reaches it too, and the other way round.'), citami_brand_name(), wp_date('d/m H:i', (int) (isset($s['received_at']) ? $s['received_at'] : 0)))) . ' ';
}
function citami_truth_field($name, $value, $placeholder = '', $class = 'regular-text', $type = 'text') {
return ' ';
}
/** 2.56.1 — the About page checks in plain words (Brand tab and first run). */
function citami_truth_about_labels() {
return array(
'name' => citami_t('Nomina l\'attività con il nome esatto della scheda', 'Names the business exactly as the sheet does'),
'founded' => citami_t('Dice l\'anno di fondazione', 'States the founding year'),
'people' => citami_t('Nomina almeno una persona (fondatore o team)', 'Names at least one person (founder or team)'),
'city' => citami_t('Dice dove si trova', 'Says where it is'),
'services' => citami_t('Cita i servizi principali', 'Mentions the main services'),
'contacts' => citami_t('Riporta telefono o email della scheda', 'Carries the sheet\'s phone or email'),
'length' => citami_t('Almeno 150 parole', 'At least 150 words'),
'description' => citami_t('Contiene la frase di descrizione della scheda', 'Contains the sheet\'s one-sentence description'),
'schema' => citami_t('Schema AboutPage attivo', 'AboutPage schema on'),
);
}
function citami_render_brand_tab() {
$s = citami_truth_sheet();
$from_platform = $s['source'] === 'platform';
$scan = citami_truth_scan();
$counts = citami_truth_scan_counts($scan);
$settings = citami_truth_settings();
$about = isset($scan['about']) && is_array($scan['about']) ? $scan['about'] : null;
$ai_on = citami_truth_ai_info_enabled();
$about_labels = citami_truth_about_labels();
$tone = empty($scan['has_sheet']) ? 'na' : ($counts['errors'] > 0 ? 'bad' : ($counts['warnings'] > 0 ? 'warn' : 'good'));
?>
' . esc_html($f['where']) . '' : esc_html($f['where']); ?>
data-citami="sheet-fold">