[ '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(); ?>

Affiliate Amazon Shortcode

PRO FREE

>

false, 'message' => 'The selected API profile is incomplete.', 'guidance' => [ 'Complete the Credential ID / Access Key, Secret, Partner Tag and Marketplace fields for the selected API.', 'Click Test API Connection again. The button saves the current values automatically before testing.', ], 'details' => 'No complete credentials were found for the selected API profile.' ]; } $api_version = $credentials['api_version'] ?? 'creators_v3'; $debug_info = []; $debug_info[] = '=== TEST API CONNECTION DEBUG INFO ==='; $debug_info[] = 'API Version: ' . affiamsh_get_api_label($api_version); $debug_info[] = 'Marketplace: ' . ($credentials['marketplace'] ?? 'N/A'); if (affiamsh_is_creators_api($api_version)) { $major = (int) ($credentials['major'] ?? affiamsh_get_creators_major($api_version)); $debug_info[] = 'API Mode: ' . affiamsh_get_api_label($api_version); $debug_info[] = 'Credential Region: automatic detection'; $debug_info[] = 'Partner Tag: ' . ($credentials['partner_tag'] ?? 'N/A'); $debug_info[] = ''; $debug_info[] = '--- Step 1: OAuth Token Request ---'; $access_token = affiamsh_get_oauth_token( $credentials['credential_id'], $credentials['credential_secret'], $major, $credentials['marketplace'], $credentials['version_hint'] ?? '' ); if ($access_token === false) { $debug_info[] = 'Result: FAILED'; $debug_info[] = 'Credential ID: ' . substr($credentials['credential_id'], 0, 28) . '...'; $debug_info[] = ''; $debug_info[] = 'The plugin automatically tried the regional OAuth endpoints for Creators API v' . $major . '.'; $attempts = $GLOBALS['affiamsh_oauth_attempts'] ?? []; if (!empty($attempts)) { $debug_info[] = 'Endpoints tried:'; foreach ($attempts as $attempt) { $line = ' - v' . ($attempt['version'] ?? '?') . ' (' . affiamsh_get_creators_version_region_label($attempt['version'] ?? '') . '): ' . ($attempt['endpoint'] ?? ''); if (!empty($attempt['http_code'])) $line .= ' -> HTTP ' . $attempt['http_code']; $debug_info[] = $line; } } $oauth_error = $GLOBALS['affiamsh_last_oauth_error'] ?? null; if (is_array($oauth_error)) { if (!empty($oauth_error['http_code'])) $debug_info[] = 'Last Token HTTP Status: ' . $oauth_error['http_code']; if (!empty($oauth_error['message'])) $debug_info[] = 'Last Token Error: ' . $oauth_error['message']; if (!empty($oauth_error['body'])) $debug_info[] = 'Last Token Response: ' . $oauth_error['body']; } $oauth_body = is_array($oauth_error) ? (string) ($oauth_error['body'] ?? '') : ''; $oauth_guidance = [ 'Verify that the Credential ID and Credential Secret belong to the same Creators API credential.', 'Make sure the selected API type is correct: use Creators API v3 for current 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

' . '
'; } // Determina quale API usare $api_version = $settings['api_version'] ?? 'creators_v3'; // ✅ Only PRO: parametro number="x" (1..9) sovrascrive num_products impostato $requested_number = isset($atts['number']) ? absint($atts['number']) : 0; if ($requested_number < 1 || $requested_number > 9) { $requested_number = 0; } if ($requested_number && function_exists('affiamsh_is_pro') && affiamsh_is_pro()) { $settings['num_products'] = $requested_number; } // PRO: per-shortcode display overrides. Existing shortcodes remain unchanged. if (function_exists('affiamsh_is_pro') && affiamsh_is_pro()) { $allowed_templates = ['card','list','compact','modern','minimal','highlight','deal','comparison']; $allowed_themes = ['slate','blue','emerald','amber','rose','violet','indigo','teal','zinc','custom']; $requested_template = sanitize_key((string) ($atts['template'] ?? '')); $requested_theme = sanitize_key((string) ($atts['theme'] ?? '')); $requested_columns = absint($atts['columns'] ?? 0); if (in_array($requested_template, $allowed_templates, true)) $settings['_template_override'] = $requested_template; if (in_array($requested_theme, $allowed_themes, true)) $settings['_theme_override'] = $requested_theme; if ($requested_columns >= 1 && $requested_columns <= 4) $settings['num_columns'] = $requested_columns; if (!empty($atts['button'])) $settings['_button_override'] = sanitize_text_field($atts['button']); if (!empty($atts['badge'])) $settings['_badge_override'] = sanitize_text_field($atts['badge']); if ($atts['highlight_words'] !== '') { $requested_highlight_words = absint($atts['highlight_words']); if ($requested_highlight_words <= 200) { $settings['_features_word_limit_override'] = $requested_highlight_words; } } } // Language override is available in both FREE and PRO. $requested_language = sanitize_key((string) ($atts['language'] ?? '')); if ($requested_language !== '' && array_key_exists($requested_language, affiamsh_get_supported_box_languages())) { $settings['_language_override'] = $requested_language; } // ✅ Cache output shortcode (riduce throttling e accelera le pagine) $effective_num = absint($settings['num_products'] ?? 3); if ($effective_num < 1) $effective_num = 1; if ($effective_num > 9) $effective_num = 9; $cache_ver = affiamsh_get_cache_ver(); $cache_ttl = 30 * MINUTE_IN_SECONDS; // 30 min $cache_key = 'affiamsh_sc_' . AFFIAMSH_VER . '_v' . $cache_ver . '_' . md5(wp_json_encode([ 'api' => (string)$api_version, 'kw' => (string)($atts['keyword'] ?? ''), 'n' => (int)$effective_num, 'cols' => (int)absint($settings['num_columns'] ?? 3), 'img' => (string)($settings['image_size'] ?? ''), 'mp' => (string)($credentials['marketplace'] ?? ''), 'ver' => (string)($credentials['major'] ?? ($credentials['region'] ?? '')), // ✅ aggiunti per vedere subito cambi partner tag / credenziali 'tag' => (string)($credentials['partner_tag'] ?? ''), 'ak' => (string)($credentials['access_key'] ?? ''), // opzionale ma utile; NON include secret 'tpl' => (string)($settings['_template_override'] ?? ''), 'thm' => (string)($settings['_theme_override'] ?? ''), 'btn' => (string)($settings['_button_override'] ?? ''), 'bdg' => (string)($settings['_badge_override'] ?? ''), 'hlw' => (int)($settings['_features_word_limit_override'] ?? -1), 'lng' => (string)($settings['_language_override'] ?? ($settings['display_language'] ?? 'auto')), 'site_locale' => (string)get_locale(), 'pro_display' => get_option('affiamsh_pro_display_options', []), ])); $cached = get_transient($cache_key); if ($cached !== false && is_string($cached) && $cached !== '') { return $cached; } if (affiamsh_is_creators_api($api_version)) { $html = affiamsh_call_creators_api($atts, $settings, $credentials); } else { $html = affiamsh_call_paapi5($atts, $settings, $credentials); } // Non cachare errori if (is_string($html) && stripos($html, 'request throttling') === false && stripos($html, 'throttl') === false && stripos($html, 'Authentication failed') === false && stripos($html, 'InvalidSignature') === false && stripos($html, 'SignatureDoesNotMatch') === false && stripos($html, 'Unauthorized') === false && stripos($html, 'AccessDenied') === false && stripos($html, 'invalid_client') === false && stripos($html, 'invalid_grant') === false && stripos($html, 'eligible') === false && stripos($html, 'not eligible') === false && stripos($html, 'not authorized') === false && stripos($html, 'API Error') === false && stripos($html, '

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']) . ')

' : '') . '
'; } // Construct the payload con naming lowerCamelCase per Creators API $image_sizes = affiamsh_get_image_sizes(); $image_size_key = $settings['image_size'] ?? 'Medium'; $image_size_mapping = [ 'Small' => 'images.primary.small', 'Medium' => 'images.primary.medium', 'Large' => 'images.primary.large' ]; $image_resource = $image_size_mapping[$image_size_key] ?? 'images.primary.medium'; $payload = json_encode([ 'keywords' => $atts['keyword'], 'resources' => [ $image_resource, 'itemInfo.title', 'itemInfo.features', 'offersV2.listings.price' ], 'searchIndex' => 'All', 'partnerTag' => $credentials['partner_tag'], 'marketplace' => $credentials['marketplace'], 'itemCount' => absint($settings['num_products'] ?? 3) ]); // Global endpoint; target marketplace is in x-marketplace + JSON payload. $url = 'https://creatorsapi.amazon/catalog/v1/searchItems'; $headers = [ 'Authorization' => affiamsh_get_creators_authorization_header($access_token, $detected_version), 'Content-Type' => 'application/json', 'x-marketplace' => $credentials['marketplace'] ]; // 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); // 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 = '
'; $error_message .= ''; $error_message .= esc_html($fallback_label); $error_message .= ''; $error_message .= ''; // Dettagli tecnici SOLO per admin, sotto alla CTA e richiudibili. if (current_user_can('manage_options')) { $error_message .= '
'; $error_message .= 'API Error (HTTP ' . esc_html($http_code) . ') — Technical details'; $error_message .= '
'; $parsed_error = json_decode($error_body, true); $reason = is_array($parsed_error) ? ($parsed_error['reason'] ?? '') : ''; if ($reason === 'AssociateNotEligible') { $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_message .= 'API: Creators API (OAuth 2.0)
'; $error_message .= 'Endpoint: ' . esc_html($url) . '
'; $error_message .= 'Detected Credential Version: v' . esc_html($detected_version) . ' (' . esc_html(affiamsh_get_creators_version_region_label($detected_version)) . ')
'; $error_message .= 'Marketplace: ' . esc_html($credentials['marketplace']) . '
'; $error_message .= 'Token: ' . ($access_token ? 'Present' : 'Missing') . '

'; $error_message .= 'Response: ' . esc_html($error_body); $error_message .= '
'; $error_message .= '
'; $error_message .= '
'; } $error_message .= '
'; return $error_message; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (empty($data)) { return '

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 = '
'; $error_message .= '

API Error (HTTP ' . esc_html($http_code) . ')

'; // Dettagli tecnici SOLO per admin, molto discreti if (current_user_can('manage_options')) { $error_message .= '
'; $error_message .= 'Technical details'; $error_message .= '
'; if ($http_code === 401) { // Distingui tra UnrecognizedClient e InvalidSignature if (strpos($error_type, 'UnrecognizedClient') !== false || $error_code === 'UnrecognizedClient') { $error_message .= '

UnrecognizedClient Error

'; $error_message .= '

The Access Key ID is invalid or not recognized.

'; $error_message .= '
    '; $error_message .= '
  • Access Key is incorrect
  • '; $error_message .= '
  • Access Key was revoked or deleted
  • '; $error_message .= '
  • Access Key is from wrong Amazon account
  • '; $error_message .= '
  • Access Key not yet active (if just created)
  • '; $error_message .= '
'; $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 .= '
    '; $error_message .= '
  • Secret Key is incorrect, OR
  • '; $error_message .= '
  • AWS Region does not match marketplace
  • '; $error_message .= '
'; $error_message .= '

🔍 How to diagnose:

'; $error_message .= '
    '; $error_message .= '
  1. First, verify AWS Region below - this is the most common cause!
  2. '; $error_message .= '
  3. If region is correct for your marketplace → Secret Key is wrong
  4. '; $error_message .= '
  5. If not sure → Try "Save Changes" to recalculate region
  6. '; $error_message .= '
'; } else { // Fallback generico 401 $error_message .= '

Authentication Error

'; $error_message .= '

Authentication failed. Possible causes:

'; $error_message .= '
    '; $error_message .= '
  • Incorrect Access Key or Secret Key
  • '; $error_message .= '
  • Wrong AWS Region for marketplace
  • '; $error_message .= '
  • Credentials expired or revoked
  • '; $error_message .= '
'; } } $error_message .= '
'; $error_message .= 'Configuration Used:
'; $error_message .= 'API: PA-API 5.0 (' . esc_html(affiamsh_admin_t('legacy')) . ')
'; $error_message .= 'Marketplace: ' . esc_html($credentials['marketplace']) . '
'; $error_message .= 'AWS Region: ' . esc_html($credentials['region']) . '
'; $error_message .= 'Access Key: ' . esc_html(substr($credentials['access_key'], 0, 8)) . '...
'; $error_message .= 'Endpoint: ' . esc_html($url) . '
'; // Validazione region per marketplace $marketplaces = affiamsh_get_amazon_marketplaces(); $expected_region = $marketplaces[$credentials['marketplace']] ?? 'us-east-1'; if ($credentials['region'] !== $expected_region) { $error_message .= '
⚠️ REGION MISMATCH DETECTED!
'; $error_message .= 'Current region: ' . esc_html($credentials['region']) . '
'; $error_message .= 'Expected region: ' . esc_html($expected_region) . '
'; $error_message .= '→ Go to Settings and click "Save Changes" to fix!
'; } else { $error_message .= '
✓ Region is correct for ' . esc_html($credentials['marketplace']) . '
'; if (strpos($error_type, 'InvalidSignature') !== false) { $error_message .= '→ This means your Secret Key is incorrect!
'; } } $error_message .= '
Amazon Response:
'; $error_message .= '' . esc_html($error_body) . ''; $error_message .= '
'; $error_message .= '
'; $error_message .= '
'; } $error_message .= '
'; return $error_message; } $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if (empty($data)) { return '

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 = '
'; $html .= '
'; foreach ($products as $i => $p) { $html .= ''; } $html .= ''; foreach ($products as $p) $html .= ''; $html .= ''; if ($show_features) { $html .= ''; foreach ($products as $p) { $html .= ''; } $html .= ''; } $html .= ''; foreach ($products as $p) $html .= ''; $html .= '
' . esc_html($box_text['compare']) . '' . ($i === 0 ? '' . esc_html($box_text['top_pick']) . '' : '') . '' . esc_attr($p['title']) . '' . esc_html($p['short_title']) . '
' . esc_html($box_text['price']) . '' . esc_html($p['price']) . '
' . esc_html($box_text['highlights']) . '
    '; $display_features = affiamsh_limit_feature_words($p['features'], $features_word_limit, 4); foreach ($display_features as $feature) $html .= '
  • ' . esc_html($feature) . '
  • '; if (!$display_features) $html .= '
  • ' . esc_html($box_text['details']) . '
  • '; $html .= '
' . esc_html($button_text) . '
'; return $html; } $html = '
'; foreach ($products as $index => $p) { $html .= '
'; if ($badge_text !== '' || ($template === 'highlight' && $index === 0)) { $html .= '' . esc_html($badge_text !== '' ? $badge_text : $box_text['recommended']) . ''; } $html .= ''; $html .= '' . esc_attr($p['title']) . ''; $html .= '
' . esc_html($p['short_title']) . '
'; $html .= '
'; if ($p['percentage']) $html .= '-' . esc_html($p['percentage']) . '% '; $html .= '' . esc_html($p['price']) . '
'; if ($is_pro && $show_features && in_array($template, ['modern','highlight','deal'], true) && $p['features']) { $html .= ''; } if ($is_pro && in_array($template, ['modern','minimal','highlight','deal'], true)) { $html .= '' . esc_html($button_text) . ''; } $html .= '
'; } $html .= '
'; return $html; } /** * Classe AwsV4 */ class Affiamsh_AwsV4 { private $accessKey = null; private $secretKey = null; private $path = null; private $regionName = null; private $serviceName = null; private $httpMethodName = null; private $queryParametes = array(); private $awsHeaders = array(); private $payload = ""; private $HMACAlgorithm = "AWS4-HMAC-SHA256"; private $aws4Request = "aws4_request"; private $strSignedHeader = null; private $xAmzDate = null; private $currentDate = null; public function __construct($accessKey, $secretKey) { $this->accessKey = $accessKey; $this->secretKey = $secretKey; $this->xAmzDate = $this->getTimeStamp(); $this->currentDate = $this->getDate(); } function setPath($path) { $this->path = $path; } function setServiceName($serviceName) { $this->serviceName = $serviceName; } function setRegionName($regionName) { $this->regionName = $regionName; } function setPayload($payload) { $this->payload = $payload; } function setRequestMethod($method) { $this->httpMethodName = $method; } function addHeader($headerName, $headerValue) { $this->awsHeaders[$headerName] = $headerValue; } private function prepareCanonicalRequest() { $canonicalURL = ""; $canonicalURL .= $this->httpMethodName . "\n"; $canonicalURL .= $this->path . "\n" . "\n"; $signedHeaders = ''; foreach ($this->awsHeaders as $key => $value) { $signedHeaders .= $key . ";"; $canonicalURL .= $key . ":" . $value . "\n"; } $canonicalURL .= "\n"; $this->strSignedHeader = substr($signedHeaders, 0, -1); $canonicalURL .= $this->strSignedHeader . "\n"; $canonicalURL .= $this->generateHex($this->payload); return $canonicalURL; } private function prepareStringToSign($canonicalURL) { $stringToSign = ''; $stringToSign .= $this->HMACAlgorithm . "\n"; $stringToSign .= $this->xAmzDate . "\n"; $stringToSign .= $this->currentDate . "/" . $this->regionName . "/" . $this->serviceName . "/" . $this->aws4Request . "\n"; $stringToSign .= $this->generateHex($canonicalURL); return $stringToSign; } private function calculateSignature($stringToSign) { $signatureKey = $this->getSignatureKey($this->secretKey, $this->currentDate, $this->regionName, $this->serviceName); $signature = hash_hmac("sha256", $stringToSign, $signatureKey, true); $strHexSignature = strtolower(bin2hex($signature)); return $strHexSignature; } public function getHeaders() { $this->awsHeaders['x-amz-date'] = $this->xAmzDate; ksort($this->awsHeaders); $canonicalURL = $this->prepareCanonicalRequest(); $stringToSign = $this->prepareStringToSign($canonicalURL); $signature = $this->calculateSignature($stringToSign); if ($signature) { $this->awsHeaders['Authorization'] = $this->buildAuthorizationString($signature); return $this->awsHeaders; } } private function buildAuthorizationString($strSignature) { return $this->HMACAlgorithm . " " . "Credential=" . $this->accessKey . "/" . $this->getDate() . "/" . $this->regionName . "/" . $this->serviceName . "/" . $this->aws4Request . "," . "SignedHeaders=" . $this->strSignedHeader . "," . "Signature=" . $strSignature; } private function generateHex($data) { return strtolower(bin2hex(hash("sha256", $data, true))); } private function getSignatureKey($key, $date, $regionName, $serviceName) { $kSecret = "AWS4" . $key; $kDate = hash_hmac("sha256", $date, $kSecret, true); $kRegion = hash_hmac("sha256", $regionName, $kDate, true); $kService = hash_hmac("sha256", $serviceName, $kRegion, true); $kSigning = hash_hmac("sha256", $this->aws4Request, $kService, true); return $kSigning; } private function getTimeStamp() { return gmdate("Ymd\THis\Z"); } private function getDate() { return gmdate("Ymd"); } } // === PRO Licensing & Display Options === if (!defined('AFFIAMSH_PRO_KEY_PLAIN')) { define('AFFIAMSH_PRO_KEY_PLAIN', 'AMSH1899271'); } if (!function_exists('affiamsh_is_pro')) { function affiamsh_is_pro(): bool { return (bool) get_option('affiamsh_is_pro', false); } } add_action('admin_menu', function () { add_submenu_page( 'affiamsh-amazon-api-settings', affiamsh_admin_t('activate_pro'), affiamsh_admin_t('activate_pro'), 'manage_options', 'affiamsh-activate-pro', 'affiamsh_pro_activation_page' ); add_submenu_page( 'affiamsh-amazon-api-settings', affiamsh_admin_t('display_pro'), affiamsh_admin_t('display_pro'), 'manage_options', 'affiamsh-pro-display', 'affiamsh_pro_display_settings_page' ); }); add_action('admin_init', function () { if (!isset($_POST['affiamsh_activate_pro'])) { return; } if (!current_user_can('manage_options')) { return; } check_admin_referer('affiamsh_activate_pro'); $key = isset($_POST['affiamsh_license_key']) ? sanitize_text_field( wp_unslash($_POST['affiamsh_license_key']) ) : ''; if (hash_equals(AFFIAMSH_PRO_KEY_PLAIN, $key)) { update_option('affiamsh_is_pro', 1); update_option('affiamsh_license_key', $key); add_settings_error('affiamsh_pro', 'pro_ok', affiamsh_admin_t('pro_activate_success'), 'updated'); } else { update_option('affiamsh_is_pro', 0); add_settings_error('affiamsh_pro', 'pro_ko', affiamsh_admin_t('license_invalid'), 'error'); } }); add_action('admin_init', function () { register_setting('affiamsh_pro_display_group', 'affiamsh_pro_display_options', function ($value) { $out = [ 'template' => 'card', 'theme' => 'slate', 'text_color' => '', 'title_color' => '', 'price_color' => '', 'box_bg' => '', 'ticket_bg' => '', 'discount_fg' => '', 'box_bg_hover' => '', 'button_text' => '', 'badge_text' => '', 'show_features' => 0, 'features_word_limit' => 35, ]; if (is_array($value)) { $tpl = isset($value['template']) ? sanitize_text_field($value['template']) : 'card'; $out['template'] = in_array($tpl, ['card','list','compact','modern','minimal','highlight','deal','comparison'], true) ? $tpl : 'card'; $allowed_themes = ['slate','blue','emerald','amber','rose','violet','indigo','teal','zinc','custom']; $th = isset($value['theme']) ? sanitize_text_field($value['theme']) : 'slate'; if (!in_array($th, $allowed_themes, true)) { $th = 'slate'; } if (!affiamsh_is_pro()) { $th = 'slate'; } $out['theme'] = $th; if ($th === 'custom') { foreach (['text_color','title_color','price_color','box_bg','ticket_bg','discount_fg','box_bg_hover'] as $k) { $c = isset($value[$k]) ? sanitize_text_field($value[$k]) : ''; if ($c && preg_match('/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $c)) { $out[$k] = $c; } } } else { $out['text_color'] = ''; $out['title_color'] = ''; $out['price_color'] = ''; $out['box_bg'] = ''; $out['ticket_bg'] = ''; $out['discount_fg'] = ''; $out['box_bg_hover'] = ''; } $out['button_text'] = isset($value['button_text']) ? sanitize_text_field($value['button_text']) : ''; $out['badge_text'] = isset($value['badge_text']) ? sanitize_text_field($value['badge_text']) : ''; $out['show_features'] = !empty($value['show_features']) ? 1 : 0; $out['features_word_limit'] = isset($value['features_word_limit']) ? min(200, absint($value['features_word_limit'])) : 35; } return $out; }); }); function affiamsh_pro_activation_page() { ?>

✅

'card', 'theme' => 'slate', 'text_color' => '', 'title_color' => '', 'price_color' => '', 'box_bg' => '', 'ticket_bg' => '', 'discount_fg' => '', 'box_bg_hover' => '', 'button_text' => '', 'badge_text' => '', 'show_features' => 0, 'features_word_limit' => 35, ]); $opts = wp_parse_args($opts, [ 'template' => 'card', 'theme' => 'slate', 'text_color' => '', 'title_color' => '', 'price_color' => '', 'box_bg' => '', 'ticket_bg' => '', 'discount_fg' => '', 'box_bg_hover' => '', 'button_text' => '', 'badge_text' => '', 'show_features' => 0, 'features_word_limit' => 35, ]); $theme = isset($opts['theme']) ? $opts['theme'] : 'slate'; $is_pro = affiamsh_is_pro(); $show_colors = ($theme === 'custom'); ?>

