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 . '' : $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 .= '
';
}
if (in_array('email', $show, true) && $loc['email'] !== '') {
$html .= '
';
}
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 .= '
';
}
}
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 .= '| ' . esc_html($names[1]) . ' | ' . esc_html($line) . ' |
';
}
return $html . '
';
}
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 '';
};
?>