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.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 1.0.0 All 52 releases
← All changes | includes/abilities/content/class-update-post-seo.php +72 -2 2.2.0 → 2.11.0 View file →
@@ -10,8 +10,9 @@
10 10 namespace ThinkRank\Abilities\Content;
11 11
12 12 use ThinkRank\Abilities\Ability_Base;
13 13 use ThinkRank\Admin\Metabox_Manager;
14 +use ThinkRank\SEO\Object_Redirect;
14 15
15 16 if ( ! defined( 'ABSPATH' ) ) {
16 17 exit; // Exit if accessed directly.
17 18 }
@@ -50,8 +51,17 @@
50 51 'properties' => [
51 52 'title' => [ 'type' => 'string' ],
52 53 'description' => [ 'type' => 'string' ],
53 54 'canonical_url' => [ 'type' => 'string' ],
55 + 'redirect_url' => [
56 + 'type' => 'string',
57 + 'description' => __( 'Send visitors from this post to this URL. Empty string removes the redirect. Requires ThinkRank Pro.', 'thinkrank' ),
58 + ],
59 + 'redirect_type' => [
60 + 'type' => 'integer',
61 + 'enum' => Object_Redirect::TYPES,
62 + 'description' => __( 'Redirect status code. Defaults to 301.', 'thinkrank' ),
63 + ],
54 64 'focus_keyword' => [ 'type' => 'string' ],
55 65 'focus_keywords' => [
56 66 'type' => 'array',
57 67 'items' => [ 'type' => 'string' ],
@@ -58,8 +68,16 @@
58 68 ],
59 69 'robots_meta_enabled' => [ 'type' => 'boolean' ],
60 70 'robots_meta' => [ 'type' => 'object' ],
61 71 'advanced_robots_meta' => [ 'type' => 'object' ],
72 + 'exclude_from_search' => [
73 + 'type' => 'boolean',
74 + 'description' => __( 'Keep this post out of this site\'s own search results. Not noindex: search engines are unaffected, and the post keeps its own URL. Use robots_meta for search engines.', 'thinkrank' ),
75 + ],
76 + 'exclude_from_archives' => [
77 + 'type' => 'boolean',
78 + 'description' => __( 'Keep this post out of category, tag, author, date and blog listings on this site. Not noindex: search engines are unaffected, and the post keeps its own URL, its feed entry and its sitemap entry.', 'thinkrank' ),
79 + ],
62 80 'og_title' => [ 'type' => 'string' ],
63 81 'og_description' => [ 'type' => 'string' ],
64 82 'og_image' => [ 'type' => 'string' ],
65 83 'twitter_title' => [ 'type' => 'string' ],
@@ -131,10 +149,24 @@
131 149 [ 'status' => 400 ]
132 150 );
133 151 }
134 152
135 - ( new Metabox_Manager() )->save_seo_fields( $post_id, $fields );
153 + $manager = new Metabox_Manager();
154 + $manager->save_seo_fields( $post_id, $fields );
136 155
156 + // Everything else is stored unconditionally; the redirect can be
157 + // refused (no Pro, plain permalinks, a destination that is this post's
158 + // own URL). Reporting success for a redirect that was not written would
159 + // leave the caller believing the site now redirects when it does not.
160 + $redirect_error = $manager->get_last_redirect_error();
161 + if ( null !== $redirect_error ) {
162 + return new \WP_Error(
163 + $redirect_error->get_error_code(),
164 + $redirect_error->get_error_message(),
165 + [ 'status' => 400 ]
166 + );
167 + }
168 +
137 169 return [
138 170 'success' => true,
139 171 'message' => __( 'Post SEO metadata updated.', 'thinkrank' ),
140 172 'post_id' => $post_id,
@@ -154,8 +186,9 @@
154 186 $simple = [
155 187 'title' => 'thinkrank_seo_title',
156 188 'description' => 'thinkrank_meta_description',
157 189 'canonical_url' => 'thinkrank_canonical_url',
190 + 'redirect_url' => 'thinkrank_redirect_url',
158 191 'focus_keyword' => 'thinkrank_focus_keyword',
159 192 'og_title' => 'thinkrank_og_title',
160 193 'og_description' => 'thinkrank_og_description',
161 194 'og_image' => 'thinkrank_og_image',
@@ -169,8 +202,15 @@
169 202 $fields[ $field ] = (string) $settings[ $key ];
170 203 }
171 204 }
172 205
206 + // Only meaningful alongside a destination: sending a code on its own
207 + // would be read by save_object_redirect() as "no redirect submitted"
208 + // and dropped, so requiring the pair keeps the payload honest.
209 + if ( array_key_exists( 'redirect_type', $settings ) && array_key_exists( 'redirect_url', $settings ) ) {
210 + $fields['thinkrank_redirect_type'] = Object_Redirect::normalize_type( $settings['redirect_type'] );
211 + }
212 +
173 213 if ( array_key_exists( 'focus_keywords', $settings ) && is_array( $settings['focus_keywords'] ) ) {
174 214 $fields['thinkrank_focus_keywords'] = (string) wp_json_encode( array_values( $settings['focus_keywords'] ) );
175 215 }
176 216
@@ -176,9 +216,11 @@
176 216
177 217 $has_robots = array_key_exists( 'robots_meta', $settings ) || array_key_exists( 'advanced_robots_meta', $settings );
178 218
179 219 if ( array_key_exists( 'robots_meta_enabled', $settings ) ) {
180 - $fields['thinkrank_robots_meta_enabled'] = (int) (bool) $settings['robots_meta_enabled'];
220 + // Sanitized for the same reason as the visibility flags below: the
221 + // string "false" passes the schema and is truthy in PHP.
222 + $fields['thinkrank_robots_meta_enabled'] = (int) rest_sanitize_boolean( $settings['robots_meta_enabled'] );
181 223 } elseif ( $has_robots ) {
182 224 // The metabox only persists robots blobs when the toggle key is
183 225 // present; default to the currently stored value (or 1).
184 226 $existing = get_post_meta( $post_id, '_thinkrank_robots_meta_enabled', true );
@@ -190,8 +232,36 @@
190 232 }
191 233
192 234 if ( array_key_exists( 'advanced_robots_meta', $settings ) ) {
193 235 $fields['thinkrank_advanced_robots_meta'] = (string) wp_json_encode( (array) $settings['advanced_robots_meta'] );
236 + }
237 +
238 + // On-site visibility (#633). get-post-seo already reports both, because
239 + // it returns get_post_metadata() wholesale — so without these an agent
240 + // could read a field it had no way to change, which is the worst shape
241 + // a tool pair can have.
242 + //
243 + // The value goes through Metabox_Manager::save_visibility_meta(), which
244 + // stores 1 on a truthy submission and DELETES the meta on a falsy one,
245 + // so '' is how a flag is cleared. Passing the boolean straight through
246 + // would work too; the string keeps this in the same shape as every
247 + // other field in this map.
248 + //
249 + // rest_sanitize_boolean() rather than PHP truthiness: WP_Ability only
250 + // VALIDATES the input against the schema, it never sanitizes it, and
251 + // rest_is_boolean() accepts the strings 'true'/'false'/'1'/'0' for a
252 + // boolean property. So a caller that sends "false" — which the schema
253 + // accepts — reaches this line with a truthy string and would have the
254 + // flag SET, the opposite of what it asked for, reported as a success.
255 + foreach (
256 + [
257 + 'exclude_from_search' => 'thinkrank_exclude_from_search',
258 + 'exclude_from_archives' => 'thinkrank_exclude_from_archives',
259 + ] as $key => $field
260 + ) {
261 + if ( array_key_exists( $key, $settings ) ) {
262 + $fields[ $field ] = rest_sanitize_boolean( $settings[ $key ] ) ? '1' : '';
263 + }
194 264 }
195 265
196 266 return $fields;
197 267 }