'public',
'url' => $site_url,
),
$api_base_url
);
$response = wp_remote_get(
$api_url,
array(
'timeout' => 10,
'sslverify' => true,
)
);
if ( is_wp_error( $response ) ) {
return null;
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
if ( ! isset( $data['success'] ) || true !== $data['success'] ) {
return null;
}
$variables = isset( $data['variables'] ) ? $data['variables'] : array();
// Cache the result.
$cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY );
if ( $cache_expiry > 0 ) {
set_transient( A1TOOLS_CACHE_KEY, $variables, $cache_expiry );
}
return $variables;
}
/**
* Get a single site variable.
*
* @since 1.0.0
* @param string $key Variable key (e.g., 'phone_primary', 'facebook_url').
* @param string $default Default value if not found.
* @return string Variable value.
*/
function a1tools_get_variable( $key, $default = '' ) {
$variables = a1tools_get_site_variables();
if ( null === $variables || ! isset( $variables[ $key ] ) ) {
return $default;
}
return $variables[ $key ];
}
/**
* Clear the site variables cache.
*
* @since 1.0.0
* @return void
*/
function a1tools_clear_cache() {
delete_transient( A1TOOLS_CACHE_KEY );
}
// ============================================================================
// SHORTCODES
// ============================================================================
/**
* Process A1 Tools shortcodes inside HTML attributes (href, src, etc.).
*
* WordPress does not run do_shortcode() inside HTML attributes. This filter
* finds occurrences like href="tel:[a1tools_var key="phone_primary"]"
* or href="mailto:[a1tools_var ...]" and resolves them before normal rendering.
*
* @since 1.5.0
* @param string $content Post content.
* @return string Content with shortcodes resolved inside attributes.
*/
function a1tools_process_shortcodes_in_attributes( $content ) {
// Match href="...anything with [a1tools_ shortcode..." (handles HTML-encoded quotes too)
$content = preg_replace_callback(
'/(?<=href=["\'])([^"\']*\[a1tools_[^\]]*\][^"\']*)/i',
function ( $matches ) {
// Decode HTML entities so shortcode parser can read the attributes
$decoded = html_entity_decode( $matches[1], ENT_QUOTES, 'UTF-8' );
// Process any shortcodes found inside the href value
return do_shortcode( $decoded );
},
$content
);
return $content;
}
add_filter( 'the_content', 'a1tools_process_shortcodes_in_attributes', 7 );
add_filter( 'widget_text', 'a1tools_process_shortcodes_in_attributes', 7 );
/**
* Shortcode: [a1tools_var key="phone_primary"]
* Outputs a site variable value.
*
* @since 1.0.0
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_var( $atts ) {
$atts = shortcode_atts(
array(
'key' => '',
'link' => 'false',
'target' => '_blank',
'class' => '',
'default' => '',
),
$atts,
'a1tools_var'
);
if ( empty( $atts['key'] ) ) {
return '';
}
$value = a1tools_get_variable( $atts['key'], $atts['default'] );
if ( empty( $value ) ) {
return esc_html( $atts['default'] );
}
// Wrap in link if requested and value is a URL.
if ( 'true' === $atts['link'] && filter_var( $value, FILTER_VALIDATE_URL ) ) {
$class_attr = ! empty( $atts['class'] ) ? ' class="' . esc_attr( $atts['class'] ) . '"' : '';
$rel_attr = '_blank' === $atts['target'] ? ' rel="noopener noreferrer"' : '';
return '' . esc_html( $value ) . '';
}
// Wrap in span if class provided.
if ( ! empty( $atts['class'] ) ) {
return '' . esc_html( $value ) . '';
}
return esc_html( $value );
}
add_shortcode( 'a1tools_var', 'a1tools_shortcode_var' );
/**
* Shortcode: [a1tools_website_url]
* Outputs the website URL as text or a clickable link.
*
* @since 1.5.0
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_website_url( $atts ) {
$atts = shortcode_atts(
array(
'link' => 'false',
'target' => '_blank',
'class' => '',
'text' => '',
),
$atts,
'a1tools_website_url'
);
$url = home_url( '/' );
// If link="true", output as a clickable link.
if ( 'true' === $atts['link'] ) {
$link_text = ! empty( $atts['text'] ) ? esc_html( $atts['text'] ) : esc_html( $url );
$class_attr = ! empty( $atts['class'] ) ? ' class="' . esc_attr( $atts['class'] ) . '"' : '';
$rel_attr = '_blank' === $atts['target'] ? ' rel="noopener noreferrer"' : '';
return '' . $link_text . '';
}
// Plain text output.
if ( ! empty( $atts['class'] ) ) {
return '' . esc_html( $url ) . '';
}
return esc_html( $url );
}
add_shortcode( 'a1tools_website_url', 'a1tools_shortcode_website_url' );
/**
* Shortcode: [a1tools_address format="full"]
* Outputs formatted address.
*
* @since 1.0.0
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_address( $atts ) {
$atts = shortcode_atts(
array(
'format' => 'full',
'separator' => '
',
),
$atts,
'a1tools_address'
);
$vars = a1tools_get_site_variables();
if ( ! $vars ) {
return '';
}
$parts = array();
switch ( $atts['format'] ) {
case 'street':
$parts = array_filter(
array(
isset( $vars['address_line1'] ) ? $vars['address_line1'] : '',
isset( $vars['address_line2'] ) ? $vars['address_line2'] : '',
)
);
break;
case 'city_state_zip':
$city_state = array_filter(
array(
isset( $vars['city'] ) ? $vars['city'] : '',
isset( $vars['state'] ) ? $vars['state'] : '',
)
);
$zip = isset( $vars['zip'] ) ? $vars['zip'] : '';
$parts = array( implode( ', ', $city_state ) . ' ' . $zip );
break;
case 'full':
default:
$city = isset( $vars['city'] ) ? $vars['city'] : '';
$state = isset( $vars['state'] ) ? $vars['state'] : '';
$zip = isset( $vars['zip'] ) ? $vars['zip'] : '';
$state_zip = trim( $state . ' ' . $zip );
$city_line = $city ? $city . ( $state_zip ? ', ' . $state_zip : '' ) : $state_zip;
$parts = array_filter(
array(
isset( $vars['address_line1'] ) ? $vars['address_line1'] : '',
isset( $vars['address_line2'] ) ? $vars['address_line2'] : '',
$city_line,
isset( $vars['country'] ) ? $vars['country'] : '',
)
);
break;
}
// Handle separator - allow
for HTML output, otherwise escape.
$separator = $atts['separator'];
if ( '
' === $separator || '
' === $separator || '
' === $separator ) {
return implode( '
', array_map( 'esc_html', $parts ) );
}
return esc_html( implode( $separator, $parts ) );
}
add_shortcode( 'a1tools_address', 'a1tools_shortcode_address' );
/**
* Shortcode: [a1tools_full_address]
* Outputs the complete address in a single line.
*
* @since 1.2.0
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_full_address( $atts ) {
$atts = shortcode_atts(
array(
'separator' => ', ',
'class' => '',
),
$atts,
'a1tools_full_address'
);
$vars = a1tools_get_site_variables();
if ( ! $vars ) {
return '';
}
// Use standalone full_address field if set.
if ( ! empty( $vars['full_address'] ) ) {
$output = esc_html( $vars['full_address'] );
if ( ! empty( $atts['class'] ) ) {
$output = '' . $output . '';
}
return $output;
}
// Fallback: assemble from individual address fields.
$parts = array();
// Address Line 1.
if ( ! empty( $vars['address_line1'] ) ) {
$parts[] = $vars['address_line1'];
}
// Address Line 2.
if ( ! empty( $vars['address_line2'] ) ) {
$parts[] = $vars['address_line2'];
}
// City.
if ( ! empty( $vars['city'] ) ) {
$parts[] = $vars['city'];
}
// State + ZIP (combined).
$state = ! empty( $vars['state'] ) ? $vars['state'] : '';
$zip = ! empty( $vars['zip'] ) ? $vars['zip'] : '';
if ( $state || $zip ) {
$parts[] = trim( $state . ' ' . $zip );
}
if ( empty( $parts ) ) {
return '';
}
$output = esc_html( implode( $atts['separator'], $parts ) );
if ( ! empty( $atts['class'] ) ) {
$output = '' . $output . '';
}
return $output;
}
add_shortcode( 'a1tools_full_address', 'a1tools_shortcode_full_address' );
/**
* Shortcode: [a1tools_hours]
* Outputs formatted operating hours table.
*
* @since 1.0.0
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_hours( $atts ) {
$atts = shortcode_atts(
array(
'format' => 'table',
'class' => 'a1tools-hours',
),
$atts,
'a1tools_hours'
);
$vars = a1tools_get_site_variables();
if ( ! $vars || empty( $vars['operating_hours'] ) ) {
return '';
}
$hours = $vars['operating_hours'];
$days = array(
'mon' => __( 'Monday', 'a1-tools' ),
'tue' => __( 'Tuesday', 'a1-tools' ),
'wed' => __( 'Wednesday', 'a1-tools' ),
'thu' => __( 'Thursday', 'a1-tools' ),
'fri' => __( 'Friday', 'a1-tools' ),
'sat' => __( 'Saturday', 'a1-tools' ),
'sun' => __( 'Sunday', 'a1-tools' ),
);
$output = '';
if ( 'list' === $atts['format'] ) {
$output = '
';
foreach ( $days as $key => $label ) {
$time = isset( $hours[ $key ] ) && ! empty( $hours[ $key ] ) ? esc_html( $hours[ $key ] ) : esc_html__( 'Closed', 'a1-tools' );
$output .= '- ' . esc_html( $label ) . ': ' . $time . '
';
}
$output .= '
';
} else {
$output = '';
foreach ( $days as $key => $label ) {
$time = isset( $hours[ $key ] ) && ! empty( $hours[ $key ] ) ? esc_html( $hours[ $key ] ) : esc_html__( 'Closed', 'a1-tools' );
$output .= '| ' . esc_html( $label ) . ' | ' . $time . ' |
';
}
$output .= '
';
}
return $output;
}
add_shortcode( 'a1tools_hours', 'a1tools_shortcode_hours' );
/**
* Get SVG markup for icons not available in Font Awesome 5.
*
* @since 1.5.0
* @param string $icon_key The SVG icon key (e.g. 'svg-x').
* @return string SVG markup or empty string.
*/
function a1tools_get_svg_icon( $icon_key ) {
$svgs = array(
'svg-x' => '',
);
return isset( $svgs[ $icon_key ] ) ? $svgs[ $icon_key ] : '';
}
/**
* Get icon HTML (SVG or Font Awesome tag).
*
* @since 1.5.0
* @param string $icon Icon class or SVG key.
* @return string HTML markup for the icon.
*/
function a1tools_get_icon_html( $icon ) {
$svg = a1tools_get_svg_icon( $icon );
if ( $svg ) {
return $svg;
}
return '';
}
/**
* Shortcode: [a1tools_social_links]
* Outputs social media icons/links with optional styling.
*
* @since 1.0.0
* @since 1.2.0 Added styling parameters.
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_social_links( $atts ) {
$atts = shortcode_atts(
array(
'class' => 'a1tools-social-links',
'icon_class' => '',
'style' => '', // default, outline, minimal, light, dark
'shape' => 'rounded', // rounded, circle, square
'size' => '40', // button size in px
'icon_size' => '18', // icon size in px
'spacing' => '8', // spacing between icons in px
'alignment' => 'left', // left, center, right
'color' => '', // custom color (hex)
'hover' => 'scale', // scale, lift, rotate, pulse, none
),
$atts,
'a1tools_social_links'
);
$vars = a1tools_get_site_variables();
if ( ! $vars ) {
return '';
}
$platforms = array(
'facebook_url' => array( 'label' => 'Facebook', 'icon' => 'fab fa-facebook-f', 'color' => '#1877F2' ),
'instagram_url' => array( 'label' => 'Instagram', 'icon' => 'fab fa-instagram', 'color' => '#E4405F' ),
'youtube_url' => array( 'label' => 'YouTube', 'icon' => 'fab fa-youtube', 'color' => '#FF0000' ),
'twitter_url' => array( 'label' => 'X (Twitter)', 'icon' => 'svg-x', 'color' => '#000000' ),
'linkedin_url' => array( 'label' => 'LinkedIn', 'icon' => 'fab fa-linkedin-in', 'color' => '#0A66C2' ),
'tiktok_url' => array( 'label' => 'TikTok', 'icon' => 'fab fa-tiktok', 'color' => '#000000' ),
'yelp_url' => array( 'label' => 'Yelp', 'icon' => 'fab fa-yelp', 'color' => '#D32323' ),
'google_business_url' => array( 'label' => 'Google', 'icon' => 'fab fa-google', 'color' => '#4285F4' ),
'pinterest_url' => array( 'label' => 'Pinterest', 'icon' => 'fab fa-pinterest-p', 'color' => '#E60023' ),
'bbb_url' => array( 'label' => 'BBB', 'icon' => 'fas fa-shield-alt', 'color' => '#006CB7' ),
'nextdoor_url' => array( 'label' => 'Nextdoor', 'icon' => 'fas fa-home', 'color' => '#8ED500' ),
'houzz_url' => array( 'label' => 'Houzz', 'icon' => 'fab fa-houzz', 'color' => '#4DBC15' ),
'angi_url' => array( 'label' => 'Angi', 'icon' => 'fas fa-tools', 'color' => '#FF6153' ),
'thumbtack_url' => array( 'label' => 'Thumbtack', 'icon' => 'fas fa-thumbtack', 'color' => '#009FD9' ),
'homeadvisor_url' => array( 'label' => 'HomeAdvisor', 'icon' => 'fas fa-home', 'color' => '#EF6C00' ),
);
// If no style is set, use simple output (backwards compatible).
if ( empty( $atts['style'] ) ) {
$output = '';
return $output;
}
// Styled output.
$style = $atts['style'];
$shape = $atts['shape'];
$size = (int) $atts['size'];
$icon_size = (int) $atts['icon_size'];
$spacing = (int) $atts['spacing'];
$alignment = $atts['alignment'];
$custom_color = $atts['color'];
$hover_effect = $atts['hover'];
// Generate unique ID.
static $instance_count = 0;
$instance_count++;
$widget_id = 'a1tools-social-sc-' . $instance_count;
// Border radius based on shape.
$border_radius = '8px';
if ( 'circle' === $shape ) {
$border_radius = '50%';
} elseif ( 'square' === $shape ) {
$border_radius = '0';
}
// Build CSS for this instance.
$css = '#' . esc_attr( $widget_id ) . ' { text-align:' . esc_attr( $alignment ) . '; }';
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { ';
$css .= 'width:' . (int) $size . 'px;height:' . (int) $size . 'px;';
$css .= 'margin:0 ' . (int) ( $spacing / 2 ) . 'px ' . (int) $spacing . 'px;';
$css .= 'font-size:' . (int) $icon_size . 'px;';
$css .= 'display:inline-flex;align-items:center;justify-content:center;';
$css .= 'text-decoration:none;transition:all 0.3s ease;';
$css .= 'border-radius:' . esc_attr( $border_radius ) . ';';
$css .= '}';
// Style-specific CSS.
if ( 'default' === $style || 'official' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { color: #fff; }';
} elseif ( 'outline' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: transparent; border: 2px solid; }';
} elseif ( 'minimal' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: transparent; }';
} elseif ( 'light' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: #f5f5f5; }';
} elseif ( 'dark' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: #333; color: #fff; }';
}
// Hover effects.
if ( 'scale' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { transform: scale(1.1); }';
} elseif ( 'lift' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }';
} elseif ( 'rotate' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { transform: rotate(15deg); }';
} elseif ( 'pulse' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { animation: a1tools-pulse 0.5s ease; }';
$css .= '@keyframes a1tools-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } }';
}
// Ensure the style handle exists before adding inline CSS.
if ( ! wp_style_is( 'a1tools-social', 'registered' ) ) {
wp_register_style( 'a1tools-social', false, array(), A1TOOLS_VERSION );
}
if ( ! wp_style_is( 'a1tools-social', 'enqueued' ) ) {
wp_enqueue_style( 'a1tools-social' );
}
wp_add_inline_style( 'a1tools-social', $css );
$output = '';
return $output;
}
add_shortcode( 'a1tools_social_links', 'a1tools_shortcode_social_links' );
/**
* Shortcode: [a1tools_city_name]
* Outputs the city name (for geo-targeting).
*
* @since 1.0.8
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_city_name( $atts ) {
$atts = shortcode_atts(
array(
'default' => '',
'class' => '',
),
$atts,
'a1tools_city_name'
);
$value = a1tools_get_variable( 'city_name', $atts['default'] );
if ( empty( $value ) ) {
return esc_html( $atts['default'] );
}
if ( ! empty( $atts['class'] ) ) {
return '' . esc_html( $value ) . '';
}
return esc_html( $value );
}
add_shortcode( 'a1tools_city_name', 'a1tools_shortcode_city_name' );
/**
* Shortcode: [a1tools_state]
* Outputs the state/location name (for geo-targeting).
*
* @since 1.0.8
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_state( $atts ) {
$atts = shortcode_atts(
array(
'default' => '',
'class' => '',
),
$atts,
'a1tools_state'
);
$value = a1tools_get_variable( 'location_name', $atts['default'] );
if ( empty( $value ) ) {
return esc_html( $atts['default'] );
}
if ( ! empty( $atts['class'] ) ) {
return '' . esc_html( $value ) . '';
}
return esc_html( $value );
}
add_shortcode( 'a1tools_state', 'a1tools_shortcode_state' );
/**
* Shortcode: [a1tools_google_map]
* Outputs a Google Maps embed or link.
*
* @since 1.0.8
* @param array $atts Shortcode attributes.
* @return string Shortcode output.
*/
function a1tools_shortcode_google_map( $atts ) {
$atts = shortcode_atts(
array(
'type' => 'link',
'width' => '100%',
'height' => '400',
'class' => 'a1tools-google-map',
'text' => __( 'View on Google Maps', 'a1-tools' ),
'target' => '_blank',
),
$atts,
'a1tools_google_map'
);
$url = a1tools_get_variable( 'google_maps_url', '' );
if ( empty( $url ) ) {
return '';
}
// If type is 'embed', create an iframe
if ( 'embed' === $atts['type'] ) {
// Check if URL is already an embed URL or convert it
$embed_url = $url;
// If it's a regular Google Maps URL, try to convert to embed format
if ( false !== strpos( $url, 'google.com/maps' ) && false === strpos( $url, '/embed' ) ) {
// Extract place or coordinates and create embed URL
if ( preg_match( '/place\/([^\/]+)/', $url, $matches ) ) {
$place = $matches[1];
$embed_url = 'https://www.google.com/maps/embed/v1/place?key=&q=' . urlencode( $place );
} elseif ( preg_match( '/@(-?\d+\.\d+),(-?\d+\.\d+)/', $url, $matches ) ) {
$lat = $matches[1];
$lng = $matches[2];
$embed_url = 'https://maps.google.com/maps?q=' . $lat . ',' . $lng . '&output=embed';
}
}
return '';
}
// Default: return a link
$rel = '_blank' === $atts['target'] ? ' rel="noopener noreferrer"' : '';
return '' . esc_html( $atts['text'] ) . '';
}
add_shortcode( 'a1tools_google_map', 'a1tools_shortcode_google_map' );
// ============================================================================
// REST API ENDPOINTS
// ============================================================================
/**
* Register REST API routes.
*
* @since 1.0.0
* @return void
*/
function a1tools_register_rest_routes() {
// Get site variables (public).
register_rest_route(
'a1-tools/v1',
'/site-variables',
array(
'methods' => 'GET',
'callback' => 'a1tools_rest_get_variables',
'permission_callback' => '__return_true',
)
);
// Clear cache (requires admin).
register_rest_route(
'a1-tools/v1',
'/site-variables/clear-cache',
array(
'methods' => 'POST',
'callback' => 'a1tools_rest_clear_cache',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
// Yoast meta endpoints.
register_rest_route(
'a1-tools/v1',
'/yoast-meta/(?P\d+)',
array(
'methods' => 'POST',
'callback' => 'a1tools_update_yoast_meta',
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},
'args' => array(
'id' => array(
'description' => 'Post ID',
'type' => 'integer',
'required' => true,
'validate_callback' => function ( $param ) {
return is_numeric( $param ) && $param > 0;
},
),
),
)
);
register_rest_route(
'a1-tools/v1',
'/yoast-meta/(?P\d+)',
array(
'methods' => 'GET',
'callback' => 'a1tools_get_yoast_meta',
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},
'args' => array(
'id' => array(
'description' => 'Post ID',
'type' => 'integer',
'required' => true,
'validate_callback' => function ( $param ) {
return is_numeric( $param ) && $param > 0;
},
),
),
)
);
}
add_action( 'rest_api_init', 'a1tools_register_rest_routes' );
/**
* REST: Get site variables.
*
* @since 1.0.0
* @return WP_REST_Response|WP_Error Response object.
*/
function a1tools_rest_get_variables() {
$variables = a1tools_get_site_variables();
if ( null === $variables ) {
return new WP_Error( 'fetch_error', __( 'Could not fetch site variables', 'a1-tools' ), array( 'status' => 500 ) );
}
return new WP_REST_Response(
array(
'success' => true,
'variables' => $variables,
'cached' => false !== get_transient( A1TOOLS_CACHE_KEY ),
),
200
);
}
/**
* REST: Clear variables cache.
*
* @since 1.0.0
* @return WP_REST_Response Response object.
*/
function a1tools_rest_clear_cache() {
a1tools_clear_cache();
return new WP_REST_Response(
array(
'success' => true,
'message' => __( 'Cache cleared', 'a1-tools' ),
),
200
);
}
// ============================================================================
// YOAST SEO INTEGRATION
// ============================================================================
/**
* Get Yoast meta field mapping.
*
* @since 1.0.0
* @return array Mapping of API keys to Yoast meta keys.
*/
function a1tools_get_yoast_meta_mapping() {
return array(
'title' => '_yoast_wpseo_title',
'metadesc' => '_yoast_wpseo_metadesc',
'focuskw' => '_yoast_wpseo_focuskw',
'focuskeywords' => '_yoast_wpseo_focuskeywords',
'keywordsynonyms' => '_yoast_wpseo_keywordsynonyms',
'meta-robots-noindex' => '_yoast_wpseo_meta-robots-noindex',
'meta-robots-nofollow' => '_yoast_wpseo_meta-robots-nofollow',
'meta-robots-adv' => '_yoast_wpseo_meta-robots-adv',
'canonical' => '_yoast_wpseo_canonical',
'redirect' => '_yoast_wpseo_redirect',
'is_cornerstone' => '_yoast_wpseo_is_cornerstone',
'opengraph-title' => '_yoast_wpseo_opengraph-title',
'opengraph-description' => '_yoast_wpseo_opengraph-description',
'opengraph-image' => '_yoast_wpseo_opengraph-image',
'opengraph-image-id' => '_yoast_wpseo_opengraph-image-id',
'twitter-title' => '_yoast_wpseo_twitter-title',
'twitter-description' => '_yoast_wpseo_twitter-description',
'twitter-image' => '_yoast_wpseo_twitter-image',
'twitter-image-id' => '_yoast_wpseo_twitter-image-id',
'schema_page_type' => '_yoast_wpseo_schema_page_type',
'schema_article_type' => '_yoast_wpseo_schema_article_type',
'linkdex' => '_yoast_wpseo_linkdex',
'content_score' => '_yoast_wpseo_content_score',
'primary_category' => '_yoast_wpseo_primary_category',
);
}
/**
* Sanitize Yoast meta value based on field type.
*
* @since 1.0.0
* @param string $key Field key.
* @param mixed $value Value to sanitize.
* @return mixed Sanitized value.
*/
function a1tools_sanitize_yoast_value( $key, $value ) {
$json_fields = array( 'focuskeywords', 'keywordsynonyms' );
if ( in_array( $key, $json_fields, true ) ) {
if ( is_array( $value ) ) {
return wp_json_encode( $value );
}
if ( is_string( $value ) ) {
$decoded = json_decode( $value );
if ( JSON_ERROR_NONE === json_last_error() ) {
return $value;
}
return wp_json_encode( array( $value ) );
}
return '';
}
$bool_fields = array( 'is_cornerstone', 'meta-robots-noindex', 'meta-robots-nofollow' );
if ( in_array( $key, $bool_fields, true ) ) {
return $value ? '1' : '0';
}
$int_fields = array( 'linkdex', 'content_score', 'primary_category', 'opengraph-image-id', 'twitter-image-id' );
if ( in_array( $key, $int_fields, true ) ) {
return (int) $value;
}
$url_fields = array( 'canonical', 'redirect', 'opengraph-image', 'twitter-image' );
if ( in_array( $key, $url_fields, true ) ) {
return esc_url_raw( $value );
}
$text_fields = array( 'title', 'metadesc', 'opengraph-title', 'opengraph-description', 'twitter-title', 'twitter-description' );
if ( in_array( $key, $text_fields, true ) ) {
return sanitize_text_field( $value );
}
if ( is_string( $value ) ) {
return sanitize_text_field( $value );
}
return $value;
}
/**
* Update Yoast SEO meta fields for a post.
*
* @since 1.0.0
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response|WP_Error Response object.
*/
function a1tools_update_yoast_meta( $request ) {
$post_id = (int) $request['id'];
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error( 'post_not_found', __( 'Post not found', 'a1-tools' ), array( 'status' => 404 ) );
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new WP_Error( 'forbidden', __( 'You do not have permission to edit this post', 'a1-tools' ), array( 'status' => 403 ) );
}
$body = $request->get_json_params();
if ( empty( $body ) ) {
return new WP_Error( 'no_data', __( 'No data provided', 'a1-tools' ), array( 'status' => 400 ) );
}
$mapping = a1tools_get_yoast_meta_mapping();
$updated = array();
$errors = array();
foreach ( $body as $key => $value ) {
if ( isset( $mapping[ $key ] ) ) {
$meta_key = $mapping[ $key ];
} elseif ( 0 === strpos( $key, '_yoast_wpseo_' ) ) {
$meta_key = $key;
} else {
continue;
}
$sanitized_value = a1tools_sanitize_yoast_value( $key, $value );
$result = update_post_meta( $post_id, $meta_key, $sanitized_value );
if ( false !== $result ) {
$updated[ $key ] = $sanitized_value;
} else {
$current_value = get_post_meta( $post_id, $meta_key, true );
if ( $current_value === $sanitized_value || $current_value === (string) $sanitized_value ) {
$updated[ $key ] = $sanitized_value;
} else {
$errors[ $key ] = 'Failed to update';
}
}
}
if ( class_exists( 'WPSEO_Meta' ) ) {
// Trigger Yoast SEO's native action hook to refresh their internal cache.
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is Yoast's hook, not ours.
do_action( 'wpseo_saved_postdata' );
}
return new WP_REST_Response(
array(
'success' => true,
'post_id' => $post_id,
'updated' => $updated,
'errors' => $errors,
),
200
);
}
/**
* Get Yoast SEO meta fields for a post.
*
* @since 1.0.0
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response|WP_Error Response object.
*/
function a1tools_get_yoast_meta( $request ) {
$post_id = (int) $request['id'];
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error( 'post_not_found', __( 'Post not found', 'a1-tools' ), array( 'status' => 404 ) );
}
$mapping = a1tools_get_yoast_meta_mapping();
$meta = array();
foreach ( $mapping as $api_key => $meta_key ) {
$value = get_post_meta( $post_id, $meta_key, true );
if ( '' !== $value && false !== $value ) {
$meta[ $api_key ] = $value;
}
}
return new WP_REST_Response(
array(
'success' => true,
'post_id' => $post_id,
'meta' => $meta,
),
200
);
}
// ============================================================================
// ELEMENTOR INTEGRATION
// ============================================================================
/**
* Register Elementor dynamic tags.
*
* @since 1.0.0
* @param \Elementor\Core\DynamicTags\Manager $dynamic_tags_manager Dynamic tags manager.
* @return void
*/
function a1tools_register_elementor_tags( $dynamic_tags_manager ) {
// Register dynamic tag group.
$dynamic_tags_manager->register_group(
'a1-tools',
array(
'title' => __( 'A1 Tools', 'a1-tools' ),
)
);
// Include and register the dynamic tag class.
require_once A1TOOLS_PLUGIN_DIR . 'includes/class-a1-tools-elementor-tag.php';
if ( class_exists( 'A1Tools_Elementor_Tag' ) ) {
$dynamic_tags_manager->register( new A1Tools_Elementor_Tag() );
}
}
add_action( 'elementor/dynamic_tags/register', 'a1tools_register_elementor_tags' );
/**
* Register Elementor widgets.
*
* @since 1.2.0
* @param \Elementor\Widgets_Manager $widgets_manager Widgets manager.
* @return void
*/
function a1tools_register_elementor_widgets( $widgets_manager ) {
// Include and register the Social Icons widget.
require_once A1TOOLS_PLUGIN_DIR . 'includes/class-a1-tools-elementor-widget.php';
if ( class_exists( 'A1Tools_Social_Icons_Elementor_Widget' ) ) {
$widgets_manager->register( new A1Tools_Social_Icons_Elementor_Widget() );
}
// Include and register the Hours widget.
require_once A1TOOLS_PLUGIN_DIR . 'includes/class-a1-tools-hours-widget.php';
if ( class_exists( 'A1Tools_Hours_Elementor_Widget' ) ) {
$widgets_manager->register( new A1Tools_Hours_Elementor_Widget() );
}
}
add_action( 'elementor/widgets/register', 'a1tools_register_elementor_widgets' );
/**
* Register Elementor widget styles.
*
* Registers the base CSS for the A1 Social Icons Elementor widget as an inline
* stylesheet so it loads once and works in both frontend and editor.
*
* @since 1.4.8
* @return void
*/
function a1tools_register_elementor_styles() {
$css = '
.a1tools-social-icons-styled {
display: flex;
flex-wrap: wrap;
align-items: center;
}
.a1tools-social-icons-styled .a1tools-social-icon {
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none !important;
transition: all 0.3s ease;
box-sizing: border-box;
}
.a1tools-social-icons-styled .a1tools-social-icon i {
color: inherit;
line-height: 1;
}
.a1tools-social-icons-styled .a1tools-social-icon .a1tools-svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
}
/* Style presets for non-custom styles */
.a1tools-style-default .a1tools-social-icon,
.a1tools-style-official .a1tools-social-icon { color: #fff; }
.a1tools-style-default .a1tools-social-icon i,
.a1tools-style-official .a1tools-social-icon i { color: #fff; }
.a1tools-style-outline .a1tools-social-icon { background: transparent; border: 2px solid; }
.a1tools-style-minimal .a1tools-social-icon { background: transparent; }
.a1tools-style-light .a1tools-social-icon { background: #f5f5f5; }
.a1tools-style-dark .a1tools-social-icon { background: #333; color: #fff; }
.a1tools-style-dark .a1tools-social-icon i { color: #fff; }
/* Hover effects via prefix_class */
.a1tools-hover-scale .a1tools-social-icon:hover { transform: scale(1.1); }
.a1tools-hover-lift .a1tools-social-icon:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
.a1tools-hover-rotate .a1tools-social-icon:hover { transform: rotate(15deg); }
.a1tools-hover-pulse .a1tools-social-icon:hover { animation: a1tools-pulse 0.5s ease; }
@keyframes a1tools-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } }
';
wp_register_style( 'a1tools-elementor-icons', false, array(), A1TOOLS_VERSION );
wp_enqueue_style( 'a1tools-elementor-icons' );
wp_add_inline_style( 'a1tools-elementor-icons', $css );
}
add_action( 'wp_enqueue_scripts', 'a1tools_register_elementor_styles' );
add_action( 'elementor/editor/before_enqueue_styles', 'a1tools_register_elementor_styles' );
add_action( 'elementor/preview/enqueue_styles', 'a1tools_register_elementor_styles' );
/**
* Register Hours Elementor widget styles.
*
* @since 1.5.2
* @return void
*/
function a1tools_register_hours_styles() {
$css = '
.a1tools-hours-widget {
box-sizing: border-box;
}
.a1tools-hours-title {
display: flex;
align-items: center;
}
.a1tools-hours-widget[style*="text-align: center"] .a1tools-hours-title,
.a1tools-hours-widget[style*="text-align:center"] .a1tools-hours-title {
justify-content: center;
}
.a1tools-hours-widget[style*="text-align: right"] .a1tools-hours-title,
.a1tools-hours-widget[style*="text-align:right"] .a1tools-hours-title {
justify-content: flex-end;
}
.a1tools-hours-title-text {
font-size: inherit;
font-weight: bold;
}
.a1tools-hours-icon {
display: inline-flex;
align-items: center;
line-height: 1;
}
.a1tools-hours-icon i {
color: inherit;
}
.a1tools-hours-row {
display: flex;
align-items: baseline;
gap: 16px;
}
.a1tools-hours-has-divider .a1tools-hours-row {
border-bottom: 1px solid #e0e0e0;
}
.a1tools-hours-has-divider .a1tools-hours-row:last-child {
border-bottom: none;
}
.a1tools-hours-day {
font-weight: 600;
flex-shrink: 0;
}
/* Fixed-width day column so times always align */
.a1tools-hours-layout-list .a1tools-hours-day,
.a1tools-hours-layout-table .a1tools-hours-day {
min-width: 110px;
}
/* Inline layout: horizontal flow */
.a1tools-hours-layout-inline .a1tools-hours-list {
display: flex;
flex-wrap: wrap;
gap: 8px 20px;
}
.a1tools-hours-layout-inline .a1tools-hours-row {
gap: 6px;
}
';
wp_register_style( 'a1tools-elementor-hours', false, array(), A1TOOLS_VERSION );
wp_enqueue_style( 'a1tools-elementor-hours' );
wp_add_inline_style( 'a1tools-elementor-hours', $css );
}
add_action( 'wp_enqueue_scripts', 'a1tools_register_hours_styles' );
add_action( 'elementor/editor/before_enqueue_styles', 'a1tools_register_hours_styles' );
add_action( 'elementor/preview/enqueue_styles', 'a1tools_register_hours_styles' );
// ============================================================================
// ADMIN NOTICES
// ============================================================================
/**
* Display admin notice if site variables cannot be fetched.
*
* @since 1.0.0
* @return void
*/
function a1tools_admin_notices() {
// Only show on plugins page.
$screen = get_current_screen();
if ( ! $screen || 'plugins' !== $screen->id ) {
return;
}
$variables = a1tools_get_site_variables();
if ( null === $variables ) {
?>
'integer',
'default' => A1TOOLS_DEFAULT_CACHE_EXPIRY,
'sanitize_callback' => 'absint',
) );
// Form submission integration settings.
register_setting( 'a1tools_settings', 'a1tools_form_enabled', array(
'type' => 'boolean',
'default' => false,
'sanitize_callback' => 'rest_sanitize_boolean',
) );
register_setting( 'a1tools_settings', 'a1tools_form_secret', array(
'type' => 'string',
'default' => '',
'sanitize_callback' => 'sanitize_text_field',
) );
register_setting( 'a1tools_settings', 'a1tools_form_board_id', array(
'type' => 'integer',
'default' => 0,
'sanitize_callback' => 'absint',
) );
register_setting( 'a1tools_settings', 'a1tools_form_group_id', array(
'type' => 'integer',
'default' => 0,
'sanitize_callback' => 'absint',
) );
}
add_action( 'admin_init', 'a1tools_register_settings' );
/**
* Enqueue admin styles and scripts.
*
* @since 1.3.2
* @param string $hook The current admin page.
* @return void
*/
function a1tools_admin_enqueue_scripts( $hook ) {
// Only load on our settings page.
if ( 'toplevel_page_a1-tools' !== $hook ) {
return;
}
// Register and enqueue admin styles.
wp_register_style( 'a1tools-admin', false, array(), A1TOOLS_VERSION );
wp_enqueue_style( 'a1tools-admin' );
$admin_css = '
.a1tools-shortcode-cell {
display: flex;
align-items: center;
gap: 8px;
}
.a1tools-shortcode-cell code {
font-size: 11px;
flex: 1;
}
.a1tools-copy-btn {
background: none;
border: 1px solid #ddd;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #666;
transition: all 0.2s ease;
}
.a1tools-copy-btn:hover {
background: #f0f0f0;
border-color: #999;
color: #333;
}
.a1tools-copy-btn.copied {
background: #46b450;
border-color: #46b450;
color: #fff;
}
.a1tools-copy-btn .dashicons {
font-size: 14px;
width: 14px;
height: 14px;
}
';
wp_add_inline_style( 'a1tools-admin', $admin_css );
// Register and enqueue admin scripts.
wp_register_script( 'a1tools-admin', false, array(), A1TOOLS_VERSION, true );
wp_enqueue_script( 'a1tools-admin' );
$admin_js = '
function a1toolsGenerateSecret() {
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var secret = "";
for (var i = 0; i < 48; i++) {
secret += chars.charAt(Math.floor(Math.random() * chars.length));
}
document.getElementById("a1tools_form_secret").value = secret;
}
function a1toolsCopyShortcode(btn, shortcode) {
navigator.clipboard.writeText(shortcode).then(function() {
btn.classList.add("copied");
btn.innerHTML = \' Copied!\';
setTimeout(function() {
btn.classList.remove("copied");
btn.innerHTML = \' Copy\';
}, 2000);
}).catch(function(err) {
var textarea = document.createElement("textarea");
textarea.value = shortcode;
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
btn.classList.add("copied");
btn.innerHTML = \' Copied!\';
setTimeout(function() {
btn.classList.remove("copied");
btn.innerHTML = \' Copy\';
}, 2000);
});
}
';
wp_add_inline_script( 'a1tools-admin', $admin_js );
}
add_action( 'admin_enqueue_scripts', 'a1tools_admin_enqueue_scripts' );
/**
* Render settings page.
*
* @since 1.0.9
* @return void
*/
function a1tools_settings_page() {
// Handle cache clear action.
if ( isset( $_POST['a1tools_clear_cache'] ) && check_admin_referer( 'a1tools_clear_cache_nonce' ) ) {
a1tools_clear_cache();
echo '' . esc_html__( 'Cache cleared successfully!', 'a1-tools' ) . '
';
}
$cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY );
$cached_data = get_transient( A1TOOLS_CACHE_KEY );
$is_cached = false !== $cached_data;
?>
array(
'business_name' => array( 'label' => 'Business Name', 'shortcode' => '[a1tools_var key="business_name"]' ),
'city_name' => array( 'label' => 'City Name', 'shortcode' => '[a1tools_city_name]' ),
'location_name' => array( 'label' => 'State', 'shortcode' => '[a1tools_state]' ),
'tagline' => array( 'label' => 'Tagline', 'shortcode' => '[a1tools_var key="tagline"]' ),
'google_maps_url' => array( 'label' => 'Google Maps URL', 'shortcode' => '[a1tools_google_map]' ),
),
'Contact Information' => array(
'phone_primary' => array( 'label' => 'Primary Phone', 'shortcode' => '[a1tools_var key="phone_primary"]' ),
'phone_secondary' => array( 'label' => 'Secondary Phone', 'shortcode' => '[a1tools_var key="phone_secondary"]' ),
'email_primary' => array( 'label' => 'Primary Email', 'shortcode' => '[a1tools_var key="email_primary"]' ),
'email_secondary' => array( 'label' => 'Secondary Email', 'shortcode' => '[a1tools_var key="email_secondary"]' ),
),
'Address' => array(
'address_line1' => array( 'label' => 'Address Line 1', 'shortcode' => '[a1tools_var key="address_line1"]' ),
'address_line2' => array( 'label' => 'Address Line 2', 'shortcode' => '[a1tools_var key="address_line2"]' ),
'city' => array( 'label' => 'City', 'shortcode' => '[a1tools_var key="city"]' ),
'state' => array( 'label' => 'State', 'shortcode' => '[a1tools_var key="state"]' ),
'zip' => array( 'label' => 'ZIP Code', 'shortcode' => '[a1tools_var key="zip"]' ),
'country' => array( 'label' => 'Country', 'shortcode' => '[a1tools_var key="country"]' ),
'address_formatted' => array( 'label' => 'Full Address (multi-line)', 'shortcode' => '[a1tools_address]' ),
'full_address' => array( 'label' => 'Full Address (single line)', 'shortcode' => '[a1tools_full_address]' ),
),
'Social Media' => array(
'facebook_url' => array( 'label' => 'Facebook', 'shortcode' => '[a1tools_var key="facebook_url"]' ),
'instagram_url' => array( 'label' => 'Instagram', 'shortcode' => '[a1tools_var key="instagram_url"]' ),
'youtube_url' => array( 'label' => 'YouTube', 'shortcode' => '[a1tools_var key="youtube_url"]' ),
'twitter_url' => array( 'label' => 'Twitter/X', 'shortcode' => '[a1tools_var key="twitter_url"]' ),
'linkedin_url' => array( 'label' => 'LinkedIn', 'shortcode' => '[a1tools_var key="linkedin_url"]' ),
'tiktok_url' => array( 'label' => 'TikTok', 'shortcode' => '[a1tools_var key="tiktok_url"]' ),
'yelp_url' => array( 'label' => 'Yelp', 'shortcode' => '[a1tools_var key="yelp_url"]' ),
'google_business_url' => array( 'label' => 'Google Business', 'shortcode' => '[a1tools_var key="google_business_url"]' ),
'pinterest_url' => array( 'label' => 'Pinterest', 'shortcode' => '[a1tools_var key="pinterest_url"]' ),
'bbb_url' => array( 'label' => 'BBB', 'shortcode' => '[a1tools_var key="bbb_url"]' ),
'nextdoor_url' => array( 'label' => 'Nextdoor', 'shortcode' => '[a1tools_var key="nextdoor_url"]' ),
'houzz_url' => array( 'label' => 'Houzz', 'shortcode' => '[a1tools_var key="houzz_url"]' ),
'angi_url' => array( 'label' => 'Angi', 'shortcode' => '[a1tools_var key="angi_url"]' ),
'thumbtack_url' => array( 'label' => 'Thumbtack', 'shortcode' => '[a1tools_var key="thumbtack_url"]' ),
'homeadvisor_url' => array( 'label' => 'HomeAdvisor', 'shortcode' => '[a1tools_var key="homeadvisor_url"]' ),
),
);
?>
$fields ) : ?>
|
|
'Monday',
'tue' => 'Tuesday',
'wed' => 'Wednesday',
'thu' => 'Thursday',
'fri' => 'Friday',
'sat' => 'Saturday',
'sun' => 'Sunday',
);
foreach ( $days as $key => $label ) :
$hours = isset( $variables['operating_hours'][ $key ] ) ? $variables['operating_hours'][ $key ] : '';
?>
|
|
[a1tools_hours]
[a1tools_hours format="list"]
__( 'Display social media icons from A1 Tools with customizable styling.', 'a1-tools' ),
'classname' => 'a1tools-social-widget',
)
);
}
/**
* Frontend display.
*
* @param array $args Widget arguments.
* @param array $instance Widget instance.
*/
public function widget( $args, $instance ) {
$vars = a1tools_get_site_variables();
if ( ! $vars ) {
return;
}
// Widget settings with defaults.
$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
$shape = ! empty( $instance['shape'] ) ? $instance['shape'] : 'rounded';
$style = ! empty( $instance['style'] ) ? $instance['style'] : 'default';
$size = ! empty( $instance['size'] ) ? (int) $instance['size'] : 40;
$icon_size = ! empty( $instance['icon_size'] ) ? (int) $instance['icon_size'] : 18;
$spacing = ! empty( $instance['spacing'] ) ? (int) $instance['spacing'] : 8;
$alignment = ! empty( $instance['alignment'] ) ? $instance['alignment'] : 'left';
$custom_color = ! empty( $instance['custom_color'] ) ? $instance['custom_color'] : '';
$hover_effect = ! empty( $instance['hover_effect'] ) ? $instance['hover_effect'] : 'scale';
// Platform configurations with official colors.
$platforms = array(
'facebook_url' => array( 'label' => 'Facebook', 'icon' => 'fab fa-facebook-f', 'color' => '#1877F2' ),
'instagram_url' => array( 'label' => 'Instagram', 'icon' => 'fab fa-instagram', 'color' => '#E4405F' ),
'youtube_url' => array( 'label' => 'YouTube', 'icon' => 'fab fa-youtube', 'color' => '#FF0000' ),
'twitter_url' => array( 'label' => 'X (Twitter)', 'icon' => 'svg-x', 'color' => '#000000' ),
'linkedin_url' => array( 'label' => 'LinkedIn', 'icon' => 'fab fa-linkedin-in', 'color' => '#0A66C2' ),
'tiktok_url' => array( 'label' => 'TikTok', 'icon' => 'fab fa-tiktok', 'color' => '#000000' ),
'yelp_url' => array( 'label' => 'Yelp', 'icon' => 'fab fa-yelp', 'color' => '#D32323' ),
'google_business_url' => array( 'label' => 'Google', 'icon' => 'fab fa-google', 'color' => '#4285F4' ),
'pinterest_url' => array( 'label' => 'Pinterest', 'icon' => 'fab fa-pinterest-p', 'color' => '#E60023' ),
'bbb_url' => array( 'label' => 'BBB', 'icon' => 'fas fa-shield-alt', 'color' => '#006CB7' ),
'nextdoor_url' => array( 'label' => 'Nextdoor', 'icon' => 'fas fa-home', 'color' => '#8ED500' ),
'houzz_url' => array( 'label' => 'Houzz', 'icon' => 'fab fa-houzz', 'color' => '#4DBC15' ),
'angi_url' => array( 'label' => 'Angi', 'icon' => 'fas fa-tools', 'color' => '#FF6153' ),
'thumbtack_url' => array( 'label' => 'Thumbtack', 'icon' => 'fas fa-thumbtack', 'color' => '#009FD9' ),
'homeadvisor_url' => array( 'label' => 'HomeAdvisor', 'icon' => 'fas fa-home', 'color' => '#EF6C00' ),
);
// Check if any platforms have URLs.
$has_platforms = false;
foreach ( $platforms as $key => $data ) {
if ( ! empty( $vars[ $key ] ) ) {
$has_platforms = true;
break;
}
}
if ( ! $has_platforms ) {
return;
}
// Generate unique ID for this widget instance.
$widget_id = 'a1tools-social-' . $this->number;
// Build inline styles.
$container_styles = array(
'text-align' => $alignment,
);
$link_styles = array(
'width' => $size . 'px',
'height' => $size . 'px',
'margin' => '0 ' . ( $spacing / 2 ) . 'px ' . $spacing . 'px',
'font-size' => $icon_size . 'px',
'display' => 'inline-flex',
'align-items' => 'center',
'justify-content' => 'center',
'text-decoration' => 'none',
'transition' => 'all 0.3s ease',
);
// Shape styles.
switch ( $shape ) {
case 'circle':
$link_styles['border-radius'] = '50%';
break;
case 'square':
$link_styles['border-radius'] = '0';
break;
case 'rounded':
default:
$link_styles['border-radius'] = '8px';
break;
}
// Output widget.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget args are pre-escaped by WordPress.
echo $args['before_widget'];
if ( $title ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget args are pre-escaped by WordPress.
echo $args['before_title'] . esc_html( apply_filters( 'widget_title', $title ) ) . $args['after_title'];
}
// Build CSS for this widget instance.
$css = '#' . esc_attr( $widget_id ) . ' { ';
foreach ( $container_styles as $prop => $val ) {
$css .= esc_attr( $prop ) . ':' . esc_attr( $val ) . ';';
}
$css .= '}';
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { ';
foreach ( $link_styles as $prop => $val ) {
$css .= esc_attr( $prop ) . ':' . esc_attr( $val ) . ';';
}
$css .= '}';
// Style-specific CSS.
if ( 'default' === $style || 'official' === $style ) {
// Official colors or custom color background.
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { color: #fff; }';
} elseif ( 'outline' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: transparent; border: 2px solid; }';
} elseif ( 'minimal' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: transparent; }';
} elseif ( 'light' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: #f5f5f5; }';
} elseif ( 'dark' === $style ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon { background: #333; color: #fff; }';
}
// Hover effects.
if ( 'scale' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { transform: scale(1.1); }';
} elseif ( 'lift' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.2); }';
} elseif ( 'rotate' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { transform: rotate(15deg); }';
} elseif ( 'pulse' === $hover_effect ) {
$css .= '#' . esc_attr( $widget_id ) . ' .a1tools-social-icon:hover { animation: a1tools-pulse 0.5s ease; }';
$css .= '@keyframes a1tools-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.15); } }';
}
// Add CSS using WordPress enqueue system.
wp_register_style( 'a1tools-social-widget-' . $this->number, false, array(), A1TOOLS_VERSION );
wp_enqueue_style( 'a1tools-social-widget-' . $this->number );
wp_add_inline_style( 'a1tools-social-widget-' . $this->number, $css );
// Output icons container.
echo '';
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Widget args are pre-escaped by WordPress.
echo $args['after_widget'];
}
/**
* Backend form.
*
* @param array $instance Widget instance.
*/
public function form( $instance ) {
$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
$shape = ! empty( $instance['shape'] ) ? $instance['shape'] : 'rounded';
$style = ! empty( $instance['style'] ) ? $instance['style'] : 'default';
$size = ! empty( $instance['size'] ) ? (int) $instance['size'] : 40;
$icon_size = ! empty( $instance['icon_size'] ) ? (int) $instance['icon_size'] : 18;
$spacing = ! empty( $instance['spacing'] ) ? (int) $instance['spacing'] : 8;
$alignment = ! empty( $instance['alignment'] ) ? $instance['alignment'] : 'left';
$custom_color = ! empty( $instance['custom_color'] ) ? $instance['custom_color'] : '';
$hover_effect = ! empty( $instance['hover_effect'] ) ? $instance['hover_effect'] : 'scale';
?>
get( 'form_settings' );
$form_id = $form_settings['id'] ?? 'unknown';
$form_name = $form_settings['form_name'] ?? 'Contact Form';
// Get the raw form field values.
$raw_fields = $record->get( 'fields' );
// Build a clean associative array: field_id => value.
$fields = array();
foreach ( $raw_fields as $field_id => $field_data ) {
$fields[ $field_id ] = $field_data['value'] ?? '';
}
// Get source page URL.
$referrer = isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_url( $_SERVER['HTTP_REFERER'] ) : '';
// Build the API payload.
$payload = array(
'action' => 'submit',
'form_id' => $form_id,
'form_name' => $form_name,
'fields' => $fields,
'source_url' => $referrer,
'source_ip' => a1tools_get_client_ip(),
'form_secret' => $form_secret,
);
// API endpoint.
$api_url = 'https://tools.a-1chimney.com/api/sunday/forms.php?action=submit';
/**
* Filter the A1 Tools form submission API URL.
*
* @since 1.5.0
* @param string $api_url The API URL.
*/
$api_url = apply_filters( 'a1tools_form_api_url', $api_url );
// Defer the API call to PHP shutdown so it doesn't interfere with
// Elementor's form processing pipeline (MailChimp, email, etc.).
// The AJAX response is sent to the browser first, then our request fires.
add_action( 'shutdown', function() use ( $api_url, $payload, $form_secret ) {
$response = wp_remote_post(
$api_url,
array(
'timeout' => 15,
'httpversion' => '1.1',
'blocking' => false,
'headers' => array(
'Content-Type' => 'application/json',
'X-Form-Secret' => $form_secret,
),
'body' => wp_json_encode( $payload ),
'sslverify' => true,
)
);
if ( is_wp_error( $response ) ) {
error_log( '[A1 Tools] Form submission API error: ' . $response->get_error_message() );
}
});
}
// Only register the form hook when form forwarding is enabled.
// This prevents any interference with other Elementor form actions (e.g., MailChimp).
if ( get_option( 'a1tools_form_enabled', false ) ) {
add_action( 'elementor_pro/forms/new_record', 'a1tools_handle_elementor_form_submission', 99, 2 );
}
/**
* Get client IP address, handling proxies.
*
* @since 1.5.0
* @return string Client IP.
*/
function a1tools_get_client_ip() {
if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
$ips = explode( ',', sanitize_text_field( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
return trim( $ips[0] );
}
if ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) ) {
return sanitize_text_field( $_SERVER['HTTP_X_REAL_IP'] );
}
return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : '0.0.0.0';
}