'', 'address' => '', 'city' => '', 'state' => '', 'postcode' => '', 'country' => '', ) ); $result = array( 'status' => 'none', 'zone' => null, 'lat' => null, 'lon' => null, ); if ( empty( $zones ) ) { return $memo[ $key ] = $result; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found } $street = '' !== trim( (string) $d['address_1'] ) ? $d['address_1'] : $d['address']; $postcode = trim( (string) $d['postcode'] ); if ( '' === trim( (string) $street ) && '' === $postcode ) { $result['status'] = 'incomplete'; return $memo[ $key ] = $result; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found } $point = null; if ( Api_Client::enabled() && '' !== trim( (string) $street ) ) { $point = self::geocode_cached( $street, $d['city'], $d['state'], $postcode, $d['country'] ); } if ( null === $point ) { $point = self::session_point(); } $decision = Zones::decide( $zones, $point ? $point['lat'] : null, $point ? $point['lon'] : null, $postcode ); $result['status'] = $decision['status']; $result['zone'] = $decision['zone']; if ( $point ) { $result['lat'] = $point['lat']; $result['lon'] = $point['lon']; } return $memo[ $key ] = $result; // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found } /** * Geocode through the API with a per-session cache (coordinates under a keyed hash, never the address). * * @param string $street Street line. * @param string $city City. * @param string $state State. * @param string $postcode Postcode. * @param string $country Country. * @return array|null array( lat, lon ) or null. */ public static function geocode_cached( $street, $city, $state, $postcode, $country ) { $text = trim( implode( ', ', array_filter( array( trim( (string) $street ), trim( (string) $city ), trim( (string) $state ), trim( (string) $postcode ) ) ) ) ); if ( strlen( $text ) < 3 ) { return null; } $country = Api_Client::country( $country ); $hash = self::address_hash( $text, $country ); $cache = self::session_get( self::SESSION_CACHE ); $cache = is_array( $cache ) ? $cache : array(); if ( isset( $cache[ $hash ] ) ) { return $cache[ $hash ] ? $cache[ $hash ] : null; } $res = Api_Client::geocode( $text, $country ); if ( is_wp_error( $res ) ) { // Remember a definite miss (404) so that every cart refresh does not re-call the API; keep transient errors out. if ( 'reachzone_not_found' === $res->get_error_code() ) { $cache[ $hash ] = false; self::session_set( self::SESSION_CACHE, array_slice( $cache, -10, null, true ) ); } return null; } $point = array( 'lat' => round( $res['lat'], 5 ), 'lon' => round( $res['lon'], 5 ), ); $cache[ $hash ] = $point; self::session_set( self::SESSION_CACHE, array_slice( $cache, -10, null, true ) ); return $point; } /** * Keyed hash used as the cache key. * * @param string $text Address text. * @param string $country Country. * @return string */ public static function address_hash( $text, $country ) { $norm = strtolower( preg_replace( '/[^a-z0-9]+/i', ' ', $text ) ); return hash_hmac( 'sha256', $country . '|' . trim( $norm ), wp_salt( 'auth' ) ); } /** * The position the shopper gave in the checker (browser location or a geocoded address), if any. * * @return array|null */ public static function session_point() { $p = self::session_get( self::SESSION_POINT ); if ( is_array( $p ) && isset( $p['lat'], $p['lon'] ) && is_numeric( $p['lat'] ) && is_numeric( $p['lon'] ) ) { return array( 'lat' => (float) $p['lat'], 'lon' => (float) $p['lon'], ); } return null; } /** * Remember the checker position for this shopper's session (coordinates only). * * @param float $lat Latitude. * @param float $lon Longitude. */ public static function set_session_point( $lat, $lon ) { self::session_set( self::SESSION_POINT, array( 'lat' => round( (float) $lat, 4 ), 'lon' => round( (float) $lon, 4 ), ) ); } /** * Read a value from the WooCommerce session. * * @param string $key Key. * @return mixed */ private static function session_get( $key ) { return function_exists( 'WC' ) && WC()->session ? WC()->session->get( $key ) : null; } /** * Write a value to the WooCommerce session. * * @param string $key Key. * @param mixed $value Value. */ private static function session_set( $key, $value ) { if ( function_exists( 'WC' ) && WC()->session ) { WC()->session->set( $key, $value ); } } }