PluginProbe
Auto Alt Text / trunk
Auto Alt Text vtrunk
3.0.3 2.8.2 1.3.1 1.3.2 2.0.0 2.1.0 2.1.1 2.2.0 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.7.0 2.8.0 2.8.1 All 28 releases
auto-alt-text / src / App / Admin / PluginOptions.php

PluginOptions.php in Auto Alt Text trunk, at src/App/Admin/PluginOptions.php

984 lines 53.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AATXT\App\Admin;
4
5 use AATXT\App\Logging\DBLogger;
6 use AATXT\App\AIProviders\Anthropic\AnthropicModelsRegistry;
7 use AATXT\App\AIProviders\Gemini\GeminiModelsRegistry;
8 use AATXT\App\AIProviders\OpenAI\OpenAIModelsRegistry;
9 use AATXT\App\AIProviders\Azure\AzureTranslator;
10 use AATXT\App\Configuration\AzureConfig;
11 use AATXT\App\Core\Container;
12 use AATXT\App\Exceptions\Azure\AzureTranslateInstanceException;
13 use AATXT\App\Infrastructure\Http\WordPressHttpClient;
14 use AATXT\App\Utilities\AssetsManager;
15 use AATXT\App\Utilities\Encryption;
16 use AATXT\Config\Constants;
17
18 class PluginOptions
19 {
20 private static ?self $instance = null;
21 private static AssetsManager $assetsManager;
22
23 private function __construct()
24 {
25 //
26 }
27
28 /**
29 * Manage the necessary hooks to implement plugin options and their pages
30 * @return void
31 */
32 public static function register(): void
33 {
34 if (is_null(self::$instance)) {
35 self::$instance = new self();
36 }
37
38 self::$assetsManager = AssetsManager::make();
39
40 add_action('admin_notices', [self::$instance, 'showEncryptionConstantsNotice']);
41
42 add_action('admin_enqueue_scripts', [self::$instance, 'enqueueAdminScripts'], 1);
43 add_action('admin_menu', [self::$instance, 'addOptionsPageToTheMenu']);
44 add_action('admin_init', [self::$instance, 'setupPluginOptions'], 10);
45 add_action('admin_init', [self::$instance, 'migrateEncryptionKeys'], 9);
46
47 // Encrypt API Keys on update
48 add_action('pre_update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION, [self::$instance, 'encryptDataOnUpdate'], 10, 3);
49 add_action('pre_update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE, [self::$instance, 'encryptDataOnUpdate'], 10, 3);
50 add_action('pre_update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI, [self::$instance, 'encryptDataOnUpdate'], 10, 3);
51 add_action('pre_update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC, [self::$instance, 'encryptDataOnUpdate'], 10, 3);
52 add_action('pre_update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI, [self::$instance, 'encryptDataOnUpdate'], 10, 3);
53
54 add_action('admin_notices', [self::$instance, 'encryptionErrorNotice']);
55 add_action('admin_notices', [self::$instance, 'anthropicModelUnavailableNotice']);
56 add_action('admin_notices', [self::$instance, 'openAiModelUnavailableNotice']);
57 add_action('admin_notices', [self::$instance, 'geminiModelUnavailableNotice']);
58
59 // Bust the cached Anthropic models list whenever the API key changes,
60 // so the next admin page load fetches the model list with the new credentials.
61 add_action('update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC, [self::$instance, 'flushAnthropicModelsCache']);
62 add_action('add_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC, [self::$instance, 'flushAnthropicModelsCache']);
63
64 // Same for the cached OpenAI models list.
65 add_action('update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI, [self::$instance, 'flushOpenAiModelsCache']);
66 add_action('add_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI, [self::$instance, 'flushOpenAiModelsCache']);
67
68 // Same for the cached Gemini models list.
69 add_action('update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI, [self::$instance, 'flushGeminiModelsCache']);
70 add_action('add_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI, [self::$instance, 'flushGeminiModelsCache']);
71 }
72
73 /**
74 * Resolve the Anthropic models registry from the DI container.
75 */
76 private static function anthropicModelsRegistry(): AnthropicModelsRegistry
77 {
78 return Container::make()->get(AnthropicModelsRegistry::class);
79 }
80
81 public function flushAnthropicModelsCache(): void
82 {
83 self::anthropicModelsRegistry()->flushCache();
84 }
85
86 /**
87 * Resolve the OpenAI models registry from the DI container.
88 */
89 private static function openAiModelsRegistry(): OpenAIModelsRegistry
90 {
91 return Container::make()->get(OpenAIModelsRegistry::class);
92 }
93
94 public function flushOpenAiModelsCache(): void
95 {
96 self::openAiModelsRegistry()->flushCache();
97 }
98
99 /**
100 * Resolve the Gemini models registry from the DI container.
101 */
102 private static function geminiModelsRegistry(): GeminiModelsRegistry
103 {
104 return Container::make()->get(GeminiModelsRegistry::class);
105 }
106
107 public function flushGeminiModelsCache(): void
108 {
109 self::geminiModelsRegistry()->flushCache();
110 }
111
112 /**
113 * Warn the admin if the OpenAI model currently saved in the options
114 * is no longer returned by the registry (typically because OpenAI
115 * has retired it). The user has to pick a replacement; in the meantime
116 * OpenAIVision falls back to the least expensive available model so
117 * generation keeps working.
118 */
119 public function openAiModelUnavailableNotice(): void
120 {
121 if (!current_user_can('manage_options')) {
122 return;
123 }
124
125 $savedModel = get_option(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI);
126 if (!is_string($savedModel) || $savedModel === '') {
127 return;
128 }
129
130 $registry = self::openAiModelsRegistry();
131 if (!$registry->hasApiKey()) {
132 return;
133 }
134
135 if ($registry->isAvailable($savedModel)) {
136 return;
137 }
138
139 $settingsUrl = esc_url(menu_page_url(Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG, false));
140
141 echo '<div class="notice notice-warning is-dismissible">';
142 echo '<p><strong>' . esc_html__('Auto Alt Text', 'auto-alt-text') . ':</strong> ';
143 printf(
144 /* translators: %s is the model id that is no longer available */
145 esc_html__('the OpenAI model "%s" you previously selected is no longer available. Please choose a new model in the plugin settings.', 'auto-alt-text'),
146 esc_html($savedModel)
147 );
148 echo ' <a href="' . $settingsUrl . '">' . esc_html__('Go to settings page', 'auto-alt-text') . '</a>.';
149 echo '</p></div>';
150 }
151
152 /**
153 * Warn the admin if the Anthropic model currently saved in the options
154 * is no longer returned by the registry (typically because Anthropic
155 * has retired it). The user has to pick a replacement; in the meantime
156 * AnthropicResponse falls back to the most recent available model so
157 * generation keeps working.
158 */
159 public function anthropicModelUnavailableNotice(): void
160 {
161 if (!current_user_can('manage_options')) {
162 return;
163 }
164
165 $savedModel = get_option(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC);
166 if (!is_string($savedModel) || $savedModel === '') {
167 return;
168 }
169
170 $registry = self::anthropicModelsRegistry();
171 if (!$registry->hasApiKey()) {
172 return;
173 }
174
175 if ($registry->isAvailable($savedModel)) {
176 return;
177 }
178
179 $settingsUrl = esc_url(menu_page_url(Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG, false));
180
181 echo '<div class="notice notice-warning is-dismissible">';
182 echo '<p><strong>' . esc_html__('Auto Alt Text', 'auto-alt-text') . ':</strong> ';
183 printf(
184 /* translators: %s is the model id that is no longer available */
185 esc_html__('the Anthropic model "%s" you previously selected is no longer available. Please choose a new model in the plugin settings.', 'auto-alt-text'),
186 esc_html($savedModel)
187 );
188 echo ' <a href="' . $settingsUrl . '">' . esc_html__('Go to settings page', 'auto-alt-text') . '</a>.';
189 echo '</p></div>';
190 }
191
192 /**
193 * Warn the admin if the Gemini model currently saved in the options
194 * is no longer returned by the registry (typically because Google
195 * has retired it). The user has to pick a replacement; in the meantime
196 * GeminiResponse falls back to the least expensive available model so
197 * generation keeps working.
198 */
199 public function geminiModelUnavailableNotice(): void
200 {
201 if (!current_user_can('manage_options')) {
202 return;
203 }
204
205 $savedModel = get_option(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI);
206 if (!is_string($savedModel) || $savedModel === '') {
207 return;
208 }
209
210 $registry = self::geminiModelsRegistry();
211 if (!$registry->hasApiKey()) {
212 return;
213 }
214
215 if ($registry->isAvailable($savedModel)) {
216 return;
217 }
218
219 $settingsUrl = esc_url(menu_page_url(Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG, false));
220
221 echo '<div class="notice notice-warning is-dismissible">';
222 echo '<p><strong>' . esc_html__('Auto Alt Text', 'auto-alt-text') . ':</strong> ';
223 printf(
224 /* translators: %s is the model id that is no longer available */
225 esc_html__('the Gemini model "%s" you previously selected is no longer available. Please choose a new model in the plugin settings.', 'auto-alt-text'),
226 esc_html($savedModel)
227 );
228 echo ' <a href="' . $settingsUrl . '">' . esc_html__('Go to settings page', 'auto-alt-text') . '</a>.';
229 echo '</p></div>';
230 }
231
232 /**
233 * Show an error notice if the encryption of the API Key fails
234 * @return void
235 */
236 public function encryptionErrorNotice(): void
237 {
238 $raw = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI);
239 $decrypted = Encryption::make()->decrypt((string) $raw);
240
241 if ($raw && $decrypted === '') {
242 $screen = get_current_screen();
243 $settingsScreenId = 'toplevel_page_' . Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG;
244 $showLink = ! ( $screen && $screen->id === $settingsScreenId );
245
246 $settingsUrl = esc_url( menu_page_url(Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG, false) );
247
248 echo '<div class="notice notice-error is-dismissible">';
249 echo '<p>';
250
251 echo '<strong>' . esc_html__('Auto Alt Text', 'auto-alt-text') . ':</strong> ';
252
253 echo esc_html__(
254 'There was a problem with the encryption of your API Key for alt text generation. Please re-enter the key and save.',
255 'auto-alt-text'
256 );
257
258 if ($showLink) {
259 echo ' <a href="' . $settingsUrl . '">'
260 . esc_html__('Go to settings page', 'auto-alt-text')
261 . '</a>.';
262 }
263 echo '</p>';
264 echo '</div>';
265 }
266 }
267
268 /**
269 * Print an admin notice containing the defines to be added
270 * to the wp-config.php.
271 */
272 public function showEncryptionConstantsNotice(): void
273 {
274 if ( ! is_admin() || ! current_user_can('manage_options') ) {
275 return;
276 }
277
278 $screen = function_exists('get_current_screen') ? get_current_screen() : null;
279 if ( ! $screen || $screen->id !== 'toplevel_page_' . Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG ) {
280 return;
281 }
282
283 if ( defined('AATXT_ENCRYPTION_KEY') && defined('AATXT_ENCRYPTION_SALT') ) {
284 return;
285 }
286
287 $suggestedKey = bin2hex(random_bytes(32));
288 $suggestedSalt = bin2hex(random_bytes(32));
289 ?>
290 <div class="notice notice-info is-dismissible">
291 <p>
292 <?php
293 printf(
294 /* translators: “Optional” label */
295 __( '%s: you can add two lines to your wp-config.php to make your API key more resilient if WordPress salts ever change.', 'auto-alt-text' ),
296 '<strong>' . esc_html__( 'Optional', 'auto-alt-text' ) . '</strong>'
297 );
298 ?>
299 </p>
300 <pre style="background:#f5f5f5; padding:10px; border-radius:4px;">
301 define('AATXT_ENCRYPTION_KEY', '<?php echo esc_html( $suggestedKey ); ?>');
302 define('AATXT_ENCRYPTION_SALT', '<?php echo esc_html( $suggestedSalt ); ?>');
303 </pre>
304 <p>
305 <?php esc_html_e(
306 'Just paste them before the line “/* That\'s all, stop editing! Happy publishing. */” in wp-config.php.',
307 'auto-alt-text'
308 ); ?>
309 </p>
310 <p>
311 <?php esc_html_e(
312 "If you don't have the ability to edit the wp-config.php, don't worry because the plugin will still work without this change. If your site's salting keys change in the future, you will simply need to resave your API Key in the options below.",
313 "auto-alt-text"
314 ); ?>
315 </p>
316 </div>
317 <?php
318 }
319
320 public function migrateEncryptionKeys(): void
321 {
322 if ( ! defined('AATXT_ENCRYPTION_KEY') || ! defined('AATXT_ENCRYPTION_SALT') ) {
323 return;
324 }
325
326 update_option(Constants::AATXT_LEGACY_ENCRYPTION_MIGRATION_DONE, '0');
327
328 if ( get_option(Constants::AATXT_LEGACY_ENCRYPTION_MIGRATION_DONE) ) {
329 return;
330 }
331
332 Encryption::make()->migrateLegacyApiKeys();
333
334 update_option(Constants::AATXT_LEGACY_ENCRYPTION_MIGRATION_DONE, '1');
335 }
336
337 /**
338 * Encrypt data
339 * @param ?string $newValue
340 * @return ?string
341 */
342 public function encryptDataOnUpdate(?string $newValue): ?string
343 {
344 if (!empty($newValue)) {
345 $newValue = (Encryption::make())->encrypt($newValue);
346 }
347 return $newValue;
348 }
349
350 /**
351 * @return void
352 */
353 public static function enqueueAdminScripts(): void
354 {
355 $screen = get_current_screen();
356 $isMainOptionsPage = $screen->id === 'toplevel_page_' . Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG;
357
358 if ($isMainOptionsPage || strpos($screen->id, Constants::AATXT_PLUGIN_SLUG . '_') !== false) {
359
360 $adminCss = self::$assetsManager->getAssetUrl('resources/js/admin.js', true);
361
362 wp_enqueue_style(Constants::AATXT_PLUGIN_ASSETS_HANDLE, $adminCss, [], false);
363
364 if ($isMainOptionsPage) {
365 $adminJs = self::$assetsManager->getAssetUrl('resources/js/admin.js', false);
366 wp_enqueue_script(Constants::AATXT_PLUGIN_ASSETS_HANDLE, $adminJs, [], false);
367 }
368
369 }
370 }
371
372 /**
373 * Create options pages
374 * @return void
375 */
376 public static function addOptionsPageToTheMenu(): void
377 {
378 add_menu_page('Auto Alt Text Options', 'Auto Alt Text', 'manage_options', Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG, [self::$instance, 'optionsMainPage'], null, 99);
379 add_submenu_page(Constants::AATXT_PLUGIN_OPTIONS_PAGE_SLUG, 'Error Log', 'Error log', 'manage_options', Constants::AATXT_PLUGIN_OPTION_LOG_PAGE_SLUG, [self::$instance, 'logOptionsPage']);
380 }
381
382 /**
383 * Implement the page showing error log
384 * @return void
385 */
386 public static function logOptionsPage(): void
387 {
388 ?>
389 <div class="wrap">
390 <h1><?php esc_html_e('Auto Alt Text Error Log', 'auto-alt-text') ?></h1>
391 <div class="aat-options plugin-description">
392 <p>
393 <?php esc_html_e("If the alt text of your images was not generated correctly, you can find the reason in this log.", 'auto-alt-text'); ?>
394 <br>
395 <?php esc_html_e("Each line corresponds to an error generated by a call to the Azure or OpenAI API after uploading an image.", 'auto-alt-text'); ?>
396 </p>
397 <?php
398 $container = Container::make();
399 $logger = $container->get(DBLogger::class);
400 $logs = $logger->getImageLog();
401 if ($logs) {
402 echo '<textarea id="error-log" name="error-log" readonly>' . esc_textarea($logs) . '</textarea>';
403 } else {
404 echo '<p>' . esc_html__('There is no error log yet!', 'auto-alt-text') . '</p>';
405 }
406
407 ?>
408 </div>
409 </div>
410 <?php
411 }
412
413 /**
414 * Implement the main option page
415 * @return void
416 */
417 public static function optionsMainPage(): void
418 {
419 ?>
420 <div class="wrap">
421 <h1><?php esc_html_e('Auto Alt Text Options', 'auto-alt-text') ?></h1>
422
423 <div class="aat-options plugin-description">
424 <p>
425 <?php esc_html_e("This plugin allows you to automatically generate Alt Text for the images that are uploaded to the site's media library.", 'auto-alt-text'); ?>
426 <br>
427 <?php esc_html_e("The following methods are available to generate the alt text:", 'auto-alt-text'); ?>
428 </p>
429 <ul>
430 <li>
431 <strong><?php esc_html_e("OpenAI's APIs", 'auto-alt-text'); ?></strong>: <?php esc_html_e("the image will be analyzed by the AI services provided by OpenAI and an alt text will be generated based on the prompt you set;", 'auto-alt-text'); ?>
432 </li>
433 <li>
434 <strong><?php esc_html_e("Google Gemini's APIs", 'auto-alt-text'); ?></strong>: <?php esc_html_e("the image will be analyzed by the AI services provided by Google and an alt text will be generated based on the prompt you set;", 'auto-alt-text'); ?>
435 </li>
436 <li>
437 <strong><?php esc_html_e("Azure's APIs", 'auto-alt-text'); ?></strong>: <?php esc_html_e("the image will be analyzed by the AI services provided by Azure and an alt text will be generated in the language of your choice;", 'auto-alt-text'); ?>
438 </li>
439 <li>
440 <strong><?php esc_html_e("Title of the article (not AI)", 'auto-alt-text'); ?></strong>: <?php esc_html_e("if the image is uploaded within an article, the title of the article will be used as alt text;", 'auto-alt-text'); ?>
441 </li>
442 <li>
443 <strong><?php esc_html_e("Title of the attachment (not AI)", 'auto-alt-text'); ?></strong>: <?php esc_html_e("the title of the attachment will be copied into the alt text;", 'auto-alt-text'); ?>
444 </li>
445 </ul>
446 <p><?php esc_html_e("Once all the necessary data for the chosen generation method has been entered, the alt texts will be created automatically upon uploading each image.", 'auto-alt-text'); ?></p>
447 <p><strong>New</strong>: <?php esc_html_e("For images already in the media library, you can create bulk alt texts. Open the Media Library in the \"list\" view, select the images for which to generate the alt text, and choose the \"Generate alt text\" bulk action. (Depending on the number of images chosen and their weight, this may take some time.)", 'auto-alt-text'); ?></p>
448 <p>
449 <strong><?php esc_html_e('Pay attention please:', 'auto-alt-text') ?></strong> <?php esc_html_e("if the alt text for an image is not generated, check the logs on the", 'auto-alt-text'); ?>
450 <a href="<?php esc_url(menu_page_url(Constants::AATXT_PLUGIN_OPTION_LOG_PAGE_SLUG, true)); ?>"><?php esc_html_e('designated page.', 'auto-alt-text') ?></a>
451 </p>
452 </div>
453 <form method="post" action="options.php" class="aat-options">
454 <?php
455 settings_fields('auto_alt_text_options');
456
457 echo '<div>';
458 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_TYPOLOGY) . '">' . esc_html__('Generation method', 'auto-alt-text') . '</label>';
459 echo '<p class="description">' . esc_html__("Which method do you want to use to generate the alt text for the images?", 'auto-alt-text') . '</p>';
460 $typology = self::typology();
461
462 ?>
463
464 <select name="<?php echo esc_attr(Constants::AATXT_OPTION_FIELD_TYPOLOGY); ?>"
465 id="<?php echo esc_attr(Constants::AATXT_OPTION_FIELD_TYPOLOGY); ?>">
466 <option value="<?php echo esc_attr(Constants::AATXT_OPTION_TYPOLOGY_DEACTIVATED); ?>"<?php echo esc_attr(self::selected($typology, Constants::AATXT_OPTION_TYPOLOGY_DEACTIVATED)); ?>><?php esc_html_e("Deactivated", 'auto-alt-text'); ?></option>
467 <option value="<?php echo esc_attr(Constants::AATXT_OPTION_TYPOLOGY_CHOICE_OPENAI); ?>"<?php echo esc_attr(self::selected($typology, Constants::AATXT_OPTION_TYPOLOGY_CHOICE_OPENAI)); ?>><?php esc_html_e("OpenAI's APIs", 'auto-alt-text'); ?></option>
468 <option value="<?php echo esc_attr(Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ANTHROPIC); ?>"<?php echo esc_attr(self::selected($typology, Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ANTHROPIC)); ?>><?php esc_html_e("Antrhopic's APIs", 'auto-alt-text'); ?></option>
469 <option value="<?php echo esc_attr(Constants::AATXT_OPTION_TYPOLOGY_CHOICE_GEMINI); ?>"<?php echo esc_attr(self::selected($typology, Constants::AATXT_OPTION_TYPOLOGY_CHOICE_GEMINI)); ?>><?php esc_html_e("Google Gemini's APIs", 'auto-alt-text'); ?></option>
470 <option value="<?php echo esc_attr(Constants::AATXT_OPTION_TYPOLOGY_CHOICE_AZURE); ?>"<?php echo esc_attr(self::selected($typology, Constants::AATXT_OPTION_TYPOLOGY_CHOICE_AZURE)); ?>><?php esc_html_e("Azure's APIs", 'auto-alt-text'); ?></option>
471 <option value="<?php echo esc_attr(Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ARTICLE_TITLE); ?>"<?php echo esc_attr(self::selected($typology, Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ARTICLE_TITLE)); ?>><?php esc_html_e("Title of the article (not AI)", 'auto-alt-text'); ?></option>
472 <option value="<?php echo esc_attr(Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ATTACHMENT_TITLE); ?>"<?php echo esc_attr(self::selected($typology, Constants::AATXT_OPTION_TYPOLOGY_CHOICE_ATTACHMENT_TITLE)); ?>><?php esc_html_e("Title of the attachment (not AI)", 'auto-alt-text'); ?></option>
473 </select>
474 <?php
475 echo '</div>';
476
477 echo '<div class="plugin-option type-article-title"><strong>' . esc_html__('Notice', 'auto-alt-text') . '</strong>: ' .
478 esc_html__('If you try to insert an image into a post that has not yet been saved as a draft or published, the plugin cannot generate an alt text based on the post\'s title since the title itself has not yet been saved.', 'auto-alt-text') . ' ' .
479 esc_html__('Therefore, the alt text "Auto draft" will be inserted. To avoid this behavior, save the article draft first and then upload the image.', 'auto-alt-text') .
480 '</div>';
481
482 $openaiModel = self::openAiModel();
483 $openaiRegistry = self::openAiModelsRegistry();
484
485 echo '<div class="plugin-option type-openai">';
486 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '">' . esc_html__('Model', 'auto-alt-text') . '</label>';
487
488 if (!$openaiRegistry->hasApiKey()) {
489 echo '<p class="description">' . esc_html__('Enter and save your OpenAI API Key to load the list of available models.', 'auto-alt-text') . '</p>';
490 // The <select> below is disabled and therefore not submitted with
491 // the form; this hidden field re-submits the currently stored model
492 // so saving the settings page does not wipe the user's choice.
493 $rawSavedModel = get_option(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI);
494 $rawSavedModel = is_string($rawSavedModel) ? $rawSavedModel : '';
495 echo '<input type="hidden" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '" value="' . esc_attr($rawSavedModel) . '">';
496 echo '<select id="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '" disabled>';
497 if ($openaiModel !== '') {
498 echo '<option value="' . esc_attr($openaiModel) . '" selected>' . esc_html($openaiModel) . '</option>';
499 } else {
500 echo '<option value="" selected>' . esc_html__('— API Key required —', 'auto-alt-text') . '</option>';
501 }
502 echo '</select>';
503 } else {
504 $availableModels = $openaiRegistry->getAvailableModels();
505
506 // Read the raw saved value (not self::openAiModel(), which falls
507 // back to the static gpt-4o default). This lets us tell "nothing
508 // saved yet" apart from "gpt-4o explicitly chosen": on a fresh
509 // install we pre-select the registry's default (least expensive
510 // available) model instead of flagging gpt-4o as no longer
511 // available for a model the user never picked.
512 $savedModel = get_option(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI);
513 $savedModel = is_string($savedModel) ? $savedModel : '';
514 $selectedModel = $savedModel !== '' ? $savedModel : $openaiRegistry->getDefaultModel();
515 $savedIsLegacy = $savedModel !== '' && !array_key_exists($savedModel, $availableModels);
516
517 echo '<select name="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '" id="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '">';
518 foreach ($availableModels as $key => $value) {
519 echo '<option value="' . esc_attr($key) . '" ' . esc_attr(self::selected($selectedModel, $key)) . '>' . esc_html($value) . '</option>';
520 }
521 if ($savedIsLegacy) {
522 $legacyLabel = sprintf(
523 /* translators: %s is the saved model id */
524 esc_html__('%s (no longer available)', 'auto-alt-text'),
525 $savedModel
526 );
527 echo '<option value="' . esc_attr($savedModel) . '" selected>' . esc_html($legacyLabel) . '</option>';
528 }
529 echo '</select>';
530 }
531 echo '</div>';
532
533 echo '<div class="plugin-option type-openai"><strong>' . esc_html__('Notice', 'auto-alt-text') . '</strong>: ' .
534 esc_html__('If the model you selected is retired by OpenAI and is no longer available, the least expensive available model will be used as fallback until you choose a new one.', 'auto-alt-text') . '<br>' .
535 esc_html__('In case of errors, it is still possible to find the specific reason stated on the', 'auto-alt-text') . ' <a href="' . esc_url(menu_page_url(Constants::AATXT_PLUGIN_OPTION_LOG_PAGE_SLUG, false)) . '">' . esc_html__('error log page', 'auto-alt-text') . '</a>.' .
536 '</div>';
537
538 echo '<div class="plugin-option type-openai">';
539 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI) . '">' . esc_html__('OpenAI API Key', 'auto-alt-text') . '</label>';
540 echo '<p class="description">' . esc_html__("Enter your API Key", 'auto-alt-text') . '</p>';
541 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI);
542 echo '<input type="password" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI) . '" value="' . esc_attr((Encryption::make())->decrypt($apiKey)) . '" />';
543 echo '</div>';
544
545 echo '<div class="plugin-option type-openai">';
546 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_PROMPT_OPENAI) . '">' . esc_html__('Prompt', 'auto-alt-text') . '</label>';
547 echo '<p class="description">' . esc_html__("Enter a specific and detailed prompt according to your needs.", 'auto-alt-text') . '</p>';
548 $defaultPrompt = sprintf(esc_html__("Act like an SEO expert and write an alt text of up to 125 characters for this image.", 'auto-alt-text'), Constants::AATXT_IMAGE_URL_TAG);
549 $prompt = get_option(Constants::AATXT_OPTION_FIELD_PROMPT_OPENAI) ?: $defaultPrompt;
550 echo '<textarea name="' . esc_attr(Constants::AATXT_OPTION_FIELD_PROMPT_OPENAI) . '" rows="5" cols="50">' . esc_textarea($prompt) . '</textarea>';
551 echo '</div>';
552
553 echo '<div class="plugin-option type-azure">' . esc_html__("Fill out the following fields to leverage Azure's computer vision services to generate the Alt texts.", 'auto-alt-text') . '</div>';
554
555 echo '<div class="plugin-option type-azure">';
556 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION) . '">' . esc_html__('Azure Computer Vision API Key', 'auto-alt-text') . '</label>';
557 echo '<p class="description">' . esc_html__("Enter the API key for the Computer Vision service of your Azure account.", 'auto-alt-text') . '</p>';
558 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION);
559 echo '<input type="password" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION) . '" value="' . esc_attr((Encryption::make())->decrypt($apiKey)) . '" />';
560 echo '</div>';
561
562 echo '<div class="plugin-option type-azure">';
563 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_COMPUTER_VISION) . '">' . esc_html__('Azure Computer Vision Endpoint', 'auto-alt-text') . '</label>';
564 echo '<p class="description">' . esc_html__("Enter the endpoint of the Computer Vision service.", 'auto-alt-text') . ' (es. https://computer-vision-france-central.cognitiveservices.azure.com/)</p>';
565 $endpoint = get_option(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_COMPUTER_VISION);
566 echo '<input type="text" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_COMPUTER_VISION) . '" value="' . esc_attr($endpoint) . '" />';
567 echo '</div>';
568
569 echo '<div class="plugin-option type-azure">' .
570 '<strong>' . esc_html__('The default alt text language is English.', 'auto-alt-text') . '</strong><br>' .
571 esc_html__('If you want to translate into another language, enter the following data necessary for the translation API to work.', 'auto-alt-text') . ' ' .
572 esc_html__('After saving the changes you can select the desired language.', 'auto-alt-text') .
573 '</div>';
574
575 echo '<div class="plugin-option type-azure">';
576 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE) . '">' . esc_html__('Azure Translate Instance API Key', 'auto-alt-text') . '</label>';
577 echo '<p class="description">' . esc_html__("Enter your API key for the Azure Translate Instance service.", 'auto-alt-text') . '</p>';
578 $translationApiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE);
579 echo '<input type="password" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE) . '" value="' . esc_attr((Encryption::make())->decrypt($translationApiKey)) . '" class="notRequired" />';
580 echo '</div>';
581
582 echo '<div class="plugin-option type-azure">';
583 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_TRANSLATE_INSTANCE) . '">' . esc_html__('Azure Translate Instance Endpoint', 'auto-alt-text') . '</label>';
584 echo '<p class="description">' . esc_html__("Enter the endpoint of the Translate Instance service", 'auto-alt-text') . ' (es. https://api.cognitive.microsofttranslator.com/)</p>';
585 $translationEndpoint = get_option(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_TRANSLATE_INSTANCE);
586 echo '<input type="text" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_TRANSLATE_INSTANCE) . '" value="' . esc_attr($translationEndpoint) . '" class="notRequired" />';
587 echo '</div>';
588
589 echo '<div class="plugin-option type-azure">';
590 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_REGION_AZURE_TRANSLATE_INSTANCE) . '">' . esc_html__('Azure Translate Instance Region', 'auto-alt-text') . '</label>';
591 echo '<p class="description">' . esc_html__("Enter the region of the Azure Translate Instance service.", 'auto-alt-text') . ' (es. westeurope)</p>';
592 $translationRegion = get_option(Constants::AATXT_OPTION_FIELD_REGION_AZURE_TRANSLATE_INSTANCE);
593 echo '<input type="text" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_REGION_AZURE_TRANSLATE_INSTANCE) . '" value="' . esc_attr($translationRegion) . '" class="notRequired" />';
594 echo '</div>';
595
596 if ($translationApiKey && $translationEndpoint && $translationRegion):
597 echo '<div class="plugin-option type-azure">';
598 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE) . '">' . esc_html__('Alt Text Language', 'auto-alt-text') . '</label>';
599 echo '<p class="description">' . esc_html__("Select the language in which the alt text should be written.", 'auto-alt-text') . '</p>';
600 $currentLanguage = get_option(Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE);
601
602 try {
603 $httpClient = new WordPressHttpClient();
604 $azureConfig = new AzureConfig(
605 self::apiKeyAzureComputerVision(),
606 self::endpointAzureComputerVision(),
607 '',
608 '',
609 self::apiKeyAzureTranslateInstance(),
610 self::endpointAzureTranslateInstance(),
611 self::regionAzureTranslateInstance(),
612 self::languageAzureTranslateInstance()
613 );
614 $azureTranslator = new AzureTranslator($httpClient, $azureConfig);
615 $supportedLanguages = $azureTranslator->supportedLanguages();
616 } catch (AzureTranslateInstanceException $e) {
617 $supportedLanguages = [
618 "en" => [
619 "name" => "English",
620 "nativeName" => "English",
621 "dir" => "ltr",
622 ]
623 ];
624 echo '<p style="color:red"><strong>' . esc_html($e->getMessage()) . '</strong></p>';
625 }
626
627 ?>
628 <select name="<?php echo esc_attr(Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE); ?>"
629 id="<?php echo esc_attr(Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE); ?>">
630 <?php
631 foreach ($supportedLanguages as $key => $language):
632 ?>
633 <option value="<?php echo esc_attr($key) ?>" <?php echo esc_attr(self::selected($currentLanguage, $key)); ?>><?php echo esc_html($language['name']); ?></option>
634 <?php
635 endforeach;
636 ?>
637 </select>
638
639 <?php
640
641 echo '</div>';
642 endif;
643
644 echo '<div class="plugin-option type-anthropic">';
645 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC) . '">' . esc_html__('Antrhopic API Key', 'auto-alt-text') . '</label>';
646 echo '<p class="description">' . esc_html__("Enter your API Key", 'auto-alt-text') . '</p>';
647 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC);
648 echo '<input type="password" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC) . '" value="' . esc_attr((Encryption::make())->decrypt($apiKey)) . '" />';
649 echo '</div>';
650
651 $anthropicModel = self::anthropicModel();
652 $anthropicRegistry = self::anthropicModelsRegistry();
653
654 echo '<div class="plugin-option type-anthropic">';
655 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC) . '">' . esc_html__('Model', 'auto-alt-text') . '</label>';
656
657 if (!$anthropicRegistry->hasApiKey()) {
658 echo '<p class="description">' . esc_html__('Enter and save your Anthropic API Key to load the list of available models.', 'auto-alt-text') . '</p>';
659 // The <select> below is disabled and therefore not submitted with
660 // the form; this hidden field re-submits the currently stored model
661 // so saving the settings page does not wipe the user's choice.
662 $rawSavedModel = get_option(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC);
663 $rawSavedModel = is_string($rawSavedModel) ? $rawSavedModel : '';
664 echo '<input type="hidden" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC) . '" value="' . esc_attr($rawSavedModel) . '">';
665 echo '<select id="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC) . '" disabled>';
666 if ($anthropicModel !== '') {
667 echo '<option value="' . esc_attr($anthropicModel) . '" selected>' . esc_html($anthropicModel) . '</option>';
668 } else {
669 echo '<option value="" selected>' . esc_html__('— API Key required —', 'auto-alt-text') . '</option>';
670 }
671 echo '</select>';
672 } else {
673 $availableModels = $anthropicRegistry->getAvailableModels();
674 $savedIsLegacy = $anthropicModel !== '' && !array_key_exists($anthropicModel, $availableModels);
675
676 echo '<select name="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC) . '" id="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC) . '">';
677 foreach ($availableModels as $key => $value) {
678 echo '<option value="' . esc_attr($key) . '" ' . esc_attr(self::selected($anthropicModel, $key)) . '>' . esc_html($value) . '</option>';
679 }
680 if ($savedIsLegacy) {
681 $legacyLabel = sprintf(
682 /* translators: %s is the saved model id */
683 esc_html__('%s (no longer available)', 'auto-alt-text'),
684 $anthropicModel
685 );
686 echo '<option value="' . esc_attr($anthropicModel) . '" selected>' . esc_html($legacyLabel) . '</option>';
687 }
688 echo '</select>';
689 }
690 echo '</div>';
691
692 echo '<div class="plugin-option type-anthropic">';
693 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_PROMPT_ANTHROPIC) . '">' . esc_html__('Prompt', 'auto-alt-text') . '</label>';
694 echo '<p class="description">' . esc_html__("Enter a specific and detailed prompt according to your needs.", 'auto-alt-text') . '</p>';
695 $defaultPrompt = sprintf(esc_html__("Act like an SEO expert and write an alt text of up to 125 characters for this image. Return only the complete alt text.", 'auto-alt-text'), Constants::AATXT_IMAGE_URL_TAG);
696 $prompt = get_option(Constants::AATXT_OPTION_FIELD_PROMPT_ANTHROPIC) ?: $defaultPrompt;
697 echo '<textarea name="' . esc_attr(Constants::AATXT_OPTION_FIELD_PROMPT_ANTHROPIC) . '" rows="5" cols="50">' . esc_textarea($prompt) . '</textarea>';
698 echo '</div>';
699
700 echo '<div class="plugin-option type-gemini">';
701 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI) . '">' . esc_html__('Gemini API Key', 'auto-alt-text') . '</label>';
702 echo '<p class="description">' . esc_html__("Enter your API Key", 'auto-alt-text') . '</p>';
703 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI);
704 echo '<input type="password" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI) . '" value="' . esc_attr((Encryption::make())->decrypt($apiKey)) . '" />';
705 echo '</div>';
706
707 $geminiModel = self::geminiModel();
708 $geminiRegistry = self::geminiModelsRegistry();
709
710 echo '<div class="plugin-option type-gemini">';
711 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI) . '">' . esc_html__('Model', 'auto-alt-text') . '</label>';
712
713 if (!$geminiRegistry->hasApiKey()) {
714 echo '<p class="description">' . esc_html__('Enter and save your Gemini API Key to load the list of available models.', 'auto-alt-text') . '</p>';
715 // The <select> below is disabled and therefore not submitted with
716 // the form; this hidden field re-submits the currently stored model
717 // so saving the settings page does not wipe the user's choice.
718 $rawSavedModel = get_option(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI);
719 $rawSavedModel = is_string($rawSavedModel) ? $rawSavedModel : '';
720 echo '<input type="hidden" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI) . '" value="' . esc_attr($rawSavedModel) . '">';
721 echo '<select id="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI) . '" disabled>';
722 if ($geminiModel !== '') {
723 echo '<option value="' . esc_attr($geminiModel) . '" selected>' . esc_html($geminiModel) . '</option>';
724 } else {
725 echo '<option value="" selected>' . esc_html__('— API Key required —', 'auto-alt-text') . '</option>';
726 }
727 echo '</select>';
728 } else {
729 $availableModels = $geminiRegistry->getAvailableModels();
730 $savedIsLegacy = $geminiModel !== '' && !array_key_exists($geminiModel, $availableModels);
731
732 echo '<select name="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI) . '" id="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI) . '">';
733 foreach ($availableModels as $key => $value) {
734 echo '<option value="' . esc_attr($key) . '" ' . esc_attr(self::selected($geminiModel, $key)) . '>' . esc_html($value) . '</option>';
735 }
736 if ($savedIsLegacy) {
737 $legacyLabel = sprintf(
738 /* translators: %s is the saved model id */
739 esc_html__('%s (no longer available)', 'auto-alt-text'),
740 $geminiModel
741 );
742 echo '<option value="' . esc_attr($geminiModel) . '" selected>' . esc_html($legacyLabel) . '</option>';
743 }
744 echo '</select>';
745 }
746 echo '</div>';
747
748 echo '<div class="plugin-option type-gemini"><strong>' . esc_html__('Notice', 'auto-alt-text') . '</strong>: ' .
749 esc_html__('If the model you selected is retired by Google and is no longer available, the least expensive available model will be used as fallback until you choose a new one.', 'auto-alt-text') . '<br>' .
750 esc_html__('In case of errors, it is still possible to find the specific reason stated on the', 'auto-alt-text') . ' <a href="' . esc_url(menu_page_url(Constants::AATXT_PLUGIN_OPTION_LOG_PAGE_SLUG, false)) . '">' . esc_html__('error log page', 'auto-alt-text') . '</a>.' .
751 '</div>';
752
753 echo '<div class="plugin-option type-gemini">';
754 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_PROMPT_GEMINI) . '">' . esc_html__('Prompt', 'auto-alt-text') . '</label>';
755 echo '<p class="description">' . esc_html__("Enter a specific and detailed prompt according to your needs.", 'auto-alt-text') . '</p>';
756 $defaultPrompt = sprintf(esc_html__("Act like an SEO expert and write an alt text of up to 125 characters for this image. Return only the complete alt text.", 'auto-alt-text'), Constants::AATXT_IMAGE_URL_TAG);
757 $prompt = get_option(Constants::AATXT_OPTION_FIELD_PROMPT_GEMINI) ?: $defaultPrompt;
758 echo '<textarea name="' . esc_attr(Constants::AATXT_OPTION_FIELD_PROMPT_GEMINI) . '" rows="5" cols="50">' . esc_textarea($prompt) . '</textarea>';
759 echo '</div>';
760
761 echo '<div class="plugin-option type-article-title type-attachment-title type-openai type-azure type-anthropic type-gemini">';
762 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT) . '">' . esc_html__('Keep existing alt text', 'auto-alt-text') . '</label>';
763 echo '<p class="description">' . esc_html__("If checked, the existing alt text of images will not be overwritten.", 'auto-alt-text') . '</p>';
764 $preserveAltText = get_option(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT);
765 echo '<input type="checkbox" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT) . '" value="1" class="notRequired" ' . checked(1, $preserveAltText, false) . ' />';
766 echo '</div>';
767
768 // Standalone option (no .plugin-option class, so it is always visible
769 // regardless of the selected generation method): this feature works on
770 // the front-end content and is independent of how alt texts are created.
771 echo '<div>';
772 echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_AUTO_RENDER_ALT_TEXT) . '">' . esc_html__('Automatically render alt text in content', 'auto-alt-text') . '</label>';
773 echo '<p class="description">' . esc_html__("If checked, when a page is displayed, images in the content that have no alt text are automatically filled with the alt text stored in the media library (when available). It does not modify your saved content.", 'auto-alt-text') . '</p>';
774 $autoRenderAltText = get_option(Constants::AATXT_OPTION_FIELD_AUTO_RENDER_ALT_TEXT);
775 echo '<input type="checkbox" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_AUTO_RENDER_ALT_TEXT) . '" value="1" class="notRequired" ' . checked(1, $autoRenderAltText, false) . ' />';
776 echo '</div>';
777
778 submit_button();
779 ?>
780 </form>
781 </div>
782 <?php
783 }
784
785 /**
786 * If option is selected return the selected attribute
787 * @param string $selectedValue
788 * @param string $inputValue
789 * @return string
790 */
791 public static function selected(string $selectedValue, string $inputValue): string
792 {
793 if (empty($selectedValue) && 'en' == $inputValue) {
794 return ' selected';
795 }
796 return $selectedValue == $inputValue ? ' selected' : '';
797 }
798
799
800 /**
801 * Register input fields and option settings
802 * @return void
803 */
804 public static function setupPluginOptions(): void
805 {
806 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI);
807 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_PROMPT_OPENAI, [self::class, 'sanitizeTextArea']);
808 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_TYPOLOGY, [self::class, 'sanitizeText']);
809 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_MODEL_OPENAI, [self::class, 'sanitizeText']);
810 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION);
811 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_COMPUTER_VISION, [self::class, 'sanitizeUrl']);
812 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE);
813 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_TRANSLATE_INSTANCE, [self::class, 'sanitizeUrl']);
814 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_REGION_AZURE_TRANSLATE_INSTANCE, [self::class, 'sanitizeText']);
815 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE, [self::class, 'sanitizeText']);
816 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT);
817 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_AUTO_RENDER_ALT_TEXT);
818 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC);
819 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_PROMPT_ANTHROPIC, [self::class, 'sanitizeTextArea']);
820 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC, [self::class, 'sanitizeText']);
821 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI);
822 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_PROMPT_GEMINI, [self::class, 'sanitizeTextArea']);
823 register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_MODEL_GEMINI, [self::class, 'sanitizeText']);
824 }
825
826 /**
827 * @return string
828 */
829 public static function typology(): string
830 {
831 return get_option(Constants::AATXT_OPTION_FIELD_TYPOLOGY);
832 }
833
834 public static function openAiModel(): string
835 {
836 return get_option(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) ?: Constants::AATXT_GPT4O;
837 }
838
839 public static function anthropicModel(): string
840 {
841 return get_option(Constants::AATXT_OPTION_FIELD_MODEL_ANTHROPIC) ?: Constants::AATXT_CLAUDE_FALLBACK_MODEL;
842 }
843
844 public static function geminiModel(): string
845 {
846 return get_option(Constants::AATXT_OPTION_FIELD_MODEL_GEMINI) ?: Constants::AATXT_GEMINI_FALLBACK_MODEL;
847 }
848
849 /**
850 * @return string
851 */
852 public static function openAiPrompt(): string
853 {
854 return get_option(Constants::AATXT_OPTION_FIELD_PROMPT_OPENAI);
855 }
856
857 /**
858 * @return string
859 */
860 public static function anthropicPrompt(): string
861 {
862 return get_option(Constants::AATXT_OPTION_FIELD_PROMPT_ANTHROPIC);
863 }
864
865 /**
866 * @return string
867 */
868 public static function geminiPrompt(): string
869 {
870 return get_option(Constants::AATXT_OPTION_FIELD_PROMPT_GEMINI);
871 }
872
873 /**
874 * @return string
875 */
876 public static function apiKeyOpenAI(): string
877 {
878 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI);
879 return (Encryption::make())->decrypt($apiKey);
880 }
881
882 /**
883 * @return string
884 */
885 public static function apiKeyAnthropic(): string
886 {
887 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_ANTHROPIC);
888 return (Encryption::make())->decrypt($apiKey);
889 }
890
891 /**
892 * @return string
893 */
894 public static function apiKeyGemini(): string
895 {
896 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_GEMINI);
897 return (Encryption::make())->decrypt($apiKey);
898 }
899
900 /**
901 * @return string
902 */
903 public static function apiKeyAzureComputerVision(): string
904 {
905 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION);
906 return (Encryption::make())->decrypt($apiKey);
907 }
908
909 /**
910 * @return string
911 */
912 public static function endpointAzureComputerVision(): string
913 {
914 return get_option(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_COMPUTER_VISION);
915 }
916
917 /**
918 * @return string
919 */
920 public static function apiKeyAzureTranslateInstance(): string
921 {
922 $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE);
923 return (Encryption::make())->decrypt($apiKey);
924 }
925
926 /**
927 * @return string
928 */
929 public static function endpointAzureTranslateInstance(): string
930 {
931 return get_option(Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_TRANSLATE_INSTANCE);
932 }
933
934 /**
935 * @return string
936 */
937 public static function regionAzureTranslateInstance(): string
938 {
939 return get_option(Constants::AATXT_OPTION_FIELD_REGION_AZURE_TRANSLATE_INSTANCE);
940 }
941
942 /**
943 * @return string
944 */
945 public static function languageAzureTranslateInstance(): string
946 {
947 return get_option(Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE) ?: 'en';
948 }
949
950 /**
951 * @return bool
952 */
953 public static function preserveExistingAltText(): bool
954 {
955 return get_option(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT);
956 }
957
958 /**
959 * Whether missing alt text should be filled automatically when rendering
960 * post content on the front-end. Unchecked (disabled) by default.
961 *
962 * @return bool
963 */
964 public static function autoRenderAltText(): bool
965 {
966 return (bool) get_option(Constants::AATXT_OPTION_FIELD_AUTO_RENDER_ALT_TEXT);
967 }
968
969 public static function sanitizeUrl($input): string
970 {
971 return sanitize_url($input);
972 }
973
974 public static function sanitizeText($input): string
975 {
976 return sanitize_text_field($input);
977 }
978
979 public static function sanitizeTextArea($input): string
980 {
981 return sanitize_textarea_field($input);
982 }
983 }
984