'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 ); delete_transient( A1TOOLS_STORES_CACHE_KEY ); delete_transient( A1TOOLS_FRANCHISES_CACHE_KEY ); delete_transient( A1TOOLS_CITY_PAGES_CACHE_KEY ); delete_transient( A1TOOLS_SERVICES_CACHE_KEY ); delete_transient( A1TOOLS_REVIEWS_CACHE_KEY ); delete_transient( A1TOOLS_BEFORE_AFTER_CACHE_KEY ); delete_transient( A1TOOLS_TEAM_CACHE_KEY ); delete_transient( A1TOOLS_FAQ_CACHE_KEY ); if ( defined( 'A1TOOLS_SEO_LOCATIONS_CACHE_KEY' ) ) { delete_transient( A1TOOLS_SEO_LOCATIONS_CACHE_KEY ); } } /** * Get group store locations from the A1 Tools API. * * Fetches store data for the group that this site belongs to. * Results are cached using transients. * * @since 1.7.0 * @param bool $force_refresh Whether to bypass cache. * @return array|null Array of stores or null on failure. */ function a1tools_get_group_stores( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_STORES_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_stores_api_url', A1TOOLS_DEFAULT_STORES_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } $stores = isset( $data['stores'] ) ? $data['stores'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_STORES_CACHE_KEY, $stores, $cache_expiry ); } return $stores; } /** * Get franchise locations from the A1 Tools API. * * Fetches franchise data for this specific site. * Results are cached using transients. * * @since 1.7.1 * @param bool $force_refresh Whether to bypass cache. * @return array|null Array of franchises or null on failure. */ function a1tools_get_site_franchises( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_FRANCHISES_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_franchises_api_url', A1TOOLS_DEFAULT_FRANCHISES_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } $franchises = isset( $data['franchises'] ) ? $data['franchises'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_FRANCHISES_CACHE_KEY, $franchises, $cache_expiry ); } return $franchises; } /** * Get city page data from the A1 Tools API. * * Fetches city-specific phone/address data for this site's WordPress city pages. * Results are cached using transients and keyed by wp_page_id for O(1) shortcode lookup. * * @since 1.7.9 * @param bool $force_refresh Whether to bypass cache. * @return array|null Associative array keyed by wp_page_id, or null on failure. */ function a1tools_get_site_city_pages( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_CITY_PAGES_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_city_pages_api_url', A1TOOLS_DEFAULT_CITY_PAGES_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } // city_pages is already keyed by wp_page_id from the API $city_pages = isset( $data['city_pages'] ) ? $data['city_pages'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_CITY_PAGES_CACHE_KEY, $city_pages, $cache_expiry ); } return $city_pages; } /** * Get ALL WordPress city pages by querying the local WP database directly. * * City pages are identified by author display names containing "City" * (e.g., "Single City", "Location City"), excluding "SEO City" authors. * This returns every published city page regardless of whether it has * saved data in the A1 Tools API. * * Uses $wpdb direct queries and WP_Query for maximum reliability across * all WordPress versions and configurations. * * @since 1.8.2 * @return array Array of city pages keyed by WP page ID. */ function a1tools_get_wp_city_pages() { static $cached = null; if ( null !== $cached ) { return $cached; } global $wpdb; // Direct DB query for city authors — most reliable across all WP versions. // Match display_name containing "city" but NOT "seo" (excludes "SEO City" authors). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- cached via static variable above. $authors = $wpdb->get_results( $wpdb->prepare( "SELECT ID, display_name FROM {$wpdb->users} WHERE LOWER(display_name) LIKE %s AND LOWER(display_name) NOT LIKE %s", '%city%', '%seo%' ) ); if ( empty( $authors ) ) { $cached = array(); return $cached; } $city_author_ids = array(); $author_names = array(); foreach ( $authors as $author ) { $city_author_ids[] = (int) $author->ID; $author_names[ (int) $author->ID ] = $author->display_name; } // Use WP_Query for pages — handles large result sets reliably. $query = new WP_Query( array( 'post_type' => 'page', 'author__in' => $city_author_ids, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', 'no_found_rows' => true, // Skip counting for performance. ) ); $result = array(); if ( $query->have_posts() ) { foreach ( $query->posts as $page ) { $author_id = (int) $page->post_author; $author_name = isset( $author_names[ $author_id ] ) ? $author_names[ $author_id ] : ''; $page_type = ( false !== stripos( $author_name, 'location' ) ) ? 'location_city' : 'single_city'; $result[ $page->ID ] = array( 'wp_page_id' => $page->ID, 'page_title' => $page->post_title, 'page_type' => $page_type, 'author_name' => $author_name, 'group_name' => null, 'group_order' => 0, ); } } wp_reset_postdata(); $cached = $result; return $cached; } /** * Get ALL WordPress service pages by querying the local WP database directly. * * Service pages are identified by author display names containing "large service" * (e.g., "Large Service"), excluding "SEO" authors. * This returns every published service page regardless of whether it has * saved data in the A1 Tools API. * * @since 1.9.0 * @return array Array of service pages keyed by WP page ID. */ function a1tools_get_wp_service_pages() { static $cached = null; if ( null !== $cached ) { return $cached; } global $wpdb; // Direct DB query for service authors — most reliable across all WP versions. // Match display_name containing "large service" but NOT "seo". // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- cached via static variable above. $authors = $wpdb->get_results( $wpdb->prepare( "SELECT ID, display_name FROM {$wpdb->users} WHERE LOWER(display_name) LIKE %s AND LOWER(display_name) NOT LIKE %s", '%large service%', '%seo%' ) ); if ( empty( $authors ) ) { $cached = array(); return $cached; } $service_author_ids = array(); $author_names = array(); foreach ( $authors as $author ) { $service_author_ids[] = (int) $author->ID; $author_names[ (int) $author->ID ] = $author->display_name; } // Use WP_Query for pages — handles large result sets reliably. $query = new WP_Query( array( 'post_type' => 'page', 'author__in' => $service_author_ids, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', 'no_found_rows' => true, // Skip counting for performance. ) ); $result = array(); if ( $query->have_posts() ) { foreach ( $query->posts as $page ) { $author_id = (int) $page->post_author; $author_name = isset( $author_names[ $author_id ] ) ? $author_names[ $author_id ] : ''; $result[ $page->ID ] = array( 'wp_page_id' => $page->ID, 'page_title' => $page->post_title, 'author_name' => $author_name, ); } } wp_reset_postdata(); $cached = $result; return $cached; } /** * Get service data from the A1 Tools API. * * Fetches service-specific data for this site's WordPress service pages. * Results are cached using transients and keyed by wp_page_id for O(1) shortcode lookup. * * @since 1.9.0 * @param bool $force_refresh Whether to bypass cache. * @return array|null Associative array keyed by wp_page_id, or null on failure. */ function a1tools_get_site_services( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_SERVICES_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_services_api_url', A1TOOLS_DEFAULT_SERVICES_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } $services = isset( $data['services'] ) ? $data['services'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_SERVICES_CACHE_KEY, $services, $cache_expiry ); } return $services; } /** * Get group reviews from the A1 Tools API. * * Fetches review data for the group that this site belongs to. * Results are cached using transients. * * @since 1.9.0 * @param bool $force_refresh Whether to bypass cache. * @return array|null Array of reviews or null on failure. */ function a1tools_get_group_reviews( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_REVIEWS_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_reviews_api_url', A1TOOLS_DEFAULT_REVIEWS_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } $reviews = isset( $data['reviews'] ) ? $data['reviews'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_REVIEWS_CACHE_KEY, $reviews, $cache_expiry ); } return $reviews; } /** * Get group before/after items from the A1 Tools API. * * Fetches before/after comparison data for the group that this site belongs to. * Results are cached using transients. * * @since 1.9.0 * @param bool $force_refresh Whether to bypass cache. * @return array|null Array of before/after items or null on failure. */ function a1tools_get_group_before_after( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_BEFORE_AFTER_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_before_after_api_url', A1TOOLS_DEFAULT_BEFORE_AFTER_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } $items = isset( $data['items'] ) ? $data['items'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_BEFORE_AFTER_CACHE_KEY, $items, $cache_expiry ); } return $items; } /** * Get group team members from the A1 Tools API. * * Fetches team member data for the group that this site belongs to. * Results are cached using transients. * * @since 1.9.0 * @param bool $force_refresh Whether to bypass cache. * @return array|null Array of team members or null on failure. */ function a1tools_get_group_team( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_TEAM_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_team_api_url', A1TOOLS_DEFAULT_TEAM_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } $members = isset( $data['members'] ) ? $data['members'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_TEAM_CACHE_KEY, $members, $cache_expiry ); } return $members; } /** * Get group FAQ items from the A1 Tools API. * * Fetches FAQ data for the group that this site belongs to. * Results are cached using transients. * * @since 1.9.0 * @param bool $force_refresh Whether to bypass cache. * @return array|null Array of FAQ items or null on failure. */ function a1tools_get_group_faq( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_FAQ_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_faq_api_url', A1TOOLS_DEFAULT_FAQ_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } $faqs = isset( $data['faqs'] ) ? $data['faqs'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_FAQ_CACHE_KEY, $faqs, $cache_expiry ); } return $faqs; } /** * Get SEO location data from the A1 Tools API. * * Fetches SEO-specific location data (phone, address, web URLs, map, viewpoint) * for this site's WordPress SEO city pages. * Results are cached using transients and keyed by wp_page_id for O(1) shortcode lookup. * * @since 2.1.0 * @param bool $force_refresh Whether to bypass cache. * @return array|null Associative array keyed by wp_page_id, or null on failure. */ function a1tools_get_seo_locations( $force_refresh = false ) { // Check cache first. if ( ! $force_refresh ) { $cached = get_transient( A1TOOLS_SEO_LOCATIONS_CACHE_KEY ); if ( false !== $cached ) { return $cached; } } $site_url = site_url(); $api_base_url = apply_filters( 'a1tools_seo_locations_api_url', A1TOOLS_DEFAULT_SEO_LOCATIONS_API_URL ); $api_url = add_query_arg( array( 'action' => '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; } // seo_locations is already keyed by wp_page_id from the API. $seo_locations = isset( $data['seo_locations'] ) ? $data['seo_locations'] : array(); // Cache the result. $cache_expiry = (int) get_option( 'a1tools_cache_expiry', A1TOOLS_DEFAULT_CACHE_EXPIRY ); if ( $cache_expiry > 0 ) { set_transient( A1TOOLS_SEO_LOCATIONS_CACHE_KEY, $seo_locations, $cache_expiry ); } return $seo_locations; } /** * Get ALL WordPress SEO city pages by querying the local WP database directly. * * SEO city pages are identified by author display names containing BOTH * 'seo' AND 'city' (e.g., "SEO City"). * This returns every published SEO city page regardless of whether it has * saved data in the A1 Tools API. * * Uses $wpdb direct queries and WP_Query for maximum reliability across * all WordPress versions and configurations. * * @since 2.1.0 * @return array Array of SEO city pages keyed by WP page ID. */ function a1tools_get_wp_seo_city_pages() { static $cached = null; if ( null !== $cached ) { return $cached; } global $wpdb; // Direct DB query for SEO city authors — match display_name containing // both "seo" and "city" (e.g., "SEO City"). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- cached via static variable above. $authors = $wpdb->get_results( $wpdb->prepare( "SELECT ID, display_name FROM {$wpdb->users} WHERE LOWER(display_name) LIKE %s AND LOWER(display_name) LIKE %s", '%seo%', '%city%' ) ); if ( empty( $authors ) ) { $cached = array(); return $cached; } $seo_author_ids = array(); $author_names = array(); foreach ( $authors as $author ) { $seo_author_ids[] = (int) $author->ID; $author_names[ (int) $author->ID ] = $author->display_name; } // Use WP_Query for pages — handles large result sets reliably. $query = new WP_Query( array( 'post_type' => 'page', 'author__in' => $seo_author_ids, 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC', 'no_found_rows' => true, // Skip counting for performance. ) ); $result = array(); if ( $query->have_posts() ) { foreach ( $query->posts as $page ) { $author_id = (int) $page->post_author; $author_name = isset( $author_names[ $author_id ] ) ? $author_names[ $author_id ] : ''; $result[ $page->ID ] = array( 'wp_page_id' => $page->ID, 'page_title' => $page->post_title, 'author_name' => $author_name, 'group_name' => null, 'group_order' => 0, ); } } wp_reset_postdata(); $cached = $result; return $cached; }