PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.10.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 1.0.1 All 51 releases
thinkrank / includes / abilities / content / class-get-thin-content.php

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

196 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Get thin content 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\SEO\Thin_Content;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Reports published pages whose body is shorter than the threshold for their
21 * post type (#565).
22 *
23 * The count is the length a visitor reads, resolved through the page builder
24 * where one is in use, so an Elementor or Bricks page reports its real length
25 * rather than the empty `post_content` column an agent would otherwise see.
26 */
27 class Get_Thin_Content extends Ability_Base {
28 /**
29 * Constructor.
30 */
31 public function __construct() {
32 $this->id = 'thinkrank/get-thin-content';
33 $this->label = __( 'Get ThinkRank Thin Content', 'thinkrank' );
34 $this->description = __( 'Find published pages with too little content, grouped by post type, thinnest first. The length is what a visitor reads: page builder content (Elementor, Divi, Oxygen, Beaver, Bricks) is resolved, so a builder page is not reported as empty. Each post type has its own threshold, since a short product page is not a short blog post; change one with update-thin-content-settings. Covers published content only. Lengths are in words, or in characters for locales that count characters - read the unit field rather than assuming.', 'thinkrank' );
35 }
36
37 /**
38 * {@inheritDoc}
39 *
40 * @return array<string, bool|float|string>
41 */
42 public function get_annotations() {
43 return [
44 'readonly' => true,
45 'destructive' => false,
46 'idempotent' => true,
47 'priority' => 1.0,
48 'openWorldHint' => false,
49 ];
50 }
51
52 /**
53 * {@inheritDoc}
54 *
55 * @return array<string, mixed>
56 */
57 public function get_input_schema() {
58 return [
59 'type' => 'object',
60 'additionalProperties' => false,
61 'properties' => [
62 'post_type' => [
63 'type' => 'string',
64 'enum' => array_merge( [ '' ], Thin_Content::post_types() ),
65 'default' => '',
66 'description' => __( 'Only report this post type. Empty reports every post type that has published content.', 'thinkrank' ),
67 ],
68 'refresh' => [
69 'type' => 'boolean',
70 'default' => false,
71 'description' => __( 'Rebuild the report instead of reading the cached one. The cache is already discarded when content or a threshold changes, so this is only needed to force a rescan.', 'thinkrank' ),
72 ],
73 ],
74 ];
75 }
76
77 /**
78 * {@inheritDoc}
79 *
80 * @return array<string, mixed>
81 */
82 public function get_output_schema() {
83 return [
84 'type' => 'object',
85 'properties' => [
86 'pending' => [
87 'type' => 'integer',
88 'description' => __( 'Pages not counted yet. When above zero the numbers are a floor, not the answer; call again to continue. Counting resolves builder content, so it is done a batch at a time.', 'thinkrank' ),
89 ],
90 'counted' => [
91 'type' => 'integer',
92 'description' => __( 'Published pages the report covers.', 'thinkrank' ),
93 ],
94 'thin' => [
95 'type' => 'integer',
96 'description' => __( 'Pages under their post type threshold.', 'thinkrank' ),
97 ],
98 'unit' => [
99 'type' => 'string',
100 'description' => __( 'What the lengths are counted in: words, or characters on locales that count characters.', 'thinkrank' ),
101 ],
102 'types' => [
103 'type' => 'array',
104 'items' => [
105 'type' => 'object',
106 'properties' => [
107 'post_type' => [ 'type' => 'string' ],
108 'threshold' => [ 'type' => 'integer' ],
109 'counted' => [ 'type' => 'integer' ],
110 'thin' => [ 'type' => 'integer' ],
111 'pages' => [
112 'type' => 'array',
113 'items' => [
114 'type' => 'object',
115 'properties' => [
116 'post_id' => [ 'type' => 'integer' ],
117 'post_title' => [ 'type' => 'string' ],
118 'count' => [ 'type' => 'integer' ],
119 'permalink' => [ 'type' => 'string' ],
120 ],
121 ],
122 ],
123 ],
124 ],
125 ],
126 ],
127 ];
128 }
129
130 /**
131 * Execute ability.
132 *
133 * @param array<string, mixed> $input Ability input payload.
134 * @return array<string, mixed>|\WP_Error
135 */
136 public function execute( $input ) {
137 $post_type = isset( $input['post_type'] ) ? sanitize_key( (string) $input['post_type'] ) : '';
138 $refresh = ! empty( $input['refresh'] );
139
140 if ( '' !== $post_type && ! in_array( $post_type, Thin_Content::post_types(), true ) ) {
141 return new \WP_Error(
142 'thinkrank_invalid_post_type',
143 __( 'That post type is not one ThinkRank reports on. Use list-content-types for valid slugs.', 'thinkrank' ),
144 [ 'status' => 400 ]
145 );
146 }
147
148 $report = Thin_Content::report( $refresh );
149
150 $types = [];
151 foreach ( $report['types'] as $type ) {
152 if ( '' !== $post_type && $type['post_type'] !== $post_type ) {
153 continue;
154 }
155
156 $types[] = [
157 'post_type' => $type['post_type'],
158 'threshold' => $type['threshold'],
159 'counted' => $type['counted'],
160 'thin' => $type['thin'],
161 'pages' => array_map(
162 static function ( array $page ): array {
163 // The screen's edit URL and capability flag stay on the
164 // REST response; an agent acts through update-post-seo.
165 return [
166 'post_id' => $page['post_id'],
167 'post_title' => $page['post_title'],
168 'count' => $page['count'],
169 'permalink' => $page['permalink'],
170 ];
171 },
172 $type['posts']
173 ),
174 ];
175 }
176
177 // Totals describe what was asked for. Reporting the sitewide numbers
178 // beside a list filtered to one post type reads as "41 thin" above
179 // four named pages, which is a contradiction an agent cannot resolve.
180 $counted = $report['counted'];
181 $thin = $report['thin'];
182 if ( '' !== $post_type ) {
183 $counted = (int) array_sum( array_column( $types, 'counted' ) );
184 $thin = (int) array_sum( array_column( $types, 'thin' ) );
185 }
186
187 return [
188 'pending' => $report['pending'],
189 'counted' => $counted,
190 'thin' => $thin,
191 'unit' => $report['unit'],
192 'types' => $types,
193 ];
194 }
195 }
196