'empty' ); } if ( '' === $lat || '' === $lng ) { return self::invalid( 'half' ); } $lat_value = self::number( $lat ); $lng_value = self::number( $lng ); if ( null === $lat_value || null === $lng_value ) { return self::invalid( 'not_number' ); } if ( $lat_value < -90.0 || $lat_value > 90.0 ) { return self::invalid( 'lat_range' ); } if ( $lng_value < -180.0 || $lng_value > 180.0 ) { return self::invalid( 'lng_range' ); } return array( 'status' => 'valid', 'lat' => $lat_value, 'lng' => $lng_value, ); } /** * Steps 1 and 2: unify look-alike characters, then trim. * * @param string $raw Field as typed. */ private static function normalise( string $raw ): string { $raw = str_replace( self::SPACES, ' ', $raw ); $raw = str_replace( "\u{2212}", '-', $raw ); return trim( $raw, self::TRIM ); } /** * Steps 4 to 6: one number, dot or comma decimal, plain notation only. * * @param string $raw Normalised, non-empty field. */ private static function number( string $raw ): ?float { if ( false === strpos( $raw, '.' ) && 1 === substr_count( $raw, ',' ) ) { $raw = str_replace( ',', '.', $raw ); } if ( 1 !== preg_match( '/^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)\z/', $raw ) ) { return null; } $value = (float) $raw; // -0.0 == 0.0, so this turns negative zero into plain zero. return 0.0 === $value ? 0.0 : $value; } /** * An invalid result. * * @param 'half'|'not_number'|'lat_range'|'lng_range' $error Error code. * @return array{status: 'invalid', error: 'half'|'not_number'|'lat_range'|'lng_range'} */ private static function invalid( string $error ): array { return array( 'status' => 'invalid', 'error' => $error, ); } }