'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 = '
| ' . esc_html( $label ) . ' | ' . $time . ' |
' . esc_html__( 'Cache cleared successfully!', 'a1-tools' ) . '
|
[a1tools_hours]
[a1tools_hours format="list"]
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'; }