d_action( 'admin_footer', 'wpseo_enqueue_geocoder' ); $asset_manager = new WPSEO_Local_Admin_Assets(); $asset_manager->enqueue_script( 'google-maps' ); $noscript_output = ''; $map = ''; $wpseo_enqueue_geocoder = true; if ( ! is_array( $lats ) || empty( $lats ) || ! is_array( $longs ) || empty( $longs ) ) { return; } if ( $atts['center'] === '' ) { $center_lat = ( min( $lats ) + ( ( max( $lats ) - min( $lats ) ) / 2 ) ); $center_long = ( min( $longs ) + ( ( max( $longs ) - min( $longs ) ) / 2 ) ); } else { $center_lat = get_post_meta( $atts['center'], '_wpseo_coordinates_lat', true ); $center_long = get_post_meta( $atts['center'], '_wpseo_coordinates_long', true ); } // Default to zoom 10 if there's only one location as a center + bounds would zoom in far too much. if ( $atts['zoom'] == -1 && count( $location_array ) === 1 ) { $atts['zoom'] = 10; } if ( $location_array_str != '' ) { $wpseo_map .= '' . PHP_EOL; // Override(reset) the setting for images inside the map. $map .= '
' . $noscript_output . '
'; $route_tag = apply_filters( 'wpseo_local_location_route_title_name', 'h3' ); $route_label = apply_filters( 'wpseo_local_location_route_label', __( 'Route', 'yoast-local-seo' ) ); /** * Show the route planner. Only do so when 'show_route' is set to true and the number of locations is equal to 1. * Also show it when it's the store locator. */ if ( $atts['show_route'] && ( count( $locations ) === 1 || $atts['from_sl'] === true ) ) { $location = reset( $locations ); $map .= ''; } // Show the filter if categories are set, there's more than 1 and if the filter is enabled. if ( isset( $all_categories ) && count( $all_categories ) > 1 && $atts['show_category_filter'] ) { $map .= ''; } } if ( $atts['echo'] ) { echo $map; } return $map; } /** * Opening hours shortcode handler, for not breaking backwards compatibility * * @param array $atts Array of shortcode attributes. * * @return string */ function wpseo_local_show_openinghours_shortcode_cb( $atts ) { return wpseo_local_show_opening_hours( $atts ); } /** * Function for displaying opening hours * * @since 0.1 * * @param array $atts Array of shortcode parameters. * @param bool $standalone Whether the opening hours are used stand alone or part of another function (like address). * * @return string|false Opening hours HTML display string or an empty string if the location is unclear * or false when opening hours are hidden. */ function wpseo_local_show_opening_hours( $atts, $standalone = true ) { $opening_hours_repo = new WPSEO_Local_Opening_Hours_Repository(); $defaults = [ 'id' => '', 'term_id' => '', 'hide_closed' => false, 'echo' => false, 'comment' => '', 'show_days' => array_keys( $opening_hours_repo->get_days() ), 'show_open_label' => false, ]; $atts = wpseo_check_falses( shortcode_atts( $defaults, $atts, 'wpseo_local_opening_hours' ) ); // Bail if no current location is chosen when using multiple location setup. if ( wpseo_has_multiple_locations() && empty( $atts['id'] ) ) { return ''; } $options = get_option( 'wpseo_local' ); $open_24h_label = ( ! empty( $options['open_24h_label'] ) ? $options['open_24h_label'] : __( 'Open 24 hours', 'yoast-local-seo' ) ); $open_247_label = ( ! empty( $options['open_247_label'] ) ? $options['open_247_label'] : __( 'Open 24/7', 'yoast-local-seo' ) ); if ( isset( $options['hide_opening_hours'] ) && $options['hide_opening_hours'] === 'on' ) { return false; } // Initiate the Locations repository and get query the locations. $repo = new WPSEO_Local_Locations_Repository(); $filter_args = [ 'id' => explode( ',', $atts['id'] ), 'category_id' => $atts['term_id'], ]; $locations = $repo->get( $filter_args ); $container_id = 'wpseo-opening-hours-' . $atts['id']; $output = ''; foreach ( $locations as $location ) { if ( $location['business_type'] == '' ) { $location['business_type'] = 'LocalBusiness'; } // Output meta tags with required address information when using this as stand alone. if ( $standalone === true ) { $output .= '
'; } $output .= ''; // Check if the location is open 24/7. if ( ( wpseo_has_multiple_locations() && get_post_meta( $location['post_id'], '_wpseo_open_247', true ) === 'on' ) || ( ! wpseo_has_multiple_locations() && isset( $options['open_247'] ) && $options['open_247'] === 'on' ) ) { $output .= ''; $output .= ''; $output .= ''; } else { // Make the array itterable (Is that a word?). $opening_hours_repo = new WPSEO_Local_Opening_Hours_Repository(); $days = $opening_hours_repo->get_days(); $timezone_repository = new WPSEO_Local_Timezone_Repository(); $location_datetime = $timezone_repository->get_location_datetime( $location['post_id'] ); $format_24h = wpseo_check_falses( $location['format_24h'] ); if ( ! is_array( $atts['show_days'] ) ) { $show_days = explode( ',', $atts['show_days'] ); } else { $show_days = (array) $atts['show_days']; } // Loop through the days array where start_of_week is the first key, with a max of 7. if ( ! $show_days == 0 ) { foreach ( $days as $key => $day ) { // Check if the opening hours for this day should be shown. if ( is_array( $show_days ) && ! empty( $show_days ) && ! in_array( $key, $show_days, true ) ) { continue; } $oh_post_id = ( wpseo_has_multiple_locations() === true ) ? $location['post_id'] : 'options'; $opening_hours = $opening_hours_repo->get_opening_hours( $key, $oh_post_id, $options, $format_24h ); // Skip when it's closed on this day. if ( ( $opening_hours['value_from'] === 'closed' || $opening_hours['value_to'] === 'closed' ) && $atts['hide_closed'] ) { continue; } $output .= ''; $output .= ''; $output .= ''; $output .= ''; } } } $output .= '
' . $open_247_label . '
' . $day . ''; $output_time = ''; if ( $opening_hours['value_from'] !== 'closed' && $opening_hours['value_to'] !== 'closed' && $opening_hours['open_24h'] !== 'on' ) { $output_time .= '' . $opening_hours['value_from_formatted'] . ' - ' . $opening_hours['value_to_formatted'] . ''; } elseif ( $opening_hours['open_24h'] === 'on' ) { $output_time .= '' . ( $open_24h_label ) . ''; } else { $output_time .= ( ! empty( $options['closed_label'] ) ? $options['closed_label'] : __( 'Closed', 'yoast-local-seo' ) ); } if ( $opening_hours['use_multiple_times'] && $opening_hours['open_24h'] !== 'on' ) { if ( $opening_hours['value_from'] !== 'closed' && $opening_hours['value_to'] !== 'closed' && $opening_hours['value_second_from'] !== 'closed' && $opening_hours['value_second_to'] !== 'closed' ) { $output_time .= ' ' . __( 'and', 'yoast-local-seo' ) . ' '; $output_time .= '' . $opening_hours['value_second_from_formatted'] . ' - ' . $opening_hours['value_second_to_formatted'] . ''; } } $output_time = apply_filters( 'wpseo_opening_hours_time', $output_time, $day, $opening_hours['value_from'], $opening_hours['value_to'], $atts ); $show_open_now_label = apply_filters( 'wpseo_local_show_open_now_label', $atts['show_open_label'] ); $location_open = $timezone_repository->is_location_open( $location['post_id'] ); $output .= $output_time; if ( ! empty( $location_datetime ) && $key === strtolower( $location_datetime->format( 'l' ) ) && ( ! is_wp_error( $location_open ) && ! empty( $location_open ) ) && $show_open_now_label ) { $output .= ' ' . __( 'Open now', 'yoast-local-seo' ) . ''; } $output .= '
'; if ( $standalone === true ) { $output .= '
'; // .wpseo-opening-hours-wrapper } if ( $atts['comment'] != '' ) { $output .= '
' . wpautop( html_entity_decode( $atts['comment'], ENT_COMPAT, get_bloginfo( 'charset' ) ) ) . '
'; } } // Add filter to add optional output. if ( $standalone !== false ) { $output = apply_filters( 'wpseo_show_opening_hours_after', $output, $atts['id'], $container_id ); } if ( $atts['echo'] ) { echo $output; } return $output; } /** * Checks whether website uses multiple location (Custom Post Type) or not (info from options). * * @return bool Multiple locations enabled or not. */ function wpseo_has_multiple_locations() { $options = get_option( 'wpseo_local' ); return isset( $options['use_multiple_locations'] ) && $options['use_multiple_locations'] === 'on'; } /** * Checks whether website uses multiple location (Custom Post Type) or not (info from options) and they're all in the same organization. * * @return bool Multiple locations, same organization enabled or not. */ function wpseo_multiple_location_one_organization() { $options = get_option( 'wpseo_local' ); if ( isset( $options['use_multiple_locations'] ) && $options['use_multiple_locations'] === 'on' ) { return isset( $options['multiple_locations_same_organization'] ) && $options['multiple_locations_same_organization'] === 'on'; } return false; } /** * Check whether the usage of current location for Map routes is allowed. * * @return bool Is allowed to use current location or not. */ function wpseo_may_use_current_location() { $options = get_option( 'wpseo_local' ); return isset( $options['detect_location'] ) && $options['detect_location'] === 'on'; } /** * @param bool $use_24h True if time should be displayed in 24 hours. False if time should be displayed in AM/PM mode. * @param string $selected Optional. Selected time for dropdown. * Defaults to "09:00". * * @return string Complete dropdown with all options. */ function wpseo_show_hour_options( $use_24h = false, $selected = '09:00' ) { $options = get_option( 'wpseo_local' ); $output = ''; for ( $i = 0; $i < 24; $i++ ) { $time = strtotime( sprintf( '%1$02d', $i ) . ':00' ); $time_quarter = strtotime( sprintf( '%1$02d', $i ) . ':15' ); $time_half = strtotime( sprintf( '%1$02d', $i ) . ':30' ); $time_threequarters = strtotime( sprintf( '%1$02d', $i ) . ':45' ); $value = date( 'H:i', $time ); $value_quarter = date( 'H:i', $time_quarter ); $value_half = date( 'H:i', $time_half ); $value_threequarters = date( 'H:i', $time_threequarters ); $time_value = date( 'g:i A', $time ); $time_quarter_value = date( 'g:i A', $time_quarter ); $time_half_value = date( 'g:i A', $time_half ); $time_threequarters_value = date( 'g:i A', $time_threequarters ); if ( $use_24h ) { $time_value = date( 'H:i', $time ); $time_quarter_value = date( 'H:i', $time_quarter ); $time_half_value = date( 'H:i', $time_half ); $time_threequarters_value = date( 'H:i', $time_threequarters ); } $output .= ''; $output .= ''; $output .= ''; $output .= ''; } return $output; } /** * Checks whether array values are meant to mean false but aren't set to false. * * @param array|string $input Array or string to check. * * @return array|bool */ function wpseo_check_falses( $input ) { $atts = []; if ( ! is_array( $input ) ) { $atts[] = $input; } else { $atts = $input; } foreach ( $atts as $key => $value ) { if ( $value === 'false' || $value === 'off' || $value === 'no' || $value === '0' ) { $atts[ $key ] = false; } else { if ( $value === 'true' || $value === 'on' || $value === 'yes' || $value === '1' ) { $atts[ $key ] = true; } } } if ( ! is_array( $input ) ) { return $atts[0]; } return $atts; } /** * Places scripts in footer for Google Maps use. */ function wpseo_enqueue_geocoder() { global $wpseo_map; $options = get_option( 'wpseo_local' ); $detect_location = isset( $options['detect_location'] ) && $options['detect_location'] === 'on'; $default_country = isset( $options['default_country'] ) ? $options['default_country'] : ''; if ( $default_country != '' ) { $default_country = WPSEO_Local_Frontend::get_country( $default_country ); } // Load frontend scripts. $asset_manager = new WPSEO_Local_Admin_Assets(); $asset_manager->register_assets(); $asset_manager->enqueue_script( 'frontend' ); $localization_data = [ 'ajaxurl' => 'admin-ajax.php', 'adminurl' => admin_url(), 'has_multiple_locations' => wpseo_has_multiple_locations(), 'unit_system' => ! empty( $options['unit_system'] ) ? $options['unit_system'] : 'METRIC', 'default_country' => $default_country, 'detect_location' => $detect_location, 'marker_cluster_image_path' => apply_filters( 'wpseo_local_marker_cluster_image_path', esc_url( plugins_url( 'images/m', WPSEO_LOCAL_FILE ) ) ), ]; wp_localize_script( WPSEO_Local_Admin_Assets::PREFIX . 'frontend', 'wpseo_local_data', $localization_data ); echo '' . PHP_EOL; echo $wpseo_map; } /** * This function will clean up the given string and remove all unwanted characters. * * @param string $string String that has to be cleaned. * * @return string The clean string. * @uses wpseo_unicode_to_utf8() to convert the unicode array back to a regular string. * @uses wpseo_utf8_to_unicode() to convert string to array of unicode characters. */ function wpseo_cleanup_string( $string ) { $string = esc_attr( $string ); // First generate array of all unicodes of this string. $unicode_array = wpseo_utf8_to_unicode( $string ); foreach ( $unicode_array as $key => $unicode_item ) { // Remove unwanted unicode characters. if ( in_array( $unicode_item, [ 8232 ], true ) ) { unset( $unicode_array[ $key ] ); } } // Revert back to normal string. $string = wpseo_unicode_to_utf8( $unicode_array ); return $string; } /** * Converts a string to array of unicode characters. * * @param string $str String that has to be converted to unicde array. * * @return array Array of unicode characters. */ function wpseo_utf8_to_unicode( $str ) { $unicode = []; $values = []; $looking_for = 1; $strlen = strlen( $str ); for ( $i = 0; $i < $strlen; $i++ ) { $this_value = ord( $str[ $i ] ); if ( $this_value < 128 ) { $unicode[] = $this_value; } else { if ( count( $values ) === 0 ) { $looking_for = ( $this_value < 224 ) ? 2 : 3; } $values[] = $this_value; if ( count( $values ) === $looking_for ) { $number = ( $looking_for === 3 ) ? ( ( ( $values[0] % 16 ) * 4096 ) + ( ( $values[1] % 64 ) * 64 ) + ( $values[2] % 64 ) ) : ( ( ( $values[0] % 32 ) * 64 ) + ( $values[1] % 64 ) ); $unicode[] = $number; $values = []; $looking_for = 1; } } } return $unicode; } /** * Converts unicode character array back to regular string. * * @param array $string_array Array of unicode characters. * * @return string Converted string. */ function wpseo_unicode_to_utf8( $string_array ) { $utf8 = ''; foreach ( $string_array as $unicode ) { if ( $unicode < 128 ) { $utf8 .= chr( $unicode ); } else { if ( $unicode < 2048 ) { $utf8 .= chr( 192 + ( ( $unicode - ( $unicode % 64 ) ) / 64 ) ); $utf8 .= chr( 128 + ( $unicode % 64 ) ); } else { $utf8 .= chr( 224 + ( ( $unicode - ( $unicode % 4096 ) ) / 4096 ) ); $utf8 .= chr( 128 + ( ( ( $unicode % 4096 ) - ( $unicode % 64 ) ) / 64 ) ); $utf8 .= chr( 128 + ( $unicode % 64 ) ); } } } return $utf8; } /** * Run the upgrade procedures. * * @param array $options Options from database to check with. */ function wpseo_local_do_upgrade( $options ) { if ( ! is_array( $options ) ) { return; } $db_version = WPSEO_Local_Core::get_db_version( $options ); if ( version_compare( $db_version, '1.3.1', '<' ) ) { $options_to_convert = [ 'use_multiple_locations', 'opening_hours_24h', 'multiple_opening_hours', ]; // Convert checkbox values from "1" to "on". foreach ( $options as $key => $value ) { if ( ! in_array( $key, $options_to_convert, true ) ) { continue; } if ( $value == '1' ) { WPSEO_Options::set( $key, 'on' ); } } } if ( version_compare( $db_version, '3.4', '<=' ) ) { // Update businesstypes from Attorneys to LegalServices if upgrading from version 3.4 or below. yoast_wpseo_local_update_business_type(); } if ( version_compare( $db_version, '11.0', '<' ) ) { if ( class_exists( 'Yoast_Notification_Center' ) ) { $notification_center = Yoast_Notification_Center::get(); $notification = $notification_center->get_notification_by_id( 'PersonOrCompanySettingError' ); if ( $notification instanceof Yoast_Notification ) { $notification_center->remove_notification( $notification ); } } } if ( version_compare( $db_version, '11.9', '<' ) ) { if ( class_exists( 'Yoast_Notification_Center' ) ) { $notification_center = Yoast_Notification_Center::get(); $notification = $notification_center->get_notification_by_id( 'LocalSEOServerKey' ); if ( $notification instanceof Yoast_Notification ) { $notification_center->remove_notification( $notification ); } } } if ( version_compare( $db_version, '12.1.1', '<' ) ) { // In some situations a wrong value was stored into the database, which is being cleaned here. if ( isset( $options['location_timezone'] ) && is_wp_error( $options['location_timezone'] ) === true ) { WPSEO_Options::set( 'location_timezone', '' ); } } if ( version_compare( $db_version, '12.8', '<' ) ) { if ( class_exists( 'woocommerce' ) ) { $local_pickup_settings = get_option( 'woocommerce_yoast_wcseo_local_pickup_settings' ); if ( isset( $local_pickup_settings['enabled'] ) ) { $value = $local_pickup_settings['enabled']; if ( $local_pickup_settings['enabled'] === 'yes' ) { $value = 0; foreach ( $local_pickup_settings['location_specific'] as $location ) { if ( $location['allowed'] === 'yes' ) { $value++; } } } WPSEO_Options::set( 'woocommerce_local_pickup_setting', $value ); } } } /** * Several options are being prefixed to prevent ambiguity. * * @since 12.3 */ if ( version_compare( $db_version, '12.3', '<' ) ) { $api_key_browser = WPSEO_Options::get( 'api_key_browser' ); $api_key_server = WPSEO_Options::get( 'api_key' ); $custom_marker = WPSEO_Options::get( 'custom_marker' ); $enhanced_search = WPSEO_Options::get( 'enhanced_search' ); if ( ! empty( $api_key_browser ) ) { WPSEO_Options::set( 'local_api_key_browser', $api_key_browser ); } if ( ! empty( $api_key_server ) ) { WPSEO_Options::set( 'local_api_key', $api_key_server ); } if ( ! empty( $custom_marker ) ) { WPSEO_Options::set( 'local_custom_marker', $custom_marker ); } if ( ! empty( $enhanced_search ) ) { WPSEO_Options::set( 'local_enhanced_search', $enhanced_search ); } } } /** * Retrieves excerpt from specific post. * * @param int $post_id The post ID of which the excerpt should be retrieved. * * @return string */ function wpseo_local_get_excerpt( $post_id ) { global $post; $original_post = $post; $post = get_post( $post_id ); setup_postdata( $post ); $output = get_the_excerpt(); // Set back original $post;. $post = $original_post; wp_reset_postdata(); return $output; } /** * Create an upload field for an image */ function wpseo_local_upload_image() { $output = ''; $output = '

' . __( 'If you want the map to display a custom marker pin for your locations, please upload it here.', 'yoast-local-seo' ) . '

'; $output .= ''; $output .= '
'; return $output; } /** * @param string $value The value of the Business types array. */ function wpseo_local_sanitize_business_types( &$value ) { $value = str_replace( '—', '', $value ); $value = trim( $value ); } /** * @param array $atts Attributes array for the logo shortcode. * * @return string */ function wpseo_local_show_logo( $atts ) { $defaults = [ 'id' => get_the_ID(), ]; $atts = wpseo_check_falses( shortcode_atts( $defaults, $atts ) ); $output = ''; if ( get_post_type( $atts['id'] ) !== 'wpseo_locations' ) { return ''; } $location_logo = get_post_meta( $atts['id'], '_wpseo_business_location_logo', true ); if ( $location_logo === '' ) { $wpseo_options = get_option( 'wpseo' ); $location_logo = $wpseo_options['company_logo']; } if ( $location_logo !== '' ) { $output = '' . esc_attr( get_post_meta( yoast_wpseo_local_get_attachment_id_from_src( $location_logo ), '_wp_attachment_image_alt', true ) ) . ''; } if ( ! empty( $output ) ) { return $output; } } /** * Return the ID of an image by src. * * @param string $src The image src. * * @return string|null ID. */ function yoast_wpseo_local_get_attachment_id_from_src( $src ) { global $wpdb; return $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE guid = %s", [ $src ] ) ); } /** * Update business type from Attorney to Legal Service */ function yoast_wpseo_local_update_business_type() { if ( wpseo_has_multiple_locations() ) { $locations_args = [ 'post_type' => 'wpseo_locations', 'nopaging' => true, 'meta_query' => [ [ 'key' => '_wpseo_business_type', 'value' => 'Attorney', 'compare' => '=', ], ], ]; $locations = new WP_Query( $locations_args ); if ( $locations->have_posts() ) { while ( $locations->have_posts() ) { $locations->the_post(); update_post_meta( get_the_ID(), '_wpseo_business_type', 'LegalService' ); } } } else { $options = get_option( 'wpseo_local' ); if ( isset( $options['business_type'] ) && $options['business_type'] === 'Attorney' ) { $options['business_type'] = 'LegalService'; update_option( 'wpseo_local', $options ); } } } /** * Wrapper function to check whether a location is currently open or closed. * * @since 4.2 * * @param WP_Post|int|null $post A post ID. * * @return bool|WP_Error */ function yoast_seo_local_is_location_open( $post = null ) { $timezone_repository = new WPSEO_Local_Timezone_Repository(); return $timezone_repository->is_location_open( $post ); } /** * Flattens a version number for use in a filename * * @param string $version The original version number. * * @return string The flattened version number. */ function yoast_seo_local_flatten_version( $version ) { $parts = explode( '.', $version ); if ( count( $parts ) === 2 && preg_match( '/^\d+$/', $parts[1] ) === 1 ) { $parts[] = '0'; } return implode( '', $parts ); }