PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.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 1.0.0 1.0.1 1.0.2 All 50 releases
← All changes | includes/seo/class-abstract-seo-manager.php +86 -10 2.2.0 → 2.9.0 View file →
@@ -104,8 +104,15 @@
104 104
105 105 /**
106 106 * Get SEO settings for a specific context
107 107 *
108 + * The returned array always carries every key the context defines: the
109 + * saved rows are merged OVER the context defaults, so callers can read a
110 + * key without checking whether it exists. That also means the result never
111 + * distinguishes "the user saved this" from "this is the built-in default" —
112 + * when a caller needs that distinction (an audit asking whether anything
113 + * was configured at all), use get_stored_settings() instead.
114 + *
108 115 * @since 1.0.0
109 116 *
110 117 * @param string $context_type The context type
111 118 * @param int|null $context_id Optional. Context ID
@@ -117,14 +124,65 @@
117 124 if (!in_array($context_type, $this->get_supported_contexts(), true)) {
118 125 return $this->get_default_settings($context_type);
119 126 }
120 127
128 + // Serve from the object cache when available. This runs on every front-end
129 + // request (the_content, thumbnails), so avoiding a DB hit per request matters.
130 + $cache_key = $this->get_cache_key($context_type, $context_id);
131 + $cached = wp_cache_get($cache_key, 'thinkrank_seo');
132 + if (is_array($cached)) {
133 + return $cached;
134 + }
135 +
136 + // Merge with defaults to ensure all required keys exist
137 + $merged = array_merge(
138 + $this->get_default_settings($context_type),
139 + $this->get_stored_settings($context_type, $context_id)
140 + );
141 +
142 + // Cache the resolved settings; invalidated on every save via clear_cache().
143 + wp_cache_set($cache_key, $merged, 'thinkrank_seo');
144 +
145 + return $merged;
146 + }
147 +
148 + /**
149 + * Get ONLY the settings actually saved for a context — no defaults merged.
150 + *
151 + * get_settings() layers the context defaults under the stored rows, which
152 + * makes "has the user configured anything?" unanswerable through it: a site
153 + * with zero saved rows still gets back a fully populated array. Any audit
154 + * or first-run check that must tell a configured site from an untouched one
155 + * has to read the storage layer directly, which is what this exposes.
156 + *
157 + * Returns an empty array when nothing has been saved for the context, or
158 + * when the context type is not supported by this manager.
159 + *
160 + * Note this is the storage layer, not the settings a manager reports: a
161 + * subclass that overrides get_settings() to layer in another source —
162 + * Schema_Management_System fills its logo and organization fields from Site
163 + * Identity, Social_Meta_Manager has its own override — contributes nothing
164 + * here. Read it to ask what the site saved, never to read a value out.
165 + *
166 + * @since 2.3.1
167 + *
168 + * @param string $context_type The context type
169 + * @param int|null $context_id Optional. Context ID
170 + * @return array Saved settings, keyed by setting key. Empty when nothing is stored.
171 + */
172 + public function get_stored_settings(string $context_type, ?int $context_id = null): array {
173 + $context_type = sanitize_key($context_type);
174 +
175 + if (!in_array($context_type, $this->get_supported_contexts(), true)) {
176 + return [];
177 + }
178 +
121 179 // Convert NULL context_id to 0 for site-wide settings to match save behavior
122 180 $db_context_id = $context_id === null ? 0 : $context_id;
123 181
124 - // Serve from the object cache when available. This runs on every front-end
125 - // request (the_content, thumbnails), so avoiding a DB hit per request matters.
126 - $cache_key = $this->get_cache_key($context_type, $context_id);
182 + // Cached under its own key so the merged and unmerged views can never be
183 + // served for one another. clear_cache() drops both on every save.
184 + $cache_key = $this->get_stored_cache_key($context_type, $context_id);
127 185 $cached = wp_cache_get($cache_key, 'thinkrank_seo');
128 186 if (is_array($cached)) {
129 187 return $cached;
130 188 }
@@ -151,9 +209,9 @@
151 209 ARRAY_A
152 210 );
153 211
154 212 $settings = [];
155 - foreach ($results as $row) {
213 + foreach ((array) $results as $row) {
156 214 $value = maybe_unserialize($row['setting_value']);
157 215
158 216 // Ensure proper data type conversion for boolean fields
159 217 if (in_array($row['setting_key'], $this->boolean_setting_keys(), true)) {
@@ -172,15 +230,11 @@
172 230
173 231 $settings[$row['setting_key']] = $value;
174 232 }
175 233
176 - // Merge with defaults to ensure all required keys exist
177 - $merged = array_merge($this->get_default_settings($context_type), $settings);
234 + wp_cache_set($cache_key, $settings, 'thinkrank_seo');
178 235
179 - // Cache the resolved settings; invalidated on every save via clear_cache().
180 - wp_cache_set($cache_key, $merged, 'thinkrank_seo');
181 -
182 - return $merged;
236 + return $settings;
183 237 }
184 238
185 239 /**
186 240 * Save SEO settings for a specific context
@@ -698,8 +752,12 @@
698 752 protected function clear_cache(string $context_type, ?int $context_id): void {
699 753 $cache_key = $this->get_cache_key($context_type, $context_id);
700 754 wp_cache_delete($cache_key, 'thinkrank_seo');
701 755
756 + // The defaults-free view is cached separately, so a save has to drop it
757 + // too or get_stored_settings() keeps answering with the pre-save rows.
758 + wp_cache_delete($this->get_stored_cache_key($context_type, $context_id), 'thinkrank_seo');
759 +
702 760 // Clear related transients
703 761 delete_transient("thinkrank_seo_{$this->manager_type}_{$context_type}_{$context_id}");
704 762 }
705 763
@@ -750,8 +808,26 @@
750 808 // (which pass 0) resolve to the SAME cache entry — otherwise a save would
751 809 // never invalidate the value a front-end read cached.
752 810 $db_context_id = $context_id === null ? 0 : $context_id;
753 811 return "seo_settings_{$this->manager_type}_{$context_type}_{$db_context_id}";
812 + }
813 +
814 + /**
815 + * Cache key for the defaults-free view of a context.
816 + *
817 + * Deliberately distinct from get_cache_key(): the two views hold different
818 + * data (one merged with defaults, one only what was saved), so sharing an
819 + * entry would let whichever ran first answer for the other.
820 + *
821 + * @since 2.3.1
822 + *
823 + * @param string $context_type The context type
824 + * @param int|null $context_id Optional. Context ID
825 + * @return string
826 + */
827 + protected function get_stored_cache_key(string $context_type, ?int $context_id): string {
828 + $db_context_id = $context_id === null ? 0 : $context_id;
829 + return "seo_stored_settings_{$this->manager_type}_{$context_type}_{$db_context_id}";
754 830 }
755 831
756 832 /**
757 833 * Ensure settings table exists