'_yoast_wpseo_title', 'description' => '_yoast_wpseo_metadesc', 'canonical' => '_yoast_wpseo_canonical', 'noindex' => '_yoast_wpseo_meta-robots-noindex', 'nofollow' => '_yoast_wpseo_meta-robots-nofollow', 'advanced' => '_yoast_wpseo_meta-robots-adv', 'keyword' => '_yoast_wpseo_focuskw', 'og_title' => '_yoast_wpseo_opengraph-title', 'og_description' => '_yoast_wpseo_opengraph-description', 'og_image' => '_yoast_wpseo_opengraph-image', ); } return array( 'title' => 'rank_math_title', 'description' => 'rank_math_description', 'canonical' => 'rank_math_canonical_url', 'robots' => 'rank_math_robots', 'advanced' => 'rank_math_advanced_robots', 'keyword' => 'rank_math_focus_keyword', 'og_title' => 'rank_math_facebook_title', 'og_description' => 'rank_math_facebook_description', 'og_image' => 'rank_math_facebook_image', ); } /** Does this post carry any meta from the source plugin? */ function citami_import_post_has_source($source, $post_id) { foreach (citami_import_meta_keys($source) as $key) { $v = get_post_meta($post_id, $key, true); if ($v !== '' && $v !== null && $v !== false && $v !== array()) { return true; } } if ($source === 'rankmath') { foreach ((array) get_post_meta($post_id) as $k => $v) { if (strpos((string) $k, 'rank_math_schema_') === 0) { return true; } } } return false; } /** All public-type posts (any status but trash), ids only, in batches. */ function citami_import_candidate_posts($offset, $limit) { $types = citami_seo_post_types(); $posts = get_posts(array( 'post_type' => $types, 'post_status' => array('publish', 'draft', 'pending', 'private', 'future'), 'numberposts' => $limit, 'offset' => $offset, 'orderby' => 'ID', 'order' => 'ASC', 'fields' => 'ids', )); $ids = array(); foreach ((array) $posts as $p) { $ids[] = is_object($p) ? (int) $p->ID : (int) $p; } return $ids; } /** How many posts the importer walks through (all batches). */ function citami_import_candidate_total() { $total = 0; $offset = 0; do { $ids = citami_import_candidate_posts($offset, 1000); $total += count($ids); $offset += 1000; } while (count($ids) === 1000); return $total; } /** * Summary of what each source has to offer. Walking every post is slow on * a big site: the answer is cached an hour and dropped after an import. */ function citami_import_detect($force = false) { $cached = get_transient(CITAMI_IMPORT_DETECT_TRANSIENT); if (!$force && is_array($cached)) { return $cached; } $out = citami_import_detect_scan(); set_transient(CITAMI_IMPORT_DETECT_TRANSIENT, $out, HOUR_IN_SECONDS); return $out; } function citami_import_detect_scan() { $out = array(); foreach (array('yoast', 'rankmath') as $source) { $posts = 0; $offset = 0; do { $ids = citami_import_candidate_posts($offset, 500); foreach ($ids as $id) { if (citami_import_post_has_source($source, $id)) { $posts++; } } $offset += 500; } while (count($ids) === 500); $templates = citami_import_read_templates($source); $redirects = citami_import_read_redirects($source); $locations = function_exists('citami_import_read_locations') ? citami_import_read_locations($source) : array(); $installed = $source === 'yoast' ? (defined('WPSEO_VERSION') || get_option('wpseo_titles', null) !== null) : (class_exists('RankMath') || get_option('rank-math-options-titles', null) !== null); if ($posts > 0 || !empty($templates) || !empty($redirects) || !empty($locations) || $installed) { $out[$source] = array( 'installed' => (bool) $installed, 'posts' => $posts, 'templates' => count($templates), 'redirects' => count($redirects), 'locations' => count($locations), ); } } return $out; } /* ---------------------------------------------------------------- */ /* Mapping: per-post fields */ /* ---------------------------------------------------------------- */ /** Yoast/Rank Math template variables → Citami variables. */ function citami_import_convert_template($tpl) { $tpl = (string) $tpl; $map = array( '%%title%%' => '%title%', '%%sep%%' => '%sep%', '%%sitename%%' => '%sitename%', '%%sitedesc%%' => '%tagline%', '%%excerpt%%' => '%excerpt%', '%%excerpt_only%%' => '%excerpt%', '%%category%%' => '%category%', '%%primary_category%%' => '%category%', '%%term_title%%' => '%term%', '%%page%%' => '%page%', '%%searchphrase%%' => '%search%', '%%name%%' => '%author%', '%%date%%' => '%date%', '%%tag%%' => '', '%%pt_single%%' => '', '%%pt_plural%%' => '', '%%currentdate%%' => '', '%%currentyear%%' => '', // Rank Math (single %) — mostly identical names. '%sitedesc%' => '%tagline%', '%seo_title%' => '%title%', '%seo_description%' => '%excerpt%', '%searchphrase%' => '%search%', '%name%' => '%author%', '%primary_category%' => '%category%', '%term_description%' => '', '%currentyear%' => '', '%currentdate%' => '', '%pt_single%' => '', '%pt_plural%' => '', ); $out = strtr($tpl, $map); $out = preg_replace('/%%[a-z_]+%%/i', '', $out); // any other Yoast var return citami_seo_sanitize_template($out); } /** Reads one post's values from the source plugin → Citami field array. */ function citami_import_map_post($source, $post_id) { $keys = citami_import_meta_keys($source); $fields = array(); $title = (string) get_post_meta($post_id, $keys['title'], true); if (trim($title) !== '') { $fields['title'] = citami_import_convert_template($title); } $desc = (string) get_post_meta($post_id, $keys['description'], true); if (trim($desc) !== '') { $fields['description'] = citami_import_convert_template($desc); } $canonical = (string) get_post_meta($post_id, $keys['canonical'], true); if (trim($canonical) !== '') { $fields['canonical'] = trim($canonical); } $keyword = (string) get_post_meta($post_id, $keys['keyword'], true); if (trim($keyword) !== '') { $fields['keyword'] = wp_strip_all_tags($keyword); } foreach (array('og_title', 'og_description', 'og_image') as $k) { $v = (string) get_post_meta($post_id, $keys[$k], true); if (trim($v) !== '') { $fields[$k] = $k === 'og_image' ? trim($v) : citami_import_convert_template($v); } } if ($source === 'yoast') { $noindex = (string) get_post_meta($post_id, $keys['noindex'], true); if ($noindex === '1') { $fields['robots_index'] = 'noindex'; } elseif ($noindex === '2') { $fields['robots_index'] = 'index'; } if ((string) get_post_meta($post_id, $keys['nofollow'], true) === '1') { $fields['robots_follow'] = 'nofollow'; } $adv = (string) get_post_meta($post_id, $keys['advanced'], true); if ($adv !== '' && $adv !== 'none') { $fields['robots_advanced'] = array_map('trim', explode(',', $adv)); } } else { $robots = get_post_meta($post_id, $keys['robots'], true); if (is_string($robots)) { $robots = maybe_unserialize($robots); } if (is_array($robots)) { if (in_array('noindex', $robots, true)) { $fields['robots_index'] = 'noindex'; } elseif (in_array('index', $robots, true)) { $fields['robots_index'] = 'index'; } if (in_array('nofollow', $robots, true)) { $fields['robots_follow'] = 'nofollow'; } $adv = array_values(array_intersect($robots, array('noarchive', 'nosnippet', 'noimageindex', 'notranslate'))); if (!empty($adv)) { $fields['robots_advanced'] = $adv; } } } return $fields; } /** Rank Math per-post schema (rank_math_schema_) as JSON-LD nodes. */ function citami_import_map_schema($post_id) { $nodes = array(); foreach ((array) get_post_meta($post_id) as $k => $v) { if (strpos((string) $k, 'rank_math_schema_') !== 0) { continue; } $val = is_array($v) ? (isset($v[0]) ? $v[0] : $v) : $v; if (is_string($val)) { $val = maybe_unserialize($val); } if (is_array($val) && !empty($val['@type'])) { unset($val['metadata']); // Rank Math editor bookkeeping, not schema $nodes[] = $val; } } return $nodes; } /** * Imports one post. $overwrite=false keeps existing Citami values. * Returns array(field => value) of what was (or would be) written. */ function citami_import_post($source, $post_id, $dry_run = true, $overwrite = false) { $mapped = citami_import_map_post($source, $post_id); $current = array_merge(citami_seo_get_post_fields($post_id), function_exists('citami_social_get_post_fields') ? citami_social_get_post_fields($post_id) : array()); $changes = array(); foreach ($mapped as $k => $v) { if ($k === 'keyword') { $existing = (string) get_post_meta($post_id, CITAMI_META_FOCUS_KEYWORD, true); if ($existing === '' || $overwrite) { $changes[$k] = $v; } continue; } $existingVal = isset($current[$k]) ? $current[$k] : ''; if ($existingVal === '' || $overwrite) { $changes[$k] = $v; } } $schema = $source === 'rankmath' ? citami_import_map_schema($post_id) : array(); if (!empty($schema) && ($overwrite || (string) get_post_meta($post_id, CITAMI_META_SCHEMA_IMPORTED, true) === '')) { $changes['schema'] = count($schema); } if (!$dry_run && !empty($changes)) { $fields = array_intersect_key($changes, array_flip(array('title', 'description', 'canonical', 'robots_index', 'robots_follow', 'robots_advanced'))); if (!empty($fields)) { citami_seo_save_post_fields($post_id, $fields); } if (isset($changes['keyword'])) { update_post_meta($post_id, CITAMI_META_FOCUS_KEYWORD, $changes['keyword']); } $social = array_intersect_key($changes, array_flip(array('og_title', 'og_description', 'og_image'))); if (!empty($social) && function_exists('citami_social_save_post_fields')) { citami_social_save_post_fields($post_id, $social); } if (isset($changes['schema'])) { update_post_meta($post_id, CITAMI_META_SCHEMA_IMPORTED, wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG)); } } return $changes; } /* ---------------------------------------------------------------- */ /* Templates (site-wide) */ /* ---------------------------------------------------------------- */ /** Source templates → our settings shape (only the parts present). */ function citami_import_read_templates($source) { $out = array(); if ($source === 'yoast') { $o = get_option('wpseo_titles', null); if (!is_array($o)) { return $out; } $seps = array('sc-dash' => '-', 'sc-ndash' => '–', 'sc-mdash' => '—', 'sc-colon' => ':', 'sc-middot' => '·', 'sc-bull' => '•', 'sc-star' => '*', 'sc-smstar' => '⋆', 'sc-pipe' => '|', 'sc-tilde' => '~', 'sc-laquo' => '«', 'sc-raquo' => '»', 'sc-lt' => '<', 'sc-gt' => '>'); if (!empty($o['separator']) && isset($seps[$o['separator']])) { $out['separator'] = $seps[$o['separator']]; } if (!empty($o['title-home-wpseo']) || !empty($o['metadesc-home-wpseo'])) { $out['home'] = array( 'title' => citami_import_convert_template(isset($o['title-home-wpseo']) ? $o['title-home-wpseo'] : ''), 'description' => citami_import_convert_template(isset($o['metadesc-home-wpseo']) ? $o['metadesc-home-wpseo'] : ''), ); } foreach (citami_seo_post_types() as $type) { if (!empty($o['title-' . $type]) || !empty($o['metadesc-' . $type])) { $out['templates'][$type] = array( 'title' => citami_import_convert_template(isset($o['title-' . $type]) ? $o['title-' . $type] : ''), 'description' => citami_import_convert_template(isset($o['metadesc-' . $type]) ? $o['metadesc-' . $type] : ''), ); } } return $out; } $o = get_option('rank-math-options-titles', null); if (!is_array($o)) { return $out; } if (!empty($o['title_separator'])) { $out['separator'] = wp_strip_all_tags((string) $o['title_separator']); } if (!empty($o['homepage_title']) || !empty($o['homepage_description'])) { $out['home'] = array( 'title' => citami_import_convert_template(isset($o['homepage_title']) ? $o['homepage_title'] : ''), 'description' => citami_import_convert_template(isset($o['homepage_description']) ? $o['homepage_description'] : ''), ); } foreach (citami_seo_post_types() as $type) { if (!empty($o['pt_' . $type . '_title']) || !empty($o['pt_' . $type . '_description'])) { $out['templates'][$type] = array( 'title' => citami_import_convert_template(isset($o['pt_' . $type . '_title']) ? $o['pt_' . $type . '_title'] : ''), 'description' => citami_import_convert_template(isset($o['pt_' . $type . '_description']) ? $o['pt_' . $type . '_description'] : ''), ); } } return $out; } /** 2.59 — readable names for the imported fields. */ function citami_import_field_label($k) { $l = array( 'title' => citami_t('titolo SEO', 'SEO title'), 'description' => citami_t('descrizione', 'description'), 'canonical' => 'canonical', 'robots_index' => citami_t('indicizzazione (noindex)', 'indexing (noindex)'), 'robots_follow' => citami_t('link seguiti (nofollow)', 'followed links (nofollow)'), 'focus_keyword' => citami_t('parola chiave', 'focus keyword'), 'schema' => 'schema', ); return isset($l[$k]) ? $l[$k] : (string) $k; } function citami_import_templates($source, $dry_run = true, $overwrite = false) { $tpl = citami_import_read_templates($source); if (empty($tpl) || $dry_run) { return $tpl; } $current = get_option(CITAMI_SEO_SETTINGS_OPT, array()); if (!is_array($current)) { $current = array(); } // 2.59 — like the per-post values: what Citami already has stays unless "Sovrascrivi" is ticked. $empty = function ($v) { return $v === null || $v === '' || $v === array() || (is_array($v) && implode('', array_map(function ($x) { return is_array($x) ? implode('', $x) : (string) $x; }, $v)) === ''); }; if (isset($tpl['separator']) && ($overwrite || $empty(isset($current['separator']) ? $current['separator'] : null))) { $current['separator'] = $tpl['separator']; } if (isset($tpl['home']) && ($overwrite || $empty(isset($current['home']) ? $current['home'] : null))) { $current['home'] = $tpl['home']; } if (isset($tpl['templates'])) { $mine = isset($current['templates']) && is_array($current['templates']) ? $current['templates'] : array(); foreach ($tpl['templates'] as $type => $t) { if ($overwrite || $empty(isset($mine[$type]) ? $mine[$type] : null)) { $mine[$type] = $t; } } $current['templates'] = $mine; } update_option(CITAMI_SEO_SETTINGS_OPT, citami_sanitize_seo_settings($current), false); return $tpl; } /* ---------------------------------------------------------------- */ /* Redirects */ /* ---------------------------------------------------------------- */ /** Reads source redirects → normalised rows {source, target, code, regex, active}. */ function citami_import_read_redirects($source) { $rows = array(); if ($source === 'yoast') { foreach (array('wpseo-premium-redirects-base', 'wpseo-premium-redirects') as $opt) { $o = get_option($opt, null); if (!is_array($o)) { continue; } foreach ($o as $key => $r) { if (!is_array($r)) { continue; } $origin = isset($r['origin']) ? (string) $r['origin'] : (is_string($key) ? $key : ''); $target = isset($r['url']) ? (string) $r['url'] : ''; if ($origin === '') { continue; } $rows[] = array( 'source' => $origin, 'target' => $target, 'code' => isset($r['type']) ? (int) $r['type'] : 301, 'regex' => isset($r['format']) && $r['format'] === 'regex', 'active' => true, ); } if (!empty($rows)) { break; } } return $rows; } global $wpdb; if (!isset($wpdb) || !is_object($wpdb)) { return $rows; } $table = $wpdb->prefix . 'rank_math_redirections'; $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->esc_like($table))); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off import from Rank Math's own table if ($exists !== $table) { return $rows; } // The table name is $wpdb->prefix + a fixed string (checked above with SHOW TABLES); identifiers cannot be prepared. $results = $wpdb->get_results("SELECT id, sources, url_to, header_code, status FROM {$table}", ARRAY_A); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- fixed table name, one-time import. foreach ((array) $results as $r) { $sources = isset($r['sources']) ? maybe_unserialize($r['sources']) : array(); if (!is_array($sources)) { continue; } foreach ($sources as $s) { if (!is_array($s) || empty($s['pattern'])) { continue; } $comparison = isset($s['comparison']) ? (string) $s['comparison'] : 'exact'; $rows[] = array( 'source' => (string) $s['pattern'], 'target' => isset($r['url_to']) ? (string) $r['url_to'] : '', 'code' => isset($r['header_code']) ? (int) $r['header_code'] : 301, 'regex' => $comparison === 'regex', 'active' => (!isset($r['status']) || $r['status'] === 'active'), 'match' => $comparison, // exact | contains | start | end | regex ); } } return $rows; } /** Normalise a path for matching: leading slash, no trailing slash, no query. */ function citami_redirect_normalise_path($path) { $path = (string) $path; if (preg_match('#^https?://#i', $path)) { $parts = wp_parse_url($path); $path = isset($parts['path']) ? $parts['path'] : '/'; } $q = strpos($path, '?'); if ($q !== false) { $path = substr($path, 0, $q); } $path = '/' . ltrim($path, '/'); return $path === '/' ? '/' : rtrim($path, '/'); } /** Does the redirect row match this request path? */ function citami_redirect_matches(array $row, $request_path) { $request = citami_redirect_normalise_path($request_path); $source = (string) $row['source']; $match = isset($row['match']) ? (string) $row['match'] : (!empty($row['regex']) ? 'regex' : 'exact'); if ($match === 'regex') { $pattern = '#' . str_replace('#', '\#', $source) . '#i'; return @preg_match($pattern, $request) === 1; } $src = citami_redirect_normalise_path($source); if ($match === 'contains') { return strpos($request, rtrim($source, '/')) !== false; } if ($match === 'start') { return strpos($request, $src) === 0; } if ($match === 'end') { return substr($request, -strlen(rtrim($source, '/'))) === rtrim($source, '/'); } return $request === $src; } /** Target URL for a matching row (regex rows may use $1 backrefs). */ function citami_redirect_target(array $row, $request_path) { $target = (string) $row['target']; if (!empty($row['regex']) || (isset($row['match']) && $row['match'] === 'regex')) { $pattern = '#' . str_replace('#', '\#', (string) $row['source']) . '#i'; $replaced = @preg_replace($pattern, $target, citami_redirect_normalise_path($request_path)); if (is_string($replaced) && $replaced !== '') { $target = $replaced; } } if ($target === '') { return home_url('/'); } if (!preg_match('#^https?://#i', $target)) { $target = home_url('/' . ltrim($target, '/')); } return $target; } /** Merge imported rows into our list (dedupe by source+target). */ function citami_import_redirects($source, $dry_run = true) { $rows = citami_import_read_redirects($source); if (empty($rows)) { return array('found' => 0, 'added' => 0); } $current = get_option(CITAMI_REDIRECTS_OPT, array()); if (!is_array($current)) { $current = array(); } $seen = array(); foreach ($current as $c) { if (is_array($c)) { $seen[$c['source'] . '→' . $c['target']] = true; } } $added = 0; foreach ($rows as $r) { $k = $r['source'] . '→' . $r['target']; if (isset($seen[$k])) { continue; } $seen[$k] = true; $added++; if (!$dry_run) { $current[] = array( 'id' => uniqid('rd_', true), 'source' => $r['source'], 'target' => $r['target'], 'code' => in_array($r['code'], array(301, 302, 307, 410, 451), true) ? $r['code'] : 301, 'regex' => !empty($r['regex']), 'match' => isset($r['match']) ? $r['match'] : (!empty($r['regex']) ? 'regex' : 'exact'), 'active' => !empty($r['active']), 'hits' => 0, 'imported' => $source, 'created' => gmdate('c'), ); } } if (!$dry_run && $added > 0) { update_option(CITAMI_REDIRECTS_OPT, array_values($current), false); } return array('found' => count($rows), 'added' => $added); } /** Minimal front-end handler: first active matching redirect wins. */ function citami_redirect_handle_request($request_uri) { $rules = get_option(CITAMI_REDIRECTS_OPT, array()); if (!is_array($rules) || empty($rules)) { return null; } foreach ($rules as $idx => $rule) { if (!is_array($rule) || empty($rule['active'])) { continue; } if (citami_redirect_matches($rule, $request_uri)) { $rules[$idx]['hits'] = (isset($rule['hits']) ? (int) $rule['hits'] : 0) + 1; update_option(CITAMI_REDIRECTS_OPT, $rules, false); $code = isset($rule['code']) ? (int) $rule['code'] : 301; return array('target' => citami_redirect_target($rule, $request_uri), 'code' => $code); } } return null; } add_action('template_redirect', function () { if (is_admin() || empty($_SERVER['REQUEST_URI'])) { return; } $hit = citami_redirect_handle_request(esc_url_raw(wp_unslash($_SERVER['REQUEST_URI']))); if ($hit === null) { return; } if ($hit['code'] === 410 || $hit['code'] === 451) { status_header($hit['code']); exit; } // Admin-defined redirect: the target may be another domain by design (validated with esc_url_raw when the rule is saved). wp_redirect($hit['target'], $hit['code']); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- admin-configured external targets are allowed. exit; }, 1); /* ---------------------------------------------------------------- */ /* Run + report */ /* ---------------------------------------------------------------- */ /** * One import run (batched on posts). $what: array of 'meta','templates', * 'redirects'. Returns the report; stores it in CITAMI_IMPORT_LAST_OPT. */ function citami_import_run($source, array $what, $dry_run = true, $overwrite = false, $offset = 0) { $report = array( 'source' => $source, 'dry_run' => (bool) $dry_run, 'overwrite' => (bool) $overwrite, 'ts' => time(), 'posts_scanned' => 0, 'posts_changed' => 0, 'fields' => array(), 'schema_posts' => 0, 'templates' => array(), 'redirects' => array('found' => 0, 'added' => 0), 'locations' => array('found' => 0, 'added' => 0, 'skipped' => 0), 'next_offset' => null, 'samples' => array(), 'what' => array_values($what), 'offset' => (int) $offset, 'posts_total' => 0, ); if (in_array('meta', $what, true)) { $ids = citami_import_candidate_posts($offset, CITAMI_IMPORT_BATCH); foreach ($ids as $id) { $report['posts_scanned']++; if (!citami_import_post_has_source($source, $id)) { continue; } $changes = citami_import_post($source, $id, $dry_run, $overwrite); if (empty($changes)) { continue; } $report['posts_changed']++; foreach ($changes as $k => $v) { if ($k === 'schema') { $report['schema_posts']++; continue; } $report['fields'][$k] = (isset($report['fields'][$k]) ? $report['fields'][$k] : 0) + 1; } if (count($report['samples']) < 10) { $report['samples'][] = array('post_id' => $id, 'changes' => array_keys($changes)); } } if (count($ids) === CITAMI_IMPORT_BATCH) { $report['next_offset'] = $offset + CITAMI_IMPORT_BATCH; } } if (in_array('templates', $what, true)) { $report['templates'] = citami_import_templates($source, $dry_run, $overwrite); } if (in_array('redirects', $what, true)) { $report['redirects'] = citami_import_redirects($source, $dry_run); } if (in_array('locations', $what, true) && function_exists('citami_import_locations')) { $report['locations'] = citami_import_locations($source, $dry_run); } if ($offset > 0) { $report = citami_import_accumulate(get_option(CITAMI_IMPORT_LAST_OPT, null), $report); } if (in_array('meta', $what, true)) { $report['posts_total'] = $report['next_offset'] !== null ? citami_import_candidate_total() : (int) $offset + count(isset($ids) ? $ids : array()); } update_option(CITAMI_IMPORT_LAST_OPT, $report, false); if ($dry_run) { // The preview is what unlocks "Importa" for this source, with its counts in the confirm. $previews = get_transient(CITAMI_IMPORT_PREVIEWS_TRANSIENT); $previews = is_array($previews) ? $previews : array(); $summary = $report; unset($summary['samples']); $previews[$source] = $summary; set_transient(CITAMI_IMPORT_PREVIEWS_TRANSIENT, $previews, DAY_IN_SECONDS); } else { delete_transient(CITAMI_IMPORT_DETECT_TRANSIENT); if ($report['next_offset'] === null) { // Done: the old preview no longer describes the site. $previews = get_transient(CITAMI_IMPORT_PREVIEWS_TRANSIENT); if (is_array($previews) && isset($previews[$source])) { unset($previews[$source]); set_transient(CITAMI_IMPORT_PREVIEWS_TRANSIENT, $previews, DAY_IN_SECONDS); } } } return $report; } /** * "Continua con i prossimi post": the new batch is added to the previous * one (same source, same mode, the batch that was announced) so the totals * on screen are the site's, not the last 300 posts'. */ function citami_import_accumulate($prev, array $report) { if (!is_array($prev) || !isset($prev['source'], $prev['next_offset']) || $prev['source'] !== $report['source'] || (bool) $prev['dry_run'] !== (bool) $report['dry_run'] || (bool) $prev['overwrite'] !== (bool) $report['overwrite'] || (int) $prev['next_offset'] !== (int) $report['offset']) { return $report; } foreach (array('posts_scanned', 'posts_changed', 'schema_posts') as $k) { $report[$k] = (int) $prev[$k] + (int) $report[$k]; } foreach ((array) $prev['fields'] as $k => $n) { $report['fields'][$k] = (int) $n + (isset($report['fields'][$k]) ? (int) $report['fields'][$k] : 0); } $report['samples'] = array_slice(array_merge((array) $prev['samples'], (array) $report['samples']), 0, 10); // The site-wide parts ran with the first batch: keep their result. $prev_what = isset($prev['what']) ? (array) $prev['what'] : array(); foreach (array('templates', 'redirects', 'locations') as $k) { if (!in_array($k, $report['what'], true) && in_array($k, $prev_what, true)) { $report[$k] = $prev[$k]; $report['what'][] = $k; } } $report['offset'] = isset($prev['offset']) ? (int) $prev['offset'] : 0; return $report; } /** The parts of a run, in words: "12 post cambierebbero su 300 analizzati", "4 redirect da aggiungere"… */ function citami_import_summary_parts(array $r) { $r += array('posts_changed' => 0, 'posts_scanned' => 0, 'templates' => array(), 'redirects' => array(), 'locations' => array()); $r['redirects'] = (array) $r['redirects'] + array('found' => 0, 'added' => 0); $r['locations'] = (array) $r['locations'] + array('found' => 0, 'added' => 0, 'skipped' => 0); $dry = !empty($r['dry_run']); $what = isset($r['what']) ? (array) $r['what'] : array('meta', 'templates', 'redirects', 'locations'); $parts = array(); if (in_array('meta', $what, true)) { $parts[] = $dry ? sprintf(citami_t('%1$d post cambierebbero su %2$d analizzati', '%1$d posts would change out of %2$d scanned'), (int) $r['posts_changed'], (int) $r['posts_scanned']) : sprintf(citami_t('%1$d post aggiornati su %2$d analizzati', '%1$d posts updated out of %2$d scanned'), (int) $r['posts_changed'], (int) $r['posts_scanned']); } if (in_array('templates', $what, true)) { $n = is_array($r['templates']) ? count($r['templates']) : 0; $parts[] = $n === 0 ? citami_t('nessun template trovato', 'no templates found') : ($dry ? sprintf(citami_t('%d gruppi di template da importare', '%d template groups to import'), $n) : sprintf(citami_t('%d gruppi di template importati', '%d template groups imported'), $n)); } if (in_array('redirects', $what, true)) { $parts[] = $dry ? sprintf(citami_t('%1$d redirect da aggiungere (su %2$d trovati)', '%1$d redirects to add (out of %2$d found)'), (int) $r['redirects']['added'], (int) $r['redirects']['found']) : sprintf(citami_t('%1$d redirect aggiunti (su %2$d trovati)', '%1$d redirects added (out of %2$d found)'), (int) $r['redirects']['added'], (int) $r['redirects']['found']); } if (in_array('locations', $what, true)) { $parts[] = $dry ? sprintf(citami_t('%d sedi trovate', '%d locations found'), (int) $r['locations']['found']) : sprintf(citami_t('%1$d sedi aggiunte, %2$d già presenti', '%1$d locations added, %2$d already there'), (int) $r['locations']['added'], (int) $r['locations']['skipped']); } return $parts; } /** The notice after a run: says what it was (preview or import), what it counted, and if posts are left. */ function citami_import_notice_message(array $r) { $dry = !empty($r['dry_run']); $more = !empty($r['next_offset']); $head = $dry ? citami_t('Anteprima: ', 'Preview: ') : ($more ? citami_t('Importazione in corso: ', 'Import in progress: ') : citami_t('Importazione completata: ', 'Import complete: ')); $parts = citami_import_summary_parts($r); $msg = $head . ($parts ? implode(' · ', $parts) : citami_t('niente da fare', 'nothing to do')) . '.'; if ($dry) { $msg .= ' ' . citami_t('Nulla è stato scritto.', 'Nothing was written.'); } if ($more) { $msg .= ' ' . sprintf(citami_t('%1$d di %2$d post fatti: continua con i prossimi post qui sotto.', '%1$d of %2$d posts done: continue with the next posts below.'), (int) $r['next_offset'], max((int) $r['next_offset'], (int) $r['posts_total'])); } return $msg; } /* ---------------------------------------------------------------- */ /* Admin: tab + handler */ /* ---------------------------------------------------------------- */ function citami_handle_import_action() { if (!current_user_can('manage_options')) { wp_die('Permesso negato.', '', array('response' => 403)); } $nonce = isset($_POST['_citami_import_nonce']) ? sanitize_text_field(wp_unslash($_POST['_citami_import_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'citami_import')) { wp_die('Nonce non valido.', '', array('response' => 403)); } $source = isset($_POST['source']) ? sanitize_key((string) $_POST['source']) : ''; if (!in_array($source, array('yoast', 'rankmath'), true)) { wp_die('Sorgente non valida.', '', array('response' => 400)); } $what = array(); foreach (array('meta', 'templates', 'redirects', 'locations') as $w) { if (!empty($_POST['what'][$w])) { $what[] = $w; } } $dry_run = empty($_POST['run']) || $_POST['run'] !== 'import'; $overwrite = !empty($_POST['overwrite']); $offset = isset($_POST['offset']) ? absint($_POST['offset']) : 0; $back = citami_admin_url('import'); if (empty($what)) { set_transient('citami_action_notice', array('type' => 'error', 'message' => citami_t('Scegli almeno una cosa da importare.', 'Pick at least one thing to import.')), 60); wp_safe_redirect($back); exit; } // A first import batch needs a preview of the same source: the owner has seen the numbers. $previews = get_transient(CITAMI_IMPORT_PREVIEWS_TRANSIENT); if (!$dry_run && $offset === 0 && (!is_array($previews) || empty($previews[$source]))) { set_transient('citami_action_notice', array('type' => 'error', 'message' => citami_t('Fai prima l\'anteprima: mostra cosa cambierebbe senza scrivere nulla. Poi il pulsante Importa si attiva.', 'Run the preview first: it shows what would change without writing anything. Then the Import button turns on.')), 60); wp_safe_redirect($back); exit; } $report = citami_import_run($source, $what, $dry_run, $overwrite, $offset); set_transient('citami_action_notice', array( 'type' => $dry_run ? 'info' : 'success', 'message' => citami_import_notice_message($report), ), 60); wp_safe_redirect($back); exit; } add_action('admin_post_citami_import_action', 'citami_handle_import_action'); function citami_render_import_tab() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: re-scan the sources $detected = citami_import_detect(!empty($_GET['redetect'])); $last = get_option(CITAMI_IMPORT_LAST_OPT, null); $previews = get_transient(CITAMI_IMPORT_PREVIEWS_TRANSIENT); $previews = is_array($previews) ? $previews : array(); $labels = array('yoast' => 'Yoast SEO', 'rankmath' => 'Rank Math'); $recheck_url = citami_admin_url('import', array('redetect' => 1)); $recheck_busy = citami_t('Ricontrollo…', 'Checking…'); ?>

$info): $pv = isset($previews[$source]) && is_array($previews[$source]) ? $previews[$source] : null; ?>
$n) { $counts[] = array(ucfirst(citami_import_field_label($k)), (string) (int) $n, true); } if (!empty($last['schema_posts'])) { $counts[] = array(citami_t('Schema Rank Math messo in attesa (verrà usato dal motore schema)', 'Rank Math schema staged (used by the schema engine)'), sprintf(citami_t('%d post', '%d posts'), (int) $last['schema_posts'])); } if (!empty($last['templates'])) { $counts[] = array($last['dry_run'] ? citami_t('Template trovati: verrebbero importati (senza "Sovrascrivi", solo dove Citami non ne ha già)', 'Templates found: they would be imported (without "Overwrite", only where Citami has none yet)') : citami_t('Template importati', 'Templates imported'), (string) count((array) $last['templates'])); } if (in_array('redirects', $last_what, true)) { $counts[] = array(citami_t('Redirect trovati', 'Redirects found'), (string) (int) $last['redirects']['found']); $counts[] = array($last['dry_run'] ? citami_t('Redirect da aggiungere', 'Redirects to add') : citami_t('Redirect aggiunti', 'Redirects added'), (string) (int) $last['redirects']['added']); } if (!empty($last['locations']['found'])) { $counts[] = array(citami_t('Sedi trovate', 'Locations found'), (string) (int) $last['locations']['found']); if (!$last['dry_run']) { $counts[] = array(citami_t('Sedi aggiunte', 'Locations added'), (string) (int) $last['locations']['added']); $counts[] = array(citami_t('Sedi già presenti', 'Locations already there'), (string) (int) ($last['locations']['skipped'] ?? 0)); } } ?>
—
    >