PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.5.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.5.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-get-post-seo.php

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

153 lines 3.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Get 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\AI\SEOScoreCalculator;
15 use ThinkRank\Core\Database;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Retrieves ThinkRank SEO data for a post object.
23 */
24 class Get_Post_Seo extends Ability_Base {
25 /**
26 * Constructor.
27 */
28 public function __construct() {
29 $this->id = 'thinkrank/get-post-seo';
30 $this->label = __( 'Get ThinkRank Post SEO', 'thinkrank' );
31 $this->description = __( 'Retrieve ThinkRank SEO metadata for a post, page, or custom post type item. Use update-post-seo to change them, and get-post-seo-checks to see what would improve.', 'thinkrank' );
32 }
33
34 /**
35 * {@inheritDoc}
36 *
37 * @return array<string, bool|float|string>
38 */
39 public function get_annotations() {
40 return [
41 'readonly' => true,
42 'destructive' => false,
43 'idempotent' => true,
44 'priority' => 1.0,
45 'openWorldHint' => false,
46 ];
47 }
48
49 /**
50 * {@inheritDoc}
51 *
52 * @return array<string, mixed>
53 */
54 public function get_input_schema() {
55 return [
56 'type' => 'object',
57 'additionalProperties' => false,
58 'properties' => [
59 'post_id' => [
60 'type' => 'integer',
61 'description' => __( 'The target post ID.', 'thinkrank' ),
62 ],
63 ],
64 'required' => [ 'post_id' ],
65 ];
66 }
67
68 /**
69 * {@inheritDoc}
70 *
71 * @return array<string, mixed>
72 */
73 public function get_output_schema() {
74 return [
75 'type' => 'object',
76 'properties' => [
77 'post_id' => [ 'type' => 'integer' ],
78 'post_type' => [ 'type' => 'string' ],
79 'data' => [ 'type' => 'object' ],
80 ],
81 ];
82 }
83
84 /**
85 * Execute ability.
86 *
87 * @param array<string, mixed> $input Ability input payload.
88 * @return array<string, mixed>|\WP_Error
89 */
90 public function execute( $input ) {
91 $post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0;
92
93 if ( ! $post_id ) {
94 return new \WP_Error(
95 'thinkrank_invalid_post_target',
96 __( 'A valid post ID is required.', 'thinkrank' ),
97 [ 'status' => 400 ]
98 );
99 }
100
101 $post = get_post( $post_id );
102 if ( ! $post instanceof \WP_Post ) {
103 return new \WP_Error(
104 'thinkrank_post_not_found',
105 __( 'The provided post ID does not exist.', 'thinkrank' ),
106 [ 'status' => 404 ]
107 );
108 }
109
110 $data = ( new Metabox_Manager() )->get_post_metadata( $post_id );
111
112 $data['robots_meta'] = $this->decode_json_field( $data['robots_meta'] ?? '' );
113 $data['advanced_robots_meta'] = $this->decode_json_field( $data['advanced_robots_meta'] ?? '' );
114
115 // Report the score from the scores table, which is where every scoring
116 // run persists. The `_thinkrank_seo_score` meta the metabox reads is
117 // written only by the AI analyze flow and reset on each save, so on its
118 // own it reports a misleading 0 for posts that have really been scored.
119 $latest = ( new SEOScoreCalculator( new Database() ) )->get_latest_score( $post_id );
120 if ( is_array( $latest ) && isset( $latest['overall_score'] ) ) {
121 $data['seo_score'] = (int) $latest['overall_score'];
122 $data['seo_grade'] = (string) ( $latest['grade'] ?? '' );
123 $data['generated_at'] = (string) ( $latest['calculated_at'] ?? $latest['created_at'] ?? '' );
124 }
125
126 return [
127 'post_id' => $post_id,
128 'post_type' => (string) $post->post_type,
129 'data' => $data,
130 ];
131 }
132
133 /**
134 * Decode a JSON-encoded meta field into an array.
135 *
136 * @param mixed $value Raw stored value.
137 * @return array<string, mixed>
138 */
139 private function decode_json_field( $value ) {
140 if ( is_array( $value ) ) {
141 return $value;
142 }
143
144 if ( ! is_string( $value ) || '' === $value ) {
145 return [];
146 }
147
148 $decoded = json_decode( $value, true );
149
150 return is_array( $decoded ) ? $decoded : [];
151 }
152 }
153