: template=""

#333333

#111111

#b12704

#ffffff

#f8fafc

#fff1f2

#be123c

/>

/>
/>

highlight_words="25"


: [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"]

'.esc_html(affiamsh_admin_t('settings')).''; $links[] = ''.esc_html(affiamsh_admin_t('activate_pro')).''; $links[] = ''.esc_html(affiamsh_admin_t('purchase_pro')).''; return $links; }); add_action('admin_post_affiamsh_reset_pro', function () { if (!current_user_can('manage_options')) { wp_die( esc_html__( 'Forbidden', 'affiliate-amazon-shortcode' ) ); } check_admin_referer('affiamsh_reset_pro'); delete_option('affiamsh_is_pro'); add_settings_error('affiamsh_pro', 'pro_reset', affiamsh_admin_t('pro_disabled'), 'updated'); wp_redirect( admin_url('admin.php?page=affiamsh-activate-pro') ); exit; }); add_action('wp_enqueue_scripts', function () { wp_register_script('affiamsh-frontend', '', [], AFFIAMSH_VER, true); wp_enqueue_script('affiamsh-frontend'); $opts = get_option('affiamsh_pro_display_options', []); $data = [ 'isPro' => affiamsh_is_pro() ? 1 : 0, 'template' => isset($opts['template']) ? $opts['template'] : 'default', 'theme' => isset($opts['theme']) ? $opts['theme'] : 'slate', ]; $js = '(function(d,w){try{var CFG=' . wp_json_encode($data) . ';' . 'function injectWatermark(){' . 'if(CFG.isPro){return;}' . 'var wrappers=d.querySelectorAll(".affiamsh-amazon-products");' . 'wrappers.forEach(function(w){' . 'if(w.querySelector(".affiamsh-watermark"))return;' . 'var wm=d.createElement("span");' . 'wm.className="affiamsh-watermark";' . 'wm.setAttribute("aria-hidden","true");' . 'wm.textContent="Made with Affiliate Amazon Shortcode Plugin";' . 'w.appendChild(wm);' . 'try{' . 'var cs=w.ownerDocument.defaultView.getComputedStyle(w);' . 'if(!cs||cs.position==="static"){w.style.position="relative";}' . '}catch(e){if(!w.style.position){w.style.position="relative";}}' . '});' . '}' . 'injectWatermark();' . 'w.addEventListener("load",injectWatermark);' . '}catch(e){}})(document,window);'; wp_add_inline_script('affiamsh-frontend', $js); }, 40); ?>