PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.10.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.10.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 / seo / class-image-seo-manager.php

class-image-seo-manager.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.10.0, at includes/seo/class-image-seo-manager.php

256 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Image SEO Manager Class
5 *
6 * Manages image-specific SEO settings including automatic
7 * ALT and TITLE attribute management.
8 *
9 * @package ThinkRank
10 * @subpackage SEO
11 * @since 1.0.0
12 */
13
14 declare(strict_types=1);
15
16 namespace ThinkRank\SEO;
17
18 /**
19 * Image SEO Manager Class
20 *
21 * Handles image attribute optimization and settings management.
22 *
23 * @since 1.0.0
24 */
25 class Image_SEO_Manager extends Abstract_SEO_Manager {
26
27 /**
28 * Constructor
29 *
30 * @since 1.0.0
31 */
32 public function __construct() {
33 parent::__construct('image_seo');
34 }
35
36 /**
37 * Validate SEO settings (implements interface)
38 *
39 * @since 1.0.0
40 *
41 * @param array $settings Settings array to validate
42 * @return array Validation results
43 */
44 public function validate_settings(array $settings): array {
45 $validation = [
46 'valid' => true,
47 'errors' => [],
48 'warnings' => [],
49 'suggestions' => [],
50 'score' => 100
51 ];
52
53 $boolean_fields = ['add_missing_alt', 'add_missing_title'];
54 foreach ($boolean_fields as $field) {
55 if (isset($settings[$field]) && !is_bool($settings[$field])) {
56 $validation['errors'][] = sprintf('%s must be a boolean value', $field);
57 $validation['valid'] = false;
58 }
59 }
60
61 $string_fields = ['alt_format', 'title_format'];
62 foreach ($string_fields as $field) {
63 if (isset($settings[$field]) && !is_string($settings[$field])) {
64 $validation['errors'][] = sprintf('%s must be a string', $field);
65 $validation['valid'] = false;
66 }
67 }
68
69 return $validation;
70 }
71
72 /**
73 * Get output data for frontend rendering (implements interface)
74 *
75 * @since 1.0.0
76 *
77 * @param string $context_type The context type
78 * @param int|null $context_id Optional. Context ID
79 * @return array Output data ready for frontend rendering
80 */
81 public function get_output_data(string $context_type, ?int $context_id): array {
82 return $this->get_settings($context_type, $context_id);
83 }
84
85 /**
86 * Get default settings for a context type (implements interface)
87 *
88 * @since 1.0.0
89 *
90 * @param string $context_type The context type to get defaults for
91 * @return array Default settings array
92 */
93 public function get_default_settings(string $context_type): array {
94 return [
95 'add_missing_alt' => false,
96 'alt_format' => '%filename%',
97 'add_missing_title' => false,
98 'title_format' => '%title% %separator% %sitename%',
99 ];
100 }
101
102 /**
103 * Get settings schema definition (implements interface)
104 *
105 * @since 1.0.0
106 *
107 * @param string $context_type The context type to get schema for
108 * @return array Settings schema definition
109 */
110 public function get_settings_schema(string $context_type): array {
111 return [
112 'add_missing_alt' => [
113 'type' => 'boolean',
114 'title' => __('Add Missing Alt Attributes', 'thinkrank'),
115 'description' => __('Automatically add ALT attributes to images if they are missing.', 'thinkrank'),
116 'default' => false
117 ],
118 'alt_format' => [
119 'type' => 'string',
120 'title' => __('Alt attribute format', 'thinkrank'),
121 'description' => __('The format to use for automatically generated ALT attributes.', 'thinkrank'),
122 'default' => '%filename%'
123 ],
124 'add_missing_title' => [
125 'type' => 'boolean',
126 'title' => __('Add Missing Title Attributes', 'thinkrank'),
127 'description' => __('Automatically add TITLE attributes to images if they are missing.', 'thinkrank'),
128 'default' => false
129 ],
130 'title_format' => [
131 'type' => 'string',
132 'title' => __('Title attribute format', 'thinkrank'),
133 'description' => __('The format to use for automatically generated TITLE attributes.', 'thinkrank'),
134 'default' => '%title% %separator% %sitename%'
135 ]
136 ];
137 }
138
139 /**
140 * Process content and inject missing image attributes
141 *
142 * @since 1.0.0
143 * @param string $content The content to process
144 * @return string Processed content
145 */
146 public function process_content(string $content, $post_id = null): string {
147 $settings = $this->get_settings('site');
148
149 if (!$settings['add_missing_alt'] && !$settings['add_missing_title']) {
150 return $content;
151 }
152
153 static $count = 0;
154 $post_id ??= get_the_ID();
155 $id_to_pass = is_int($post_id) ? $post_id : 0;
156
157 // Use regex for high performance, but careful with HTML structure
158 return preg_replace_callback('/<img([^>]+)>/i', function ($matches) use ($settings, &$count, $id_to_pass) {
159 $count++;
160 $img_tag = $matches[0];
161 $attributes_str = $matches[1];
162
163 // Parse attributes
164 preg_match_all('/([a-z-]+)=["\']([^"\']*)["\']/i', $attributes_str, $attr_matches);
165 $attributes = array_combine($attr_matches[1], $attr_matches[2]);
166
167 $src = $attributes['src'] ?? '';
168 $attachment_id = $src ? attachment_url_to_postid($src) : 0;
169
170 // Handle ALT attribute
171 if ($settings['add_missing_alt'] && (empty($attributes['alt']) || trim($attributes['alt']) === '')) {
172 $alt_val = $this->generate_attribute_value($settings['alt_format'] ?? '', $attachment_id, $id_to_pass, $count, $src);
173 if ($alt_val) {
174 if (isset($attributes['alt'])) {
175 $img_tag = preg_replace('/alt=["\'][^"\']*["\']/i', 'alt="' . esc_attr($alt_val) . '"', $img_tag);
176 } else {
177 $img_tag = str_replace('<img', '<img alt="' . esc_attr($alt_val) . '"', $img_tag);
178 }
179 }
180 }
181
182 // Handle TITLE attribute
183 if ($settings['add_missing_title'] && (empty($attributes['title']) || trim($attributes['title']) === '')) {
184 $title_val = $this->generate_attribute_value($settings['title_format'] ?? '', $attachment_id, $id_to_pass, $count, $src);
185 if ($title_val) {
186 if (isset($attributes['title'])) {
187 $img_tag = preg_replace('/title=["\'][^"\']*["\']/i', 'title="' . esc_attr($title_val) . '"', $img_tag);
188 } else {
189 $img_tag = str_replace('<img', '<img title="' . esc_attr($title_val) . '"', $img_tag);
190 }
191 }
192 }
193
194 return $img_tag;
195 }, $content);
196 }
197
198 /**
199 * Generate attribute value based on format and context
200 *
201 * @since 1.0.0
202 * @param string $format The format string
203 * @param int $attachment_id Attachment ID
204 * @param int $post_id Current Post ID
205 * @param int $count Image counter
206 * @param string $src Image source URL
207 * @return string Generated value
208 */
209 private function generate_attribute_value(string $format, int $attachment_id, int $post_id, int $count, string $src): string {
210 $replacements = [
211 '%site_title%' => get_bloginfo('name'),
212 '%sitename%' => get_bloginfo('name'),
213 '%title%' => $post_id > 0 ? get_the_title($post_id) : get_bloginfo('name'),
214 '%count%' => (string) $count,
215 '%separator%' => $this->get_separator(),
216 '%sep%' => $this->get_separator(),
217 '%filename%' => '',
218 '%image_title%' => '',
219 '%image_caption%' => '',
220 ];
221
222 // Get filename from src
223 if ($src) {
224 $filename = pathinfo($src, PATHINFO_FILENAME);
225 $replacements['%filename%'] = str_replace(['-', '_'], ' ', $filename);
226 }
227
228 // Get attachment data if ID exists
229 if ($attachment_id) {
230 $attachment = get_post($attachment_id);
231 if ($attachment) {
232 $replacements['%image_title%'] = $attachment->post_title;
233 $replacements['%image_caption%'] = $attachment->post_excerpt;
234 }
235 }
236
237 // Apply replacements
238 $value = str_replace(array_keys($replacements), array_values($replacements), $format);
239
240 // Clean up double spaces if any
241 $value = preg_replace('/\s+/', ' ', $value);
242
243 return trim($value);
244 }
245
246 /**
247 * Get site separator
248 *
249 * @since 1.0.0
250 * @return string
251 */
252 private function get_separator(): string {
253 return Site_Identity_Manager::get_active_separator_symbol();
254 }
255 }
256