array('Monday', citami_t('Lunedì', 'Monday')), 'tue' => array('Tuesday', citami_t('Martedì', 'Tuesday')), 'wed' => array('Wednesday', citami_t('Mercoledì', 'Wednesday')), 'thu' => array('Thursday', citami_t('Giovedì', 'Thursday')), 'fri' => array('Friday', citami_t('Venerdì', 'Friday')), 'sat' => array('Saturday', citami_t('Sabato', 'Saturday')), 'sun' => array('Sunday', citami_t('Domenica', 'Sunday')), ); } function citami_location_defaults() { return array( 'id' => '', 'name' => '', 'type' => '', 'street' => '', 'city' => '', 'postal' => '', 'region' => '', 'country' => '', 'telephone' => '', 'email' => '', 'url' => '', 'page_id' => 0, 'hours' => array(), 'lat' => '', 'lng' => '', 'map_url' => '', 'price_range' => '', 'image' => '', 'description' => '', 'area_served' => '', 'primary' => false, 'imported' => '', 'source_id' => '', ); } /** All locations, normalised, primary first. */ function citami_locations() { $raw = get_option(CITAMI_LOCATIONS_OPT, array()); if (!is_array($raw)) { return array(); } $out = array(); foreach ($raw as $loc) { if (!is_array($loc) || empty($loc['id']) || empty($loc['name'])) { continue; } $out[] = array_merge(citami_location_defaults(), $loc); } usort($out, function ($a, $b) { return (int) !empty($b['primary']) - (int) !empty($a['primary']); }); return $out; } function citami_location_get($id) { foreach (citami_locations() as $loc) { if ($loc['id'] === (string) $id) { return $loc; } } return null; } /** "09:00-13:00,15:00-19:00" → [[opens, closes], …]; "closed"/"chiuso" → []; '' → null. */ function citami_local_parse_hours($spec) { $spec = strtolower(trim((string) $spec)); if ($spec === '') { return null; } if (in_array($spec, array('closed', 'chiuso', 'chiusa', 'geschlossen', 'zu', '-', 'x'), true)) { return array(); } $ranges = array(); foreach (preg_split('/\s*[,;]\s*/', $spec) as $part) { if (preg_match('/^(\d{1,2})[:.](\d{2})\s*[-–]\s*(\d{1,2})[:.](\d{2})$/', trim($part), $m)) { $o = (int) $m[1]; $c = (int) $m[3]; if ($o > 23 || $c > 24 || (int) $m[2] > 59 || (int) $m[4] > 59) { continue; } $ranges[] = array(sprintf('%02d:%s', $o, $m[2]), sprintf('%02d:%s', $c, $m[4])); } } // Nothing usable ("abc", "25:00-26:00") is "not stated", never "closed". return empty($ranges) ? null : $ranges; } function citami_local_sanitize_hours($in) { $out = array(); if (!is_array($in)) { return $out; } foreach (array_keys(citami_local_days()) as $d) { if (!isset($in[$d])) { continue; } $spec = trim(sanitize_text_field((string) $in[$d])); if ($spec === '') { continue; } $parsed = citami_local_parse_hours($spec); if ($parsed === array()) { $out[$d] = 'closed'; } elseif (!empty($parsed)) { $out[$d] = implode(',', array_map(function ($r) { return $r[0] . '-' . $r[1]; }, $parsed)); } } return $out; } /** Validates + cleans a location; returns the clean array or a WP_Error. */ function citami_sanitize_location(array $in, $existing_id = '') { $d = citami_location_defaults(); $out = $d; $name = mb_substr(sanitize_text_field((string) (isset($in['name']) ? $in['name'] : '')), 0, 120); if ($name === '') { return new WP_Error('citami_location_name', citami_t('Nome della sede obbligatorio.', 'Location name is required.')); } $out['name'] = $name; $id = $existing_id !== '' ? $existing_id : sanitize_title(isset($in['id']) && $in['id'] !== '' ? (string) $in['id'] : $name); if ($id === '') { $id = 'sede-' . substr(md5($name), 0, 6); } $out['id'] = $id; $out['type'] = preg_replace('/[^A-Za-z]/', '', (string) (isset($in['type']) ? $in['type'] : '')); foreach (array('street', 'city', 'postal', 'region', 'telephone', 'price_range', 'area_served') as $k) { $out[$k] = mb_substr(sanitize_text_field((string) (isset($in[$k]) ? $in[$k] : '')), 0, 200); } $out['country'] = strtoupper(mb_substr(preg_replace('/[^A-Za-z]/', '', (string) (isset($in['country']) ? $in['country'] : '')), 0, 2)); if ($out['street'] === '' && $out['city'] === '') { return new WP_Error('citami_location_address', citami_t('Serve almeno via o città.', 'Street or city is required.')); } $email = trim((string) (isset($in['email']) ? $in['email'] : '')); $out['email'] = filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : ''; foreach (array('url', 'map_url', 'image') as $k) { $v = trim((string) (isset($in[$k]) ? $in[$k] : '')); $out[$k] = preg_match('#^https?://#i', $v) ? esc_url_raw($v) : ''; } $out['page_id'] = isset($in['page_id']) ? absint($in['page_id']) : 0; $out['hours'] = citami_local_sanitize_hours(isset($in['hours']) ? $in['hours'] : array()); $lat = trim((string) (isset($in['lat']) ? $in['lat'] : '')); $lng = trim((string) (isset($in['lng']) ? $in['lng'] : '')); if ($lat !== '' && $lng !== '' && is_numeric($lat) && is_numeric($lng) && abs((float) $lat) <= 90 && abs((float) $lng) <= 180) { $out['lat'] = (string) round((float) $lat, 6); $out['lng'] = (string) round((float) $lng, 6); } $out['description'] = mb_substr(sanitize_text_field((string) (isset($in['description']) ? $in['description'] : '')), 0, 500); $out['primary'] = !empty($in['primary']); $out['imported'] = sanitize_key((string) (isset($in['imported']) ? $in['imported'] : '')); $out['source_id'] = mb_substr(sanitize_text_field((string) (isset($in['source_id']) ? $in['source_id'] : '')), 0, 60); return $out; } function citami_locations_save(array $list) { // Exactly one primary: the first flagged one wins, none flagged → the first. $list = array_values($list); $seen = false; foreach ($list as $i => $l) { if (!empty($l['primary'])) { $list[$i]['primary'] = !$seen; $seen = true; } } if (!$seen && !empty($list)) { $list[0]['primary'] = true; } update_option(CITAMI_LOCATIONS_OPT, $list, false); } function citami_location_add(array $in) { $loc = citami_sanitize_location($in); if (is_wp_error($loc)) { return $loc; } $list = citami_locations(); if (count($list) >= CITAMI_LOCATIONS_MAX) { return new WP_Error('citami_location_limit', sprintf(citami_t('Massimo %d sedi.', 'At most %d locations.'), CITAMI_LOCATIONS_MAX)); } foreach ($list as $l) { if ($l['id'] === $loc['id']) { return new WP_Error('citami_location_duplicate', citami_t('Esiste già una sede con questo nome.', 'A location with this name already exists.')); } } if (!empty($loc['primary'])) { foreach ($list as $i => $l) { $list[$i]['primary'] = false; } } $list[] = $loc; citami_locations_save($list); return $loc; } function citami_location_update($id, array $in) { $list = citami_locations(); foreach ($list as $i => $l) { if ($l['id'] === (string) $id) { $loc = citami_sanitize_location(array_merge($l, $in), $l['id']); if (is_wp_error($loc)) { return $loc; } if (!empty($loc['primary'])) { foreach ($list as $j => $x) { $list[$j]['primary'] = false; } } $list[$i] = $loc; citami_locations_save($list); return $loc; } } return null; } // 2.40 — the locations travel with the content-health snapshot (name, // city, region, country, phone, url, primary): the dashboard lists them // next to the count health.php already sends. No hours, coordinates or // descriptions: the platform only needs to know where the business is. add_filter('citami_health_extra', function ($extra) { $extra = is_array($extra) ? $extra : array(); $list = array(); foreach (array_slice(citami_locations(), 0, 20) as $loc) { $list[] = array( 'name' => mb_substr((string) $loc['name'], 0, 120), 'city' => mb_substr((string) $loc['city'], 0, 80), 'region' => mb_substr((string) $loc['region'], 0, 80), 'country' => mb_substr((string) $loc['country'], 0, 80), 'phone' => mb_substr((string) $loc['telephone'], 0, 40), 'url' => mb_substr((string) $loc['url'], 0, 2000), 'primary' => !empty($loc['primary']), ); } $extra['locations_list'] = $list; return $extra; }); function citami_location_delete($id) { $list = citami_locations(); $kept = array_values(array_filter($list, function ($l) use ($id) { return $l['id'] !== (string) $id; })); $removed = count($list) - count($kept); if ($removed > 0) { citami_locations_save($kept); } return $removed; } /** The location a post is assigned to (meta), else the one whose page_id is the post. */ function citami_location_for_post($post_id) { $post_id = (int) $post_id; $id = (string) get_post_meta($post_id, CITAMI_META_LOCATION, true); if ($id !== '') { $loc = citami_location_get($id); if ($loc) { return $loc; } } foreach (citami_locations() as $loc) { if ((int) $loc['page_id'] === $post_id && $post_id > 0) { return $loc; } } return null; } /* ---------------------------------------------------------------- */ /* Schema */ /* ---------------------------------------------------------------- */ function citami_location_schema_id($loc) { return citami_schema_id('location-' . $loc['id']); } function citami_location_type($loc) { if ($loc['type'] !== '') { return $loc['type']; } $e = citami_schema_settings()['entity']['type']; // A LocalBusiness subtype on the entity (Dentist, Restaurant…) is the // branch type too; a plain Organization/Corporation is not a place. return ($e !== '' && !in_array($e, array('Organization', 'Corporation', 'NGO', 'EducationalOrganization', 'GovernmentOrganization'), true)) ? $e : 'LocalBusiness'; } function citami_location_url($loc) { if ((int) $loc['page_id'] > 0) { $p = get_post((int) $loc['page_id']); if ($p && $p->post_status === 'publish') { return get_permalink($p); } } return $loc['url']; } /** openingHoursSpecification, days with identical ranges grouped. */ function citami_location_hours_spec($loc) { $days = citami_local_days(); $groups = array(); foreach ($days as $key => $names) { if (!isset($loc['hours'][$key])) { continue; } $ranges = citami_local_parse_hours($loc['hours'][$key]); if ($ranges === null) { continue; } if ($ranges === array()) { $groups['closed'][] = $names[0]; continue; } foreach ($ranges as $r) { $groups[$r[0] . '-' . $r[1]][] = $names[0]; } } $out = array(); foreach ($groups as $range => $daylist) { $spec = array('@type' => 'OpeningHoursSpecification', 'dayOfWeek' => $daylist); if ($range === 'closed') { $spec['opens'] = '00:00'; $spec['closes'] = '00:00'; } else { list($o, $c) = explode('-', $range); $spec['opens'] = $o; $spec['closes'] = $c; } $out[] = $spec; } return $out; } function citami_location_address_node($loc) { $addr = array('@type' => 'PostalAddress'); if ($loc['street'] !== '') $addr['streetAddress'] = $loc['street']; if ($loc['city'] !== '') $addr['addressLocality'] = $loc['city']; if ($loc['postal'] !== '') $addr['postalCode'] = $loc['postal']; if ($loc['region'] !== '') $addr['addressRegion'] = $loc['region']; if ($loc['country'] !== '') $addr['addressCountry'] = $loc['country']; return $addr; } /** The LocalBusiness node for one location. */ function citami_location_node($loc) { $node = array( '@type' => citami_location_type($loc), '@id' => citami_location_schema_id($loc), 'name' => $loc['name'], 'address' => citami_location_address_node($loc), 'parentOrganization' => array('@id' => citami_schema_id('organization')), ); $url = citami_location_url($loc); if ($url !== '') { $node['url'] = $url; } if ($loc['telephone'] !== '') $node['telephone'] = $loc['telephone']; if ($loc['email'] !== '') $node['email'] = $loc['email']; if ($loc['description'] !== '') $node['description'] = $loc['description']; if ($loc['image'] !== '') $node['image'] = $loc['image']; if ($loc['price_range'] !== '') $node['priceRange'] = $loc['price_range']; if ($loc['map_url'] !== '') $node['hasMap'] = $loc['map_url']; if ($loc['lat'] !== '' && $loc['lng'] !== '') { $node['geo'] = array('@type' => 'GeoCoordinates', 'latitude' => (float) $loc['lat'], 'longitude' => (float) $loc['lng']); } $hours = citami_location_hours_spec($loc); if (!empty($hours)) { $node['openingHoursSpecification'] = $hours; } $areas = array_values(array_filter(array_map('trim', explode(',', $loc['area_served'])))); if (!empty($areas)) { $node['areaServed'] = count($areas) === 1 ? $areas[0] : $areas; } return $node; } add_filter('citami_schema_graph', function ($graph, $ctx) { $locations = citami_locations(); if (!is_array($graph) || empty($locations)) { return $graph; } $org_id = citami_schema_id('organization'); $org_idx = null; $page_idx = null; foreach ($graph as $i => $n) { if (!is_array($n) || empty($n['@id'])) { continue; } if ($n['@id'] === $org_id) { $org_idx = $i; } elseif (substr((string) $n['@id'], -8) === '#webpage') { $page_idx = $i; } } if ($org_idx === null) { return $graph; } $current = !empty($ctx['post']) ? citami_location_for_post($ctx['post']->ID) : null; // Organization → its branches. $refs = array(); foreach ($locations as $loc) { $refs[] = array('@id' => citami_location_schema_id($loc)); } $graph[$org_idx]['subOrganization'] = $refs; // A single location with no entity address: the Organization is that // place — complete it so a reader that stops at the first node has it. if (count($locations) === 1 && empty($graph[$org_idx]['address'])) { $graph[$org_idx]['address'] = citami_location_address_node($locations[0]); if (empty($graph[$org_idx]['telephone']) && $locations[0]['telephone'] !== '') { $graph[$org_idx]['telephone'] = $locations[0]['telephone']; } } // Which nodes travel with this page. $full = $ctx['kind'] === 'home' || count($locations) <= CITAMI_LOCAL_GRAPH_MAX; foreach ($locations as $loc) { if ($full || ($current && $current['id'] === $loc['id'])) { $node = citami_location_node($loc); if ($current && $current['id'] === $loc['id'] && !empty($ctx['post'])) { $node['url'] = get_permalink($ctx['post']); $node['mainEntityOfPage'] = array('@id' => citami_schema_id('webpage', get_permalink($ctx['post']))); } $graph[] = $node; } } if ($current && $page_idx !== null) { $graph[$page_idx]['about'] = array('@id' => citami_location_schema_id($current)); } return $graph; }, 20, 2); /* ---------------------------------------------------------------- */ /* Visible cards (shortcodes) */ /* ---------------------------------------------------------------- */ function citami_location_format_hours_line($loc, $day) { $ranges = isset($loc['hours'][$day]) ? citami_local_parse_hours($loc['hours'][$day]) : null; if ($ranges === null) { return ''; } if ($ranges === array()) { return citami_t('Chiuso', 'Closed'); } return implode(', ', array_map(function ($r) { return $r[0] . '–' . $r[1]; }, $ranges)); } function citami_location_card_html($loc, $show = array('address', 'phone', 'email', 'hours', 'map'), $heading = true) { $html = '
'; if ($heading) { $url = citami_location_url($loc); $name = '' . esc_html($loc['name']) . ''; $html .= '' . ($url !== '' ? '' : $name) . ''; } if (in_array('address', $show, true) && ($loc['street'] !== '' || $loc['city'] !== '')) { $line1 = $loc['street']; $line2 = trim($loc['postal'] . ' ' . $loc['city'] . ($loc['region'] !== '' ? ' (' . $loc['region'] . ')' : '')); $html .= '
'; if ($line1 !== '') $html .= '' . esc_html($line1) . '
'; if ($line2 !== '') $html .= esc_html($line2); $html .= '
'; } if (in_array('phone', $show, true) && $loc['telephone'] !== '') { $html .= '
' . esc_html($loc['telephone']) . '
'; } if (in_array('email', $show, true) && $loc['email'] !== '') { $html .= '
' . esc_html($loc['email']) . '
'; } if (in_array('hours', $show, true) && !empty($loc['hours'])) { $html .= citami_location_hours_html($loc); } if (in_array('map', $show, true)) { $map = $loc['map_url']; if ($map === '' && $loc['lat'] !== '' && $loc['lng'] !== '') { $map = 'https://www.google.com/maps?q=' . rawurlencode($loc['lat'] . ',' . $loc['lng']); } if ($map !== '') { $html .= '
' . esc_html(citami_t('Apri la mappa', 'Open the map')) . '
'; } } return $html . '
'; } function citami_location_hours_html($loc) { $html = ''; foreach (citami_local_days() as $key => $names) { $line = citami_location_format_hours_line($loc, $key); if ($line === '') { continue; } $html .= ''; } return $html . '
' . esc_html($names[1]) . '' . esc_html($line) . '
'; } function citami_location_from_atts($atts) { $id = isset($atts['id']) ? (string) $atts['id'] : ''; if ($id !== '') { return citami_location_get($id); } $post = function_exists('get_post') ? get_post() : null; if ($post) { $loc = citami_location_for_post($post->ID); if ($loc) { return $loc; } } $all = citami_locations(); return !empty($all) ? $all[0] : null; } add_shortcode('citami_location', function ($atts) { $atts = shortcode_atts(array('id' => '', 'show' => 'address,phone,email,hours,map', 'heading' => '1'), $atts, 'citami_location'); $loc = citami_location_from_atts($atts); if (!$loc) { return ''; } return citami_location_card_html($loc, array_map('trim', explode(',', (string) $atts['show'])), (string) $atts['heading'] !== '0'); }); add_shortcode('citami_locations', function ($atts) { $atts = shortcode_atts(array('show' => 'address,phone,email,hours,map'), $atts, 'citami_locations'); $html = ''; foreach (citami_locations() as $loc) { $html .= citami_location_card_html($loc, array_map('trim', explode(',', (string) $atts['show'])), true); } return $html === '' ? '' : '
' . $html . '
'; }); add_shortcode('citami_opening_hours', function ($atts) { $atts = shortcode_atts(array('id' => ''), $atts, 'citami_opening_hours'); $loc = citami_location_from_atts($atts); return ($loc && !empty($loc['hours'])) ? citami_location_hours_html($loc) : ''; }); /* ---------------------------------------------------------------- */ /* Editor: assign a page to a location */ /* ---------------------------------------------------------------- */ add_action('citami_seo_meta_box_fields', function ($post) { $locations = citami_locations(); if (empty($locations)) { return; } $current = (string) get_post_meta($post->ID, CITAMI_META_LOCATION, true); ?>


(string) (isset($o['knowledgegraph_name']) && $o['knowledgegraph_name'] !== '' ? $o['knowledgegraph_name'] : get_bloginfo('name')), 'type' => (string) (isset($o['local_business_type']) ? $o['local_business_type'] : ''), 'street' => (string) (isset($a['streetAddress']) ? $a['streetAddress'] : ''), 'city' => (string) (isset($a['addressLocality']) ? $a['addressLocality'] : ''), 'postal' => (string) (isset($a['postalCode']) ? $a['postalCode'] : ''), 'region' => (string) (isset($a['addressRegion']) ? $a['addressRegion'] : ''), 'country' => (string) (isset($a['addressCountry']) ? $a['addressCountry'] : ''), 'telephone' => (string) (isset($o['phone']) ? $o['phone'] : ''), 'email' => (string) (isset($o['email']) ? $o['email'] : ''), 'price_range' => (string) (isset($o['price_range']) ? $o['price_range'] : ''), 'lat' => isset($geo[0]) ? trim($geo[0]) : '', 'lng' => isset($geo[1]) ? trim($geo[1]) : '', 'hours' => $hours, 'primary' => true, 'imported' => 'rankmath', 'source_id' => 'settings', ); } // Multi-location mode (Pro): one post per branch with its schema meta. foreach (get_posts(array('post_type' => 'rank_math_locations', 'post_status' => 'publish', 'numberposts' => CITAMI_LOCATIONS_MAX)) as $p) { $nodes = function_exists('citami_import_map_schema') ? citami_import_map_schema($p->ID) : array(); $n = !empty($nodes) ? $nodes[0] : array(); $addr = isset($n['address']) && is_array($n['address']) ? $n['address'] : array(); $hours = array(); if (!empty($n['openingHoursSpecification']) && is_array($n['openingHoursSpecification'])) { foreach ($n['openingHoursSpecification'] as $s) { if (!is_array($s) || empty($s['dayOfWeek']) || empty($s['opens']) || empty($s['closes'])) { continue; } foreach ((array) $s['dayOfWeek'] as $day) { $key = strtolower(substr((string) $day, 0, 3)); $range = $s['opens'] . '-' . $s['closes']; $hours[$key] = isset($hours[$key]) ? $hours[$key] . ',' . $range : $range; } } } $out[] = array( 'name' => (string) (!empty($n['name']) ? $n['name'] : $p->post_title), 'type' => (string) (isset($n['@type']) && is_string($n['@type']) ? $n['@type'] : ''), 'street' => (string) (isset($addr['streetAddress']) ? $addr['streetAddress'] : ''), 'city' => (string) (isset($addr['addressLocality']) ? $addr['addressLocality'] : ''), 'postal' => (string) (isset($addr['postalCode']) ? $addr['postalCode'] : ''), 'region' => (string) (isset($addr['addressRegion']) ? $addr['addressRegion'] : ''), 'country' => (string) (isset($addr['addressCountry']) ? $addr['addressCountry'] : ''), 'telephone' => (string) (isset($n['telephone']) ? $n['telephone'] : ''), 'email' => (string) (isset($n['email']) ? $n['email'] : ''), 'price_range' => (string) (isset($n['priceRange']) ? $n['priceRange'] : ''), 'image' => (isset($n['image']) && is_string($n['image'])) ? $n['image'] : '', 'lat' => isset($n['geo']['latitude']) ? (string) $n['geo']['latitude'] : '', 'lng' => isset($n['geo']['longitude']) ? (string) $n['geo']['longitude'] : '', 'hours' => $hours, 'page_id' => (int) $p->ID, 'imported' => 'rankmath', 'source_id' => 'post-' . $p->ID, ); } return $out; } if ($source === 'yoast') { foreach (get_posts(array('post_type' => 'wpseo_locations', 'post_status' => 'publish', 'numberposts' => CITAMI_LOCATIONS_MAX)) as $p) { $m = function ($k) use ($p) { return (string) get_post_meta($p->ID, $k, true); }; $hours = array(); foreach (array_keys(citami_local_days()) as $key) { $full = array('mon' => 'monday', 'tue' => 'tuesday', 'wed' => 'wednesday', 'thu' => 'thursday', 'fri' => 'friday', 'sat' => 'saturday', 'sun' => 'sunday'); $from = $m('_wpseo_opening_hours_' . $full[$key] . '_from'); $to = $m('_wpseo_opening_hours_' . $full[$key] . '_to'); if ($from === 'closed') { $hours[$key] = 'closed'; } elseif ($from !== '' && $to !== '') { $spec = $from . '-' . $to; $from2 = $m('_wpseo_opening_hours_' . $full[$key] . '_second_from'); $to2 = $m('_wpseo_opening_hours_' . $full[$key] . '_second_to'); if ($from2 !== '' && $to2 !== '' && $m('_wpseo_multiple_opening_hours') === 'on') { $spec .= ',' . $from2 . '-' . $to2; } $hours[$key] = $spec; } } $out[] = array( 'name' => (string) $p->post_title, 'type' => $m('_wpseo_business_type'), 'street' => $m('_wpseo_business_address'), 'city' => $m('_wpseo_business_city'), 'postal' => $m('_wpseo_business_zipcode'), 'region' => $m('_wpseo_business_state'), 'country' => $m('_wpseo_business_country'), 'telephone' => $m('_wpseo_business_phone'), 'email' => $m('_wpseo_business_email'), 'url' => $m('_wpseo_business_url'), 'lat' => $m('_wpseo_coordinates_lat'), 'lng' => $m('_wpseo_coordinates_long'), 'price_range' => $m('_wpseo_business_price_range'), 'hours' => $hours, 'page_id' => (int) $p->ID, 'imported' => 'yoast', 'source_id' => 'post-' . $p->ID, ); } } return $out; } /** Adds the source's locations; already-imported ones (same source_id) are skipped. */ function citami_import_locations($source, $dry_run = true) { $found = citami_import_read_locations($source); $report = array('found' => count($found), 'added' => 0, 'skipped' => 0); if ($dry_run) { return $report; } $existing = citami_locations(); foreach ($found as $raw) { $dup = false; foreach ($existing as $l) { if ($l['imported'] === $raw['imported'] && $l['source_id'] === $raw['source_id']) { $dup = true; break; } } if ($dup) { $report['skipped']++; continue; } if (!empty($existing) && !empty($raw['primary'])) { $raw['primary'] = false; } $res = citami_location_add($raw); if (is_wp_error($res)) { $report['skipped']++; continue; } $existing[] = $res; $report['added']++; } return $report; } /* ---------------------------------------------------------------- */ /* Admin: "Sedi" tab + handler */ /* ---------------------------------------------------------------- */ /** Per-user transient that carries the posted form back after a refused save. */ function citami_location_form_key() { return 'citami_location_form_' . (int) get_current_user_id(); } /** The posted fields, cleaned for redisplay only (nothing is saved from here). */ function citami_location_form_refill(array $in) { $out = array(); foreach (citami_location_defaults() as $k => $v) { if ($k === 'hours' || $k === 'primary' || !isset($in[$k]) || is_array($in[$k])) { continue; } $out[$k] = mb_substr(sanitize_text_field((string) $in[$k]), 0, 500); } $out['page_id'] = isset($in['page_id']) ? absint($in['page_id']) : 0; $out['primary'] = !empty($in['primary']); $out['hours'] = array(); if (isset($in['hours']) && is_array($in['hours'])) { foreach (array_keys(citami_local_days()) as $d) { if (isset($in['hours'][$d]) && !is_array($in['hours'][$d])) { $out['hours'][$d] = mb_substr(sanitize_text_field((string) $in['hours'][$d]), 0, 100); } } } return $out; } /** The current primary location, or null. */ function citami_location_primary() { foreach (citami_locations() as $l) { if (!empty($l['primary'])) { return $l; } } return null; } /** "Lunedì: dalle 09:00 alle 13:00 e dalle 15:00 alle 19:00" — one line per day. */ function citami_location_hours_words($loc) { $lines = array(); foreach (citami_local_days() as $key => $names) { $ranges = isset($loc['hours'][$key]) ? citami_local_parse_hours($loc['hours'][$key]) : null; if ($ranges === null) { $txt = citami_t('non indicato (nessun orario per questo giorno)', 'not stated (no hours for this day)'); } elseif ($ranges === array()) { $txt = citami_t('chiuso', 'closed'); } else { $txt = implode(citami_t(' e ', ' and '), array_map(function ($r) { return sprintf(citami_t('dalle %1$s alle %2$s', 'from %1$s to %2$s'), $r[0], $r[1]); }, $ranges)); } $lines[] = $names[1] . ': ' . $txt; } return $lines; } function citami_handle_location_action() { if (!current_user_can('manage_options')) { wp_die('Permesso negato.', '', array('response' => 403)); } if (!wp_verify_nonce(isset($_POST['_citami_location_nonce']) ? sanitize_text_field(wp_unslash($_POST['_citami_location_nonce'])) : '', 'citami_location')) { wp_die('Nonce non valido.', '', array('response' => 403)); } $do = isset($_POST['do']) ? sanitize_key((string) $_POST['do']) : ''; $notice = array('type' => 'success', 'message' => ''); $in = isset($_POST['loc']) && is_array($_POST['loc']) ? wp_kses_post_deep(wp_unslash($_POST['loc'])) : 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 $back = citami_admin_url('local'); $before = citami_location_primary(); $res = null; if ($do === 'add' || $do === 'update') { $id = $do === 'update' && !empty($_POST['id']) ? sanitize_title(wp_unslash($_POST['id'])) : ''; if ($do === 'add') { $res = citami_location_add($in); } elseif ($id !== '') { $res = citami_location_update($id, $in); if ($res === null) { $res = new WP_Error('citami_location_missing', citami_t('Sede non trovata: forse è stata eliminata.', 'Location not found: it may have been deleted.')); } } if (is_wp_error($res)) { // Same mode, same values: the owner fixes the one field, not the whole form. $notice = array('type' => 'error', 'message' => $res->get_error_message()); set_transient(citami_location_form_key(), array('mode' => $do, 'id' => $id, 'loc' => citami_location_form_refill($in)), 10 * MINUTE_IN_SECONDS); $back = citami_admin_url('local', $do === 'update' && $id !== '' ? array('edit' => $id) : array()) . '#citami-location-form'; } elseif (is_array($res)) { $notice['message'] = $do === 'add' ? sprintf(citami_t('Sede "%s" aggiunta.', 'Location "%s" added.'), $res['name']) : sprintf(citami_t('Sede "%s" aggiornata.', 'Location "%s" updated.'), $res['name']); if (!empty($res['primary']) && $before && $before['id'] !== $res['id']) { $notice['message'] .= ' ' . sprintf(citami_t('%1$s è ora la sede principale (prima era %2$s).', '%1$s is now the primary location (it was %2$s).'), $res['name'], $before['name']); } delete_transient(citami_location_form_key()); $back = citami_admin_url('local', array('focus' => $res['id'])) . '#citami-loc-' . $res['id']; } } elseif ($do === 'delete' && !empty($_POST['id'])) { $id = sanitize_title(wp_unslash($_POST['id'])); $gone = citami_location_get($id); citami_location_delete($id); $notice['message'] = $gone ? sprintf(citami_t('Sede "%s" eliminata.', 'Location "%s" deleted.'), $gone['name']) : citami_t('Sede eliminata.', 'Location deleted.'); $now = citami_location_primary(); if ($gone && !empty($gone['primary']) && $now) { $notice['message'] .= ' ' . sprintf(citami_t('La sede principale ora è %s.', 'The primary location is now %s.'), $now['name']); } } set_transient('citami_action_notice', $notice, 30); wp_safe_redirect($back); exit; } add_action('admin_post_citami_location_action', 'citami_handle_location_action'); function citami_render_local_tab() { $locations = citami_locations(); $edit_id = isset($_GET['edit']) ? sanitize_title(wp_unslash($_GET['edit'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: which location to edit $focus = isset($_GET['focus']) ? sanitize_title(wp_unslash($_GET['focus'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: which row to highlight // A refused save comes back with what was typed (read once). $posted = get_transient(citami_location_form_key()); if (is_array($posted)) { delete_transient(citami_location_form_key()); if ($posted['mode'] === 'update' && $edit_id === '' && !empty($posted['id'])) { $edit_id = (string) $posted['id']; } } else { $posted = null; } $edit = $edit_id !== '' ? citami_location_get($edit_id) : null; $form = $edit ? $edit : citami_location_defaults(); $refilled = $posted && isset($posted['loc']) && is_array($posted['loc']) && ($posted['mode'] === 'update' ? ($edit && (string) $posted['id'] === $edit['id']) : !$edit); if ($refilled) { $form = array_merge($form, $posted['loc']); } $pages = get_posts(array('post_type' => 'page', 'post_status' => 'publish', 'numberposts' => 200)); // One labelled field of the location form: the label sits above the input. $field = function ($key, $label, $placeholder = '', $type = 'text', $class = '', $required = false) use ($form) { echo '' . esc_html($label) . '' . ''; }; ?>

.

>

–'; ?> 0 ? esc_html(get_the_title((int) $loc['page_id'])) : '–'; ?>

$names): ?>