PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.0
2.13.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 All 54 releases
← All changes | includes/admin/importers/class-snapshot-migrator.php +20 -383 trunk → 2.10.0 View file →
@@ -54,15 +54,8 @@
54 54 'twitter_description' => '_thinkrank_twitter_description',
55 55 'twitter_image' => '_thinkrank_twitter_image',
56 56 'primary_category' => '_thinkrank_primary_category',
57 57 'schema_type' => '_thinkrank_selected_schema_type',
58 - // WooCommerce product identifier (GTIN/MPN/ISBN). Free does not read
59 - // it; ThinkRank Pro's Product_Fields does, under this exact key, so
60 - // importing it here means the identifier is already in place when Pro
61 - // is activated. Without it Google reports "missing identifier" on every
62 - // product after a switch, which is a rich-result warning the user did
63 - // not have before they migrated (#715).
64 - 'product_identifier' => '_thinkrank_product_gtin',
65 58 ];
66 59
67 60 /**
68 61 * Canonical robots meta fields. Composed into JSON-encoded
@@ -111,15 +104,8 @@
111 104 'image_seo',
112 105 'sitemap_settings',
113 106 'analytics_connected',
114 107 '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 108 // Capture-all raw buckets (whole source option sets stored verbatim).
123 109 // They live in the SNAPSHOT — cleanup never touches the snapshot — and
124 110 // a re-export recreates them, so they never block cleanup.
125 111 'raw_options',
@@ -381,15 +367,8 @@
381 367 if ($object_type === 'post' && $this->migrate_video_schema($object_id, $data, $record)) {
382 368 $record_had_writes = true;
383 369 }
384 370
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 371 // Per-object redirect. SEOPress is the one source that stores a
393 372 // redirect as object meta rather than in a rules table, so its
394 373 // per-post redirects were exported into extended.redirect_* and
395 374 // then dropped for want of anywhere to put them. They have a home
@@ -1779,9 +1758,9 @@
1779 1758 $processed++;
1780 1759 }
1781 1760 }
1782 1761
1783 - if (!empty($data['homepage_title']) || !empty($data['organization_name']) || !empty($data['organization_logo'])) {
1762 + if (!empty($data['homepage_title']) || !empty($data['homepage_description']) || !empty($data['organization_name']) || !empty($data['organization_logo'])) {
1784 1763 $site_identity = get_option('thinkrank_site_identity_settings', []);
1785 1764 $updated = false;
1786 1765
1787 1766 if (!empty($data['homepage_title']) && empty($site_identity['homepage_title'])) {
@@ -1787,8 +1766,12 @@
1787 1766 if (!empty($data['homepage_title']) && empty($site_identity['homepage_title'])) {
1788 1767 $site_identity['homepage_title'] = $data['homepage_title'];
1789 1768 $updated = true;
1790 1769 }
1770 + if (!empty($data['homepage_description']) && empty($site_identity['homepage_description'])) {
1771 + $site_identity['homepage_description'] = $data['homepage_description'];
1772 + $updated = true;
1773 + }
1791 1774 if (!empty($data['organization_name']) && empty($site_identity['organization_name'])) {
1792 1775 $site_identity['organization_name'] = $data['organization_name'];
1793 1776 $updated = true;
1794 1777 }
@@ -1802,10 +1785,21 @@
1802 1785 $processed++;
1803 1786 }
1804 1787 }
1805 1788
1806 - if (!empty($data['social_profiles']) && is_array($data['social_profiles'])) {
1807 - if ($this->migrate_social_profiles($data['social_profiles'])) {
1789 + if (!empty($data['social_profiles'])) {
1790 + $social = get_option('thinkrank_social_media_settings', []);
1791 + $updated = false;
1792 +
1793 + foreach ($data['social_profiles'] as $platform => $url) {
1794 + if (!empty($url) && empty($social[$platform])) {
1795 + $social[$platform] = $url;
1796 + $updated = true;
1797 + }
1798 + }
1799 +
1800 + if ($updated) {
1801 + update_option('thinkrank_social_media_settings', $social);
1808 1802 $processed++;
1809 1803 }
1810 1804 }
1811 1805
@@ -1862,23 +1856,8 @@
1862 1856 $processed++;
1863 1857 }
1864 1858 }
1865 1859
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 1860 // Site-identity settings (homepage/org/breadcrumbs/local SEO) are served to
1882 1861 // the frontend from the wp_thinkrank_seo_settings table via the manager, not
1883 1862 // from the option written above — route them through the manager so they
1884 1863 // actually take effect.
@@ -2014,12 +1993,8 @@
2014 1993 // real template and beats the literal-resolved data.homepage_title, so it
2015 1994 // wins when the source provided one.
2016 1995 $title_formats = is_array($extended['title_formats'] ?? null) ? $extended['title_formats'] : [];
2017 1996 $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 1997 $set('site_name', $data['organization_name'] ?? '');
2023 1998 $set('alternate_name', $data['alternate_name'] ?? '');
2024 1999 $set('logo_url', $data['organization_logo'] ?? '');
2025 2000
@@ -2061,23 +2036,8 @@
2061 2036 $set('breadcrumb_separator', $breadcrumbs['separator'] ?? '');
2062 2037 $set('breadcrumb_prefix', $breadcrumbs['prefix'] ?? '');
2063 2038 }
2064 2039
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 2040 // Local SEO (extended.local_seo) — migrate the full NAP + geo when there
2081 2041 // is any meaningful business data (name, phone, address or coordinates),
2082 2042 // and enable the feature alongside it. Each field is written only while
2083 2043 // ThinkRank's Business Info still holds its default (non-destructive).
@@ -2224,10 +2184,9 @@
2224 2184 }
2225 2185
2226 2186 $app_id = trim((string) ($defaults['facebook_app_id'] ?? ''));
2227 2187 $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 === '') {
2188 + if ($app_id === '' && $og_image === '') {
2230 2189 return false;
2231 2190 }
2232 2191
2233 2192 $manager = new \ThinkRank\SEO\Social_Meta_Manager();
@@ -2243,12 +2202,8 @@
2243 2202 // outside the allow-list never reaches the database.
2244 2203 if ($og_image !== '' && empty($current['default_og_image'])) {
2245 2204 $updates['default_og_image'] = $og_image;
2246 2205 }
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 2206
2252 2207 if (empty($updates)) {
2253 2208 return false;
2254 2209 }
@@ -2256,97 +2211,8 @@
2256 2211 return (bool) $manager->save_settings('site', null, $updates);
2257 2212 }
2258 2213
2259 2214 /**
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 2215 * Migrate the source plugin's Pinterest site-verification code into
2350 2216 * ThinkRank's core `pinterest_site_verification` setting. Never overwrites
2351 2217 * a configured code.
2352 2218 *
@@ -3193,39 +3059,11 @@
3193 3059 foreach ($extended as $key => $value) {
3194 3060 if (in_array($key, self::HANDLED_EXTENDED_SETTINGS, true) || empty($value)) {
3195 3061 continue;
3196 3062 }
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 3063 $buckets[] = [
3226 3064 'key' => 'settings.' . $key,
3227 - 'label' => self::extended_bucket_label((string) $key) ?? ucfirst(str_replace('_', ' ', (string) $key)),
3065 + 'label' => (string) $key,
3228 3066 'count' => 1,
3229 3067 ];
3230 3068 }
3231 3069 }
@@ -3230,209 +3068,8 @@
3230 3068 }
3231 3069 }
3232 3070
3233 3071 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 3072 }
3436 3073
3437 3074 /**
3438 3075 * Fold post IDs the source excluded from its sitemap into ThinkRank's