| @@ -2,10 +2,16 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace AATXT\App\Admin; |
| 4 | 4 | |
| 5 | 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; | |
| 6 | 9 | use AATXT\App\AIProviders\Azure\AzureTranslator; |
| 10 | +use AATXT\App\Configuration\AzureConfig; | |
| 11 | +use AATXT\App\Core\Container; | |
| 7 | 12 | use AATXT\App\Exceptions\Azure\AzureTranslateInstanceException; |
| 13 | +use AATXT\App\Infrastructure\Http\WordPressHttpClient; | |
| 8 | 14 | use AATXT\App\Utilities\AssetsManager; |
| 9 | 15 | use AATXT\App\Utilities\Encryption; |
| 10 | 16 | use AATXT\Config\Constants; |
| 11 | 17 | |
| @@ -30,25 +36,311 @@ | ||
| 30 | 36 | } |
| 31 | 37 | |
| 32 | 38 | self::$assetsManager = AssetsManager::make(); |
| 33 | 39 | |
| 40 | + add_action('admin_notices', [self::$instance, 'showEncryptionConstantsNotice']); | |
| 41 | + | |
| 34 | 42 | add_action('admin_enqueue_scripts', [self::$instance, 'enqueueAdminScripts'], 1); |
| 35 | 43 | add_action('admin_menu', [self::$instance, 'addOptionsPageToTheMenu']); |
| 36 | - add_action('admin_init', [self::$instance, 'setupPluginOptions']); | |
| 44 | + add_action('admin_init', [self::$instance, 'setupPluginOptions'], 10); | |
| 45 | + add_action('admin_init', [self::$instance, 'migrateEncryptionKeys'], 9); | |
| 37 | 46 | |
| 38 | 47 | // Encrypt API Keys on update |
| 39 | 48 | add_action('pre_update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION, [self::$instance, 'encryptDataOnUpdate'], 10, 3); |
| 40 | 49 | add_action('pre_update_option_' . Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_TRANSLATE_INSTANCE, [self::$instance, 'encryptDataOnUpdate'], 10, 3); |
| 41 | 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']); | |
| 42 | 71 | } |
| 43 | 72 | |
| 44 | 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 | + /** | |
| 45 | 338 | * Encrypt data |
| 46 | 339 | * @param ?string $newValue |
| 47 | - * @param ?string $oldValue | |
| 48 | 340 | * @return ?string |
| 49 | 341 | */ |
| 50 | - public function encryptDataOnUpdate(?string $newValue, ?string $oldValue): ?string | |
| 342 | + public function encryptDataOnUpdate(?string $newValue): ?string | |
| 51 | 343 | { |
| 52 | 344 | if (!empty($newValue)) { |
| 53 | 345 | $newValue = (Encryption::make())->encrypt($newValue); |
| 54 | 346 | } |
| @@ -102,9 +394,11 @@ | ||
| 102 | 394 | <br> |
| 103 | 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'); ?> |
| 104 | 396 | </p> |
| 105 | 397 | <?php |
| 106 | - $logs = DBLogger::make()->getImageLog(); | |
| 398 | + $container = Container::make(); | |
| 399 | + $logger = $container->get(DBLogger::class); | |
| 400 | + $logs = $logger->getImageLog(); | |
| 107 | 401 | if ($logs) { |
| 108 | 402 | echo '<textarea id="error-log" name="error-log" readonly>' . esc_textarea($logs) . '</textarea>'; |
| 109 | 403 | } else { |
| 110 | 404 | echo '<p>' . esc_html__('There is no error log yet!', 'auto-alt-text') . '</p>'; |
| @@ -136,8 +430,11 @@ | ||
| 136 | 430 | <li> |
| 137 | 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'); ?> |
| 138 | 432 | </li> |
| 139 | 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> | |
| 140 | 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'); ?> |
| 141 | 438 | </li> |
| 142 | 439 | <li> |
| 143 | 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'); ?> |
| @@ -167,8 +464,10 @@ | ||
| 167 | 464 | <select name="<?php echo esc_attr(Constants::AATXT_OPTION_FIELD_TYPOLOGY); ?>" |
| 168 | 465 | id="<?php echo esc_attr(Constants::AATXT_OPTION_FIELD_TYPOLOGY); ?>"> |
| 169 | 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> |
| 170 | 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> | |
| 171 | 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> |
| 172 | 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> |
| 173 | 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> |
| 174 | 473 | </select> |
| @@ -180,22 +479,60 @@ | ||
| 180 | 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') . |
| 181 | 480 | '</div>'; |
| 182 | 481 | |
| 183 | 482 | $openaiModel = self::openAiModel(); |
| 483 | + $openaiRegistry = self::openAiModelsRegistry(); | |
| 184 | 484 | |
| 185 | 485 | echo '<div class="plugin-option type-openai">'; |
| 186 | 486 | echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '">' . esc_html__('Model', 'auto-alt-text') . '</label>'; |
| 187 | 487 | |
| 188 | - echo '<select name="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '" id="' . esc_attr(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) . '">'; | |
| 189 | - foreach(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI_OPTIONS as $key => $value) { | |
| 190 | - echo '<option value="' . esc_attr($key) . '" ' . esc_attr(self::selected($openaiModel, $key)) . '>' . esc_html($value) . '</option>'; | |
| 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>'; | |
| 191 | 530 | } |
| 192 | - echo '</select>'; | |
| 193 | 531 | echo '</div>'; |
| 194 | 532 | |
| 195 | 533 | echo '<div class="plugin-option type-openai"><strong>' . esc_html__('Notice', 'auto-alt-text') . '</strong>: ' . |
| 196 | - esc_html__('Rarely it may happen that the chosen model fails to generate correct alt text for the image.', 'auto-alt-text') . ' ' . | |
| 197 | - esc_html__('In these cases, the call to the api will be re-performed using the gpt-4o-mini model as fallback.', 'auto-alt-text') . '<br>' . | |
| 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>' . | |
| 198 | 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>.' . |
| 199 | 536 | '</div>'; |
| 200 | 537 | |
| 201 | 538 | echo '<div class="plugin-option type-openai">'; |
| @@ -262,9 +599,21 @@ | ||
| 262 | 599 | echo '<p class="description">' . esc_html__("Select the language in which the alt text should be written.", 'auto-alt-text') . '</p>'; |
| 263 | 600 | $currentLanguage = get_option(Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE); |
| 264 | 601 | |
| 265 | 602 | try { |
| 266 | - $supportedLanguages = (AzureTranslator::make())->supportedLanguages(); | |
| 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(); | |
| 267 | 616 | } catch (AzureTranslateInstanceException $e) { |
| 268 | 617 | $supportedLanguages = [ |
| 269 | 618 | "en" => [ |
| 270 | 619 | "name" => "English", |
| @@ -291,9 +640,126 @@ | ||
| 291 | 640 | |
| 292 | 641 | echo '</div>'; |
| 293 | 642 | endif; |
| 294 | 643 | |
| 295 | - echo '<div class="plugin-option type-article-title type-attachment-title type-openai type-azure">'; | |
| 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">'; | |
| 296 | 762 | echo '<label for="' . esc_attr(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT) . '">' . esc_html__('Keep existing alt text', 'auto-alt-text') . '</label>'; |
| 297 | 763 | echo '<p class="description">' . esc_html__("If checked, the existing alt text of images will not be overwritten.", 'auto-alt-text') . '</p>'; |
| 298 | 764 | $preserveAltText = get_option(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT); |
| 299 | 765 | echo '<input type="checkbox" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT) . '" value="1" class="notRequired" ' . checked(1, $preserveAltText, false) . ' />'; |
| @@ -298,8 +764,18 @@ | ||
| 298 | 764 | $preserveAltText = get_option(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT); |
| 299 | 765 | echo '<input type="checkbox" name="' . esc_attr(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT) . '" value="1" class="notRequired" ' . checked(1, $preserveAltText, false) . ' />'; |
| 300 | 766 | echo '</div>'; |
| 301 | 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 | + | |
| 302 | 778 | submit_button(); |
| 303 | 779 | ?> |
| 304 | 780 | </form> |
| 305 | 781 | </div> |
| @@ -337,8 +813,15 @@ | ||
| 337 | 813 | register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_ENDPOINT_AZURE_TRANSLATE_INSTANCE, [self::class, 'sanitizeUrl']); |
| 338 | 814 | register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_REGION_AZURE_TRANSLATE_INSTANCE, [self::class, 'sanitizeText']); |
| 339 | 815 | register_setting('auto_alt_text_options', Constants::AATXT_OPTION_FIELD_LANGUAGE_AZURE_TRANSLATE_INSTANCE, [self::class, 'sanitizeText']); |
| 340 | 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']); | |
| 341 | 824 | } |
| 342 | 825 | |
| 343 | 826 | /** |
| 344 | 827 | * @return string |
| @@ -352,12 +835,22 @@ | ||
| 352 | 835 | { |
| 353 | 836 | return get_option(Constants::AATXT_OPTION_FIELD_MODEL_OPENAI) ?: Constants::AATXT_GPT4O; |
| 354 | 837 | } |
| 355 | 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 | + | |
| 356 | 849 | /** |
| 357 | 850 | * @return string |
| 358 | 851 | */ |
| 359 | - public static function prompt(): string | |
| 852 | + public static function openAiPrompt(): string | |
| 360 | 853 | { |
| 361 | 854 | return get_option(Constants::AATXT_OPTION_FIELD_PROMPT_OPENAI); |
| 362 | 855 | } |
| 363 | 856 | |
| @@ -363,8 +856,24 @@ | ||
| 363 | 856 | |
| 364 | 857 | /** |
| 365 | 858 | * @return string |
| 366 | 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 | + */ | |
| 367 | 876 | public static function apiKeyOpenAI(): string |
| 368 | 877 | { |
| 369 | 878 | $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_OPENAI); |
| 370 | 879 | return (Encryption::make())->decrypt($apiKey); |
| @@ -372,8 +881,26 @@ | ||
| 372 | 881 | |
| 373 | 882 | /** |
| 374 | 883 | * @return string |
| 375 | 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 | + */ | |
| 376 | 903 | public static function apiKeyAzureComputerVision(): string |
| 377 | 904 | { |
| 378 | 905 | $apiKey = get_option(Constants::AATXT_OPTION_FIELD_API_KEY_AZURE_COMPUTER_VISION); |
| 379 | 906 | return (Encryption::make())->decrypt($apiKey); |
| @@ -427,8 +954,19 @@ | ||
| 427 | 954 | { |
| 428 | 955 | return get_option(Constants::AATXT_OPTION_FIELD_PRESERVE_EXISTING_ALT_TEXT); |
| 429 | 956 | } |
| 430 | 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 | + | |
| 431 | 969 | public static function sanitizeUrl($input): string |
| 432 | 970 | { |
| 433 | 971 | return sanitize_url($input); |
| 434 | 972 | } |
| @@ -441,6 +979,5 @@ | ||
| 441 | 979 | public static function sanitizeTextArea($input): string |
| 442 | 980 | { |
| 443 | 981 | return sanitize_textarea_field($input); |
| 444 | 982 | } |
| 445 | - | |
| 446 | 983 | } |