- ';
foreach (affiamsh_limit_feature_words($p['features'], $features_word_limit, 3) as $feature) $html .= '
- ' . esc_html($feature) . ' '; $html .= '
[ 'settings' => 'Settings', 'upgrade_pro' => 'Upgrade to PRO', 'hero_desc' => 'Connect your Amazon API, verify it instantly, and configure how products appear in your shortcodes.', 'api_success' => 'API Connection Successful', 'api_failed' => 'API Connection Failed', 'amazon_returned' => 'Amazon returned %d item(s) for the test request.', 'how_fix' => 'How to fix it', 'show_debug' => 'Show technical debug', 'technical_debug' => 'Technical debug', 'copy_debug' => 'Copy debug info', 'copied' => 'Copied', 'api_connection' => 'Amazon API Connection', 'api_connection_desc' => 'Select the API type and enter the credentials for that profile.', 'legacy_api' => 'Legacy API:', 'legacy_api_desc' => 'A legacy AWS-key profile is kept only for existing installations. New setups should use Creators API v3.', 'legacy_credentials' => 'Legacy credentials:', 'legacy_credentials_desc' => 'Creators API v2/Cognito is kept for existing installations. New credentials should use Creators API v3.', 'api_version' => 'API Version', 'recommended' => 'Recommended', 'legacy' => 'Legacy', 'profile_isolated' => 'Each API type keeps its own credentials, Partner Tag and Marketplace.', 'v3_desc' => 'Current Login with Amazon credentials. Credential region (v3.1/v3.2/v3.3) is detected automatically.', 'v2_desc' => 'Older Cognito-based credentials. Regional endpoint detection is automatic.', 'paapi_desc' => 'Legacy AWS-key integration.', 'credential_id' => 'Credential ID', 'credential_secret' => 'Credential Secret', 'usually_starts' => 'Usually starts with %s', 'secret_saved_keep' => 'Secret saved — leave blank to keep it', 'secret_saved' => 'Secret saved.', 'secret_keep_replace' => 'Leave blank to keep it, or enter a new value to replace it.', 'partner_tag' => 'Partner Tag', 'partner_tag_desc' => 'Use the Store ID / Tracking ID registered for the marketplace selected below.', 'marketplace' => 'Marketplace', 'marketplace_desc' => 'Credentials may work globally, but the Partner Tag must belong to this exact marketplace.', 'enter_legacy_secret' => 'Enter legacy v2 Credential Secret', 'enter_original_legacy_secret' => 'Enter the original legacy Cognito Credential Secret.', 'warning' => 'Warning:', 'v2_secret_warning' => 'the saved value looks like a v3 secret. If OAuth returns invalid_client, replace it with the original v2 secret.', 'access_key' => 'Access Key', 'secret_key' => 'Secret Key', 'enter_secret_key' => 'Enter Secret Key', 'save_settings' => 'Save Settings', 'test_api' => 'Test API Connection', 'test_saves' => 'Test automatically saves the current values first.', 'product_display' => 'Product Display', 'product_display_desc' => 'Default appearance for the Amazon product grid.', 'num_products' => 'Number of Products', 'free_products' => 'FREE version supports up to 3 products.', 'upgrade_for_9' => 'Upgrade to PRO for up to 9.', 'num_columns' => 'Number of Columns', 'image_size' => 'Image Size', 'font_size' => 'Font Size', 'examples' => 'Examples:', 'box_language' => 'Box Language', 'box_language_desc' => 'Automatic uses the WordPress site language. You can force a language here, or per shortcode with language="it".', 'automatic_site_language' => 'Automatic (site language)', 'connection_status' => 'Connection status', 'profile_configured' => 'Profile configured', 'use_test_after' => 'Use Test API Connection after changing credentials, Partner Tag or Marketplace.', 'setup_required' => 'Setup required', 'complete_profile' => 'Complete the selected API profile.', 'shortcode' => 'Shortcode', 'shortcode_override' => 'You can also use number="x" to override the default product count.', 'marketplace_tip' => 'Marketplace tip', 'marketplace_tip_desc' => 'For Creators API, your credential can authenticate globally, but your Partner Tag must be valid for the selected marketplace.', 'gallery_title' => 'Premium Layout Gallery', 'gallery_desc' => 'Professional product cards, deal boxes and comparison tables designed for higher-impact affiliate content.', 'open_display' => 'Open Display (PRO)', 'explore_pro' => 'Explore PRO layouts', 'modern_card' => 'Modern Card', 'editorial' => 'Editorial', 'recommended_label' => 'Recommended', 'deal_box' => 'Deal Box', 'comparison' => 'Comparison', 'view_amazon' => 'View on Amazon', 'display_pro' => 'Display (PRO)', 'pro_only_options' => 'These options are available only with the PRO version.', 'template' => 'Template', 'card' => 'Card', 'list_legacy' => 'List (Legacy)', 'compact' => 'Compact', 'editorial_minimal' => 'Editorial Minimal', 'recommended_highlight' => 'Recommended Highlight', 'comparison_table' => 'Comparison Table', 'premium_requires_pro' => 'Premium templates require the PRO version. Card and List remain available for existing layouts.', 'theme' => 'Theme', 'default' => 'Default', 'custom' => 'Custom', 'theme_desc' => 'Choose a ready-made theme or select Custom to customize all colors.', 'custom_colors' => 'Custom Style Colors', 'text_color' => 'Text Color', 'title_color' => 'Title Color', 'price_color' => 'Price Color', 'box_bg' => 'Box Background', 'box_hover_bg' => 'Box Hover Background', 'ticket_bg' => 'Ticket Background', 'discount_color' => 'Discount Color', 'example_abbr' => 'E.g.', 'cta_text' => 'CTA button text', 'cta_placeholder' => 'Automatic — localized (e.g. View on Amazon)', 'cta_desc' => 'Leave blank to use the automatic translated CTA. Enter text only if you want a custom fixed label.', 'optional_badge' => 'Optional badge', 'our_pick' => 'Our pick', 'product_highlights' => 'Product highlights', 'show_features' => 'Show available feature bullets in premium layouts', 'template_shortcode_value' => 'Shortcode value', 'template_shortcode_desc' => 'Use this value only when you want to override the global template for a specific shortcode.', 'highlights_word_limit' => 'Highlights word limit', 'highlights_word_limit_desc' => 'Maximum total words shown for product highlights in each product box. Use 0 for no limit.', 'words' => 'words', 'gallery_choose' => 'Choose a global layout above or override it per shortcode.', 'unlock_premium' => 'Unlock premium layouts', 'shortcode_format' => 'Shortcode Format', 'add_shortcode' => 'Add this shortcode in your post/page', 'only_pro' => 'Only PRO version', 'pro_attributes_desc' => 'PRO attributes override global display settings for one shortcode. Existing shortcodes continue using their saved legacy style.', 'activate_pro' => 'Activate PRO', 'pro_active' => 'PRO active.', 'reset_free' => 'Reset (Back to FREE)', 'pro_benefit' => 'Hide watermark and enable custom design options', 'license_code' => 'License Code', 'activate' => 'Activate', 'purchase_pro' => 'Purchase PRO version', 'settings_saved' => 'Settings saved successfully.', 'test_saved' => 'Current settings were saved automatically before running the API test.', 'pro_activate_success'=>'PRO version activated successfully.', 'license_invalid'=>'Invalid license code.', 'pro_disabled'=>'PRO disabled. You are using the FREE version.' ], 'it' => [ 'settings' => 'Impostazioni', 'upgrade_pro' => 'Passa alla PRO', 'hero_desc' => 'Collega le API Amazon, verifica subito la connessione e configura come vengono mostrati i prodotti negli shortcode.', 'api_success' => 'Connessione API riuscita', 'api_failed' => 'Connessione API non riuscita', 'amazon_returned' => 'Amazon ha restituito %d prodotto/i per la richiesta di test.', 'how_fix' => 'Come risolvere', 'show_debug' => 'Mostra debug tecnico', 'technical_debug' => 'Debug tecnico', 'copy_debug' => 'Copia debug', 'copied' => 'Copiato', 'api_connection' => 'Connessione API Amazon', 'api_connection_desc' => 'Seleziona il tipo di API e inserisci le credenziali del relativo profilo.', 'legacy_api' => 'API legacy:', 'legacy_api_desc' => 'Un profilo legacy basato su chiavi AWS viene mantenuto solo per le installazioni esistenti. Per le nuove configurazioni usa Creators API v3.', 'legacy_credentials' => 'Credenziali legacy:', 'legacy_credentials_desc' => 'Creators API v2/Cognito viene mantenuta per le installazioni esistenti. Le nuove credenziali devono usare Creators API v3.', 'api_version' => 'Versione API', 'recommended' => 'Consigliata', 'legacy' => 'Legacy', 'profile_isolated' => 'Ogni tipo di API conserva separatamente credenziali, Partner Tag e Marketplace.', 'v3_desc' => 'Credenziali attuali Login with Amazon. La regione della credenziale (v3.1/v3.2/v3.3) viene rilevata automaticamente.', 'v2_desc' => 'Vecchie credenziali basate su Cognito. Il rilevamento dell’endpoint regionale è automatico.', 'paapi_desc' => 'Integrazione legacy tramite chiavi AWS.', 'credential_id' => 'ID credenziale', 'credential_secret' => 'Segreto credenziale', 'usually_starts' => 'Di solito inizia con %s', 'secret_saved_keep' => 'Segreto salvato — lascia vuoto per mantenerlo', 'secret_saved' => 'Segreto salvato.', 'secret_keep_replace' => 'Lascia vuoto per mantenerlo oppure inserisci un nuovo valore per sostituirlo.', 'partner_tag' => 'Partner Tag', 'partner_tag_desc' => 'Usa lo Store ID / Tracking ID registrato per il marketplace selezionato qui sotto.', 'marketplace' => 'Marketplace', 'marketplace_desc' => 'Le credenziali possono funzionare globalmente, ma il Partner Tag deve appartenere esattamente al marketplace selezionato.', 'enter_legacy_secret' => 'Inserisci il Segreto credenziale v2 legacy', 'enter_original_legacy_secret' => 'Inserisci il Segreto credenziale Cognito v2 originale.', 'warning' => 'Attenzione:', 'v2_secret_warning' => 'il valore salvato sembra un secret v3. Se OAuth restituisce invalid_client, sostituiscilo con il secret v2 originale.', 'access_key' => 'Access Key', 'secret_key' => 'Secret Key', 'enter_secret_key' => 'Inserisci Secret Key', 'save_settings' => 'Salva impostazioni', 'test_api' => 'Test connessione API', 'test_saves' => 'Il test salva automaticamente prima i valori attualmente inseriti.', 'product_display' => 'Visualizzazione prodotti', 'product_display_desc' => 'Aspetto predefinito della griglia dei prodotti Amazon.', 'num_products' => 'Numero di prodotti', 'free_products' => 'La versione FREE supporta fino a 3 prodotti.', 'upgrade_for_9' => 'Passa alla PRO per arrivare fino a 9.', 'num_columns' => 'Numero di colonne', 'image_size' => 'Dimensione immagine', 'font_size' => 'Dimensione carattere', 'examples' => 'Esempi:', 'box_language' => 'Lingua dei box', 'box_language_desc' => 'Automatico usa la lingua del sito WordPress. Puoi forzare una lingua qui oppure nel singolo shortcode con language="it".', 'automatic_site_language' => 'Automatico (lingua del sito)', 'connection_status' => 'Stato connessione', 'profile_configured' => 'Profilo configurato', 'use_test_after' => 'Usa Test connessione API dopo aver modificato credenziali, Partner Tag o Marketplace.', 'setup_required' => 'Configurazione richiesta', 'complete_profile' => 'Completa il profilo API selezionato.', 'shortcode' => 'Shortcode', 'shortcode_override' => 'Puoi anche usare number="x" per sovrascrivere il numero predefinito di prodotti.', 'marketplace_tip' => 'Suggerimento Marketplace', 'marketplace_tip_desc' => 'Con Creators API la credenziale può autenticarsi globalmente, ma il Partner Tag deve essere valido per il marketplace selezionato.', 'gallery_title' => 'Galleria layout Premium', 'gallery_desc' => 'Box prodotto professionali, box offerte e tabelle comparative pensati per contenuti affiliate più efficaci.', 'open_display' => 'Apri Visualizzazione (PRO)', 'explore_pro' => 'Scopri i layout PRO', 'modern_card' => 'Card moderna', 'editorial' => 'Editoriale', 'recommended_label' => 'Consigliato', 'deal_box' => 'Box offerta', 'comparison' => 'Confronto', 'view_amazon' => 'Vedi su Amazon', 'display_pro' => 'Visualizzazione (PRO)', 'pro_only_options' => 'Queste opzioni sono disponibili solo con la versione PRO.', 'template' => 'Template', 'card' => 'Card', 'list_legacy' => 'Lista (Legacy)', 'compact' => 'Compatto', 'editorial_minimal' => 'Editoriale minimale', 'recommended_highlight' => 'Consigliato in evidenza', 'comparison_table' => 'Tabella di confronto', 'premium_requires_pro' => 'I template premium richiedono la versione PRO. Card e Lista restano disponibili per i layout esistenti.', 'theme' => 'Tema', 'default' => 'Predefinito', 'custom' => 'Personalizzato', 'theme_desc' => 'Scegli un tema pronto oppure seleziona Personalizzato per modificare tutti i colori.', 'custom_colors' => 'Colori stile personalizzato', 'text_color' => 'Colore testo', 'title_color' => 'Colore titolo', 'price_color' => 'Colore prezzo', 'box_bg' => 'Sfondo box', 'box_hover_bg' => 'Sfondo box al passaggio del mouse', 'ticket_bg' => 'Sfondo etichetta', 'discount_color' => 'Colore sconto', 'example_abbr' => 'Es.', 'cta_text' => 'Testo pulsante CTA', 'cta_placeholder' => 'Automatico — localizzato (es. Vedi su Amazon)', 'cta_desc' => 'Lascia vuoto per usare automaticamente la CTA tradotta. Inserisci un testo solo se vuoi un’etichetta personalizzata fissa.', 'optional_badge' => 'Badge opzionale', 'our_pick' => 'La nostra scelta', 'product_highlights' => 'Caratteristiche prodotto', 'show_features' => 'Mostra i punti elenco disponibili nei layout premium', 'template_shortcode_value' => 'Valore shortcode', 'template_shortcode_desc' => 'Usa questo valore solo quando vuoi sovrascrivere il template globale per uno specifico shortcode.', 'highlights_word_limit' => 'Limite parole caratteristiche', 'highlights_word_limit_desc' => 'Numero massimo totale di parole mostrate nelle caratteristiche di ciascun prodotto. Usa 0 per nessun limite.', 'words' => 'parole', 'gallery_choose' => 'Scegli un layout globale qui sopra oppure sovrascrivilo nel singolo shortcode.', 'unlock_premium' => 'Sblocca i layout premium', 'shortcode_format' => 'Formato shortcode', 'add_shortcode' => 'Aggiungi questo shortcode nell’articolo/pagina', 'only_pro' => 'Solo versione PRO', 'pro_attributes_desc' => 'Gli attributi PRO sovrascrivono le impostazioni globali solo per il singolo shortcode. Gli shortcode esistenti continuano a usare lo stile legacy già salvato.', 'activate_pro' => 'Attiva PRO', 'pro_active' => 'PRO attiva.', 'reset_free' => 'Reset (torna alla FREE)', 'pro_benefit' => 'Nascondi il watermark e abilita le opzioni di design personalizzato', 'license_code' => 'Codice licenza', 'activate' => 'Attiva', 'purchase_pro' => 'Acquista versione PRO', 'settings_saved' => 'Impostazioni salvate correttamente.', 'test_saved' => 'Le impostazioni correnti sono state salvate automaticamente prima del test API.', 'pro_activate_success'=>'Versione PRO attivata correttamente.', 'license_invalid'=>'Codice licenza non valido.', 'pro_disabled'=>'PRO disattivata. Stai usando la versione FREE.' ], 'es' => [ 'settings'=>'Ajustes','upgrade_pro'=>'Actualizar a PRO','hero_desc'=>'Conecta la API de Amazon, verifica la conexión y configura cómo aparecen los productos en tus shortcodes.','api_success'=>'Conexión API correcta','api_failed'=>'Error de conexión API','amazon_returned'=>'Amazon devolvió %d producto(s) para la prueba.','how_fix'=>'Cómo solucionarlo','show_debug'=>'Mostrar depuración técnica','technical_debug'=>'Depuración técnica','copy_debug'=>'Copiar depuración','copied'=>'Copiado','api_connection'=>'Conexión API de Amazon','api_connection_desc'=>'Selecciona el tipo de API e introduce las credenciales de ese perfil.','legacy_api'=>'API heredada:','legacy_api_desc'=>'Se mantiene un perfil heredado basado en claves AWS solo para instalaciones existentes. Las nuevas configuraciones deben usar Creators API v3.','legacy_credentials'=>'Credenciales heredadas:','legacy_credentials_desc'=>'Creators API v2/Cognito se mantiene para instalaciones existentes. Las nuevas credenciales deben usar Creators API v3.','api_version'=>'Versión API','recommended'=>'Recomendada','legacy'=>'Heredada','profile_isolated'=>'Cada tipo de API conserva sus propias credenciales, Partner Tag y Marketplace.','v3_desc'=>'Credenciales actuales de Login with Amazon. La región (v3.1/v3.2/v3.3) se detecta automáticamente.','v2_desc'=>'Credenciales antiguas basadas en Cognito. La región se detecta automáticamente.','paapi_desc'=>'Integración heredada mediante claves AWS.','credential_id'=>'ID de credencial','credential_secret'=>'Secreto de credencial','usually_starts'=>'Normalmente empieza por %s','secret_saved_keep'=>'Secreto guardado — déjalo vacío para conservarlo','secret_saved'=>'Secreto guardado.','secret_keep_replace'=>'Déjalo vacío para conservarlo o introduce un valor nuevo para sustituirlo.','partner_tag'=>'Partner Tag','partner_tag_desc'=>'Usa el Store ID / Tracking ID registrado para el marketplace seleccionado.','marketplace'=>'Marketplace','marketplace_desc'=>'Las credenciales pueden funcionar globalmente, pero el Partner Tag debe pertenecer exactamente al marketplace seleccionado.','enter_legacy_secret'=>'Introduce el secreto v2 heredado','enter_original_legacy_secret'=>'Introduce el secreto Cognito v2 original.','warning'=>'Aviso:','v2_secret_warning'=>'el valor guardado parece un secreto v3. Si OAuth devuelve invalid_client, sustitúyelo por el secreto v2 original.','access_key'=>'Access Key','secret_key'=>'Secret Key','enter_secret_key'=>'Introduce Secret Key','save_settings'=>'Guardar ajustes','test_api'=>'Probar conexión API','test_saves'=>'La prueba guarda automáticamente primero los valores actuales.','product_display'=>'Visualización de productos','product_display_desc'=>'Aspecto predeterminado de la cuadrícula de productos Amazon.','num_products'=>'Número de productos','free_products'=>'La versión FREE admite hasta 3 productos.','upgrade_for_9'=>'Actualiza a PRO para usar hasta 9.','num_columns'=>'Número de columnas','image_size'=>'Tamaño de imagen','font_size'=>'Tamaño de fuente','examples'=>'Ejemplos:','box_language'=>'Idioma de los cuadros','box_language_desc'=>'Automático usa el idioma del sitio WordPress. Puedes forzar un idioma aquí o por shortcode con language="it".','automatic_site_language'=>'Automático (idioma del sitio)','connection_status'=>'Estado de conexión','profile_configured'=>'Perfil configurado','use_test_after'=>'Usa Probar conexión API después de cambiar credenciales, Partner Tag o Marketplace.','setup_required'=>'Configuración necesaria','complete_profile'=>'Completa el perfil API seleccionado.','shortcode'=>'Shortcode','shortcode_override'=>'También puedes usar number="x" para cambiar el número predeterminado de productos.','marketplace_tip'=>'Consejo sobre Marketplace','marketplace_tip_desc'=>'Con Creators API la credencial puede autenticar globalmente, pero el Partner Tag debe ser válido para el marketplace seleccionado.','gallery_title'=>'Galería de diseños Premium','gallery_desc'=>'Tarjetas profesionales, cajas de ofertas y tablas comparativas para contenidos de afiliación más eficaces.','open_display'=>'Abrir Visualización (PRO)','explore_pro'=>'Explorar diseños PRO','modern_card'=>'Tarjeta moderna','editorial'=>'Editorial','recommended_label'=>'Recomendado','deal_box'=>'Caja de oferta','comparison'=>'Comparación','view_amazon'=>'Ver en Amazon','display_pro'=>'Visualización (PRO)','pro_only_options'=>'Estas opciones solo están disponibles con la versión PRO.','template'=>'Plantilla','card'=>'Tarjeta','list_legacy'=>'Lista (heredada)','compact'=>'Compacta','editorial_minimal'=>'Editorial minimalista','recommended_highlight'=>'Recomendado destacado','comparison_table'=>'Tabla comparativa','premium_requires_pro'=>'Las plantillas premium requieren PRO. Tarjeta y Lista siguen disponibles para diseños existentes.','theme'=>'Tema','default'=>'Predeterminado','custom'=>'Personalizado','theme_desc'=>'Elige un tema preparado o selecciona Personalizado para modificar todos los colores.','custom_colors'=>'Colores de estilo personalizado','text_color'=>'Color del texto','title_color'=>'Color del título','price_color'=>'Color del precio','box_bg'=>'Fondo del cuadro','box_hover_bg'=>'Fondo al pasar el ratón','ticket_bg'=>'Fondo de etiqueta','discount_color'=>'Color del descuento','example_abbr'=>'Ej.','cta_text'=>'Texto del botón CTA','cta_placeholder'=>'Automático — localizado (p. ej. Ver en Amazon)','cta_desc'=>'Déjalo vacío para usar la CTA traducida automáticamente. Introduce texto solo si quieres una etiqueta personalizada fija.','optional_badge'=>'Insignia opcional','our_pick'=>'Nuestra elección','product_highlights'=>'Características del producto','show_features'=>'Mostrar viñetas disponibles en los diseños premium','template_shortcode_value'=>'Valor del shortcode','template_shortcode_desc'=>'Usa este valor solo si quieres sustituir la plantilla global para un shortcode concreto.','highlights_word_limit'=>'Límite de palabras de características','highlights_word_limit_desc'=>'Número máximo total de palabras mostradas en las características de cada producto. Usa 0 para no limitar.','words'=>'palabras','gallery_choose'=>'Elige un diseño global arriba o modifícalo por shortcode.','unlock_premium'=>'Desbloquear diseños premium','shortcode_format'=>'Formato del shortcode','add_shortcode'=>'Añade este shortcode en tu entrada/página','only_pro'=>'Solo versión PRO','pro_attributes_desc'=>'Los atributos PRO sustituyen los ajustes globales solo para un shortcode. Los shortcodes existentes mantienen su estilo heredado guardado.','activate_pro'=>'Activar PRO','pro_active'=>'PRO activa.','reset_free'=>'Restablecer (volver a FREE)','pro_benefit'=>'Oculta la marca de agua y activa opciones de diseño personalizadas','license_code'=>'Código de licencia','activate'=>'Activar','purchase_pro'=>'Comprar versión PRO','settings_saved'=>'Ajustes guardados correctamente.','test_saved'=>'Los ajustes actuales se guardaron automáticamente antes de ejecutar la prueba API.','pro_activate_success'=>'Versión PRO activada correctamente.','license_invalid'=>'Código de licencia no válido.','pro_disabled'=>'PRO desactivada. Estás usando la versión FREE.' ], 'fr' => [ 'settings'=>'Réglages','upgrade_pro'=>'Passer à PRO','hero_desc'=>'Connectez votre API Amazon, vérifiez-la immédiatement et configurez l’affichage des produits dans vos shortcodes.','api_success'=>'Connexion API réussie','api_failed'=>'Échec de la connexion API','amazon_returned'=>'Amazon a renvoyé %d produit(s) pour la requête de test.','how_fix'=>'Comment corriger','show_debug'=>'Afficher le débogage technique','technical_debug'=>'Débogage technique','copy_debug'=>'Copier le débogage','copied'=>'Copié','api_connection'=>'Connexion API Amazon','api_connection_desc'=>'Sélectionnez le type d’API et saisissez les identifiants de ce profil.','legacy_api'=>'API héritée :','legacy_api_desc'=>'Un profil hérité basé sur des clés AWS est conservé uniquement pour les installations existantes. Les nouvelles configurations doivent utiliser Creators API v3.','legacy_credentials'=>'Identifiants hérités :','legacy_credentials_desc'=>'Creators API v2/Cognito est conservée pour les installations existantes. Les nouveaux identifiants doivent utiliser Creators API v3.','api_version'=>'Version API','recommended'=>'Recommandée','legacy'=>'Héritée','profile_isolated'=>'Chaque type d’API conserve ses propres identifiants, Partner Tag et Marketplace.','v3_desc'=>'Identifiants Login with Amazon actuels. La région (v3.1/v3.2/v3.3) est détectée automatiquement.','v2_desc'=>'Anciens identifiants basés sur Cognito. La région est détectée automatiquement.','paapi_desc'=>'Intégration héritée avec clés AWS.','credential_id'=>'ID d’identifiant','credential_secret'=>'Secret de l’identifiant','usually_starts'=>'Commence généralement par %s','secret_saved_keep'=>'Secret enregistré — laissez vide pour le conserver','secret_saved'=>'Secret enregistré.','secret_keep_replace'=>'Laissez vide pour le conserver ou saisissez une nouvelle valeur pour le remplacer.','partner_tag'=>'Partner Tag','partner_tag_desc'=>'Utilisez le Store ID / Tracking ID enregistré pour le marketplace sélectionné.','marketplace'=>'Marketplace','marketplace_desc'=>'Les identifiants peuvent fonctionner globalement, mais le Partner Tag doit appartenir exactement au marketplace sélectionné.','enter_legacy_secret'=>'Saisissez le secret v2 hérité','enter_original_legacy_secret'=>'Saisissez le secret Cognito v2 d’origine.','warning'=>'Attention :','v2_secret_warning'=>'la valeur enregistrée ressemble à un secret v3. Si OAuth renvoie invalid_client, remplacez-la par le secret v2 d’origine.','access_key'=>'Access Key','secret_key'=>'Secret Key','enter_secret_key'=>'Saisissez la Secret Key','save_settings'=>'Enregistrer les réglages','test_api'=>'Tester la connexion API','test_saves'=>'Le test enregistre automatiquement les valeurs actuelles avant de démarrer.','product_display'=>'Affichage des produits','product_display_desc'=>'Apparence par défaut de la grille de produits Amazon.','num_products'=>'Nombre de produits','free_products'=>'La version FREE prend en charge jusqu’à 3 produits.','upgrade_for_9'=>'Passez à PRO pour aller jusqu’à 9.','num_columns'=>'Nombre de colonnes','image_size'=>'Taille de l’image','font_size'=>'Taille de police','examples'=>'Exemples :','box_language'=>'Langue des encadrés','box_language_desc'=>'Automatique utilise la langue du site WordPress. Vous pouvez forcer une langue ici ou par shortcode avec language="it".','automatic_site_language'=>'Automatique (langue du site)','connection_status'=>'État de la connexion','profile_configured'=>'Profil configuré','use_test_after'=>'Utilisez Tester la connexion API après modification des identifiants, du Partner Tag ou du Marketplace.','setup_required'=>'Configuration requise','complete_profile'=>'Complétez le profil API sélectionné.','shortcode'=>'Shortcode','shortcode_override'=>'Vous pouvez aussi utiliser number="x" pour remplacer le nombre de produits par défaut.','marketplace_tip'=>'Conseil Marketplace','marketplace_tip_desc'=>'Avec Creators API, l’identifiant peut s’authentifier globalement, mais le Partner Tag doit être valide pour le marketplace sélectionné.','gallery_title'=>'Galerie de mises en page Premium','gallery_desc'=>'Cartes produit professionnelles, encadrés d’offres et tableaux comparatifs pour un contenu affilié plus efficace.','open_display'=>'Ouvrir Affichage (PRO)','explore_pro'=>'Découvrir les mises en page PRO','modern_card'=>'Carte moderne','editorial'=>'Éditorial','recommended_label'=>'Recommandé','deal_box'=>'Encadré offre','comparison'=>'Comparaison','view_amazon'=>'Voir sur Amazon','display_pro'=>'Affichage (PRO)','pro_only_options'=>'Ces options sont disponibles uniquement avec la version PRO.','template'=>'Modèle','card'=>'Carte','list_legacy'=>'Liste (héritée)','compact'=>'Compact','editorial_minimal'=>'Éditorial minimal','recommended_highlight'=>'Recommandé en évidence','comparison_table'=>'Tableau comparatif','premium_requires_pro'=>'Les modèles premium nécessitent PRO. Carte et Liste restent disponibles pour les mises en page existantes.','theme'=>'Thème','default'=>'Par défaut','custom'=>'Personnalisé','theme_desc'=>'Choisissez un thème prêt à l’emploi ou Personnalisé pour modifier toutes les couleurs.','custom_colors'=>'Couleurs du style personnalisé','text_color'=>'Couleur du texte','title_color'=>'Couleur du titre','price_color'=>'Couleur du prix','box_bg'=>'Fond de l’encadré','box_hover_bg'=>'Fond au survol','ticket_bg'=>'Fond de l’étiquette','discount_color'=>'Couleur de remise','example_abbr'=>'Ex.','cta_text'=>'Texte du bouton CTA','cta_placeholder'=>'Automatique — localisé (ex. Voir sur Amazon)','cta_desc'=>'Laissez vide pour utiliser automatiquement la CTA traduite. Saisissez du texte uniquement pour une étiquette personnalisée fixe.','optional_badge'=>'Badge facultatif','our_pick'=>'Notre choix','product_highlights'=>'Points forts du produit','show_features'=>'Afficher les puces de caractéristiques disponibles dans les mises en page premium','template_shortcode_value'=>'Valeur du shortcode','template_shortcode_desc'=>'Utilisez cette valeur uniquement pour remplacer le modèle global dans un shortcode précis.','highlights_word_limit'=>'Limite de mots des points forts','highlights_word_limit_desc'=>'Nombre total maximal de mots affichés dans les points forts de chaque produit. Utilisez 0 pour aucune limite.','words'=>'mots','gallery_choose'=>'Choisissez une mise en page globale ci-dessus ou remplacez-la par shortcode.','unlock_premium'=>'Débloquer les mises en page premium','shortcode_format'=>'Format du shortcode','add_shortcode'=>'Ajoutez ce shortcode dans votre article/page','only_pro'=>'Version PRO uniquement','pro_attributes_desc'=>'Les attributs PRO remplacent les réglages globaux uniquement pour un shortcode. Les shortcodes existants conservent leur style hérité enregistré.','activate_pro'=>'Activer PRO','pro_active'=>'PRO active.','reset_free'=>'Réinitialiser (retour à FREE)','pro_benefit'=>'Masquez le filigrane et activez les options de design personnalisées','license_code'=>'Code de licence','activate'=>'Activer','purchase_pro'=>'Acheter la version PRO','settings_saved'=>'Réglages enregistrés.','test_saved'=>'Les réglages actuels ont été enregistrés automatiquement avant le test API.','pro_activate_success'=>'Version PRO activée avec succès.','license_invalid'=>'Code de licence invalide.','pro_disabled'=>'PRO désactivée. Vous utilisez la version FREE.' ], 'de' => [ 'settings'=>'Einstellungen','upgrade_pro'=>'Auf PRO upgraden','hero_desc'=>'Verbinden Sie Ihre Amazon-API, prüfen Sie die Verbindung sofort und konfigurieren Sie die Produktdarstellung in Shortcodes.','api_success'=>'API-Verbindung erfolgreich','api_failed'=>'API-Verbindung fehlgeschlagen','amazon_returned'=>'Amazon hat %d Produkt(e) für die Testanfrage zurückgegeben.','how_fix'=>'So beheben Sie das Problem','show_debug'=>'Technisches Debug anzeigen','technical_debug'=>'Technisches Debug','copy_debug'=>'Debug kopieren','copied'=>'Kopiert','api_connection'=>'Amazon API-Verbindung','api_connection_desc'=>'Wählen Sie den API-Typ und geben Sie die Zugangsdaten für dieses Profil ein.','legacy_api'=>'Legacy-API:','legacy_api_desc'=>'Ein älteres AWS-Schlüsselprofil bleibt nur für bestehende Installationen erhalten. Neue Einrichtungen sollten Creators API v3 verwenden.','legacy_credentials'=>'Legacy-Zugangsdaten:','legacy_credentials_desc'=>'Creators API v2/Cognito bleibt für bestehende Installationen erhalten. Neue Zugangsdaten sollten Creators API v3 verwenden.','api_version'=>'API-Version','recommended'=>'Empfohlen','legacy'=>'Legacy','profile_isolated'=>'Jeder API-Typ speichert eigene Zugangsdaten, Partner Tag und Marketplace.','v3_desc'=>'Aktuelle Login-with-Amazon-Zugangsdaten. Die Region (v3.1/v3.2/v3.3) wird automatisch erkannt.','v2_desc'=>'Ältere Cognito-Zugangsdaten. Die Region wird automatisch erkannt.','paapi_desc'=>'Legacy-Integration mit AWS-Schlüsseln.','credential_id'=>'Credential ID','credential_secret'=>'Credential Secret','usually_starts'=>'Beginnt normalerweise mit %s','secret_saved_keep'=>'Secret gespeichert — leer lassen, um es zu behalten','secret_saved'=>'Secret gespeichert.','secret_keep_replace'=>'Leer lassen, um es zu behalten, oder einen neuen Wert zum Ersetzen eingeben.','partner_tag'=>'Partner Tag','partner_tag_desc'=>'Verwenden Sie die Store ID / Tracking ID des ausgewählten Marketplace.','marketplace'=>'Marketplace','marketplace_desc'=>'Zugangsdaten können global funktionieren, aber der Partner Tag muss genau zu diesem Marketplace gehören.','enter_legacy_secret'=>'Legacy-v2-Credential-Secret eingeben','enter_original_legacy_secret'=>'Originales Cognito-v2-Credential-Secret eingeben.','warning'=>'Warnung:','v2_secret_warning'=>'der gespeicherte Wert sieht wie ein v3-Secret aus. Wenn OAuth invalid_client zurückgibt, ersetzen Sie ihn durch das ursprüngliche v2-Secret.','access_key'=>'Access Key','secret_key'=>'Secret Key','enter_secret_key'=>'Secret Key eingeben','save_settings'=>'Einstellungen speichern','test_api'=>'API-Verbindung testen','test_saves'=>'Der Test speichert zuerst automatisch die aktuellen Werte.','product_display'=>'Produktdarstellung','product_display_desc'=>'Standarddarstellung des Amazon-Produktrasters.','num_products'=>'Anzahl Produkte','free_products'=>'Die FREE-Version unterstützt bis zu 3 Produkte.','upgrade_for_9'=>'Mit PRO sind bis zu 9 möglich.','num_columns'=>'Anzahl Spalten','image_size'=>'Bildgröße','font_size'=>'Schriftgröße','examples'=>'Beispiele:','box_language'=>'Box-Sprache','box_language_desc'=>'Automatisch verwendet die WordPress-Sitesprache. Eine Sprache kann hier oder per Shortcode mit language="it" erzwungen werden.','automatic_site_language'=>'Automatisch (Sitesprache)','connection_status'=>'Verbindungsstatus','profile_configured'=>'Profil konfiguriert','use_test_after'=>'Testen Sie die API-Verbindung nach Änderungen an Zugangsdaten, Partner Tag oder Marketplace.','setup_required'=>'Einrichtung erforderlich','complete_profile'=>'Vervollständigen Sie das ausgewählte API-Profil.','shortcode'=>'Shortcode','shortcode_override'=>'Mit number="x" können Sie auch die Standardanzahl der Produkte überschreiben.','marketplace_tip'=>'Marketplace-Hinweis','marketplace_tip_desc'=>'Bei Creators API kann sich die Zugangsdaten global authentifizieren, der Partner Tag muss jedoch für den ausgewählten Marketplace gültig sein.','gallery_title'=>'Premium-Layout-Galerie','gallery_desc'=>'Professionelle Produktkarten, Deal-Boxen und Vergleichstabellen für wirkungsvollere Affiliate-Inhalte.','open_display'=>'Darstellung (PRO) öffnen','explore_pro'=>'PRO-Layouts entdecken','modern_card'=>'Moderne Karte','editorial'=>'Editorial','recommended_label'=>'Empfohlen','deal_box'=>'Deal-Box','comparison'=>'Vergleich','view_amazon'=>'Bei Amazon ansehen','display_pro'=>'Darstellung (PRO)','pro_only_options'=>'Diese Optionen sind nur in der PRO-Version verfügbar.','template'=>'Vorlage','card'=>'Karte','list_legacy'=>'Liste (Legacy)','compact'=>'Kompakt','editorial_minimal'=>'Editorial Minimal','recommended_highlight'=>'Empfohlen hervorgehoben','comparison_table'=>'Vergleichstabelle','premium_requires_pro'=>'Premium-Vorlagen benötigen PRO. Karte und Liste bleiben für bestehende Layouts verfügbar.','theme'=>'Theme','default'=>'Standard','custom'=>'Benutzerdefiniert','theme_desc'=>'Wählen Sie ein fertiges Theme oder Benutzerdefiniert, um alle Farben anzupassen.','custom_colors'=>'Benutzerdefinierte Stilfarben','text_color'=>'Textfarbe','title_color'=>'Titelfarbe','price_color'=>'Preisfarbe','box_bg'=>'Box-Hintergrund','box_hover_bg'=>'Box-Hintergrund bei Hover','ticket_bg'=>'Label-Hintergrund','discount_color'=>'Rabattfarbe','example_abbr'=>'Z. B.','cta_text'=>'CTA-Button-Text','cta_placeholder'=>'Automatisch — lokalisiert (z. B. Bei Amazon ansehen)','cta_desc'=>'Leer lassen, um automatisch die übersetzte CTA zu verwenden. Nur für eine feste eigene Beschriftung Text eingeben.','optional_badge'=>'Optionales Badge','our_pick'=>'Unsere Wahl','product_highlights'=>'Produkt-Highlights','show_features'=>'Verfügbare Feature-Punkte in Premium-Layouts anzeigen','template_shortcode_value'=>'Shortcode-Wert','template_shortcode_desc'=>'Diesen Wert nur verwenden, wenn die globale Vorlage für einen bestimmten Shortcode überschrieben werden soll.','highlights_word_limit'=>'Wortlimit für Produkt-Highlights','highlights_word_limit_desc'=>'Maximale Gesamtzahl der Wörter, die pro Produkt in den Highlights angezeigt werden. 0 bedeutet unbegrenzt.','words'=>'Wörter','gallery_choose'=>'Wählen Sie oben ein globales Layout oder überschreiben Sie es pro Shortcode.','unlock_premium'=>'Premium-Layouts freischalten','shortcode_format'=>'Shortcode-Format','add_shortcode'=>'Diesen Shortcode in Beitrag/Seite einfügen','only_pro'=>'Nur PRO-Version','pro_attributes_desc'=>'PRO-Attribute überschreiben globale Anzeigeeinstellungen nur für einen Shortcode. Bestehende Shortcodes verwenden weiterhin ihren gespeicherten Legacy-Stil.','activate_pro'=>'PRO aktivieren','pro_active'=>'PRO aktiv.','reset_free'=>'Zurücksetzen (zur FREE-Version)','pro_benefit'=>'Wasserzeichen ausblenden und benutzerdefinierte Designoptionen aktivieren','license_code'=>'Lizenzcode','activate'=>'Aktivieren','purchase_pro'=>'PRO-Version kaufen','settings_saved'=>'Einstellungen erfolgreich gespeichert.','test_saved'=>'Die aktuellen Einstellungen wurden vor dem API-Test automatisch gespeichert.','pro_activate_success'=>'PRO-Version erfolgreich aktiviert.','license_invalid'=>'Ungültiger Lizenzcode.','pro_disabled'=>'PRO deaktiviert. Sie verwenden die FREE-Version.' ], 'pt' => [ 'settings'=>'Definições','upgrade_pro'=>'Atualizar para PRO','hero_desc'=>'Ligue a API da Amazon, teste a ligação imediatamente e configure como os produtos aparecem nos shortcodes.','api_success'=>'Ligação API bem-sucedida','api_failed'=>'Falha na ligação API','amazon_returned'=>'A Amazon devolveu %d produto(s) no pedido de teste.','how_fix'=>'Como corrigir','show_debug'=>'Mostrar depuração técnica','technical_debug'=>'Depuração técnica','copy_debug'=>'Copiar depuração','copied'=>'Copiado','api_connection'=>'Ligação à API Amazon','api_connection_desc'=>'Selecione o tipo de API e introduza as credenciais desse perfil.','legacy_api'=>'API antiga:','legacy_api_desc'=>'Um perfil legado baseado em chaves AWS é mantido apenas para instalações existentes. Novas configurações devem usar Creators API v3.','legacy_credentials'=>'Credenciais antigas:','legacy_credentials_desc'=>'Creators API v2/Cognito é mantida para instalações existentes. Novas credenciais devem usar Creators API v3.','api_version'=>'Versão da API','recommended'=>'Recomendada','legacy'=>'Antiga','profile_isolated'=>'Cada tipo de API mantém as suas próprias credenciais, Partner Tag e Marketplace.','v3_desc'=>'Credenciais atuais Login with Amazon. A região (v3.1/v3.2/v3.3) é detetada automaticamente.','v2_desc'=>'Credenciais antigas baseadas em Cognito. A região é detetada automaticamente.','paapi_desc'=>'Integração antiga com chaves AWS.','credential_id'=>'ID da credencial','credential_secret'=>'Segredo da credencial','usually_starts'=>'Normalmente começa por %s','secret_saved_keep'=>'Segredo guardado — deixe vazio para manter','secret_saved'=>'Segredo guardado.','secret_keep_replace'=>'Deixe vazio para manter ou introduza um novo valor para substituir.','partner_tag'=>'Partner Tag','partner_tag_desc'=>'Use o Store ID / Tracking ID registado para o marketplace selecionado.','marketplace'=>'Marketplace','marketplace_desc'=>'As credenciais podem funcionar globalmente, mas o Partner Tag deve pertencer exatamente ao marketplace selecionado.','enter_legacy_secret'=>'Introduza o segredo v2 antigo','enter_original_legacy_secret'=>'Introduza o segredo Cognito v2 original.','warning'=>'Aviso:','v2_secret_warning'=>'o valor guardado parece um segredo v3. Se o OAuth devolver invalid_client, substitua-o pelo segredo v2 original.','access_key'=>'Access Key','secret_key'=>'Secret Key','enter_secret_key'=>'Introduza a Secret Key','save_settings'=>'Guardar definições','test_api'=>'Testar ligação API','test_saves'=>'O teste guarda automaticamente primeiro os valores atuais.','product_display'=>'Visualização de produtos','product_display_desc'=>'Aspeto predefinido da grelha de produtos Amazon.','num_products'=>'Número de produtos','free_products'=>'A versão FREE suporta até 3 produtos.','upgrade_for_9'=>'Atualize para PRO para usar até 9.','num_columns'=>'Número de colunas','image_size'=>'Tamanho da imagem','font_size'=>'Tamanho da fonte','examples'=>'Exemplos:','box_language'=>'Idioma dos boxes','box_language_desc'=>'Automático usa o idioma do site WordPress. Pode forçar um idioma aqui ou por shortcode com language="it".','automatic_site_language'=>'Automático (idioma do site)','connection_status'=>'Estado da ligação','profile_configured'=>'Perfil configurado','use_test_after'=>'Use Testar ligação API depois de alterar credenciais, Partner Tag ou Marketplace.','setup_required'=>'Configuração necessária','complete_profile'=>'Complete o perfil API selecionado.','shortcode'=>'Shortcode','shortcode_override'=>'Também pode usar number="x" para substituir o número predefinido de produtos.','marketplace_tip'=>'Dica de Marketplace','marketplace_tip_desc'=>'Com Creators API, a credencial pode autenticar globalmente, mas o Partner Tag deve ser válido para o marketplace selecionado.','gallery_title'=>'Galeria de layouts Premium','gallery_desc'=>'Cartões de produto profissionais, boxes de ofertas e tabelas de comparação para conteúdo afiliado mais eficaz.','open_display'=>'Abrir Visualização (PRO)','explore_pro'=>'Explorar layouts PRO','modern_card'=>'Cartão moderno','editorial'=>'Editorial','recommended_label'=>'Recomendado','deal_box'=>'Box de oferta','comparison'=>'Comparação','view_amazon'=>'Ver na Amazon','display_pro'=>'Visualização (PRO)','pro_only_options'=>'Estas opções estão disponíveis apenas na versão PRO.','template'=>'Modelo','card'=>'Cartão','list_legacy'=>'Lista (antiga)','compact'=>'Compacto','editorial_minimal'=>'Editorial minimalista','recommended_highlight'=>'Recomendado em destaque','comparison_table'=>'Tabela de comparação','premium_requires_pro'=>'Os modelos premium requerem PRO. Cartão e Lista continuam disponíveis para layouts existentes.','theme'=>'Tema','default'=>'Predefinido','custom'=>'Personalizado','theme_desc'=>'Escolha um tema pronto ou selecione Personalizado para alterar todas as cores.','custom_colors'=>'Cores do estilo personalizado','text_color'=>'Cor do texto','title_color'=>'Cor do título','price_color'=>'Cor do preço','box_bg'=>'Fundo do box','box_hover_bg'=>'Fundo do box ao passar o rato','ticket_bg'=>'Fundo da etiqueta','discount_color'=>'Cor do desconto','example_abbr'=>'Ex.','cta_text'=>'Texto do botão CTA','cta_placeholder'=>'Automático — localizado (ex. Ver na Amazon)','cta_desc'=>'Deixe vazio para usar automaticamente a CTA traduzida. Introduza texto apenas se quiser uma etiqueta personalizada fixa.','optional_badge'=>'Badge opcional','our_pick'=>'A nossa escolha','product_highlights'=>'Destaques do produto','show_features'=>'Mostrar os pontos de características disponíveis nos layouts premium','template_shortcode_value'=>'Valor do shortcode','template_shortcode_desc'=>'Use este valor apenas quando quiser substituir o modelo global num shortcode específico.','highlights_word_limit'=>'Limite de palavras dos destaques','highlights_word_limit_desc'=>'Número máximo total de palavras mostrado nos destaques de cada produto. Use 0 para não limitar.','words'=>'palavras','gallery_choose'=>'Escolha um layout global acima ou substitua-o por shortcode.','unlock_premium'=>'Desbloquear layouts premium','shortcode_format'=>'Formato do shortcode','add_shortcode'=>'Adicione este shortcode no artigo/página','only_pro'=>'Apenas versão PRO','pro_attributes_desc'=>'Os atributos PRO substituem as definições globais apenas para um shortcode. Os shortcodes existentes continuam a usar o estilo antigo guardado.','activate_pro'=>'Ativar PRO','pro_active'=>'PRO ativa.','reset_free'=>'Repor (voltar à FREE)','pro_benefit'=>'Ocultar a marca de água e ativar opções de design personalizadas','license_code'=>'Código de licença','activate'=>'Ativar','purchase_pro'=>'Comprar versão PRO','settings_saved'=>'Definições guardadas com sucesso.','test_saved'=>'As definições atuais foram guardadas automaticamente antes do teste API.','pro_activate_success'=>'Versão PRO ativada com sucesso.','license_invalid'=>'Código de licença inválido.','pro_disabled'=>'PRO desativada. Está a usar a versão FREE.' ], ]; } $lang = affiamsh_admin_language(); if (isset($strings[$lang][$key])) return $strings[$lang][$key]; return $strings['en'][$key] ?? $key; } /** * Cache versioning: incrementa una versione globale per invalidare immediatamente la cache * senza dover cancellare i transient (funziona anche con Redis/Memcached). */ function affiamsh_get_cache_ver() { $v = (int) get_option('affiamsh_cache_ver', 1); return ($v > 0) ? $v : 1; } function affiamsh_bump_cache_ver() { $v = affiamsh_get_cache_ver(); update_option('affiamsh_cache_ver', $v + 1, false); } /** * Invalida la cache quando salvi un post/pagina che contiene lo shortcode. */ add_action('save_post', function($post_id, $post, $update) { if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) return; if (!isset($post->post_content) || !is_string($post->post_content)) return; if (strpos($post->post_content, '[affiamsh_amazon') !== false) { affiamsh_bump_cache_ver(); } }, 10, 3); /** * Helpers API profile / migration. * * UI values: * - creators_v3 = current Creators API credentials (v3.1 / v3.2 / v3.3, auto-detected) * - creators_v2 = legacy Creators API credentials (v2.1 / v2.2 / v2.3, auto-detected) * - paapi5 = legacy Product Advertising API 5.0 */ function affiamsh_is_creators_api($api_version) { return in_array((string) $api_version, ['creators_v3', 'creators_v2', 'creators'], true); } function affiamsh_get_creators_major($api_version) { if ($api_version === 'creators_v2') return 2; if ($api_version === 'creators_v3') return 3; return 0; } function affiamsh_get_api_label($api_version) { if ($api_version === 'creators_v3') return 'Creators API v3'; if ($api_version === 'creators_v2') return 'Creators API v2 (' . affiamsh_admin_t('legacy') . ')'; if ($api_version === 'paapi5') return 'PA-API 5.0 (' . affiamsh_admin_t('legacy') . ')'; if ($api_version === 'creators') return 'Creators API'; return 'Amazon API'; } /** * Normalizza la versione esatta delle credenziali Creators API. */ function affiamsh_normalize_creators_version($version) { $version = strtolower(trim((string) $version)); $version = ltrim($version, 'v'); $allowed = ['3.1', '3.2', '3.3', '2.1', '2.2', '2.3']; return in_array($version, $allowed, true) ? $version : ''; } /** * La versione esatta identifica solo l'endpoint OAuth regionale. * Le credenziali Creators API possono poi essere usate globalmente; * il marketplace target resta indipendente. */ function affiamsh_infer_creators_version_from_marketplace($marketplace, $major = 3) { $marketplace = (string) $marketplace; $na = ['www.amazon.com', 'www.amazon.ca', 'www.amazon.com.mx', 'www.amazon.com.br']; $fe = ['www.amazon.co.jp', 'www.amazon.com.au', 'www.amazon.sg']; if (in_array($marketplace, $na, true)) { return $major === 2 ? '2.1' : '3.1'; } if (in_array($marketplace, $fe, true)) { return $major === 2 ? '2.3' : '3.3'; } return $major === 2 ? '2.2' : '3.2'; } function affiamsh_is_creators_v3($version) { return strpos(affiamsh_normalize_creators_version($version), '3.') === 0; } function affiamsh_get_creators_version_region_label($version) { $version = affiamsh_normalize_creators_version($version); $labels = [ '3.1' => 'North America', '3.2' => 'Europe', '3.3' => 'Far East', '2.1' => 'North America', '2.2' => 'Europe', '2.3' => 'Far East', ]; return $labels[$version] ?? 'Unknown'; } function affiamsh_get_creators_token_endpoint($version) { $version = affiamsh_normalize_creators_version($version); $endpoints = [ // Current Login with Amazon credentials '3.1' => 'https://api.amazon.com/auth/o2/token', '3.2' => 'https://api.amazon.co.uk/auth/o2/token', '3.3' => 'https://api.amazon.co.jp/auth/o2/token', // Legacy Cognito credentials '2.1' => 'https://creatorsapi.auth.us-east-1.amazoncognito.com/oauth2/token', '2.2' => 'https://creatorsapi.auth.eu-south-2.amazoncognito.com/oauth2/token', '2.3' => 'https://creatorsapi.auth.us-west-2.amazoncognito.com/oauth2/token', ]; return $endpoints[$version] ?? ''; } function affiamsh_get_creators_authorization_header($access_token, $version) { $version = affiamsh_normalize_creators_version($version); if (affiamsh_is_creators_v3($version)) { return 'Bearer ' . $access_token; } return 'Bearer ' . $access_token . ', Version ' . $version; } /** * Candidate endpoint order. The user never needs to choose 3.1/3.2/3.3. * We try the most likely endpoint first, then the other two, and remember success. */ function affiamsh_get_creators_version_candidates($major, $marketplace, $credential_id, $preferred_version = '') { $major = ((int) $major === 2) ? 2 : 3; $allowed = $major === 2 ? ['2.1', '2.2', '2.3'] : ['3.1', '3.2', '3.3']; $candidates = []; $preferred_version = affiamsh_normalize_creators_version($preferred_version); if ($preferred_version !== '' && strpos($preferred_version, $major . '.') === 0) { $candidates[] = $preferred_version; } $detected_key = 'affiamsh_oauth_region_' . md5($major . '|' . (string) $credential_id); $detected = affiamsh_normalize_creators_version(get_transient($detected_key)); if ($detected !== '' && strpos($detected, $major . '.') === 0) { $candidates[] = $detected; } // Marketplace is only a smart first guess, never a restriction. $candidates[] = affiamsh_infer_creators_version_from_marketplace($marketplace, $major); foreach ($allowed as $version) $candidates[] = $version; return array_values(array_unique(array_filter($candidates))); } /** * Single token request for one exact regional credential version. */ function affiamsh_request_oauth_token_for_version($credential_id, $credential_secret, $version) { $version = affiamsh_normalize_creators_version($version); if ($version === '') { return ['success' => false, 'error' => ['http_code' => 0, 'message' => 'Invalid credential version', 'body' => '']]; } $cache_key = 'affiamsh_oauth_token_' . md5($version . '|' . $credential_id . '|' . $credential_secret); $cached_token = get_transient($cache_key); if ($cached_token !== false && is_string($cached_token) && $cached_token !== '') { return ['success' => true, 'token' => $cached_token, 'cached' => true]; } $token_url = affiamsh_get_creators_token_endpoint($version); if ($token_url === '') { return ['success' => false, 'error' => ['http_code' => 0, 'message' => 'No token endpoint available', 'body' => '']]; } if (affiamsh_is_creators_v3($version)) { $response = wp_remote_post($token_url, [ 'headers' => ['Content-Type' => 'application/json'], 'body' => wp_json_encode([ 'grant_type' => 'client_credentials', 'client_id' => $credential_id, 'client_secret' => $credential_secret, 'scope' => 'creatorsapi::default', ]), 'timeout' => 15, ]); } else { $response = wp_remote_post($token_url, [ 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'], 'body' => [ 'grant_type' => 'client_credentials', 'client_id' => $credential_id, 'client_secret' => $credential_secret, 'scope' => 'creatorsapi/default', ], 'timeout' => 15, ]); } if (is_wp_error($response)) { return ['success' => false, 'error' => [ 'http_code' => 0, 'message' => $response->get_error_message(), 'body' => '', ]]; } $http_code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($http_code !== 200) { return ['success' => false, 'error' => [ 'http_code' => $http_code, 'message' => 'Token endpoint returned HTTP ' . $http_code, 'body' => $body, ]]; } $data = json_decode($body, true); if (empty($data['access_token'])) { return ['success' => false, 'error' => [ 'http_code' => $http_code, 'message' => 'Token endpoint response did not contain access_token', 'body' => $body, ]]; } $expires_in = isset($data['expires_in']) ? absint($data['expires_in']) : HOUR_IN_SECONDS; set_transient($cache_key, $data['access_token'], max(60, $expires_in - 300)); return ['success' => true, 'token' => $data['access_token'], 'cached' => false]; } /** * OAuth token with automatic regional endpoint detection. * v3: tries 3.1/3.2/3.3; v2: tries 2.1/2.2/2.3. */ function affiamsh_get_oauth_token($credential_id, $credential_secret, $major = 3, $marketplace = 'www.amazon.com', $preferred_version = '') { $major = ((int) $major === 2) ? 2 : 3; $GLOBALS['affiamsh_last_oauth_error'] = null; $GLOBALS['affiamsh_last_oauth_version'] = ''; $GLOBALS['affiamsh_oauth_attempts'] = []; $candidates = affiamsh_get_creators_version_candidates($major, $marketplace, $credential_id, $preferred_version); $last_error = null; foreach ($candidates as $version) { $result = affiamsh_request_oauth_token_for_version($credential_id, $credential_secret, $version); $attempt = [ 'version' => $version, 'endpoint' => affiamsh_get_creators_token_endpoint($version), 'success' => !empty($result['success']), ]; if (!empty($result['success'])) { $GLOBALS['affiamsh_last_oauth_version'] = $version; $attempt['cached'] = !empty($result['cached']); $GLOBALS['affiamsh_oauth_attempts'][] = $attempt; $detected_key = 'affiamsh_oauth_region_' . md5($major . '|' . (string) $credential_id); set_transient($detected_key, $version, 90 * DAY_IN_SECONDS); return $result['token']; } $last_error = $result['error'] ?? null; if (is_array($last_error)) { $attempt['http_code'] = $last_error['http_code'] ?? 0; $attempt['message'] = $last_error['message'] ?? ''; } $GLOBALS['affiamsh_oauth_attempts'][] = $attempt; } $GLOBALS['affiamsh_last_oauth_error'] = $last_error; return false; } /** * Migrazione automatica verso i tre profili separati. * Conserva i vecchi campi per backward compatibility/downgrade. */ function affiamsh_migrate_api_profiles() { $settings = get_option('affiamsh_plugin_settings', []); if (!is_array($settings)) $settings = []; $schema = (int) ($settings['affiamsh_profile_schema'] ?? 0); // Schema 24+ is already migrated. Never re-import legacy/common fields again. // This is critical: those old fields may have been touched by earlier plugin // versions or browser autofill and must not overwrite the separated profiles. if ($schema >= 24) return; $changed = false; // Very old PA-API fields: import only into an empty dedicated profile. if (!empty($settings['access_key']) && empty($settings['access_key_paapi5'])) { $settings['access_key_paapi5'] = $settings['access_key']; $changed = true; } if (!empty($settings['secret_key']) && empty($settings['secret_key_paapi5'])) { $settings['secret_key_paapi5'] = $settings['secret_key']; $changed = true; } /* * ONE-TIME Creators migration. * * The original plugin had one shared Creators profile: * credential_id_creators / credential_secret_creators / partner_tag / marketplace * * Versions 2.2/2.3 introduced dedicated v2/v3 profiles. From this version onward * the old shared fields are read only when the corresponding dedicated profile is * still EMPTY. Existing v2/v3 values are NEVER overwritten by legacy fields. */ $legacy_id = trim((string) ($settings['credential_id_creators'] ?? '')); $legacy_secret = (string) ($settings['credential_secret_creators'] ?? ''); $legacy_version = affiamsh_normalize_creators_version($settings['creators_credential_version'] ?? ''); $legacy_looks_v3 = strpos($legacy_id, 'amzn1.application-oa2-client.') === 0; $legacy_is_v3 = strpos($legacy_version, '3.') === 0 || $legacy_looks_v3; $suffix = $legacy_is_v3 ? 'v3' : 'v2'; if ($legacy_id !== '' || $legacy_secret !== '') { $target_id_key = 'credential_id_creators_' . $suffix; $target_secret_key = 'credential_secret_creators_' . $suffix; $target_tag_key = 'partner_tag_creators_' . $suffix; $target_market_key = 'marketplace_creators_' . $suffix; $target_hint_key = 'creators_' . $suffix . '_version_hint'; // Import only when the separated profile does not already contain credentials. // Never "repair" an existing profile from the shared legacy copy. if (empty($settings[$target_id_key]) && $legacy_id !== '') { $settings[$target_id_key] = $legacy_id; $changed = true; } if (empty($settings[$target_secret_key]) && $legacy_secret !== '') { $settings[$target_secret_key] = $legacy_secret; $changed = true; } if (empty($settings[$target_tag_key]) && !empty($settings['partner_tag'])) { $settings[$target_tag_key] = $settings['partner_tag']; $changed = true; } if (empty($settings[$target_market_key]) && !empty($settings['marketplace'])) { $settings[$target_market_key] = $settings['marketplace']; $changed = true; } if (empty($settings[$target_hint_key]) && $legacy_version !== '') { $settings[$target_hint_key] = $legacy_version; $changed = true; } if (($settings['api_version'] ?? '') === 'creators') { $settings['api_version'] = 'creators_' . $suffix; $changed = true; } } // Create a v2 backup only if none exists. Never refresh/overwrite an existing backup. if (!empty($settings['credential_id_creators_v2']) && empty($settings['legacy_v2_credential_id_backup'])) { $settings['legacy_v2_credential_id_backup'] = $settings['credential_id_creators_v2']; $settings['legacy_v2_credential_secret_backup'] = $settings['credential_secret_creators_v2'] ?? ''; $settings['legacy_v2_partner_tag_backup'] = $settings['partner_tag_creators_v2'] ?? ''; $settings['legacy_v2_marketplace_backup'] = $settings['marketplace_creators_v2'] ?? 'www.amazon.com'; $changed = true; } // Seed PA-API tag/marketplace from old common fields only when needed. if (!empty($settings['access_key_paapi5']) || !empty($settings['secret_key_paapi5'])) { if (empty($settings['partner_tag_paapi5']) && !empty($settings['partner_tag'])) { $settings['partner_tag_paapi5'] = $settings['partner_tag']; $changed = true; } if (empty($settings['marketplace_paapi5']) && !empty($settings['marketplace'])) { $settings['marketplace_paapi5'] = $settings['marketplace']; $changed = true; } } if (empty($settings['api_version'])) { if (!empty($settings['credential_id_creators_v2'])) { $settings['api_version'] = 'creators_v2'; } elseif (!empty($settings['credential_id_creators_v3'])) { $settings['api_version'] = 'creators_v3'; } elseif (!empty($settings['access_key_paapi5']) || !empty($settings['access_key'])) { $settings['api_version'] = 'paapi5'; } else { $settings['api_version'] = 'creators_v3'; } $changed = true; } $settings['affiamsh_profile_schema'] = 24; $changed = true; if ($changed) { update_option('affiamsh_plugin_settings', $settings); affiamsh_bump_cache_ver(); } } add_action('plugins_loaded', 'affiamsh_migrate_api_profiles', 5); /** * Funzione helper per ottenere le credenziali del profilo API selezionato. */ function affiamsh_get_credentials() { $settings = get_option('affiamsh_plugin_settings', []); // Safety net for plugin replacement/activation requests where plugins_loaded // may already have fired before the new file was loaded. if ((int) ($settings['affiamsh_profile_schema'] ?? 0) < 24) { affiamsh_migrate_api_profiles(); $settings = get_option('affiamsh_plugin_settings', []); } $api_version = $settings['api_version'] ?? 'creators_v3'; // Backward fallback if an older database still says "creators" before migration fires. if ($api_version === 'creators') { $old_version = affiamsh_normalize_creators_version($settings['creators_credential_version'] ?? ''); $looks_v3 = strpos((string) ($settings['credential_id_creators'] ?? ''), 'amzn1.application-oa2-client.') === 0; $api_version = (strpos($old_version, '2.') === 0 || (!$looks_v3 && strpos($old_version, '3.') !== 0)) ? 'creators_v2' : 'creators_v3'; } if ($api_version === 'creators_v3' || $api_version === 'creators_v2') { $suffix = $api_version === 'creators_v3' ? 'v3' : 'v2'; $major = $api_version === 'creators_v3' ? 3 : 2; $credential_id = $settings['credential_id_creators_' . $suffix] ?? ''; $credential_secret = $settings['credential_secret_creators_' . $suffix] ?? ''; $partner_tag = $settings['partner_tag_creators_' . $suffix] ?? ''; $marketplace = $settings['marketplace_creators_' . $suffix] ?? 'www.amazon.com'; $version_hint = affiamsh_normalize_creators_version($settings['creators_' . $suffix . '_version_hint'] ?? ''); if (empty($credential_id) || empty($credential_secret) || empty($partner_tag)) return null; return [ 'credential_id' => $credential_id, 'credential_secret' => $credential_secret, 'partner_tag' => $partner_tag, 'marketplace' => $marketplace, 'major' => $major, 'version_hint' => $version_hint, 'api_version' => $api_version, ]; } // PA-API 5.0 legacy profile. $access_key = $settings['access_key_paapi5'] ?? ''; $secret_key = $settings['secret_key_paapi5'] ?? ''; $partner_tag = $settings['partner_tag_paapi5'] ?? ($settings['partner_tag'] ?? ''); $marketplace = $settings['marketplace_paapi5'] ?? ($settings['marketplace'] ?? 'www.amazon.com'); if (empty($access_key) || empty($secret_key) || empty($partner_tag)) return null; $marketplaces = affiamsh_get_amazon_marketplaces(); $region = $marketplaces[$marketplace] ?? 'us-east-1'; return [ 'access_key' => $access_key, 'secret_key' => $secret_key, 'partner_tag' => $partner_tag, 'marketplace' => $marketplace, 'region' => $region, 'api_version' => 'paapi5', ]; } // Enqueue plugin styles add_action('wp_enqueue_scripts', function () { wp_enqueue_style( 'affiamsh-amazon-products-style', plugins_url('amazon-products.css', __FILE__), [], AFFIAMSH_VER ); }); // Add the settings menu add_action('admin_menu', function () { add_menu_page( 'Affiliate Amazon Shortcode ' . affiamsh_admin_t('settings'), 'Affiliate Amazon Shortcode', 'manage_options', 'affiamsh-amazon-api-settings', 'affiamsh_amazon_api_settings_page', 'dashicons-cart' ); }); // Get Amazon marketplaces function affiamsh_get_amazon_marketplaces() { // ✅ MAPPING UFFICIALE Amazon PA-API 5.0 // Source: https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html return [ // Europe - ALL use eu-west-1 for PA-API 5.0 'www.amazon.de' => 'eu-west-1', 'www.amazon.co.uk' => 'eu-west-1', 'www.amazon.fr' => 'eu-west-1', 'www.amazon.it' => 'eu-west-1', 'www.amazon.es' => 'eu-west-1', 'www.amazon.nl' => 'eu-west-1', 'www.amazon.se' => 'eu-west-1', 'www.amazon.pl' => 'eu-west-1', 'www.amazon.com.be' => 'eu-west-1', 'www.amazon.ie' => 'eu-west-1', // North America 'www.amazon.com' => 'us-east-1', 'www.amazon.ca' => 'us-east-1', 'www.amazon.com.mx' => 'us-east-1', // Far East 'www.amazon.co.jp' => 'us-west-2', 'www.amazon.com.au' => 'us-west-2', 'www.amazon.sg' => 'us-west-2', // India 'www.amazon.in' => 'eu-west-1', // Brazil 'www.amazon.com.br' => 'us-east-1', // Middle East & Africa 'www.amazon.ae' => 'eu-west-1', 'www.amazon.sa' => 'eu-west-1', 'www.amazon.eg' => 'eu-west-1', 'www.amazon.com.tr' => 'eu-west-1', ]; } /** * Friendly marketplace labels for the admin UI. */ function affiamsh_get_marketplace_labels() { $names = [ 'en' => ['us'=>'United States','ca'=>'Canada','mx'=>'Mexico','br'=>'Brazil','uk'=>'United Kingdom','de'=>'Germany','fr'=>'France','it'=>'Italy','es'=>'Spain','nl'=>'Netherlands','be'=>'Belgium','ie'=>'Ireland','se'=>'Sweden','pl'=>'Poland','in'=>'India','ae'=>'United Arab Emirates','sa'=>'Saudi Arabia','eg'=>'Egypt','tr'=>'Turkey','jp'=>'Japan','au'=>'Australia','sg'=>'Singapore'], 'it' => ['us'=>'Stati Uniti','ca'=>'Canada','mx'=>'Messico','br'=>'Brasile','uk'=>'Regno Unito','de'=>'Germania','fr'=>'Francia','it'=>'Italia','es'=>'Spagna','nl'=>'Paesi Bassi','be'=>'Belgio','ie'=>'Irlanda','se'=>'Svezia','pl'=>'Polonia','in'=>'India','ae'=>'Emirati Arabi Uniti','sa'=>'Arabia Saudita','eg'=>'Egitto','tr'=>'Turchia','jp'=>'Giappone','au'=>'Australia','sg'=>'Singapore'], 'es' => ['us'=>'Estados Unidos','ca'=>'Canadá','mx'=>'México','br'=>'Brasil','uk'=>'Reino Unido','de'=>'Alemania','fr'=>'Francia','it'=>'Italia','es'=>'España','nl'=>'Países Bajos','be'=>'Bélgica','ie'=>'Irlanda','se'=>'Suecia','pl'=>'Polonia','in'=>'India','ae'=>'Emiratos Árabes Unidos','sa'=>'Arabia Saudí','eg'=>'Egipto','tr'=>'Turquía','jp'=>'Japón','au'=>'Australia','sg'=>'Singapur'], 'fr' => ['us'=>'États-Unis','ca'=>'Canada','mx'=>'Mexique','br'=>'Brésil','uk'=>'Royaume-Uni','de'=>'Allemagne','fr'=>'France','it'=>'Italie','es'=>'Espagne','nl'=>'Pays-Bas','be'=>'Belgique','ie'=>'Irlande','se'=>'Suède','pl'=>'Pologne','in'=>'Inde','ae'=>'Émirats arabes unis','sa'=>'Arabie saoudite','eg'=>'Égypte','tr'=>'Turquie','jp'=>'Japon','au'=>'Australie','sg'=>'Singapour'], 'de' => ['us'=>'Vereinigte Staaten','ca'=>'Kanada','mx'=>'Mexiko','br'=>'Brasilien','uk'=>'Vereinigtes Königreich','de'=>'Deutschland','fr'=>'Frankreich','it'=>'Italien','es'=>'Spanien','nl'=>'Niederlande','be'=>'Belgien','ie'=>'Irland','se'=>'Schweden','pl'=>'Polen','in'=>'Indien','ae'=>'Vereinigte Arabische Emirate','sa'=>'Saudi-Arabien','eg'=>'Ägypten','tr'=>'Türkei','jp'=>'Japan','au'=>'Australien','sg'=>'Singapur'], 'pt' => ['us'=>'Estados Unidos','ca'=>'Canadá','mx'=>'México','br'=>'Brasil','uk'=>'Reino Unido','de'=>'Alemanha','fr'=>'França','it'=>'Itália','es'=>'Espanha','nl'=>'Países Baixos','be'=>'Bélgica','ie'=>'Irlanda','se'=>'Suécia','pl'=>'Polónia','in'=>'Índia','ae'=>'Emirados Árabes Unidos','sa'=>'Arábia Saudita','eg'=>'Egito','tr'=>'Turquia','jp'=>'Japão','au'=>'Austrália','sg'=>'Singapura'], ]; $n = $names[affiamsh_admin_language()] ?? $names['en']; return [ 'www.amazon.com' => $n['us'] . ' — amazon.com', 'www.amazon.ca' => $n['ca'] . ' — amazon.ca', 'www.amazon.com.mx' => $n['mx'] . ' — amazon.com.mx', 'www.amazon.com.br' => $n['br'] . ' — amazon.com.br', 'www.amazon.co.uk' => $n['uk'] . ' — amazon.co.uk', 'www.amazon.de' => $n['de'] . ' — amazon.de', 'www.amazon.fr' => $n['fr'] . ' — amazon.fr', 'www.amazon.it' => $n['it'] . ' — amazon.it', 'www.amazon.es' => $n['es'] . ' — amazon.es', 'www.amazon.nl' => $n['nl'] . ' — amazon.nl', 'www.amazon.com.be' => $n['be'] . ' — amazon.com.be', 'www.amazon.ie' => $n['ie'] . ' — amazon.ie', 'www.amazon.se' => $n['se'] . ' — amazon.se', 'www.amazon.pl' => $n['pl'] . ' — amazon.pl', 'www.amazon.in' => $n['in'] . ' — amazon.in', 'www.amazon.ae' => $n['ae'] . ' — amazon.ae', 'www.amazon.sa' => $n['sa'] . ' — amazon.sa', 'www.amazon.eg' => $n['eg'] . ' — amazon.eg', 'www.amazon.com.tr' => $n['tr'] . ' — amazon.com.tr', 'www.amazon.co.jp' => $n['jp'] . ' — amazon.co.jp', 'www.amazon.com.au' => $n['au'] . ' — amazon.com.au', 'www.amazon.sg' => $n['sg'] . ' — amazon.sg', ]; } // Get image sizes function affiamsh_get_image_sizes() { return [ 'Medium' => 'Images.Primary.Medium', 'Large' => 'Images.Primary.Large', ]; } // Settings Page function affiamsh_amazon_api_settings_page() { $test_result = null; $page_message = ''; $page_message_type = 'success'; $marketplaces = affiamsh_get_amazon_marketplaces(); $marketplace_labels = affiamsh_get_marketplace_labels(); $image_sizes = affiamsh_get_image_sizes(); if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['affiamsh_api_settings_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['affiamsh_api_settings_nonce'])), 'affiamsh_save_settings')) { $existing_settings = get_option('affiamsh_plugin_settings', []); if (!is_array($existing_settings)) $existing_settings = []; $allowed_api_versions = ['creators_v3', 'creators_v2', 'paapi5']; $api_version = isset($_POST['api_version']) ? sanitize_text_field(wp_unslash($_POST['api_version'])) : ($existing_settings['api_version'] ?? 'creators_v3'); if (!in_array($api_version, $allowed_api_versions, true)) $api_version = 'creators_v3'; $sanitize_marketplace = function($post_key, $fallback) use ($marketplaces) { $value = isset($_POST[$post_key]) ? sanitize_text_field(wp_unslash($_POST[$post_key])) : $fallback; return isset($marketplaces[$value]) ? $value : 'www.amazon.com'; }; $settings = $existing_settings; $settings['api_version'] = $api_version; /* * Save only the active API profile. The Test button intentionally saves * the current form first, so users always test exactly what they see. */ if ($api_version === 'creators_v3') { $new_id = isset($_POST['credential_id_creators_v3']) ? sanitize_text_field(wp_unslash($_POST['credential_id_creators_v3'])) : ($existing_settings['credential_id_creators_v3'] ?? ''); $posted_secret = isset($_POST['credential_secret_creators_v3']) ? sanitize_text_field(wp_unslash($_POST['credential_secret_creators_v3'])) : ''; $settings['credential_id_creators_v3'] = $new_id; $settings['credential_secret_creators_v3'] = ($posted_secret !== '') ? $posted_secret : ($existing_settings['credential_secret_creators_v3'] ?? ''); if (($existing_settings['credential_id_creators_v3'] ?? '') !== $new_id || $posted_secret !== '') { $settings['creators_v3_version_hint'] = ''; } $settings['partner_tag_creators_v3'] = isset($_POST['partner_tag_creators_v3']) ? sanitize_text_field(wp_unslash($_POST['partner_tag_creators_v3'])) : ($existing_settings['partner_tag_creators_v3'] ?? ''); $settings['marketplace_creators_v3'] = $sanitize_marketplace('marketplace_creators_v3', $existing_settings['marketplace_creators_v3'] ?? 'www.amazon.com'); } elseif ($api_version === 'creators_v2') { $new_id = isset($_POST['credential_id_creators_v2']) ? sanitize_text_field(wp_unslash($_POST['credential_id_creators_v2'])) : ($existing_settings['credential_id_creators_v2'] ?? ''); $posted_secret = isset($_POST['credential_secret_creators_v2']) ? sanitize_text_field(wp_unslash($_POST['credential_secret_creators_v2'])) : ''; $settings['credential_id_creators_v2'] = $new_id; $settings['credential_secret_creators_v2'] = ($posted_secret !== '') ? $posted_secret : ($existing_settings['credential_secret_creators_v2'] ?? ''); if (($existing_settings['credential_id_creators_v2'] ?? '') !== $new_id || $posted_secret !== '') { $settings['creators_v2_version_hint'] = ''; } $settings['partner_tag_creators_v2'] = isset($_POST['partner_tag_creators_v2']) ? sanitize_text_field(wp_unslash($_POST['partner_tag_creators_v2'])) : ($existing_settings['partner_tag_creators_v2'] ?? ''); $settings['marketplace_creators_v2'] = $sanitize_marketplace('marketplace_creators_v2', $existing_settings['marketplace_creators_v2'] ?? 'www.amazon.com'); $settings['legacy_v2_credential_id_backup'] = $settings['credential_id_creators_v2']; $settings['legacy_v2_credential_secret_backup'] = $settings['credential_secret_creators_v2']; $settings['legacy_v2_partner_tag_backup'] = $settings['partner_tag_creators_v2']; $settings['legacy_v2_marketplace_backup'] = $settings['marketplace_creators_v2']; } else { $settings['access_key_paapi5'] = isset($_POST['access_key_paapi5']) ? sanitize_text_field(wp_unslash($_POST['access_key_paapi5'])) : ($existing_settings['access_key_paapi5'] ?? ''); $posted_secret = isset($_POST['secret_key_paapi5']) ? sanitize_text_field(wp_unslash($_POST['secret_key_paapi5'])) : ''; $settings['secret_key_paapi5'] = ($posted_secret !== '') ? $posted_secret : ($existing_settings['secret_key_paapi5'] ?? ''); $settings['partner_tag_paapi5'] = isset($_POST['partner_tag_paapi5']) ? sanitize_text_field(wp_unslash($_POST['partner_tag_paapi5'])) : ($existing_settings['partner_tag_paapi5'] ?? ''); $settings['marketplace_paapi5'] = $sanitize_marketplace('marketplace_paapi5', $existing_settings['marketplace_paapi5'] ?? 'www.amazon.com'); } $settings['num_products'] = isset($_POST['num_products']) ? absint(wp_unslash($_POST['num_products'])) : ($existing_settings['num_products'] ?? 3); $settings['num_columns'] = isset($_POST['num_columns']) ? absint(wp_unslash($_POST['num_columns'])) : ($existing_settings['num_columns'] ?? 3); $settings['image_size'] = isset($_POST['image_size']) ? sanitize_text_field(wp_unslash($_POST['image_size'])) : ($existing_settings['image_size'] ?? 'Medium'); $settings['font_size'] = isset($_POST['font_size']) ? affiamsh_validate_font_size(sanitize_text_field(wp_unslash($_POST['font_size']))) : ($existing_settings['font_size'] ?? '16px'); $allowed_languages = ['auto','en','it','es','fr','de','pt','nl','pl','tr','sv','ja','ar']; $posted_language = isset($_POST['display_language']) ? sanitize_key(wp_unslash($_POST['display_language'])) : ($existing_settings['display_language'] ?? 'auto'); $settings['display_language'] = in_array($posted_language, $allowed_languages, true) ? $posted_language : 'auto'; if ($api_version === 'creators_v2') { $selected_marketplace = $settings['marketplace_creators_v2'] ?? 'www.amazon.com'; } elseif ($api_version === 'creators_v3') { $selected_marketplace = $settings['marketplace_creators_v3'] ?? 'www.amazon.com'; } else { $selected_marketplace = $settings['marketplace_paapi5'] ?? 'www.amazon.com'; } $settings['region'] = $marketplaces[$selected_marketplace] ?? 'us-east-1'; $limit_notes = []; if (!affiamsh_is_pro()) { if ($settings['num_products'] > 3) { $settings['num_products'] = 3; $limit_notes[] = 'FREE version: Number of products was limited to 3.'; } if ($settings['num_columns'] > 3) { $settings['num_columns'] = 3; $limit_notes[] = 'FREE version: Number of columns was limited to 3.'; } } update_option('affiamsh_plugin_settings', $settings); affiamsh_bump_cache_ver(); if (isset($_POST['test_api'])) { $test_result = affiamsh_test_api_connection(); $page_message = affiamsh_admin_t('test_saved'); $page_message_type = $test_result['success'] ? 'success' : 'info'; } else { $page_message = affiamsh_admin_t('settings_saved'); } if (!empty($limit_notes)) { $page_message .= ' ' . implode(' ', $limit_notes); } } $settings = get_option('affiamsh_plugin_settings', []); if (!is_array($settings)) $settings = []; $api_version = $settings['api_version'] ?? 'creators_v3'; if ($api_version === 'creators') { $legacy_v = affiamsh_normalize_creators_version($settings['creators_credential_version'] ?? ''); $api_version = strpos($legacy_v, '2.') === 0 ? 'creators_v2' : 'creators_v3'; } $current_api = $api_version; $credentials = affiamsh_get_credentials(); $is_pro = affiamsh_is_pro(); ?>
amzn1... credentials and Creators API v2 only for older Cognito credentials.',
'If you changed the Secret, paste the complete new value and run the test again.',
];
if (strpos($oauth_body, 'invalid_client') !== false) {
array_unshift($oauth_guidance, 'Amazon returned invalid_client: the Credential ID / Secret pair is not valid for the selected Creators API generation.');
}
return [
'success' => false,
'message' => 'OAuth authentication failed before the catalog request was sent.',
'guidance' => $oauth_guidance,
'details' => implode("\n", $debug_info)
];
}
$version = affiamsh_normalize_creators_version($GLOBALS['affiamsh_last_oauth_version'] ?? '');
$token_endpoint = affiamsh_get_creators_token_endpoint($version);
$debug_info[] = 'Result: SUCCESS';
$debug_info[] = 'Detected Credential Version: v' . $version . ' (' . affiamsh_get_creators_version_region_label($version) . ')';
$debug_info[] = 'Token Endpoint: ' . $token_endpoint;
$debug_info[] = 'Token obtained and cached.';
$debug_info[] = '';
$debug_info[] = '--- Step 2: Creators API Request ---';
$payload = wp_json_encode([
'keywords' => 'laptop',
'resources' => ['itemInfo.title'],
'searchIndex' => 'All',
'partnerTag' => $credentials['partner_tag'],
'marketplace' => $credentials['marketplace'],
'itemCount' => 1,
]);
$url = 'https://creatorsapi.amazon/catalog/v1/searchItems';
$authorization = affiamsh_get_creators_authorization_header($access_token, $version);
$debug_info[] = 'Endpoint: ' . $url;
$debug_info[] = 'Method: POST';
$debug_info[] = 'Headers:';
$debug_info[] = affiamsh_is_creators_v3($version)
? ' - Authorization: Bearer [token]'
: ' - Authorization: Bearer [token], Version ' . $version;
$debug_info[] = ' - Content-Type: application/json';
$debug_info[] = ' - x-marketplace: ' . $credentials['marketplace'];
$response = wp_remote_post($url, [
'headers' => [
'Authorization' => $authorization,
'Content-Type' => 'application/json',
'x-marketplace' => $credentials['marketplace'],
],
'body' => $payload,
'timeout' => 15,
]);
} else {
$debug_info[] = 'AWS Region: ' . ($credentials['region'] ?? 'N/A');
$debug_info[] = 'Partner Tag: ' . ($credentials['partner_tag'] ?? 'N/A');
$debug_info[] = 'Access Key: ' . substr($credentials['access_key'], 0, 8) . '...';
$debug_info[] = '';
$debug_info[] = '--- PA-API 5.0 Request ---';
$payload = wp_json_encode([
'Keywords' => 'laptop',
'Resources' => ['ItemInfo.Title'],
'SearchIndex' => 'All',
'PartnerTag' => $credentials['partner_tag'],
'PartnerType' => 'Associates',
'Marketplace' => $credentials['marketplace'],
'ItemCount' => 1,
]);
$host = str_replace('www.', 'webservices.', $credentials['marketplace']);
$uriPath = '/paapi5/searchitems';
$url = 'https://' . $host . $uriPath;
$awsv4 = new Affiamsh_AwsV4($credentials['access_key'], $credentials['secret_key']);
$awsv4->setRegionName($credentials['region']);
$awsv4->setServiceName('ProductAdvertisingAPI');
$awsv4->setPath($uriPath);
$awsv4->setPayload($payload);
$awsv4->setRequestMethod('POST');
$awsv4->addHeader('content-encoding', 'amz-1.0');
$awsv4->addHeader('content-type', 'application/json; charset=utf-8');
$awsv4->addHeader('host', $host);
$awsv4->addHeader('x-amz-target', 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems');
$debug_info[] = 'Endpoint: ' . $url;
$debug_info[] = 'Signing with AWS Signature Version 4';
$response = wp_remote_post($url, [
'headers' => $awsv4->getHeaders(),
'body' => $payload,
'timeout' => 15,
]);
}
$debug_info[] = '';
$debug_info[] = '--- Response ---';
if (is_wp_error($response)) {
$debug_info[] = 'Result: CONNECTION ERROR';
$debug_info[] = 'Error: ' . $response->get_error_message();
return [
'success' => false,
'message' => 'Connection error: ' . $response->get_error_message(),
'details' => implode("\n", $debug_info)
];
}
$http_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
$debug_info[] = 'HTTP Status: ' . $http_code;
if ($http_code !== 200) {
$error_data = json_decode($body, true);
$reason = is_array($error_data) ? ($error_data['reason'] ?? '') : '';
$type = is_array($error_data) ? ($error_data['type'] ?? ($error_data['__type'] ?? '')) : '';
$pa_error_code = is_array($error_data) ? ($error_data['Errors'][0]['Code'] ?? '') : '';
$amazon_message = is_array($error_data) ? ($error_data['message'] ?? ($error_data['Errors'][0]['Message'] ?? '')) : '';
$field_list = (is_array($error_data) && !empty($error_data['fieldList']) && is_array($error_data['fieldList'])) ? $error_data['fieldList'] : [];
$guidance = [];
$debug_info[] = 'Result: FAILED';
$debug_info[] = '';
$debug_info[] = '--- Error Details ---';
if ($type !== '') $debug_info[] = 'Type: ' . $type;
if ($reason !== '') $debug_info[] = 'Reason: ' . $reason;
if ($pa_error_code !== '') $debug_info[] = 'Code: ' . $pa_error_code;
if (!empty($field_list)) $debug_info[] = 'Fields: ' . implode(', ', array_map('sanitize_text_field', $field_list));
if (affiamsh_is_creators_api($api_version) && $reason === 'InvalidAssociate') {
$marketplace = (string) ($credentials['marketplace'] ?? '');
$partner_tag = (string) ($credentials['partner_tag'] ?? '');
$label_map = affiamsh_get_marketplace_labels();
$marketplace_label = $label_map[$marketplace] ?? $marketplace;
$message = 'Credentials are valid, but Partner Tag "' . $partner_tag . '" is not linked to ' . $marketplace_label . '.';
$guidance = [
'Selected marketplace: ' . esc_html($marketplace_label) . '. Check that this is the Amazon country/store you actually use.',
'Partner Tag currently sent: ' . esc_html($partner_tag) . '. Enter the Store ID / Tracking ID created inside the Associates account for ' . esc_html($marketplace) . '.',
'If the Partner Tag belongs to another Amazon country, change Marketplace to that country instead. Do not choose the marketplace from the credential region: v3 credentials can authenticate globally.',
'If both values are correct, verify that your Associates / Creators API access is approved for that target marketplace.',
];
$debug_info[] = '';
$debug_info[] = 'OAuth authentication: SUCCESS';
$debug_info[] = 'Credential problem: NO';
$debug_info[] = 'Marketplace / Partner Tag match: FAILED';
} elseif (affiamsh_is_creators_api($api_version) && $reason === 'InvalidPartnerTag') {
$marketplace = (string) ($credentials['marketplace'] ?? '');
$partner_tag = (string) ($credentials['partner_tag'] ?? '');
$message = 'Amazon does not recognize Partner Tag "' . $partner_tag . '" for marketplace ' . $marketplace . '.';
$guidance = [
'Re-copy the Partner Tag exactly as shown in Amazon Associates. Current value: ' . esc_html($partner_tag) . '.',
'Make sure the Partner Tag was created for ' . esc_html($marketplace) . '. A Tracking ID from another Amazon marketplace cannot be used here.',
'If you intended another country, change the Marketplace and test again.',
];
} elseif (affiamsh_is_creators_api($api_version) && $reason === 'AssociateNotEligible') {
$message = 'Your credentials are valid, but this Associates account is not currently eligible for Creators API access.';
$guidance = [
'No credential change is required: OAuth authentication succeeded.',
'Check the Creators API eligibility status for this Associates marketplace. Amazon currently documents a requirement of at least 10 qualifying sales in the trailing 30 days.',
'After the account becomes eligible, run this test again.',
];
$debug_info[] = '';
$debug_info[] = 'OAuth authentication: SUCCESS';
$debug_info[] = 'Eligibility: FAILED';
$debug_info[] = 'Amazon currently requires 10 qualified sales in the trailing 30 days.';
} elseif (affiamsh_is_creators_api($api_version) && $reason === 'FieldValidationFailed') {
$message = 'Amazon rejected one or more request fields.';
$guidance = [
!empty($field_list) ? 'Amazon identified these fields: ' . esc_html(implode(', ', $field_list)) . '.' : 'Review the Partner Tag and Marketplace values.',
'Save the corrected value and run the test again.',
];
} elseif (affiamsh_is_creators_api($api_version) && in_array($reason, ['InvalidToken', 'TokenExpired'], true)) {
$message = 'Creators API rejected the OAuth token (' . $reason . ').';
$guidance = [
'Run the test again so the plugin can obtain a fresh OAuth token.',
'If the problem persists, re-enter the Credential ID and Secret.',
];
} elseif ($http_code === 401) {
$message = affiamsh_is_creators_api($api_version)
? 'Creators API authentication failed.'
: 'PA-API 5.0 authentication failed.';
$guidance = affiamsh_is_creators_api($api_version)
? ['Check the Credential ID and Secret, then run the test again.']
: ['Check Access Key, Secret Key and the selected marketplace.'];
} elseif ($http_code === 403) {
$message = affiamsh_is_creators_api($api_version)
? 'Creators API denied access to this request.'
: 'PA-API 5.0 returned HTTP 403.';
$guidance = affiamsh_is_creators_api($api_version)
? ['Read the Amazon reason code in the technical debug. It normally identifies eligibility or permission issues.']
: ['Verify PA-API eligibility and credentials, or migrate this profile to Creators API v3.'];
} elseif ($http_code === 429) {
$message = (affiamsh_is_creators_api($api_version) ? 'Creators API' : 'PA-API 5.0') . ' rate limit reached (HTTP 429).';
$guidance = ['Wait before retrying. The plugin already caches OAuth tokens and shortcode output to reduce unnecessary API requests.'];
} else {
$message = (affiamsh_is_creators_api($api_version) ? 'Creators API' : 'PA-API 5.0') . ' returned HTTP ' . $http_code . '.';
$guidance = ['Review the Amazon message and technical debug below, then correct the indicated setting.'];
}
if ($amazon_message !== '') $debug_info[] = 'Amazon Message: ' . $amazon_message;
$debug_info[] = '';
$debug_info[] = '--- Amazon Response ---';
$debug_info[] = $body;
return [
'success' => false,
'message' => $message,
'guidance' => $guidance,
'reason' => $reason !== '' ? $reason : $pa_error_code,
'details' => implode("\n", $debug_info)
];
}
$data = json_decode($body, true);
$items = $data['searchResult']['items'] ?? $data['SearchResult']['Items'] ?? [];
$debug_info[] = 'Result: SUCCESS';
$debug_info[] = 'Items found: ' . count($items);
return [
'success' => true,
'message' => 'Connection successful!',
'item_count' => count($items),
'details' => implode("\n", $debug_info)
];
}
// Shortcode function
function affiamsh_amazon_handler($atts) {
$atts = shortcode_atts([
'keyword' => 'smartphone',
'number' => '',
'template' => '',
'theme' => '',
'columns' => '',
'button' => '',
'badge' => '',
'language' => '',
'highlight_words' => '',
], $atts, 'affiamsh_amazon');
$settings = get_option('affiamsh_plugin_settings', []);
// Ottieni le credenziali
$credentials = affiamsh_get_credentials();
// Verifica se le credenziali sono state configurate
if ($credentials === null) {
$api_version = $settings['api_version'] ?? 'creators_v3';
$api_name = affiamsh_get_api_label($api_version);
return 'API not configured
' . 'Configure ' . esc_html($api_name) . ' in ' . 'Settings
' . 'Error:') === false ) { set_transient($cache_key, $html, $cache_ttl); } return $html; } add_shortcode('affiamsh_amazon', 'affiamsh_amazon_handler'); add_shortcode('affiamsh_amazon_compare', function($atts){ $atts = is_array($atts) ? $atts : []; $atts['template'] = 'comparison'; if (empty($atts['number'])) $atts['number'] = 3; return affiamsh_amazon_handler($atts); }); /** * Funzione per chiamare Creators API con OAuth 2.0 (LwA v3.x + legacy v2.x) */ function affiamsh_call_creators_api($atts, $settings, $credentials) { // Ottieni OAuth access token $access_token = affiamsh_get_oauth_token( $credentials['credential_id'], $credentials['credential_secret'], (int) ($credentials['major'] ?? 3), $credentials['marketplace'] ?? 'www.amazon.com', $credentials['version_hint'] ?? '' ); $detected_version = affiamsh_normalize_creators_version($GLOBALS['affiamsh_last_oauth_version'] ?? ''); if ($access_token === false) { return '
Authentication failed
' . 'Check credentials in Settings
' . (current_user_can('manage_options') ? 'ID: ' . esc_html(substr($credentials['credential_id'], 0, 10)) . '... (len: ' . strlen($credentials['credential_id']) . ')
' : '') . 'Error: ' . esc_html($response->get_error_message()) . '
'; } $http_code = wp_remote_retrieve_response_code($response); if ($http_code !== 200) { $error_body = wp_remote_retrieve_body($response); // Fallback pubblico: usa lingua, Partner Tag e Marketplace già configurati nel plugin. $fallback_keyword = trim((string) ($atts['keyword'] ?? '')); $fallback_language = affiamsh_resolve_box_language($settings); $fallback_label = affiamsh_amazon_fallback_label($fallback_language, $fallback_keyword); $fallback_partner_tag = trim((string) ($credentials['partner_tag'] ?? '')); $fallback_marketplace = strtolower(trim((string) ($credentials['marketplace'] ?? 'www.amazon.com'))); // Consenti solo host Amazon salvati nel selettore Marketplace del plugin. $known_marketplaces = affiamsh_get_amazon_marketplaces(); if (!isset($known_marketplaces[$fallback_marketplace])) { $fallback_marketplace = 'www.amazon.com'; } $fallback_url = add_query_arg( array_filter([ 'k' => $fallback_keyword, 'tag' => $fallback_partner_tag, ], static function($value) { return $value !== '' && $value !== null; }), 'https://' . $fallback_marketplace . '/s' ); $error_message = 'Amazon account not currently eligible for Creators API.
'; $error_message .= 'OAuth credentials were accepted, but Amazon currently requires 10 qualified sales in the trailing 30 days.
'; } elseif ($http_code === 401 || $http_code === 403) { $error_message .= 'Request denied. Check the Amazon response below, Partner Tag and marketplace access.
'; } $error_message .= 'Error: Unable to parse the Amazon server response.
'; } // Gestione risposta Creators API if (empty($data['searchResult']['items'])) { return '' . esc_html(affiamsh_box_strings(affiamsh_resolve_box_language($settings))['no_products']) . '
'; } $items = $data['searchResult']['items']; return affiamsh_render_products($items, $settings, $image_size_key); } /** * Funzione per chiamare la vecchia PA-API 5.0 (legacy) */ function affiamsh_call_paapi5($atts, $settings, $credentials) { // Construct the payload con naming PascalCase per PA-API 5.0 $image_sizes = affiamsh_get_image_sizes(); $image_size_key = $settings['image_size'] ?? 'Medium'; $image_size = $image_sizes[$image_size_key] ?? 'Images.Primary.Medium'; $payload = "{" . " \"Keywords\": \"" . esc_js($atts['keyword']) . "\"," . " \"Resources\": [" . " \"$image_size\"," . " \"ItemInfo.ContentInfo\"," . " \"ItemInfo.Title\"," . " \"Offers.Listings.Price\"" . " ]," . " \"SearchIndex\": \"All\"," . " \"PartnerTag\": \"" . esc_js($credentials['partner_tag']) . "\"," . " \"PartnerType\": \"Associates\"," . " \"Marketplace\": \"" . esc_js($credentials['marketplace']) . "\"," . " \"ItemCount\": " . absint($settings['num_products'] ?? 3) . "}"; $host = str_replace('www.', 'webservices.', $credentials['marketplace']); $uriPath = "/paapi5/searchitems"; $url = 'https://' . $host . $uriPath; // Sign the request using AwsV4 $awsv4 = new Affiamsh_AwsV4($credentials['access_key'], $credentials['secret_key']); $awsv4->setRegionName($credentials['region']); $awsv4->setServiceName("ProductAdvertisingAPI"); $awsv4->setPath($uriPath); $awsv4->setPayload($payload); $awsv4->setRequestMethod("POST"); $awsv4->addHeader('content-encoding', 'amz-1.0'); $awsv4->addHeader('content-type', 'application/json; charset=utf-8'); $awsv4->addHeader('host', $host); $awsv4->addHeader('x-amz-target', 'com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems'); $headers = $awsv4->getHeaders(); // Perform the API request $response = wp_remote_post($url, [ 'headers' => $headers, 'body' => $payload, 'timeout' => 20, 'method' => 'POST', ]); if (is_wp_error($response)) { return 'Error: ' . esc_html($response->get_error_message()) . '
'; } $http_code = wp_remote_retrieve_response_code($response); if ($http_code !== 200) { $error_body = wp_remote_retrieve_body($response); $error_data = json_decode($error_body, true); // Determina il tipo di errore specifico $error_type = $error_data['__type'] ?? ''; $error_code = $error_data['Errors'][0]['Code'] ?? ''; // Messaggio discreto per utenti pubblici $error_message = 'API Error (HTTP ' . esc_html($http_code) . ')
'; // Dettagli tecnici SOLO per admin, molto discreti if (current_user_can('manage_options')) { $error_message .= 'UnrecognizedClient Error
'; $error_message .= 'The Access Key ID is invalid or not recognized.
'; $error_message .= '✓ Good news: Your Secret Key and Region are not being checked yet. Fix the Access Key first.
'; } elseif (strpos($error_type, 'InvalidSignature') !== false || $error_code === 'InvalidSignature') { $error_message .= 'InvalidSignature Error
'; $error_message .= 'The AWS signature failed. This means:
'; $error_message .= '🔍 How to diagnose:
'; $error_message .= 'Authentication Error
'; $error_message .= 'Authentication failed. Possible causes:
'; $error_message .= 'Error: Unable to parse the Amazon server response.
'; } if (empty($data['SearchResult']['Items'])) { return '' . esc_html(affiamsh_box_strings(affiamsh_resolve_box_language($settings))['no_products']) . '
'; } return affiamsh_render_products($data['SearchResult']['Items'], $settings, $image_size_key); } /** * Languages used by the product-box UI. "Auto" follows the WordPress site locale. */ function affiamsh_get_supported_box_languages() { return [ 'auto' => affiamsh_admin_t('automatic_site_language'), 'en' => 'English', 'it' => 'Italiano', 'es' => 'Español', 'fr' => 'Français', 'de' => 'Deutsch', 'pt' => 'Português', 'nl' => 'Nederlands', 'pl' => 'Polski', 'tr' => 'Türkçe', 'sv' => 'Svenska', 'ja' => '日本語', 'ar' => 'العربية', ]; } function affiamsh_resolve_box_language($settings = []) { $forced = sanitize_key((string)($settings['_language_override'] ?? ($settings['display_language'] ?? 'auto'))); if ($forced !== '' && $forced !== 'auto' && array_key_exists($forced, affiamsh_get_supported_box_languages())) { return $forced; } $locale = function_exists('get_locale') ? (string)get_locale() : 'en_US'; $locale = strtolower(str_replace('-', '_', $locale)); $lang = substr($locale, 0, 2); return array_key_exists($lang, affiamsh_get_supported_box_languages()) ? $lang : 'en'; } /** * Localized Amazon search fallback CTA used when the API request fails. * The language follows Box Language (or the per-shortcode language override). */ function affiamsh_amazon_fallback_label($language, $keyword = '') { $language = sanitize_key((string) $language); $keyword = trim((string) $keyword); $labels = [ 'en' => ['with_keyword' => 'DEALS ON “%s” AT AMAZON', 'generic' => 'DEALS ON AMAZON'], 'it' => ['with_keyword' => 'OFFERTE “%s” SU AMAZON', 'generic' => 'OFFERTE SU AMAZON'], 'es' => ['with_keyword' => 'OFERTAS DE “%s” EN AMAZON', 'generic' => 'OFERTAS EN AMAZON'], 'fr' => ['with_keyword' => 'OFFRES “%s” SUR AMAZON', 'generic' => 'OFFRES SUR AMAZON'], 'de' => ['with_keyword' => 'ANGEBOTE FÜR „%s“ BEI AMAZON', 'generic' => 'ANGEBOTE BEI AMAZON'], 'pt' => ['with_keyword' => 'OFERTAS “%s” NA AMAZON', 'generic' => 'OFERTAS NA AMAZON'], 'nl' => ['with_keyword' => 'AANBIEDINGEN VOOR “%s” OP AMAZON', 'generic' => 'AANBIEDINGEN OP AMAZON'], 'pl' => ['with_keyword' => 'OFERTY “%s” NA AMAZON', 'generic' => 'OFERTY NA AMAZON'], 'tr' => ['with_keyword' => 'AMAZON’DA “%s” FIRSATLARI', 'generic' => 'AMAZON FIRSATLARI'], 'sv' => ['with_keyword' => 'ERBJUDANDEN PÅ “%s” HOS AMAZON', 'generic' => 'ERBJUDANDEN HOS AMAZON'], 'ja' => ['with_keyword' => 'AMAZONで「%s」のセール', 'generic' => 'AMAZONのセール'], 'ar' => ['with_keyword' => 'عروض “%s” على AMAZON', 'generic' => 'عروض على AMAZON'], ]; $set = $labels[$language] ?? $labels['en']; return $keyword !== '' ? sprintf($set['with_keyword'], $keyword) : $set['generic']; } function affiamsh_box_strings($language) { $all = [ 'en' => ['view'=>'View on Amazon','recommended'=>'Recommended','compare'=>'Compare','top_pick'=>'Top pick','price'=>'Price','highlights'=>'Highlights','details'=>'See product details on Amazon','check_price'=>'Check price','title_na'=>'Title not available','no_products'=>'No products found.'], 'it' => ['view'=>'Vedi su Amazon','recommended'=>'Consigliato','compare'=>'Confronta','top_pick'=>'Scelta migliore','price'=>'Prezzo','highlights'=>'Caratteristiche','details'=>'Vedi i dettagli del prodotto su Amazon','check_price'=>'Controlla il prezzo','title_na'=>'Titolo non disponibile','no_products'=>'Nessun prodotto trovato.'], 'es' => ['view'=>'Ver en Amazon','recommended'=>'Recomendado','compare'=>'Comparar','top_pick'=>'Mejor opción','price'=>'Precio','highlights'=>'Características','details'=>'Ver detalles del producto en Amazon','check_price'=>'Consultar precio','title_na'=>'Título no disponible','no_products'=>'No se encontraron productos.'], 'fr' => ['view'=>'Voir sur Amazon','recommended'=>'Recommandé','compare'=>'Comparer','top_pick'=>'Meilleur choix','price'=>'Prix','highlights'=>'Points forts','details'=>'Voir les détails du produit sur Amazon','check_price'=>'Voir le prix','title_na'=>'Titre indisponible','no_products'=>'Aucun produit trouvé.'], 'de' => ['view'=>'Bei Amazon ansehen','recommended'=>'Empfohlen','compare'=>'Vergleichen','top_pick'=>'Top-Empfehlung','price'=>'Preis','highlights'=>'Highlights','details'=>'Produktdetails bei Amazon ansehen','check_price'=>'Preis prüfen','title_na'=>'Titel nicht verfügbar','no_products'=>'Keine Produkte gefunden.'], 'pt' => ['view'=>'Ver na Amazon','recommended'=>'Recomendado','compare'=>'Comparar','top_pick'=>'Melhor escolha','price'=>'Preço','highlights'=>'Destaques','details'=>'Ver detalhes do produto na Amazon','check_price'=>'Ver preço','title_na'=>'Título indisponível','no_products'=>'Nenhum produto encontrado.'], 'nl' => ['view'=>'Bekijk op Amazon','recommended'=>'Aanbevolen','compare'=>'Vergelijken','top_pick'=>'Beste keuze','price'=>'Prijs','highlights'=>'Pluspunten','details'=>'Bekijk productdetails op Amazon','check_price'=>'Bekijk prijs','title_na'=>'Titel niet beschikbaar','no_products'=>'Geen producten gevonden.'], 'pl' => ['view'=>'Zobacz na Amazon','recommended'=>'Polecany','compare'=>'Porównaj','top_pick'=>'Najlepszy wybór','price'=>'Cena','highlights'=>'Najważniejsze cechy','details'=>'Zobacz szczegóły produktu na Amazon','check_price'=>'Sprawdź cenę','title_na'=>'Tytuł niedostępny','no_products'=>'Nie znaleziono produktów.'], 'tr' => ['view'=>'Amazon’da görüntüle','recommended'=>'Önerilen','compare'=>'Karşılaştır','top_pick'=>'En iyi seçim','price'=>'Fiyat','highlights'=>'Öne çıkanlar','details'=>'Ürün ayrıntılarını Amazon’da görüntüle','check_price'=>'Fiyatı kontrol et','title_na'=>'Başlık mevcut değil','no_products'=>'Ürün bulunamadı.'], 'sv' => ['view'=>'Visa på Amazon','recommended'=>'Rekommenderad','compare'=>'Jämför','top_pick'=>'Bästa val','price'=>'Pris','highlights'=>'Höjdpunkter','details'=>'Se produktinformation på Amazon','check_price'=>'Kontrollera pris','title_na'=>'Titel saknas','no_products'=>'Inga produkter hittades.'], 'ja' => ['view'=>'Amazonで見る','recommended'=>'おすすめ','compare'=>'比較','top_pick'=>'おすすめ商品','price'=>'価格','highlights'=>'主な特徴','details'=>'Amazonで商品詳細を見る','check_price'=>'価格を確認','title_na'=>'タイトルなし','no_products'=>'商品が見つかりませんでした。'], 'ar' => ['view'=>'عرض على Amazon','recommended'=>'موصى به','compare'=>'مقارنة','top_pick'=>'الخيار الأفضل','price'=>'السعر','highlights'=>'أبرز الميزات','details'=>'عرض تفاصيل المنتج على Amazon','check_price'=>'تحقق من السعر','title_na'=>'العنوان غير متوفر','no_products'=>'لم يتم العثور على منتجات.'], ]; return $all[$language] ?? $all['en']; } /** * Limit product feature bullets by a total word budget. * A value of 0 keeps the original text unchanged. The limit is applied per product. */ function affiamsh_limit_feature_words($features, $word_limit = 0, $max_items = 4) { if (!is_array($features)) return []; $features = array_values(array_filter(array_map(function($feature) { return trim(wp_strip_all_tags((string) $feature)); }, $features))); $max_items = max(1, absint($max_items)); $features = array_slice($features, 0, $max_items); $word_limit = absint($word_limit); if ($word_limit === 0) return $features; $remaining = $word_limit; $result = []; foreach ($features as $feature) { if ($remaining <= 0) break; $words = preg_split('/\s+/u', trim($feature), -1, PREG_SPLIT_NO_EMPTY); $count = is_array($words) ? count($words) : 0; if ($count <= $remaining) { $result[] = $feature; $remaining -= $count; continue; } $result[] = wp_trim_words($feature, $remaining, '…'); $remaining = 0; } return $result; } /** * Funzione per renderizzare i prodotti (comune per entrambe le API) */ function affiamsh_render_products($items, $settings, $image_size_key) { // Generate HTML $num_products = absint($settings['num_products'] ?? 3); $num_columns = absint($settings['num_columns'] ?? 3); if ($num_products < 1) $num_products = 1; if ($num_products > 9) $num_products = 9; if ($num_columns < 1) $num_columns = 1; if ($num_columns > 4) $num_columns = 4; $font_size = esc_attr($settings['font_size'] ?? '16px'); $box_language = affiamsh_resolve_box_language($settings); $box_text = affiamsh_box_strings($box_language); // ✅ ENFORCE FREE LIMITS at render time $is_pro = affiamsh_is_pro(); if (!$is_pro) { $num_products = min($num_products, 3); // Max 3 products for FREE $num_columns = min($num_columns, 3); // Max 3 columns for FREE } // Get PRO options $pro_opts = get_option('affiamsh_pro_display_options', [ 'template' => 'card', 'theme' => 'slate', 'text_color' => '', 'title_color' => '', 'price_color' => '', 'button_text' => '', 'badge_text' => '', 'show_features' => 0, 'features_word_limit' => 35 ]); $template = !empty($settings['_template_override']) ? $settings['_template_override'] : (isset($pro_opts['template']) && !empty($pro_opts['template']) ? $pro_opts['template'] : 'card'); $allowed_templates = ['card','list','compact','modern','minimal','highlight','deal','comparison']; if (!in_array($template, $allowed_templates, true)) { $template = 'card'; } // FREE output cannot accidentally inherit a premium template saved by an older PRO configuration. if (!$is_pro && !in_array($template, ['card','list'], true)) { $template = 'card'; } $theme = !empty($settings['_theme_override']) ? $settings['_theme_override'] : (isset($pro_opts['theme']) && !empty($pro_opts['theme']) ? $pro_opts['theme'] : 'slate'); $saved_button_text = isset($pro_opts['button_text']) ? trim((string)$pro_opts['button_text']) : ''; // The old default was English. Treat that value as an automatic label so existing installs localize without losing real custom CTAs. $button_text = !empty($settings['_button_override']) ? $settings['_button_override'] : (($saved_button_text === '' || $saved_button_text === 'View on Amazon') ? $box_text['view'] : $saved_button_text); $badge_text = !empty($settings['_badge_override']) ? $settings['_badge_override'] : (!empty($pro_opts['badge_text']) ? $pro_opts['badge_text'] : ''); $show_features = !empty($pro_opts['show_features']); $features_word_limit = isset($settings['_features_word_limit_override']) ? absint($settings['_features_word_limit_override']) : (isset($pro_opts['features_word_limit']) ? absint($pro_opts['features_word_limit']) : 35); // Build classes $container_classes = 'affiamsh-amazon-products template-' . esc_attr($template); $container_classes .= ' affiamsh-theme-' . esc_attr($theme); $container_classes .= ' affiamsh-cols-' . esc_attr($num_columns); // Columns are controlled by affiamsh-cols-* CSS classes. This makes the saved global // Number of Columns setting reliable even when a WordPress theme has aggressive grid rules. // List and Comparison intentionally keep their dedicated one-dimensional/table layouts. $inline_styles = "gap:20px;margin-bottom:20px;"; // Add custom color overrides SOLO se theme è CUSTOM e PRO è attivo if ($is_pro && $theme === 'custom') { if (!empty($pro_opts['text_color'])) { $inline_styles .= " --affiamsh-text: " . esc_attr($pro_opts['text_color']) . " !important;"; } if (!empty($pro_opts['title_color'])) { $inline_styles .= " --affiamsh-title: " . esc_attr($pro_opts['title_color']) . " !important;"; } if (!empty($pro_opts['price_color'])) { $inline_styles .= " --affiamsh-price: " . esc_attr($pro_opts['price_color']) . " !important;"; } if (!empty($pro_opts['box_bg'])) { $inline_styles .= " --affiamsh-bg: " . esc_attr($pro_opts['box_bg']) . " !important;"; } if (!empty($pro_opts['ticket_bg'])) { $inline_styles .= " --affiamsh-discount-bg: " . esc_attr($pro_opts['ticket_bg']) . " !important;"; } if (!empty($pro_opts['discount_fg'])) { $inline_styles .= " --affiamsh-discount-fg: " . esc_attr($pro_opts['discount_fg']) . " !important;"; } if (!empty($pro_opts['box_bg_hover'])) { $inline_styles .= " --affiamsh-bg-hover: " . esc_attr($pro_opts['box_bg_hover']) . " !important;"; } } $products = []; foreach ($items as $index => $item) { if ($index >= $num_products) break; $raw_title = $item['itemInfo']['title']['displayValue'] ?? $item['ItemInfo']['Title']['DisplayValue'] ?? $box_text['title_na']; $short_title = function_exists('mb_strlen') && mb_strlen($raw_title) > 70 ? mb_substr($raw_title, 0, 70) . '…' : ((strlen($raw_title) > 70) ? substr($raw_title, 0, 70) . '…' : $raw_title); $_img_key = strtolower($image_size_key); $features = $item['itemInfo']['features']['displayValues'] ?? $item['ItemInfo']['Features']['DisplayValues'] ?? []; if (!is_array($features)) $features = []; $products[] = [ 'title' => $raw_title, 'short_title' => $short_title, 'image' => $item['images']['primary'][$_img_key]['url'] ?? $item['Images']['Primary'][$image_size_key]['URL'] ?? '', 'link' => $item['detailPageURL'] ?? $item['DetailPageURL'] ?? '#', 'price' => $item['offersV2']['listings'][0]['price']['money']['displayAmount'] ?? $item['offersV2']['listings'][0]['price']['displayAmount'] ?? $item['offers']['listings'][0]['price']['displayAmount'] ?? $item['Offers']['Listings'][0]['Price']['DisplayAmount'] ?? $box_text['check_price'], 'percentage' => $item['offersV2']['listings'][0]['price']['savings']['percentage'] ?? $item['offers']['listings'][0]['price']['savings']['percentage'] ?? $item['Offers']['Listings'][0]['Price']['Savings']['Percentage'] ?? null, 'features' => array_slice($features, 0, 4), ]; } if ($template === 'comparison' && $is_pro) { $html = '| ' . esc_html($box_text['compare']) . ' | '; foreach ($products as $i => $p) { $html .= '' . ($i === 0 ? '' . esc_html($box_text['top_pick']) . '' : '') . ' | ';
}
$html .= '
|---|---|
| ' . esc_html($box_text['price']) . ' | '; foreach ($products as $p) $html .= '' . esc_html($p['price']) . ' | '; $html .= '
| ' . esc_html($box_text['highlights']) . ' | '; foreach ($products as $p) { $html .= '
| ';
}
$html .= '
| '; foreach ($products as $p) $html .= ' | ' . esc_html($button_text) . ' | '; $html .= '
:
[affiamsh_amazon keyword="Your Keyword"]
:
[affiamsh_amazon keyword="Your Keyword" number="x" template="modern" columns="3" button="Check price" badge="Our pick" highlight_words="25"]