Plugin Directory


Ignore:
Timestamp:
10/25/2026 06:29:16 AM (27 hours ago)
Author:
avakodeforge
Message:

Release 1.3.0 (trunk + tag; see readme changelog)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • langforge/trunk/includes/class-migration.php

    r3740148 r3764005  
    9191                    'count' => $count,
    9292                    'note'  => sprintf(
    93                         /* translators: 1: number of WPML translation records found, 2: number of languages. */
    94                         __('Found %1$d translation records across %2$d languages.', 'langforge'),
    95                         $count, $lang_count
     93                        /* translators: 1: the record count with its noun ("%d translation record"), 2: the language count with its noun ("%d language"). */
     94                        __('Found %1$s across %2$s.', 'langforge'),
     95                        /* translators: %d: number of WPML translation records found. */
     96                        sprintf(_n('%d translation record', '%d translation records', (int) $count, 'langforge'), (int) $count),
     97                        /* translators: %d: number of languages. */
     98                        sprintf(_n('%d language', '%d languages', (int) $lang_count, 'langforge'), (int) $lang_count)
    9699                    ),
    97100                ];
    … …  
    121124                    'count' => $post_count,
    122125                    'note'  => sprintf(
    123                         /* translators: 1: number of Polylang posts assigned to languages, 2: total number of languages. */
    124                         __('Found %1$d posts assigned to languages, %2$d languages total.', 'langforge'),
    125                         $post_count, $lang_count
     126                        /* translators: 1: the post count with its noun ("%d post"), 2: the language count with its noun ("%d language"). */
     127                        __('Found %1$s assigned to languages, %2$s total.', 'langforge'),
     128                        /* translators: %d: number of Polylang posts assigned to languages. */
     129                        sprintf(_n('%d post', '%d posts', $post_count, 'langforge'), $post_count),
     130                        /* translators: %d: number of languages. */
     131                        sprintf(_n('%d language', '%d languages', $lang_count, 'langforge'), $lang_count)
    126132                    ),
    127133                ];
    … …  
    137143                'count' => $trp_stats['records'],
    138144                'note'  => sprintf(
    139                     /* translators: 1: number of TranslatePress translated strings, 2: number of languages. */
    140                     __('Found %1$d translated strings across %2$d languages.', 'langforge'),
    141                     $trp_stats['records'], $trp_stats['languages']
     145                    /* translators: 1: the string count with its noun ("%d translated string"), 2: the language count with its noun ("%d language"). */
     146                    __('Found %1$s across %2$s.', 'langforge'),
     147                    /* translators: %d: number of TranslatePress translated strings. */
     148                    sprintf(_n('%d translated string', '%d translated strings', (int) $trp_stats['records'], 'langforge'), (int) $trp_stats['records']),
     149                    /* translators: %d: number of languages. */
     150                    sprintf(_n('%d language', '%d languages', (int) $trp_stats['languages'], 'langforge'), (int) $trp_stats['languages'])
    142151                ),
    143152            ];
    … …  
    366375        // Clearing first makes re-migration idempotent.
    367376        if (!empty($rows)) {
    368             $element_ids_csv = implode(',', array_map('intval', array_column($rows, 'element_id')));
    369             $wpdb->query("DELETE FROM {$lf_table} WHERE element_id IN ({$element_ids_csv})"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix; IN list is integer IDs cast via intval; custom plugin table.
     377            $this->delete_post_translation_rows(array_column($rows, 'element_id'));
    370378        }
    371379
    … …  
    426434        );
    427435
     436        // 5b. Translations of terms (categories, tags, any taxonomy): the `tax_*` rows of
     437        // icl_translations. Counted apart from the posts: `migrated` stays the number of posts.
     438        $term_result = $this->migrate_wpml_terms();
     439        $terms       = $term_result['linked'];
     440        $errors     += $term_result['errors'];
     441        if ($term_result['linked'] || $term_result['errors']) {
     442            $log[] = $this->term_result_line($term_result);
     443        }
     444
    428445        // 6. Migrate WPML string translations if tables exist
    429446        $string_log = $this->migrate_wpml_strings();
    … …  
    437454        flush_rewrite_rules();
    438455
    439         return compact('migrated', 'skipped', 'errors', 'log');
     456        return compact('migrated', 'skipped', 'errors', 'terms', 'log');
    440457    }
    441458
    … …  
    702719            'languages' => max(1, count($languages_with_records)),
    703720        ];
     721    }
     722
     723    /**
     724 * Delete the translation rows of the POSTS with these numbers: a migration can be run again, and clears the
     725 * rows of the posts it is about to write first.
     726 *
     727 * Posts' rows only (LANGFORGE_Post_Translation::post_row_sql): the table also holds the rows of terms, a term
     728 * can have the number of a post, and a migration of posts must not delete what it does not write
     729 * (BUG-LANG-SEO-03). One place for the three migrations (WPML, Polylang, TranslatePress).
     730 *
     731 * @param array $post_ids Post IDs; anything that is not a positive integer is dropped.
     732 * @return int Rows deleted.
     733 */
     734    private function delete_post_translation_rows(array $post_ids): int {
     735        global $wpdb;
     736        $ids = array_values(array_unique(array_filter(array_map('intval', $post_ids), function ($id) { return $id > 0; })));
     737        if (!$ids) {
     738            return 0;
     739        }
     740        $lf_table = $wpdb->prefix . 'langforge_translations';
     741        $ids_csv  = implode(',', $ids);
     742        return (int) $wpdb->query("DELETE FROM {$lf_table} WHERE element_id IN ({$ids_csv}) AND " . LANGFORGE_Post_Translation::post_row_sql()); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix; IN list is integer IDs cast via intval; custom plugin table.
    704743    }
    705744
    … …  
    948987                update_post_meta($target_id, '_langforge_migrated_from_translatepress_language', $target_code);
    949988
    950                 $wpdb->query($wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- own table from $wpdb->prefix; one-time migration write
    951                     "DELETE FROM {$lf_table} WHERE element_id IN (%d, %d)", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from $wpdb->prefix, values are %-prepared.
    952                     $source_id,
    953                     $target_id
    954                 ));
     989                $this->delete_post_translation_rows([$source_id, $target_id]);
    955990                $group = (int) $wpdb->get_var("SELECT COALESCE(MAX(translation_group), 0) FROM {$lf_table}") + 1; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- own table from $wpdb->prefix; one-time migration read
    956991
    … …  
    10161051
    10171052        return $count;
     1053    }
     1054
     1055
     1056    // Translations of terms (WPML `tax_*`, Polylang `term_language` / `term_translations`)
     1057
     1058
     1059    // BUG-LANG-MIGR-01: the migrations read posts only, so the translated categories were not linked
     1060    // after them. wp_langforge_translations keeps a term the way the taxonomy screen of the plugin
     1061    // writes it: element_type "tax_<taxonomy>", element_id the term_id, one translation_group for
     1062    // the pair (the groups of posts and terms count in the same series).
     1063
     1064    /**
     1065 * Taxonomies WPML registers for its own bookkeeping; its icl_translations rows of the type
     1066 * "tax_<name>" are not translations of the content of the site. They exist on the site (taxonomy_exists
     1067 * is true) as long as WPML is active, which it is when the migration runs.
     1068 */
     1069    private const WPML_SERVICE_TAXONOMIES = ['translation_priority'];
     1070
     1071    /** The line of the log for the terms. */
     1072    private function term_result_line(array $result): string {
     1073        return sprintf('Terms: linked %d translations in %d groups, errors %d', $result['linked'], $result['groups'], $result['errors']);
     1074    }
     1075
     1076    /**
     1077 * Clear the rows of the terms that are about to be written, so that a second run (migrate, tweak, migrate
     1078 * again) does not leave one term in two groups. By element and type, never by number alone: a post and a
     1079 * term can have the same number (see delete_post_translation_rows).
     1080 *
     1081 * @param array<string,int> $term_ids_by_taxonomy Taxonomy => term IDs.
     1082 * @return int Rows deleted.
     1083 */
     1084    private function delete_term_translation_rows(array $term_ids_by_taxonomy): int {
     1085        global $wpdb;
     1086        $lf_table = $wpdb->prefix . 'langforge_translations';
     1087        $deleted  = 0;
     1088        foreach ($term_ids_by_taxonomy as $taxonomy => $term_ids) {
     1089            $taxonomy = sanitize_key((string) $taxonomy);
     1090            $ids      = array_values(array_unique(array_filter(array_map('intval', $term_ids), function ($id) { return $id > 0; })));
     1091            if ($taxonomy === '' || !$ids) {
     1092                continue;
     1093            }
     1094            foreach (array_chunk($ids, 500) as $chunk) {
     1095                $ids_csv  = implode(',', $chunk);
     1096                $deleted += (int) $wpdb->query($wpdb->prepare("DELETE FROM {$lf_table} WHERE element_type = %s AND element_id IN ({$ids_csv})", LANGFORGE_Post_Translation::TERM_ELEMENT_PREFIX . $taxonomy)); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix; IN list is integer IDs cast via intval; custom plugin table.
     1097            }
     1098        }
     1099        return $deleted;
     1100    }
     1101
     1102    /**
     1103 * Write groups of term translations.
     1104 *
     1105 * @param array<int,array<int,array{term_id:int,taxonomy:string,language:string,source:?string}>> $groups One list of members per group.
     1106 * @return array{linked:int,groups:int,errors:int} Rows written, groups with a row, rows that could not be written.
     1107 */
     1108    private function write_term_translation_groups(array $groups): array {
     1109        global $wpdb;
     1110        $lf_table = $wpdb->prefix . 'langforge_translations';
     1111        $linked   = $written = $errors = 0;
     1112
     1113        $by_taxonomy = [];
     1114        foreach ($groups as $members) {
     1115            foreach ($members as $member) {
     1116                $by_taxonomy[$member['taxonomy']][] = $member['term_id'];
     1117            }
     1118        }
     1119        if (!$by_taxonomy) {
     1120            return ['linked' => 0, 'groups' => 0, 'errors' => 0];
     1121        }
     1122        $this->delete_term_translation_rows($by_taxonomy);
     1123
     1124        $group_id = (int) $wpdb->get_var("SELECT COALESCE(MAX(translation_group), 0) FROM {$lf_table}"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- own table from $wpdb->prefix; one-time migration read
     1125        $seen     = [];
     1126        foreach ($groups as $members) {
     1127            $group_id++;
     1128            $has_row = false;
     1129            foreach ($members as $member) {
     1130                $element_type = LANGFORGE_Post_Translation::TERM_ELEMENT_PREFIX . $member['taxonomy'];
     1131                $key          = $element_type . ':' . $member['term_id'] . ':' . $member['language'];
     1132                if (isset($seen[$key])) {
     1133                    continue;
     1134                }
     1135                $seen[$key] = true;
     1136                $result = $wpdb->insert($lf_table, [ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- own table from $wpdb->prefix; one-time migration write
     1137                    'element_id'           => $member['term_id'],
     1138                    'element_type'         => $element_type,
     1139                    'translation_group'    => $group_id,
     1140                    'language_code'        => $member['language'],
     1141                    'source_language_code' => $member['source'],
     1142                ]);
     1143                if ($result === false) {
     1144                    $errors++;
     1145                    continue;
     1146                }
     1147                $linked++;
     1148                $has_row = true;
     1149            }
     1150            if ($has_row) {
     1151                $written++;
     1152            }
     1153        }
     1154
     1155        return ['linked' => $linked, 'groups' => $written, 'errors' => $errors];
     1156    }
     1157
     1158    /**
     1159 * WPML keeps the translation of a term as a row of icl_translations of the type "tax_<taxonomy>":
     1160 * element_id is the term_taxonomy_id of the term (not its term_id), the members of a pair share a trid.
     1161 *
     1162 * @return array{linked:int,groups:int,errors:int}
     1163 */
     1164    private function migrate_wpml_terms(): array {
     1165        global $wpdb;
     1166        $icl_table = $wpdb->prefix . 'icl_translations';
     1167
     1168        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- WPML legacy/core table names from $wpdb->prefix; static query.
     1169        $rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- WPML legacy table from $wpdb->prefix; one-time migration read
     1170            "SELECT t.element_type, t.trid, t.language_code, t.source_language_code, tt.term_id, tt.taxonomy
     1171             FROM {$icl_table} t
     1172             JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = t.element_id
     1173             WHERE t.element_type LIKE 'tax_%'
     1174             ORDER BY t.trid ASC, tt.term_id ASC"
     1175        );
     1176        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     1177
     1178        $by_trid = [];
     1179        foreach ((array) $rows as $row) {
     1180            // The type of the row names the taxonomy of the term it points at; a number that is the
     1181            // term_taxonomy_id of a term of another taxonomy is not this row's term.
     1182            if ($row->element_type !== LANGFORGE_Post_Translation::TERM_ELEMENT_PREFIX . $row->taxonomy) {
     1183                continue;
     1184            }
     1185            // `tax_*` is every taxonomy WPML keeps a row for: its own service taxonomies too, and the ones of
     1186            // plugins that are gone. Only the taxonomies the site registers, and not WPML's own, are brought over.
     1187            if (in_array($row->taxonomy, self::WPML_SERVICE_TAXONOMIES, true) || !taxonomy_exists($row->taxonomy)) {
     1188                continue;
     1189            }
     1190            $language = sanitize_key($row->language_code);
     1191            if ($language === '') {
     1192                continue;
     1193            }
     1194            $by_trid[(int) $row->trid][] = [
     1195                'term_id'  => (int) $row->term_id,
     1196                'taxonomy' => sanitize_key($row->taxonomy),
     1197                'language' => $language,
     1198                'source'   => $row->source_language_code ? sanitize_key($row->source_language_code) : null,
     1199            ];
     1200        }
     1201
     1202        return $this->write_term_translation_groups(array_values($by_trid));
     1203    }
     1204
     1205    /**
     1206 * Taxonomy of terms, by term_id: the taxonomy that is not one of Polylang's own.
     1207 *
     1208 * @param int $term_ids
     1209 * @return array<int,string> term_id => taxonomy
     1210 */
     1211    private function taxonomies_of_terms(array $term_ids): array {
     1212        global $wpdb;
     1213        $found = [];
     1214        $ids   = array_values(array_unique(array_filter(array_map('intval', $term_ids), function ($id) { return $id > 0; })));
     1215        foreach (array_chunk($ids, 500) as $chunk) {
     1216            $ids_csv = implode(',', $chunk);
     1217            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- core table; IN list is integer IDs cast via intval above.
     1218            $rows    = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- core table; IN list is integer IDs cast via intval; one-time migration read
     1219                "SELECT term_id, taxonomy FROM {$wpdb->term_taxonomy}
     1220                 WHERE term_id IN ({$ids_csv})
     1221                   AND taxonomy NOT IN ('language', 'term_language', 'post_translations', 'term_translations')
     1222                 ORDER BY term_taxonomy_id ASC"
     1223            );
     1224            // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     1225            foreach ((array) $rows as $row) {
     1226                if (!isset($found[(int) $row->term_id])) {
     1227                    $found[(int) $row->term_id] = sanitize_key($row->taxonomy);
     1228                }
     1229            }
     1230        }
     1231        return $found;
     1232    }
     1233
     1234    /**
     1235 * The members of a map Polylang keeps in the description of a "post_translations" or "term_translations"
     1236 * term: language code => ID.
     1237 *
     1238 * The map is serialized and carries more than the languages: a "sync" list (the posts or terms whose
     1239 * data Polylang keeps in step), so a key that is not a language of Polylang is no member, and neither is
     1240 * a value that is not a positive whole ID (a list is no ID: (int) of a non-empty one is 1).
     1241 *
     1242 * @param mixed $map Unserialized description.
     1243 * @param array<string,mixed> $lang_info Languages of Polylang by code.
     1244 * @return array<string,int> code => ID
     1245 */
     1246    private function polylang_map_members($map, array $lang_info): array {
     1247        if (!is_array($map)) {
     1248            return [];
     1249        }
     1250        $members = [];
     1251        foreach ($map as $code => $id) {
     1252            $code = sanitize_key((string) $code);
     1253            if ($code === '' || !isset($lang_info[$code]) || !is_scalar($id) || !preg_match('/^[1-9][0-9]*$/', (string) $id)) {
     1254                continue;
     1255            }
     1256            $members[$code] = (int) $id;
     1257        }
     1258        return $members;
     1259    }
     1260
     1261    /**
     1262 * Polylang gives a term its language as a "term_language" term (slug pll_<code>) related to the term, and
     1263 * keeps a pair as a "term_translations" term whose description is the serialized map code => term_id
     1264 * (the same map as "post_translations", with a "sync" list of its own besides the languages).
     1265 *
     1266 * @param array<string,mixed> $lang_info Languages of the site by code (as read from Polylang).
     1267 * @param string $default_lang
     1268 * @return array{linked:int,groups:int,errors:int}
     1269 */
     1270    private function migrate_polylang_terms(array $lang_info, string $default_lang): array {
     1271        global $wpdb;
     1272
     1273        $pairs   = [];
     1274        $in_pair = [];
     1275        $pair_terms = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table; one-time migration read
     1276            "SELECT tt.description FROM {$wpdb->term_taxonomy} tt WHERE tt.taxonomy = 'term_translations'"
     1277        );
     1278        foreach ((array) $pair_terms as $pair_term) {
     1279            $members = $this->polylang_map_members(forge_safe_unserialize($pair_term->description), $lang_info);
     1280            foreach ($members as $term_id) {
     1281                $in_pair[$term_id] = true;
     1282            }
     1283            if ($members) {
     1284                $pairs[] = $members;
     1285            }
     1286        }
     1287
     1288        // Terms that have a language and no pair: a row of their own, as single posts have.
     1289        $singles   = [];
     1290        $lang_rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- core table; one-time migration read
     1291            "SELECT tr.object_id AS term_id, t.slug
     1292             FROM {$wpdb->term_relationships} tr
     1293             JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
     1294             JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
     1295             WHERE tt.taxonomy = 'term_language'
     1296             ORDER BY tr.object_id ASC"
     1297        );
     1298        foreach ((array) $lang_rows as $row) {
     1299            $term_id = (int) $row->term_id;
     1300            $code    = sanitize_key((string) preg_replace('/^pll_/', '', (string) $row->slug));
     1301            if ($term_id <= 0 || $code === '' || isset($in_pair[$term_id]) || !isset($lang_info[$code])) {
     1302                continue;
     1303            }
     1304            $singles[$term_id] = $code;
     1305        }
     1306
     1307        $taxonomy_of = $this->taxonomies_of_terms(array_merge(array_keys($in_pair), array_keys($singles)));
     1308
     1309        $member = function (int $term_id, string $code) use ($taxonomy_of, $default_lang) {
     1310            return [
     1311                'term_id'  => $term_id,
     1312                'taxonomy' => $taxonomy_of[$term_id],
     1313                'language' => $code,
     1314                'source'   => $code === $default_lang ? null : ($default_lang !== '' ? $default_lang : null),
     1315            ];
     1316        };
     1317        $groups = [];
     1318        foreach ($pairs as $members) {
     1319            $group = [];
     1320            foreach ($members as $code => $term_id) {
     1321                if (isset($taxonomy_of[$term_id])) {
     1322                    $group[] = $member($term_id, (string) $code);
     1323                }
     1324            }
     1325            if ($group) {
     1326                $groups[] = $group;
     1327            }
     1328        }
     1329        foreach ($singles as $term_id => $code) {
     1330            if (isset($taxonomy_of[$term_id])) {
     1331                $groups[] = [$member((int) $term_id, $code)];
     1332            }
     1333        }
     1334
     1335        return $this->write_term_translation_groups($groups);
    10181336    }
    10191337
    … …  
    10911409        $group_post_ids = [];
    10921410        foreach ($group_terms as $group_term) {
    1093             $translations = forge_safe_unserialize($group_term->description);
    1094             if (!is_array($translations) || count($translations) < 1) continue;
     1411            // Only the languages of Polylang with a positive ID: the map carries a "sync" list besides them,
     1412            // and (int) of a non-empty list is 1 (BUG-LANG-MIGR-02).
     1413            $translations = $this->polylang_map_members(forge_safe_unserialize($group_term->description), $lang_info);
     1414            if (!$translations) continue;
    10951415
    10961416            $translation_groups[] = $translations;
    … …  
    11111431        $all_polylang_post_ids = array_unique(array_filter(array_map('intval', array_merge($group_post_ids, $language_post_ids))));
    11121432        if (!empty($all_polylang_post_ids)) {
    1113             $post_ids_csv = implode(',', $all_polylang_post_ids);
    1114             $wpdb->query("DELETE FROM {$lf_table} WHERE element_id IN ({$post_ids_csv})"); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name from $wpdb->prefix; IN list is integer IDs cast via intval; custom plugin table.
     1433            $this->delete_post_translation_rows($all_polylang_post_ids);
    11151434        }
    11161435
    … …  
    12201539        );
    12211540
     1541        // 8. Translations of terms: the `term_translations` pairs and the `term_language` of single terms.
     1542        $term_result = $this->migrate_polylang_terms($lang_info, (string) $default_lang);
     1543        $terms       = $term_result['linked'];
     1544        $errors     += $term_result['errors'];
     1545        if ($term_result['linked'] || $term_result['errors']) {
     1546            $log[] = $this->term_result_line($term_result);
     1547        }
     1548
    12221549        // same flush as WPML — new CPT rules need to land in wp_options.
    12231550        flush_rewrite_rules();
    12241551
    1225         return compact('migrated', 'skipped', 'errors', 'log');
     1552        return compact('migrated', 'skipped', 'errors', 'terms', 'log');
    12261553    }
    12271554
Note: See TracChangeset for help on using the changeset viewer.