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 / media / class-list-images.php

class-list-images.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/media/class-list-images.php

185 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * List images ability.
4 *
5 * @package ThinkRank\Abilities\Media
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Media;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15
16 /**
17 * Lists Media Library images with their alt-text status.
18 *
19 * The entry point for the alt-text workflow: filter to `missing`, take the ids,
20 * and write alt text one at a time with update-image-alt-text. Read-only.
21 */
22 class List_Images extends Image_Alt_Ability_Base {
23
24 /**
25 * Constructor.
26 */
27 public function __construct() {
28 $this->id = 'thinkrank/list-images';
29 $this->label = __( 'List ThinkRank Images', 'thinkrank' );
30 $this->description = __( 'List Media Library images with their alt text and whether it is missing. Filter with alt_status "missing" to find the images that need work, then use update-image-alt-text on each id. Returns a total so you can page through with page and per_page.', 'thinkrank' );
31 }
32
33 /**
34 * {@inheritDoc}
35 *
36 * @return array<string, bool|float|string>
37 */
38 public function get_annotations() {
39 return [
40 'readonly' => true,
41 'destructive' => false,
42 'idempotent' => true,
43 'priority' => 1.0,
44 'openWorldHint' => false,
45 ];
46 }
47
48 /**
49 * {@inheritDoc}
50 *
51 * @return array<string, mixed>
52 */
53 public function get_input_schema() {
54 return [
55 'type' => 'object',
56 'additionalProperties' => false,
57 'properties' => [
58 'alt_status' => [
59 'type' => 'string',
60 'enum' => [ 'all', 'missing', 'present' ],
61 'description' => __( 'Which images to return. "missing" is the one to use when filling gaps. Default "all".', 'thinkrank' ),
62 ],
63 'search' => [
64 'type' => 'string',
65 'description' => __( 'Optional title or filename search.', 'thinkrank' ),
66 ],
67 'page' => [ 'type' => 'integer' ],
68 'per_page' => [
69 'type' => 'integer',
70 'description' => __( 'Results per page, 1-100. Default 20.', 'thinkrank' ),
71 ],
72 ],
73 ];
74 }
75
76 /**
77 * {@inheritDoc}
78 *
79 * @return array<string, mixed>
80 */
81 public function get_output_schema() {
82 return [
83 'type' => 'object',
84 'properties' => [
85 'images' => [
86 'type' => 'array',
87 'items' => [
88 'type' => 'object',
89 'additionalProperties' => true,
90 'properties' => $this->image_properties(),
91 ],
92 ],
93 'total' => [
94 'type' => 'integer',
95 'description' => __( 'Images matching the filter, across every page.', 'thinkrank' ),
96 ],
97 'page' => [ 'type' => 'integer' ],
98 'per_page' => [ 'type' => 'integer' ],
99 'alt_status' => [ 'type' => 'string' ],
100 ],
101 ];
102 }
103
104 /**
105 * Execute ability.
106 *
107 * @param array<string, mixed> $input Ability input payload.
108 * @return array<string, mixed>
109 */
110 public function execute( $input ) {
111 $input = (array) $input;
112 $alt_status = (string) ( $input['alt_status'] ?? 'all' );
113 $page = isset( $input['page'] ) ? max( 1, (int) $input['page'] ) : 1;
114 $per_page = isset( $input['per_page'] ) ? max( 1, min( 100, (int) $input['per_page'] ) ) : 20;
115
116 $args = [
117 'post_type' => 'attachment',
118 'post_mime_type' => 'image',
119 'post_status' => $this->attachment_statuses(),
120 'posts_per_page' => $per_page,
121 'paged' => $page,
122 'orderby' => 'date',
123 'order' => 'DESC',
124 'fields' => 'ids',
125 ];
126
127 if ( ! empty( $input['search'] ) ) {
128 $args['s'] = (string) $input['search'];
129 }
130
131 // An image with a whitespace-only alt value is missing alt text as far
132 // as a screen reader is concerned, but its meta row exists — so
133 // 'NOT EXISTS' alone would report it as present. The trim check in
134 // describe_image() is what the caller actually sees; this query only
135 // has to be a superset, and the filter below settles it.
136 if ( 'missing' === $alt_status ) {
137 $args['meta_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- one bounded page of attachments for an agent-driven listing.
138 'relation' => 'OR',
139 [
140 'key' => self::ALT_META_KEY,
141 'compare' => 'NOT EXISTS',
142 ],
143 [
144 'key' => self::ALT_META_KEY,
145 'value' => '',
146 'compare' => '=',
147 ],
148 ];
149 } elseif ( 'present' === $alt_status ) {
150 $args['meta_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- one bounded page of attachments for an agent-driven listing.
151 [
152 'key' => self::ALT_META_KEY,
153 'value' => '',
154 'compare' => '!=',
155 ],
156 ];
157 }
158
159 $query = new \WP_Query( $args );
160 $images = [];
161
162 foreach ( $query->posts as $attachment_id ) {
163 $image = $this->describe_image( (int) $attachment_id );
164
165 // Settle the whitespace case the meta query cannot express.
166 if ( 'missing' === $alt_status && $image['has_alt'] ) {
167 continue;
168 }
169 if ( 'present' === $alt_status && ! $image['has_alt'] ) {
170 continue;
171 }
172
173 $images[] = $image;
174 }
175
176 return [
177 'images' => $images,
178 'total' => (int) $query->found_posts,
179 'page' => $page,
180 'per_page' => $per_page,
181 'alt_status' => $alt_status,
182 ];
183 }
184 }
185