'Default (current layout)',
'small' => 'Small (120 px)',
'medium' => 'Medium (180 px)',
'large' => 'Large (260 px)',
'xlarge' => 'Extra large (360 px)',
'custom' => 'Custom size',
);
}
function azlinkpro_image_size_key( $value ) {
$value = ( is_string( $value ) ? sanitize_key( $value ) : '' );
return ( array_key_exists( $value, azlinkpro_image_size_options() ) ? $value : 'default' );
}
function azlinkpro_image_custom_px( $value ) {
// Empty/bad values use a safe, predictable preview and frontend value.
return ( is_scalar( $value ) && preg_match( '/^[0-9]+$/D', trim( (string) $value ) ) ? max( 60, min( 420, absint( $value ) ) ) : 180 );
}
function azlinkpro_image_display_px( $size, $custom = 180 ) {
switch ( azlinkpro_image_size_key( $size ) ) {
case 'small':
return 120;
case 'medium':
return 180;
case 'large':
return 260;
case 'xlarge':
return 360;
case 'custom':
return azlinkpro_image_custom_px( $custom );
default:
return 0;
}
}
/**
* Escape all feature text, but optionally emphasize a short label ending in the
* first colon followed by a space. Safe for plain text from Amazon or an editor.
* No HTML supplied by a product is allowed through unescaped.
* Multiline descriptions are handled line by line.
*/
function azlinkpro_format_feature_labels( $text ) {
$text = (string) $text;
if ( get_option( 'azlinkpro_bold_feature_labels', '1' ) !== '1' ) {
return esc_html( $text );
}
$lines = preg_split( '/\\r\\n|\\r|\\n/', $text );
if ( !is_array( $lines ) ) {
return esc_html( $text );
}
$output = array();
foreach ( $lines as $line ) {
// Only a reasonable-length label followed by body text is formatted.
// This avoids changing timestamps, URLs, empty labels and prose with
// colons that are not label separators.
if ( preg_match( '/^(\\s*)([\\p{L}\\p{N}][^:\\r\\n]{1,95}:)([ \\t]+)(\\S.*)$/u', $line, $match ) ) {
$output[] = esc_html( $match[1] ) . '' . esc_html( $match[2] ) . '' . esc_html( $match[3] . $match[4] );
} else {
$output[] = esc_html( $line );
}
}
return implode( "\n", $output );
}
/**
* Collect Amazon-approved Primary / Variants image URLs from a Creators API
* response. Store only a stable hashed image identifier in product settings;
* URLs are obtained from refreshed API data and are never downloaded here.
*
* A single URL per gallery photo is kept, preferring large then medium.
*/
function azlinkpro_amazon_image_choices( $item ) {
$source = ( isset( $item['images'] ) && is_array( $item['images'] ) ? $item['images'] : array() );
$groups = array();
if ( isset( $source['primary'] ) && is_array( $source['primary'] ) ) {
$groups[] = $source['primary'];
}
if ( isset( $source['variants'] ) && is_array( $source['variants'] ) ) {
foreach ( $source['variants'] as $variant ) {
if ( is_array( $variant ) ) {
$groups[] = $variant;
}
}
}
$choices = array();
$seen = array();
foreach ( $groups as $group ) {
$url = '';
$selected_image_size = array();
foreach ( array('large', 'medium', 'small') as $size ) {
$candidate = ( isset( $group[$size]['url'] ) && is_string( $group[$size]['url'] ) ? $group[$size]['url'] : '' );
if ( !$candidate ) {
continue;
}
$parts = wp_parse_url( $candidate );
if ( !is_array( $parts ) || strtolower( $parts['scheme'] ?? '' ) !== 'https' || !in_array( strtolower( $parts['host'] ?? '' ), array('m.media-amazon.com', 'images-na.ssl-images-amazon.com', 'images-eu.ssl-images-amazon.com'), true ) ) {
continue;
}
$url = esc_url_raw( $candidate );
if ( $url ) {
$selected_image_size = $group[$size];
break;
}
}
if ( !$url ) {
continue;
}
$path = (string) wp_parse_url( $url, PHP_URL_PATH );
// Image size suffixes vary between large / medium API images. The
// underlying Amazon image asset ID provides a stable identity.
$base = preg_replace( '/\\._[^\\/]*?(?=\\.[a-z0-9]{2,5}$)/i', '', $path );
$key = md5( ( $base ?: $path ) );
if ( isset( $seen[$key] ) ) {
continue;
}
$seen[$key] = true;
$choices[] = array(
'key' => $key,
'url' => $url,
'label' => ( count( $choices ) === 0 ? 'Main Amazon photo' : 'Amazon photo ' . (count( $choices ) + 1) ),
'width' => absint( $selected_image_size['width'] ?? 0 ),
'height' => absint( $selected_image_size['height'] ?? 0 ),
);
if ( count( $choices ) >= 25 ) {
break;
}
}
return $choices;
}
/** Resolve selected photo against current API images (never store stale URLs). */
function azlinkpro_amazon_selected_image( $product, $key = '' ) {
if ( !is_array( $product ) ) {
return '';
}
$choices = ( isset( $product['images'] ) && is_array( $product['images'] ) ? $product['images'] : array() );
if ( is_string( $key ) && preg_match( '/^[a-f0-9]{32}$/D', $key ) ) {
foreach ( $choices as $choice ) {
if ( is_array( $choice ) && isset( $choice['key'], $choice['url'] ) && hash_equals( $key, (string) $choice['key'] ) ) {
return (string) $choice['url'];
}
}
}
return ( !empty( $product['image'] ) && is_string( $product['image'] ) ? $product['image'] : '' );
}
/** This suite cannot load beside its separate predecessor plugins (duplicate PHP symbols). */
function azlinkpro_conflicting_plugins() {
$legacy = array('AmaTablePro/amatablepro.php', 'amatablepro-showcase/amatablepro-showcase.php', 'amatablepro-pros-cons/amatablepro-pros-cons.php');
$active = (array) get_option( 'active_plugins', array() );
if ( is_multisite() ) {
$active = array_merge( $active, array_keys( (array) get_site_option( 'active_sitewide_plugins', array() ) ) );
}
return array_values( array_intersect( $legacy, $active ) );
}
function azlinkpro_activate() {
if ( azlinkpro_conflicting_plugins() ) {
deactivate_plugins( plugin_basename( AZLINKPRO_FILE ) );
wp_die( esc_html__( 'Deactivate the three separate AzlinkPRO / AmaTablePro plugins before activating the combined AzlinkPRO plugin. Nothing was deleted.', 'azlinkpro' ), esc_html__( 'AzlinkPRO activation conflict', 'azlinkpro' ), array(
'back_link' => true,
) );
}
// 1.5.14: move data saved under the pre-1.5.14 names first, so the empty row below never hides it.
azlinkpro_upgrade_data();
azlinkpro_clicks_install();
// No product tables, showcases, pros/cons cards, tracking codes or credentials are inserted on activation.
if ( get_option( 'azlinkpro_amazon_settings', false ) === false ) {
add_option(
'azlinkpro_amazon_settings',
array(
'tag' => '',
'client_id' => '',
'secret' => '',
'marketplace' => 'www.amazon.com',
),
'',
false
);
}
azlinkpro_setup_flag_redirect();
}
register_activation_hook( AZLINKPRO_FILE, 'azlinkpro_activate' );
function azlinkpro_load_modules() {
if ( azlinkpro_conflicting_plugins() ) {
return;
}
require_once AZLINKPRO_PATH . 'modules/tables/module-tables.php';
// Amazon lookups shared by Tables, the image block and Showcase.
require_once AZLINKPRO_PATH . 'includes/amazon-items.php';
require_once AZLINKPRO_PATH . 'modules/product-images/module-product-images.php';
require_once AZLINKPRO_PATH . 'includes/amalinks-importer.php';
// In the free build, published Showcase and Pros & Cons shortcodes print nothing.
require_once AZLINKPRO_PATH . 'includes/premium-fallbacks.php';
}
add_action( 'plugins_loaded', 'azlinkpro_load_modules', 12 );
function azlinkpro_conflict_notice() {
if ( !current_user_can( 'activate_plugins' ) || !azlinkpro_conflicting_plugins() ) {
return;
}
echo '
AzlinkPRO: The combined plugin is not loading its modules because one or more separate predecessor plugins are still active. Deactivate the old plugins first; their saved content will remain in WordPress.
'The settings form was incomplete. No settings were changed. Reload this page and try again.',
'invalid_tag' => 'Invalid Amazon tracking ID. No settings were changed.',
'invalid_marketplace' => 'Choose a supported Amazon marketplace. No settings were changed.',
'invalid_css_mode' => 'Choose a valid frontend stylesheet mode. No settings were changed.',
'invalid_credentials' => 'The Amazon credential fields are invalid or too long. No settings were changed.',
);
$azp_form_error = ( isset( $_GET['azp_error'] ) && is_string( $_GET['azp_error'] ) ? sanitize_key( wp_unslash( $_GET['azp_error'] ) ) : '' );
// phpcs:enable WordPress.Security.NonceVerification.Recommended
if ( isset( $azp_form_errors[$azp_form_error] ) ) {
?>
One place to configure every AzlinkPRO module. Your Amazon credentials are stored in this WordPress installation, not in the plugin ZIP. New to AzlinkPRO? Run the setup wizard for a step-by-step walkthrough.
Your plan
Test Amazon API connection
After saving your tag and credentials above, enter a product ASIN for your chosen marketplace. This button attempts a live authenticated GetItems request and checks whether Amazon returns the selected product and an image. Credentials are never shown in the result.
No test performed.
Example ASIN B00CH9QWOU is prefilled for convenience and can be replaced with any valid 10-character ASIN. Availability depends on your selected marketplace. This requires an active internet connection and valid Amazon Creators API access. It is a real connection test, not a simulated pass.