PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.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 1.1.0 1.10.0 All 48 releases
thinkrank / includes / abilities / content / class-update-post-seo.php

class-update-post-seo.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.7.0, at includes/abilities/content/class-update-post-seo.php

231 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Update post SEO ability.
4 *
5 * @package ThinkRank\Abilities\Content
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Content;
11
12 use ThinkRank\Abilities\Ability_Base;
13 use ThinkRank\Admin\Metabox_Manager;
14 use ThinkRank\SEO\Object_Redirect;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // Exit if accessed directly.
18 }
19
20 /**
21 * Updates ThinkRank SEO data for a post object.
22 */
23 class Update_Post_Seo extends Ability_Base {
24 /**
25 * Constructor.
26 */
27 public function __construct() {
28 $this->id = 'thinkrank/update-post-seo';
29 $this->label = __( 'Update ThinkRank Post SEO', 'thinkrank' );
30 $this->description = __( 'Update ThinkRank SEO metadata for a post, page, or custom post type item. Read the current values with get-post-seo first; only the fields you pass are changed, and get-post-seo-checks reports what would improve.', 'thinkrank' );
31 }
32
33 /**
34 * {@inheritDoc}
35 *
36 * @return array<string, mixed>
37 */
38 public function get_input_schema() {
39 return [
40 'type' => 'object',
41 'additionalProperties' => false,
42 'properties' => [
43 'post_id' => [
44 'type' => 'integer',
45 'description' => __( 'The target post ID.', 'thinkrank' ),
46 ],
47 'settings' => [
48 'type' => 'object',
49 'additionalProperties' => true,
50 'description' => __( 'ThinkRank post SEO fields to update.', 'thinkrank' ),
51 'properties' => [
52 'title' => [ 'type' => 'string' ],
53 'description' => [ 'type' => 'string' ],
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 ],
64 'focus_keyword' => [ 'type' => 'string' ],
65 'focus_keywords' => [
66 'type' => 'array',
67 'items' => [ 'type' => 'string' ],
68 ],
69 'robots_meta_enabled' => [ 'type' => 'boolean' ],
70 'robots_meta' => [ 'type' => 'object' ],
71 'advanced_robots_meta' => [ 'type' => 'object' ],
72 'og_title' => [ 'type' => 'string' ],
73 'og_description' => [ 'type' => 'string' ],
74 'og_image' => [ 'type' => 'string' ],
75 'twitter_title' => [ 'type' => 'string' ],
76 'twitter_description' => [ 'type' => 'string' ],
77 'twitter_image' => [ 'type' => 'string' ],
78 ],
79 ],
80 ],
81 'required' => [ 'post_id', 'settings' ],
82 ];
83 }
84
85 /**
86 * {@inheritDoc}
87 *
88 * @return array<string, mixed>
89 */
90 public function get_output_schema() {
91 return [
92 'type' => 'object',
93 'properties' => [
94 'success' => [ 'type' => 'boolean' ],
95 'message' => [ 'type' => 'string' ],
96 'post_id' => [ 'type' => 'integer' ],
97 ],
98 ];
99 }
100
101 /**
102 * Execute ability.
103 *
104 * @param array<string, mixed> $input Ability input payload.
105 * @return array<string, mixed>|\WP_Error
106 */
107 public function execute( $input ) {
108 $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0;
109 $settings = isset( $input['settings'] ) && is_array( $input['settings'] ) ? $input['settings'] : [];
110
111 if ( ! $post_id || empty( $settings ) ) {
112 return new \WP_Error(
113 'thinkrank_invalid_post_update',
114 __( 'A valid post ID and settings payload are required.', 'thinkrank' ),
115 [ 'status' => 400 ]
116 );
117 }
118
119 if ( ! get_post( $post_id ) instanceof \WP_Post ) {
120 return new \WP_Error(
121 'thinkrank_post_not_found',
122 __( 'No post was found for the provided ID.', 'thinkrank' ),
123 [ 'status' => 404 ]
124 );
125 }
126
127 if ( ! current_user_can( 'edit_post', $post_id ) ) {
128 return new \WP_Error(
129 'thinkrank_cannot_edit_post',
130 __( 'You are not allowed to edit this post.', 'thinkrank' ),
131 [ 'status' => 403 ]
132 );
133 }
134
135 $fields = $this->map_fields( $settings, $post_id );
136
137 if ( empty( $fields ) ) {
138 return new \WP_Error(
139 'thinkrank_no_valid_post_meta_keys',
140 __( 'No valid ThinkRank post SEO keys were provided.', 'thinkrank' ),
141 [ 'status' => 400 ]
142 );
143 }
144
145 $manager = new Metabox_Manager();
146 $manager->save_seo_fields( $post_id, $fields );
147
148 // Everything else is stored unconditionally; the redirect can be
149 // refused (no Pro, plain permalinks, a destination that is this post's
150 // own URL). Reporting success for a redirect that was not written would
151 // leave the caller believing the site now redirects when it does not.
152 $redirect_error = $manager->get_last_redirect_error();
153 if ( null !== $redirect_error ) {
154 return new \WP_Error(
155 $redirect_error->get_error_code(),
156 $redirect_error->get_error_message(),
157 [ 'status' => 400 ]
158 );
159 }
160
161 return [
162 'success' => true,
163 'message' => __( 'Post SEO metadata updated.', 'thinkrank' ),
164 'post_id' => $post_id,
165 ];
166 }
167
168 /**
169 * Map normalized input keys to metabox form field names.
170 *
171 * @param array<string, mixed> $settings Normalized settings payload.
172 * @param int $post_id Target post ID.
173 * @return array<string, mixed>
174 */
175 private function map_fields( array $settings, $post_id ) {
176 $fields = [];
177
178 $simple = [
179 'title' => 'thinkrank_seo_title',
180 'description' => 'thinkrank_meta_description',
181 'canonical_url' => 'thinkrank_canonical_url',
182 'redirect_url' => 'thinkrank_redirect_url',
183 'focus_keyword' => 'thinkrank_focus_keyword',
184 'og_title' => 'thinkrank_og_title',
185 'og_description' => 'thinkrank_og_description',
186 'og_image' => 'thinkrank_og_image',
187 'twitter_title' => 'thinkrank_twitter_title',
188 'twitter_description' => 'thinkrank_twitter_description',
189 'twitter_image' => 'thinkrank_twitter_image',
190 ];
191
192 foreach ( $simple as $key => $field ) {
193 if ( array_key_exists( $key, $settings ) ) {
194 $fields[ $field ] = (string) $settings[ $key ];
195 }
196 }
197
198 // Only meaningful alongside a destination: sending a code on its own
199 // would be read by save_object_redirect() as "no redirect submitted"
200 // and dropped, so requiring the pair keeps the payload honest.
201 if ( array_key_exists( 'redirect_type', $settings ) && array_key_exists( 'redirect_url', $settings ) ) {
202 $fields['thinkrank_redirect_type'] = Object_Redirect::normalize_type( $settings['redirect_type'] );
203 }
204
205 if ( array_key_exists( 'focus_keywords', $settings ) && is_array( $settings['focus_keywords'] ) ) {
206 $fields['thinkrank_focus_keywords'] = (string) wp_json_encode( array_values( $settings['focus_keywords'] ) );
207 }
208
209 $has_robots = array_key_exists( 'robots_meta', $settings ) || array_key_exists( 'advanced_robots_meta', $settings );
210
211 if ( array_key_exists( 'robots_meta_enabled', $settings ) ) {
212 $fields['thinkrank_robots_meta_enabled'] = (int) (bool) $settings['robots_meta_enabled'];
213 } elseif ( $has_robots ) {
214 // The metabox only persists robots blobs when the toggle key is
215 // present; default to the currently stored value (or 1).
216 $existing = get_post_meta( $post_id, '_thinkrank_robots_meta_enabled', true );
217 $fields['thinkrank_robots_meta_enabled'] = '' === $existing ? 1 : (int) (bool) $existing;
218 }
219
220 if ( array_key_exists( 'robots_meta', $settings ) ) {
221 $fields['thinkrank_robots_meta'] = (string) wp_json_encode( (array) $settings['robots_meta'] );
222 }
223
224 if ( array_key_exists( 'advanced_robots_meta', $settings ) ) {
225 $fields['thinkrank_advanced_robots_meta'] = (string) wp_json_encode( (array) $settings['advanced_robots_meta'] );
226 }
227
228 return $fields;
229 }
230 }
231