PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.11.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.11.0
2.12.0 2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk All 53 releases
← All changes | includes/admin/importers/class-snapshot-migrator.php +20 -376 trunk → 2.11.0 View file →
@@ -111,15 +111,8 @@
111 111 'image_seo',
112 112 'sitemap_settings',
113 113 'analytics_connected',
114 114 'focus_pages',
115 - // Applied by migrate_site_identity() (#886).
116 - 'robots_txt',
117 - 'feed',
118 - // Applied by migrate_markdown_for_ai() into ThinkRank Pro (#886).
119 - // Without Pro it has no home, but neither is anything lost: cleanup
120 - // removes a feature toggle, not content.
121 - 'markdown_for_ai',
122 115 // Capture-all raw buckets (whole source option sets stored verbatim).
123 116 // They live in the SNAPSHOT — cleanup never touches the snapshot — and
124 117 // a re-export recreates them, so they never block cleanup.
125 118 'raw_options',
@@ -381,15 +374,8 @@
381 374 if ($object_type === 'post' && $this->migrate_video_schema($object_id, $data, $record)) {
382 375 $record_had_writes = true;
383 376 }
384 377
385 - // The post's own schemas as ThinkRank Pro Custom Schema entries
386 - // scoped to it (extended.custom_schemas, #886).
387 - if ($object_type === 'post' && !empty($record['extended']['custom_schemas']) && is_array($record['extended']['custom_schemas'])
388 - && $this->migrate_custom_schemas($source_plugin, $record['extended']['custom_schemas']) > 0) {
389 - $record_had_writes = true;
390 - }
391 -
392 378 // Per-object redirect. SEOPress is the one source that stores a
393 379 // redirect as object meta rather than in a rules table, so its
394 380 // per-post redirects were exported into extended.redirect_* and
395 381 // then dropped for want of anywhere to put them. They have a home
@@ -1779,9 +1765,9 @@
1779 1765 $processed++;
1780 1766 }
1781 1767 }
1782 1768
1783 - if (!empty($data['homepage_title']) || !empty($data['organization_name']) || !empty($data['organization_logo'])) {
1769 + if (!empty($data['homepage_title']) || !empty($data['homepage_description']) || !empty($data['organization_name']) || !empty($data['organization_logo'])) {
1784 1770 $site_identity = get_option('thinkrank_site_identity_settings', []);
1785 1771 $updated = false;
1786 1772
1787 1773 if (!empty($data['homepage_title']) && empty($site_identity['homepage_title'])) {
@@ -1787,8 +1773,12 @@
1787 1773 if (!empty($data['homepage_title']) && empty($site_identity['homepage_title'])) {
1788 1774 $site_identity['homepage_title'] = $data['homepage_title'];
1789 1775 $updated = true;
1790 1776 }
1777 + if (!empty($data['homepage_description']) && empty($site_identity['homepage_description'])) {
1778 + $site_identity['homepage_description'] = $data['homepage_description'];
1779 + $updated = true;
1780 + }
1791 1781 if (!empty($data['organization_name']) && empty($site_identity['organization_name'])) {
1792 1782 $site_identity['organization_name'] = $data['organization_name'];
1793 1783 $updated = true;
1794 1784 }
@@ -1802,10 +1792,21 @@
1802 1792 $processed++;
1803 1793 }
1804 1794 }
1805 1795
1806 - if (!empty($data['social_profiles']) && is_array($data['social_profiles'])) {
1807 - if ($this->migrate_social_profiles($data['social_profiles'])) {
1796 + if (!empty($data['social_profiles'])) {
1797 + $social = get_option('thinkrank_social_media_settings', []);
1798 + $updated = false;
1799 +
1800 + foreach ($data['social_profiles'] as $platform => $url) {
1801 + if (!empty($url) && empty($social[$platform])) {
1802 + $social[$platform] = $url;
1803 + $updated = true;
1804 + }
1805 + }
1806 +
1807 + if ($updated) {
1808 + update_option('thinkrank_social_media_settings', $social);
1808 1809 $processed++;
1809 1810 }
1810 1811 }
1811 1812
@@ -1862,23 +1863,8 @@
1862 1863 $processed++;
1863 1864 }
1864 1865 }
1865 1866
1866 - // Per-taxonomy and blog-index noindex, into the Content Type Matrix.
1867 - // The templates beside them have no ThinkRank store and stay preserved.
1868 - if ($this->migrate_archive_noindex($extended)) {
1869 - $processed++;
1870 - }
1871 -
1872 - // Site-wide custom schemas and Markdown for AI, both ThinkRank Pro.
1873 - $global_schemas = $extended['custom_schemas']['entries'] ?? [];
1874 - if (is_array($global_schemas) && $this->migrate_custom_schemas($plugin, $global_schemas) > 0) {
1875 - $processed++;
1876 - }
1877 - if ($this->migrate_markdown_for_ai($extended)) {
1878 - $processed++;
1879 - }
1880 -
1881 1867 // Site-identity settings (homepage/org/breadcrumbs/local SEO) are served to
1882 1868 // the frontend from the wp_thinkrank_seo_settings table via the manager, not
1883 1869 // from the option written above — route them through the manager so they
1884 1870 // actually take effect.
@@ -2014,12 +2000,8 @@
2014 2000 // real template and beats the literal-resolved data.homepage_title, so it
2015 2001 // wins when the source provided one.
2016 2002 $title_formats = is_array($extended['title_formats'] ?? null) ? $extended['title_formats'] : [];
2017 2003 $set('homepage_title', $title_formats['homepage_title'] ?? ($data['homepage_title'] ?? ''));
2018 - // The homepage meta description. It used to go to the legacy
2019 - // thinkrank_site_identity_settings option, which nothing reads, so the
2020 - // homepage fell back to the tagline (#897).
2021 - $set('homepage_description', trim((string) ($data['homepage_description'] ?? '')));
2022 2004 $set('site_name', $data['organization_name'] ?? '');
2023 2005 $set('alternate_name', $data['alternate_name'] ?? '');
2024 2006 $set('logo_url', $data['organization_logo'] ?? '');
2025 2007
@@ -2061,23 +2043,8 @@
2061 2043 $set('breadcrumb_separator', $breadcrumbs['separator'] ?? '');
2062 2044 $set('breadcrumb_prefix', $breadcrumbs['prefix'] ?? '');
2063 2045 }
2064 2046
2065 - // A hand-written robots.txt (extended.robots_txt.content) goes into
2066 - // ThinkRank's Robots.txt textarea only while that is still empty:
2067 - // a file the user already wrote here is never replaced (#886).
2068 - $robots_txt = $extended['robots_txt'] ?? [];
2069 - if (is_array($robots_txt)) {
2070 - $set('robots_txt_content', trim((string) ($robots_txt['content'] ?? '')));
2071 - }
2072 -
2073 - // The "appeared first on" feed backlink (extended.feed.source_link).
2074 - // Only switches it on: a source that had it off is no reason to turn
2075 - // off a signature ThinkRank seeds for new installs.
2076 - if (!empty($extended['feed']['source_link']) && empty($current['feed_source_link'])) {
2077 - $updates['feed_source_link'] = true;
2078 - }
2079 -
2080 2047 // Local SEO (extended.local_seo) — migrate the full NAP + geo when there
2081 2048 // is any meaningful business data (name, phone, address or coordinates),
2082 2049 // and enable the feature alongside it. Each field is written only while
2083 2050 // ThinkRank's Business Info still holds its default (non-destructive).
@@ -2224,10 +2191,9 @@
2224 2191 }
2225 2192
2226 2193 $app_id = trim((string) ($defaults['facebook_app_id'] ?? ''));
2227 2194 $og_image = trim((string) ($defaults['og_default_image'] ?? ''));
2228 - $twitter_image = trim((string) ($defaults['twitter_default_image'] ?? ''));
2229 - if ($app_id === '' && $og_image === '' && $twitter_image === '') {
2195 + if ($app_id === '' && $og_image === '') {
2230 2196 return false;
2231 2197 }
2232 2198
2233 2199 $manager = new \ThinkRank\SEO\Social_Meta_Manager();
@@ -2243,12 +2209,8 @@
2243 2209 // outside the allow-list never reaches the database.
2244 2210 if ($og_image !== '' && empty($current['default_og_image'])) {
2245 2211 $updates['default_og_image'] = $og_image;
2246 2212 }
2247 - // The Twitter card fallback image (Slim SEO's default_twitter_image).
2248 - if ($twitter_image !== '' && empty($current['default_twitter_image'])) {
2249 - $updates['default_twitter_image'] = $twitter_image;
2250 - }
2251 2213
2252 2214 if (empty($updates)) {
2253 2215 return false;
2254 2216 }
@@ -2256,97 +2218,8 @@
2256 2218 return (bool) $manager->save_settings('site', null, $updates);
2257 2219 }
2258 2220
2259 2221 /**
2260 - * Social profile URLs (data.social_profiles, platform => URL) into the
2261 - * stores that are read (#898).
2262 - *
2263 - * They used to be written as `thinkrank_social_media_settings[<platform>]`,
2264 - * a key nothing reads: the consumer looks for `<platform>_url`, the
2265 - * Organization graph reads the Schema Manager's
2266 - * `organization_social_<platform>`, and `twitter:site` comes from the
2267 - * Social Meta `twitter_username`. So the profiles never reached `sameAs`
2268 - * and the X handle stopped printing once the source plugin was off. Each
2269 - * store is filled only where it is still empty.
2270 - *
2271 - * @param array $profiles platform => profile URL
2272 - * @return bool True if anything was written
2273 - */
2274 - private function migrate_social_profiles(array $profiles): bool {
2275 - $profiles = array_filter(array_map(
2276 - static fn($url): string => is_scalar($url) ? trim((string) $url) : '',
2277 - $profiles
2278 - ));
2279 - if (empty($profiles)) {
2280 - return false;
2281 - }
2282 -
2283 - $written = false;
2284 -
2285 - $social = get_option('thinkrank_social_media_settings', []);
2286 - $social = is_array($social) ? $social : [];
2287 - $social_updated = false;
2288 - foreach ($profiles as $platform => $url) {
2289 - $key = sanitize_key((string) $platform) . '_url';
2290 - if (empty($social[$key])) {
2291 - $social[$key] = esc_url_raw($url);
2292 - $social_updated = true;
2293 - }
2294 - }
2295 - if ($social_updated) {
2296 - update_option('thinkrank_social_media_settings', $social);
2297 - $written = true;
2298 - }
2299 -
2300 - // The Organization node's sameAs.
2301 - $schema_platforms = ['facebook', 'twitter', 'linkedin', 'instagram', 'youtube', 'pinterest', 'whatsapp', 'telegram'];
2302 - $manager = $this->create_schema_manager();
2303 - if ($manager !== null) {
2304 - $current = $manager->get_settings('site');
2305 - $updates = [];
2306 - foreach ($profiles as $platform => $url) {
2307 - $field = 'organization_social_' . $platform;
2308 - if (in_array($platform, $schema_platforms, true) && filter_var($url, FILTER_VALIDATE_URL) && empty($current[$field])) {
2309 - $updates[$field] = esc_url_raw($url);
2310 - }
2311 - }
2312 - if (!empty($updates) && $manager->save_settings('site', null, $updates)) {
2313 - $written = true;
2314 - }
2315 - }
2316 -
2317 - // twitter:site, from an X/Twitter profile URL.
2318 - $handle = self::twitter_handle_from_url((string) ($profiles['twitter'] ?? ''));
2319 - if ($handle !== '' && class_exists('ThinkRank\\SEO\\Social_Meta_Manager')) {
2320 - $social_meta = new \ThinkRank\SEO\Social_Meta_Manager();
2321 - $current = $social_meta->get_settings('site');
2322 - if (empty($current['twitter_username']) && $social_meta->save_settings('site', null, ['twitter_username' => $handle])) {
2323 - $written = true;
2324 - }
2325 - }
2326 -
2327 - return $written;
2328 - }
2329 -
2330 - /**
2331 - * The handle in an x.com / twitter.com profile URL, or '' when the URL is
2332 - * not one (`https://x.com/arsenal` → `arsenal`). Handles are 1-15 letters,
2333 - * digits or underscores, which is also what Social Meta accepts.
2334 - *
2335 - * @param string $url Profile URL
2336 - */
2337 - public static function twitter_handle_from_url(string $url): string {
2338 - $host = strtolower((string) wp_parse_url($url, PHP_URL_HOST));
2339 - if (!in_array(preg_replace('/^(www\.|mobile\.)/', '', $host), ['x.com', 'twitter.com'], true)) {
2340 - return '';
2341 - }
2342 - $segment = explode('/', trim((string) wp_parse_url($url, PHP_URL_PATH), '/'))[0] ?? '';
2343 - $segment = ltrim(rawurldecode($segment), '@');
2344 -
2345 - return preg_match('/^[A-Za-z0-9_]{1,15}$/', $segment) ? $segment : '';
2346 - }
2347 -
2348 - /**
2349 2222 * Migrate the source plugin's Pinterest site-verification code into
2350 2223 * ThinkRank's core `pinterest_site_verification` setting. Never overwrites
2351 2224 * a configured code.
2352 2225 *
@@ -3193,39 +3066,11 @@
3193 3066 foreach ($extended as $key => $value) {
3194 3067 if (in_array($key, self::HANDLED_EXTENDED_SETTINGS, true) || empty($value)) {
3195 3068 continue;
3196 3069 }
3197 - // A bucket the migrator applies part of blocks cleanup only for
3198 - // the part it does not.
3199 - if (is_array($value) && empty($this->unapplied_remainder((string) $key, $value))) {
3200 - continue;
3201 - }
3202 - // Custom schemas have a home only in ThinkRank Pro. Without it
3203 - // they are preserved, and cleanup would delete the source's
3204 - // copy, including every post's own schemas.
3205 - if ($key === 'custom_schemas') {
3206 - if ($this->create_custom_schema_repository() !== null) {
3207 - continue;
3208 - }
3209 - $count = count(is_array($value['entries'] ?? null) ? $value['entries'] : []) + (int) ($value['post_count'] ?? 0);
3210 - $buckets[] = [
3211 - 'key' => 'settings.custom_schemas',
3212 - 'label' => (string) self::extended_bucket_label('custom_schemas'),
3213 - 'count' => max(1, $count),
3214 - ];
3215 - continue;
3216 - }
3217 - if ($key === 'schema_templates' && is_array($value)) {
3218 - $buckets[] = [
3219 - 'key' => 'settings.schema_templates',
3220 - 'label' => (string) self::extended_bucket_label('schema_templates'),
3221 - 'count' => count($value),
3222 - ];
3223 - continue;
3224 - }
3225 3070 $buckets[] = [
3226 3071 'key' => 'settings.' . $key,
3227 - 'label' => self::extended_bucket_label((string) $key) ?? ucfirst(str_replace('_', ' ', (string) $key)),
3072 + 'label' => (string) $key,
3228 3073 'count' => 1,
3229 3074 ];
3230 3075 }
3231 3076 }
@@ -3230,209 +3075,8 @@
3230 3075 }
3231 3076 }
3232 3077
3233 3078 return $buckets;
3234 - }
3235 -
3236 - /**
3237 - * Save canonical custom schema entries into ThinkRank Pro's Custom Schema.
3238 - *
3239 - * Each entry's id is derived from the source and its `key`, so running
3240 - * the migration again finds the entry it wrote and leaves it — including
3241 - * any edit made since. An entry the Repository refuses (an invalid
3242 - * condition, an oversized document) is skipped; the source still holds
3243 - * it, and the snapshot keeps the rendered JSON.
3244 - *
3245 - * @param string $plugin Source plugin slug
3246 - * @param array $entries Canonical entries {key, title, enabled, json, conditions}
3247 - * @return int Entries written
3248 - */
3249 - private function migrate_custom_schemas(string $plugin, array $entries): int {
3250 - $repository = $this->create_custom_schema_repository();
3251 - if ($repository === null) {
3252 - return 0;
3253 - }
3254 -
3255 - $written = 0;
3256 - foreach ($entries as $entry) {
3257 - if (!is_array($entry) || trim((string) ($entry['json'] ?? '')) === '') {
3258 - continue;
3259 - }
3260 - $id = 'cs_' . substr(md5($plugin . '|' . (string) ($entry['key'] ?? wp_json_encode($entry))), 0, 12);
3261 - if ($repository->get($id) !== null) {
3262 - continue;
3263 - }
3264 -
3265 - try {
3266 - $repository->save([
3267 - 'id' => $id,
3268 - 'title' => (string) ($entry['title'] ?? ''),
3269 - 'enabled' => !empty($entry['enabled']),
3270 - 'json' => (string) $entry['json'],
3271 - 'conditions' => is_array($entry['conditions'] ?? null) ? $entry['conditions'] : ['include' => [], 'exclude' => []],
3272 - ]);
3273 - $written++;
3274 - } catch (\Throwable $e) {
3275 - continue;
3276 - }
3277 - }
3278 -
3279 - return $written;
3280 - }
3281 -
3282 - /**
3283 - * ThinkRank Pro's Custom Schema repository, when Pro is active.
3284 - *
3285 - * @return object|null
3286 - */
3287 - protected function create_custom_schema_repository(): ?object {
3288 - return class_exists('ThinkRank\\Pro\\Schema\\Repository') ? new \ThinkRank\Pro\Schema\Repository() : null;
3289 - }
3290 -
3291 - /**
3292 - * Turn on ThinkRank Pro's Markdown for AI with the source's post types
3293 - * (extended.markdown_for_ai), while its settings were never saved.
3294 - *
3295 - * @param array $extended Canonical settings `extended` payload
3296 - * @return bool True if written
3297 - */
3298 - private function migrate_markdown_for_ai(array $extended): bool {
3299 - $markdown = $extended['markdown_for_ai'] ?? [];
3300 - if (!is_array($markdown) || empty($markdown['enabled']) || !class_exists('ThinkRank\\Pro\\Markdown_For_AI\\Settings')) {
3301 - return false;
3302 - }
3303 - if (get_option('thinkrank_pro_markdown_for_ai', null) !== null) {
3304 - return false;
3305 - }
3306 -
3307 - $types = array_values(array_filter(
3308 - array_map('strval', is_array($markdown['post_types'] ?? null) ? $markdown['post_types'] : ['post']),
3309 - 'post_type_exists'
3310 - ));
3311 -
3312 - return (bool) (new \ThinkRank\Pro\Markdown_For_AI\Settings())->save([
3313 - 'enabled' => true,
3314 - 'post_types' => !empty($types) ? $types : ['post'],
3315 - ]);
3316 - }
3317 -
3318 - /**
3319 - * What a preserved settings bucket holds, as the cleanup warning names it.
3320 - *
3321 - * The warning exists so someone can decide whether to delete their old
3322 - * plugin's data; printing `code_injection` does not help them decide
3323 - * (#900). Every bucket an exporter can leave unapplied has an entry here,
3324 - * and a test fails when one is added without.
3325 - *
3326 - * @param string $key Extended bucket key
3327 - * @return string|null Label, or null when the bucket has none
3328 - */
3329 - public static function extended_bucket_label(string $key): ?string {
3330 - $labels = [
3331 - 'code_injection' => __('Header, body and footer code', 'thinkrank'),
3332 - 'redirect_settings' => __('Redirect behaviour settings', 'thinkrank'),
3333 - 'taxonomy_settings' => __('Taxonomy templates and images', 'thinkrank'),
3334 - 'post_type_archive_settings' => __('Post type archive templates and images', 'thinkrank'),
3335 - 'no_category_base' => __('Removing /category/ from category URLs', 'thinkrank'),
3336 - 'webmaster_tools' => __('Webmaster tools verification codes', 'thinkrank'),
3337 - 'access_control' => __('Role access settings', 'thinkrank'),
3338 - 'og_frontpage_title' => __('Homepage social title', 'thinkrank'),
3339 - 'og_frontpage_desc' => __('Homepage social description', 'thinkrank'),
3340 - 'og_frontpage_image' => __('Homepage social image', 'thinkrank'),
3341 - 'custom_schemas' => __('Custom schemas (need ThinkRank Pro)', 'thinkrank'),
3342 - 'schema_templates' => __('Schema templates with no ThinkRank equivalent', 'thinkrank'),
3343 - ];
3344 -
3345 - return $labels[$key] ?? null;
3346 - }
3347 -
3348 - /**
3349 - * What is left of a partially-applied extended bucket once the keys
3350 - * migrate_archive_noindex() consumes are removed.
3351 - *
3352 - * `taxonomy_settings` and `post_type_archive_settings` carry a `noindex`
3353 - * per context that is applied, beside templates and images that are not.
3354 - * Any other bucket is returned whole.
3355 - *
3356 - * @param string $key Extended bucket key
3357 - * @param array $value Bucket contents
3358 - * @return array The unapplied part
3359 - */
3360 - private function unapplied_remainder(string $key, array $value): array {
3361 - if ($key !== 'taxonomy_settings' && $key !== 'post_type_archive_settings') {
3362 - return $value;
3363 - }
3364 -
3365 - $remainder = [];
3366 - foreach ($value as $context => $settings) {
3367 - if (!is_array($settings)) {
3368 - continue;
3369 - }
3370 - // Only the blog index has an archive entity to receive a noindex.
3371 - $applied = $key === 'taxonomy_settings' || (string) $context === 'post';
3372 - $rest = $applied ? array_diff_key($settings, ['noindex' => true]) : $settings;
3373 - if (!empty($rest)) {
3374 - $remainder[$context] = $rest;
3375 - }
3376 - }
3377 -
3378 - return $remainder;
3379 - }
3380 -
3381 - /**
3382 - * Apply per-taxonomy and blog-index noindex through the Content Type
3383 - * Matrix (`taxonomy:<slug>` / `archive:blog`), the entities the front end
3384 - * reads robots directives for on those archives.
3385 - *
3386 - * Reads `extended.taxonomy_settings[<taxonomy>].noindex` (Yoast's and
3387 - * Slim SEO's shape) and `extended.post_type_archive_settings.post.noindex`.
3388 - * An entity whose robots were already customised in ThinkRank is left
3389 - * alone, and a source that did NOT noindex an archive writes nothing —
3390 - * "indexed" is the default and restating it would pin the entity.
3391 - *
3392 - * @param array $extended Canonical settings `extended` payload
3393 - * @return bool True if any entity was written
3394 - */
3395 - private function migrate_archive_noindex(array $extended): bool {
3396 - if (!class_exists('ThinkRank\\SEO\\Content_Type_Settings')) {
3397 - return false;
3398 - }
3399 -
3400 - // Only public taxonomies are matrix entities (Content_Type_Settings::get_entities()).
3401 - $entities = [];
3402 - $taxonomies = is_array($extended['taxonomy_settings'] ?? null) ? $extended['taxonomy_settings'] : [];
3403 - foreach ($taxonomies as $taxonomy => $settings) {
3404 - if (!is_array($settings) || empty($settings['noindex']) || !taxonomy_exists((string) $taxonomy)) {
3405 - continue;
3406 - }
3407 - $object = get_taxonomy((string) $taxonomy);
3408 - if (is_object($object) && !empty($object->public)) {
3409 - $entities[] = \ThinkRank\SEO\Content_Type_Settings::PREFIX_TAXONOMY . $taxonomy;
3410 - }
3411 - }
3412 - $archives = is_array($extended['post_type_archive_settings'] ?? null) ? $extended['post_type_archive_settings'] : [];
3413 - if (!empty($archives['post']['noindex'])) {
3414 - $entities[] = \ThinkRank\SEO\Content_Type_Settings::ENTITY_BLOG_INDEX;
3415 - }
3416 -
3417 - $written = false;
3418 - foreach ($entities as $entity_key) {
3419 - $existing = \ThinkRank\SEO\Content_Type_Settings::get_entity_settings($entity_key);
3420 - if (!empty($existing['robots_meta_enabled'])) {
3421 - continue;
3422 - }
3423 - $robots = \ThinkRank\SEO\Content_Type_Settings::default_robots_meta($entity_key);
3424 - $robots['index'] = false;
3425 - $robots['noindex'] = true;
3426 - if (\ThinkRank\SEO\Content_Type_Settings::update_entity_settings($entity_key, [
3427 - 'robots_meta_enabled' => true,
3428 - 'robots_meta' => $robots,
3429 - ])) {
3430 - $written = true;
3431 - }
3432 - }
3433 -
3434 - return $written;
3435 3079 }
3436 3080
3437 3081 /**
3438 3082 * Fold post IDs the source excluded from its sitemap into ThinkRank's