PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.25.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.25.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 / analysis / class-get-seo-score.php

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

149 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 SEO score ability.
4 *
5 * @package ThinkRank\Abilities\Analysis
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Analysis;
11
12 use ThinkRank\Abilities\Ability_Base;
13 use ThinkRank\AI\SEOScoreCalculator;
14 use ThinkRank\Core\Database;
15 use ThinkRank\SEO\Focus_Keywords;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Calculates the ThinkRank SEO score for a single post.
23 */
24 class Get_Seo_Score extends Ability_Base {
25 /**
26 * Constructor.
27 */
28 public function __construct() {
29 $this->id = 'thinkrank/get-seo-score';
30 $this->label = __( 'Get ThinkRank SEO Score', 'thinkrank' );
31 $this->description = __( 'Calculate the ThinkRank SEO score for a single post, analyzing its content against its configured title and meta description.', 'thinkrank' );
32 }
33
34 /**
35 * {@inheritDoc}
36 *
37 * @return array<string, bool|float|string>
38 */
39 public function get_annotations() {
40 return [
41 // Not strictly read-only: with save_score the result is persisted.
42 'readonly' => false,
43 'destructive' => false,
44 'idempotent' => true,
45 'priority' => 1.0,
46 'openWorldHint' => false,
47 ];
48 }
49
50 /**
51 * {@inheritDoc}
52 *
53 * @return array<string, mixed>
54 */
55 public function get_input_schema() {
56 return [
57 'type' => 'object',
58 'additionalProperties' => false,
59 'properties' => [
60 'post_id' => [
61 'type' => 'integer',
62 'description' => __( 'The ID of the post to score.', 'thinkrank' ),
63 ],
64 'save_score' => [
65 'type' => 'boolean',
66 'description' => __( 'Persist the calculated score so it appears in the post list SEO column and score history. Defaults to false, which only reports the score.', 'thinkrank' ),
67 ],
68 ],
69 'required' => [ 'post_id' ],
70 ];
71 }
72
73 /**
74 * {@inheritDoc}
75 *
76 * @return array<string, mixed>
77 */
78 public function get_output_schema() {
79 return [
80 'type' => 'object',
81 'properties' => [
82 'post_id' => [ 'type' => 'integer' ],
83 'score' => [
84 'type' => 'object',
85 'additionalProperties' => true,
86 ],
87 'saved' => [ 'type' => 'boolean' ],
88 'score_id' => [ 'type' => [ 'integer', 'null' ] ],
89 ],
90 ];
91 }
92
93 /**
94 * Execute ability.
95 *
96 * @param array<string, mixed> $input Ability input payload.
97 * @return array<string, mixed>|\WP_Error
98 */
99 public function execute( $input ) {
100 $post_id = isset( $input['post_id'] ) ? (int) $input['post_id'] : 0;
101
102 if ( $post_id <= 0 || ! get_post( $post_id ) ) {
103 return new \WP_Error(
104 'thinkrank_invalid_post_id',
105 __( 'A valid post ID is required.', 'thinkrank' ),
106 [ 'status' => 400 ]
107 );
108 }
109
110 $calc = new SEOScoreCalculator( new Database() );
111 $content = $calc->analyze_post_content( $post_id );
112
113 if ( empty( $content ) ) {
114 return new \WP_Error(
115 'thinkrank_post_content_unavailable',
116 __( 'Unable to analyze the content for this post.', 'thinkrank' ),
117 [ 'status' => 404 ]
118 );
119 }
120
121 // Carry the stored focus keywords so keyword-dependent factors are
122 // actually measured instead of scoring as "no focus keyword set".
123 $metadata = [
124 'title' => get_post_meta( $post_id, '_thinkrank_seo_title', true ),
125 'description' => get_post_meta( $post_id, '_thinkrank_meta_description', true ),
126 'focus_keywords' => Focus_Keywords::get( $post_id ),
127 ];
128
129 $score = $calc->calculate_score( $content, $metadata );
130
131 // Without this the score was calculated and thrown away, so the post
132 // list kept reporting "Not Analyzed" until a separate saved run
133 // happened. Opt-in so a plain read stays a plain read.
134 $saved = false;
135 $score_id = null;
136 if ( ! empty( $input['save_score'] ) ) {
137 $score_id = $calc->save_score( $post_id, get_current_user_id(), $score );
138 $saved = false !== $score_id;
139 }
140
141 return [
142 'post_id' => $post_id,
143 'score' => $score,
144 'saved' => $saved,
145 'score_id' => $saved ? (int) $score_id : null,
146 ];
147 }
148 }
149