self::METHOD_GET, 'GET' => self::METHOD_GET, 'POST' => self::METHOD_POST, 'PUT' => self::METHOD_PUT, 'PATCH' => self::METHOD_PATCH, 'DELETE' => self::METHOD_DELETE, ); /** * Requested path (relative to the API root, wp-json.php) * * @var string */ public $path = ''; /** * Requested method (GET/HEAD/POST/PUT/PATCH/DELETE) * * @var string */ public $method = 'HEAD'; /** * Request parameters * * This acts as an abstraction of the superglobals * (GET => $_GET, POST => $_POST) * * @var array */ public $params = array('GET' => array(), 'POST' => array()); /** * Request headers * * @var array */ public $headers = array(); /** * Request files (matches $_FILES) * * @var array */ public $files = array(); /** * Request/Response handler, either JSON by default * or XML if requested by client * * @var WC_API_Handler */ public $handler; /** * Setup class and set request/response handler * * @since 2.1 * @param $path * @return WC_API_Server */ public function __construct($path) { if (empty($path)) { if (isset($_SERVER['PATH_INFO'])) $path = $_SERVER['PATH_INFO']; else $path = '/'; } $this->path = $path; $this->method = $_SERVER['REQUEST_METHOD']; $this->params['GET'] = $_GET; $this->params['POST'] = $_POST; $this->headers = $this->get_headers($_SERVER); $this->files = $_FILES; // Compatibility for clients that can't use PUT/PATCH/DELETE if (isset($_GET['_method'])) { $this->method = strtoupper($_GET['_method']); } // determine type of request/response and load handler, JSON by default if ($this->is_json_request()){ $handler_class = 'AfterShip_API_JSON_Handler'; }elseif ($this->is_xml_request()){ $handler_class = 'WC_API_XML_Handler'; }else{ $handler_class = apply_filters('aftership_api_default_response_handler', 'AfterShip_API_JSON_Handler', $this->path, $this); } if ($this->is_version_two()) { $handler_class = 'AfterShip_API_V2_JSON_Handler'; } $this->handler = new $handler_class(); } public function is_version_two() { // check path if ( false !== stripos( $this->path, '/v2' ) ) { return true; } return false; } /** * Check authentication for the request * * @since 2.1 * @return WP_User|WP_Error WP_User object indicates successful login, WP_Error indicates unsuccessful login */ public function check_authentication() { // allow plugins to remove default authentication or add their own authentication $user = apply_filters('aftership_api_check_authentication', null, $this); // API requests run under the context of the authenticated user if (is_a($user, 'WP_User')) wp_set_current_user($user->ID); // WP_Errors are handled in serve_request() elseif (!is_wp_error($user)) $user = new WP_Error('aftership_api_authentication_error', __('Invalid authentication method', 'aftership'), array('code' => 500)); return $user; } /** * Convert an error to an array * * This iterates over all error codes and messages to change it into a flat * array. This enables simpler client behaviour, as it is represented as a * list in JSON rather than an object/map * * @since 2.1 * @param WP_Error $error * @return array List of associative arrays with code and message keys */ protected function error_to_array($error) { $errors = array(); foreach ((array)$error->errors as $code => $messages) { foreach ((array)$messages as $message) { $errors[] = array('code' => $code, 'message' => $message); } } return array('errors' => $errors); } /** * Handle serving an API request * * Matches the current server URI to a route and runs the first matching * callback then outputs a JSON representation of the returned value. * * @since 2.1 * @uses WC_API_Server::dispatch() */ public function serve_request() { do_action('aftership_api_server_before_serve', $this); $this->header('Content-Type', $this->handler->get_content_type(), true); // the API is enabled by default if (!apply_filters('aftership_api_enabled', true, $this) || ('no' === get_option('aftership_api_enabled'))) { $this->send_status(404); echo $this->handler->generate_response(array('errors' => array('code' => 'aftership_api_disabled', 'message' => 'The WooCommerce API is disabled on this site'))); return; } $result = $this->check_authentication(); // if authorization check was successful, dispatch the request if (!is_wp_error($result)) { $result = $this->dispatch(); } // handle any dispatch errors if (is_wp_error($result)) { $data = $result->get_error_data(); if (is_array($data) && isset($data['status'])) { $this->send_status($data['status']); } $result = $this->error_to_array($result); } // This is a filter rather than an action, since this is designed to be // re-entrant if needed $served = apply_filters('aftership_api_serve_request', false, $result, $this); if (!$served) { if ('HEAD' === $this->method) return; echo $this->handler->generate_response($result); } } /** * Retrieve the route map * * The route map is an associative array with path regexes as the keys. The * value is an indexed array with the callback function/method as the first * item, and a bitmask of HTTP methods as the second item (see the class * constants). * * Each route can be mapped to more than one callback by using an array of * the indexed arrays. This allows mapping e.g. GET requests to one callback * and POST requests to another. * * Note that the path regexes (array keys) must have @ escaped, as this is * used as the delimiter with preg_match() * * @since 2.1 * @return array `'/path/regex' => array( $callback, $bitmask )` or `'/path/regex' => array( array( $callback, $bitmask ), ...)` */ public function get_routes() { // index added by default $endpoints = array( '/' => array(array($this, 'get_index'), self::READABLE), ); $endpoints = apply_filters('aftership_api_endpoints', $endpoints); // Normalise the endpoints foreach ($endpoints as $route => &$handlers) { if (count($handlers) <= 2 && isset($handlers[1]) && !is_array($handlers[1])) { $handlers = array($handlers); } } return $endpoints; } /** * Match the request to a callback and call it * * @since 2.1 * @return mixed The value returned by the callback, or a WP_Error instance */ public function dispatch() { switch ($this->method) { case 'HEAD': case 'GET': $method = self::METHOD_GET; break; /* case 'POST': $method = self::METHOD_POST; break; case 'PUT': $method = self::METHOD_PUT; break; case 'PATCH': $method = self::METHOD_PATCH; break; case 'DELETE': $method = self::METHOD_DELETE; break; */ default: return new WP_Error('aftership_api_unsupported_method', __('Unsupported request method', 'aftership'), array('status' => 400)); } foreach ($this->get_routes() as $route => $handlers) { foreach ($handlers as $handler) { $callback = $handler[0]; $supported = isset($handler[1]) ? $handler[1] : self::METHOD_GET; if (!($supported & $method)) continue; $match = preg_match('@^' . $route . '$@i', urldecode($this->path), $args); if (!$match) continue; if (!is_callable($callback)) return new WP_Error('aftership_api_invalid_handler', __('The handler for the route is invalid', 'aftership'), array('status' => 500)); $args = array_merge($args, $this->params['GET']); if ($method & self::METHOD_POST) { $args = array_merge($args, $this->params['POST']); } if ($supported & self::ACCEPT_DATA) { $data = $this->handler->parse_body($this->get_raw_data()); $args = array_merge($args, array('data' => $data)); } elseif ($supported & self::ACCEPT_RAW_DATA) { $data = $this->get_raw_data(); $args = array_merge($args, array('data' => $data)); } $args['_method'] = $method; $args['_route'] = $route; $args['_path'] = $this->path; $args['_headers'] = $this->headers; $args['_files'] = $this->files; $args = apply_filters('aftership_api_dispatch_args', $args, $callback); // Allow plugins to halt the request via this filter if (is_wp_error($args)) { return $args; } $params = $this->sort_callback_params($callback, $args); if (is_wp_error($params)) return $params; return call_user_func_array($callback, $params); } } return new WP_Error('aftership_api_no_route', __('No route was found matching the URL and request method', 'aftership'), array('status' => 404)); } /** * Sort parameters by order specified in method declaration * * Takes a callback and a list of available params, then filters and sorts * by the parameters the method actually needs, using the Reflection API * * @since 2.1 * @param callable|array $callback the endpoint callback * @param array $provided the provided request parameters * @return array */ protected function sort_callback_params($callback, $provided) { if (is_array($callback)) $ref_func = new ReflectionMethod($callback[0], $callback[1]); else $ref_func = new ReflectionFunction($callback); $wanted = $ref_func->getParameters(); $ordered_parameters = array(); foreach ($wanted as $param) { if (isset($provided[$param->getName()])) { // We have this parameters in the list to choose from $ordered_parameters[] = is_array($provided[$param->getName()]) ? array_map('urldecode', $provided[$param->getName()]) : urldecode($provided[$param->getName()]); } elseif ($param->isDefaultValueAvailable()) { // We don't have this parameter, but it's optional $ordered_parameters[] = $param->getDefaultValue(); } else { // We don't have this parameter and it wasn't optional, abort! return new WP_Error('aftership_api_missing_callback_param', sprintf(__('Missing parameter %s', 'aftership'), $param->getName()), array('status' => 400)); } } return $ordered_parameters; } /** * Get the site index. * * This endpoint describes the capabilities of the site. * * @since 2.1 * @return array Index entity */ public function get_index() { // General site data $available = array('store' => array( 'name' => get_option('blogname'), 'description' => get_option('blogdescription'), 'URL' => get_option('siteurl'), 'wc_version' => WC()->version, 'latest_api_version' => 'v2', 'routes' => array(), 'meta' => array( 'timezone' => wc_timezone_string(), 'currency' => get_woocommerce_currency(), 'currency_format' => get_woocommerce_currency_symbol(), 'tax_included' => ('yes' === get_option('aftership_prices_include_tax')), 'weight_unit' => get_option('aftership_weight_unit'), 'dimension_unit' => get_option('aftership_dimension_unit'), 'ssl_enabled' => ('yes' === get_option('aftership_force_ssl_checkout')), 'permalinks_enabled' => ('' !== get_option('permalink_structure')), 'links' => array( 'help' => 'https://aftership.uservoice.com/knowledgebase', ), ), )); // Find the available routes foreach ($this->get_routes() as $route => $callbacks) { $data = array(); $route = preg_replace('#\(\?P(<\w+?>).*?\)#', '$1', $route); $methods = array(); foreach (self::$method_map as $name => $bitmask) { foreach ($callbacks as $callback) { // Skip to the next route if any callback is hidden if ($callback[1] & self::HIDDEN_ENDPOINT) continue 3; if ($callback[1] & $bitmask) $data['supports'][] = $name; if ($callback[1] & self::ACCEPT_DATA) $data['accepts_data'] = true; // For non-variable routes, generate links if (strpos($route, '<') === false) { $data['meta'] = array( 'self' => $route, ); } } } $available['store']['routes'][$route] = apply_filters('aftership_api_endpoints_description', $data); } return apply_filters('aftership_api_index', $available); } /** * Send a HTTP status code * * @since 2.1 * @param int $code HTTP status */ public function send_status($code) { status_header($code); } /** * Send a HTTP header * * @since 2.1 * @param string $key Header key * @param string $value Header value * @param boolean $replace Should we replace the existing header? */ public function header($key, $value, $replace = true) { header(sprintf('%s: %s', $key, $value), $replace); } /** * Send a Link header * * @internal The $rel parameter is first, as this looks nicer when sending multiple * * @link http://tools.ietf.org/html/rfc5988 * @link http://www.iana.org/assignments/link-relations/link-relations.xml * * @since 2.1 * @param string $rel Link relation. Either a registered type, or an absolute URL * @param string $link Target IRI for the link * @param array $other Other parameters to send, as an associative array */ public function link_header($rel, $link, $other = array()) { $header = sprintf('<%s>; rel="%s"', $link, esc_attr($rel)); foreach ($other as $key => $value) { if ('title' == $key) { $value = '"' . $value . '"'; } $header .= '; ' . $key . '=' . $value; } $this->header('Link', $header, false); } /** * Send pagination headers for resources * * @since 2.1 * @param WP_Query|WP_User_Query $query */ public function add_pagination_headers($query) { // WP_User_Query if (is_a($query, 'WP_User_Query')) { $page = $query->page; $single = count($query->get_results()) > 1; $total = $query->get_total(); $total_pages = $query->total_pages; // WP_Query } else { $page = $query->get('paged'); $single = $query->is_single(); $total = $query->found_posts; $total_pages = $query->max_num_pages; } if (!$page) $page = 1; $next_page = absint($page) + 1; if (!$single) { // first/prev if ($page > 1) { $this->link_header('first', $this->get_paginated_url(1)); $this->link_header('prev', $this->get_paginated_url($page - 1)); } // next if ($next_page <= $total_pages) { $this->link_header('next', $this->get_paginated_url($next_page)); } // last if ($page != $total_pages) $this->link_header('last', $this->get_paginated_url($total_pages)); } $this->header('X-WC-Total', $total); $this->header('X-WC-TotalPages', $total_pages); do_action('aftership_api_pagination_headers', $this, $query); } /** * Returns the request URL with the page query parameter set to the specified page * * @since 2.1 * @param int $page * @return string */ private function get_paginated_url($page) { // remove existing page query param $request = remove_query_arg('page'); // add provided page query param $request = urldecode(add_query_arg('page', $page, $request)); // get the home host $host = parse_url(get_home_url(), PHP_URL_HOST); return set_url_scheme("http://{$host}{$request}"); } /** * Retrieve the raw request entity (body) * * @since 2.1 * @return string */ public function get_raw_data() { return file_get_contents('php://input'); } /** * Parse an RFC3339 datetime into a MySQl datetime * * Invalid dates default to unix epoch * * @since 2.1 * @param string $datetime RFC3339 datetime * @return string MySQl datetime (YYYY-MM-DD HH:MM:SS) */ public function parse_datetime($datetime) { // Strip millisecond precision (a full stop followed by one or more digits) if (strpos($datetime, '.') !== false) { $datetime = preg_replace('/\.\d+/', '', $datetime); } // default timezone to UTC $datetime = preg_replace('/[+-]\d+:+\d+$/', '+00:00', $datetime); try { $datetime = new DateTime($datetime, new DateTimeZone('UTC')); } catch (Exception $e) { $datetime = new DateTime('@0'); } return $datetime->format('Y-m-d H:i:s'); } /** * Format a unix timestamp or MySQL datetime into an RFC3339 datetime * * @since 2.1 * @param int|string $timestamp unix timestamp or MySQL datetime * @param bool $convert_to_utc * @return string RFC3339 datetime */ public function format_datetime($timestamp, $convert_to_utc = false) { if ($convert_to_utc) { $timezone = new DateTimeZone(wc_timezone_string()); } else { $timezone = new DateTimeZone('UTC'); } try { if (is_numeric($timestamp)) { $date = new DateTime("@{$timestamp}"); } else { $date = new DateTime($timestamp, $timezone); } // convert to UTC by adjusting the time based on the offset of the site's timezone if ($convert_to_utc) { $date->modify(-1 * $date->getOffset() . ' seconds'); } } catch (Exception $e) { $date = new DateTime('@0'); } return $date->format('Y-m-d\TH:i:s\Z'); } /** * Extract headers from a PHP-style $_SERVER array * * @since 2.1 * @param array $server Associative array similar to $_SERVER * @return array Headers extracted from the input */ public function get_headers($server) { $headers = array(); // CONTENT_* headers are not prefixed with HTTP_ $additional = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true); foreach ($server as $key => $value) { if (strpos($key, 'HTTP_') === 0) { $headers[substr($key, 5)] = $value; } elseif (isset($additional[$key])) { $headers[$key] = $value; } } return $headers; } /** * Check if the current request accepts a JSON response by checking the endpoint suffix (.json) or * the HTTP ACCEPT header * * @since 2.1 * @return bool */ private function is_json_request() { // check path if (false !== stripos($this->path, '.json')) return true; // check ACCEPT header, only 'application/json' is acceptable, see RFC 4627 if (isset($this->headers['ACCEPT']) && 'application/json' == $this->headers['ACCEPT']) return true; return false; } /** * Check if the current request accepts an XML response by checking the endpoint suffix (.xml) or * the HTTP ACCEPT header * * @since 2.1 * @return bool */ private function is_xml_request() { // check path if (false !== stripos($this->path, '.xml')) return true; // check headers, 'application/xml' or 'text/xml' are acceptable, see RFC 2376 if (isset($this->headers['ACCEPT']) && ('application/xml' == $this->headers['ACCEPT'] || 'text/xml' == $this->headers['ACCEPT'])) return true; return false; } /** * Converts the WooCommerce country codes to 3-letter ISO codes * https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 * @param string WooCommerce's 2 letter country code * @return string ISO 3-letter country code */ public function convert_country_code($country) { $countries = array( 'AF' => 'AFG', //Afghanistan 'AX' => 'ALA', //Åland Islands 'AL' => 'ALB', //Albania 'DZ' => 'DZA', //Algeria 'AS' => 'ASM', //American Samoa 'AD' => 'AND', //Andorra 'AO' => 'AGO', //Angola 'AI' => 'AIA', //Anguilla 'AQ' => 'ATA', //Antarctica 'AG' => 'ATG', //Antigua and Barbuda 'AR' => 'ARG', //Argentina 'AM' => 'ARM', //Armenia 'AW' => 'ABW', //Aruba 'AU' => 'AUS', //Australia 'AT' => 'AUT', //Austria 'AZ' => 'AZE', //Azerbaijan 'BS' => 'BHS', //Bahamas 'BH' => 'BHR', //Bahrain 'BD' => 'BGD', //Bangladesh 'BB' => 'BRB', //Barbados 'BY' => 'BLR', //Belarus 'BE' => 'BEL', //Belgium 'BZ' => 'BLZ', //Belize 'BJ' => 'BEN', //Benin 'BM' => 'BMU', //Bermuda 'BT' => 'BTN', //Bhutan 'BO' => 'BOL', //Bolivia 'BQ' => 'BES', //Bonaire, Saint Estatius and Saba 'BA' => 'BIH', //Bosnia and Herzegovina 'BW' => 'BWA', //Botswana 'BV' => 'BVT', //Bouvet Islands 'BR' => 'BRA', //Brazil 'IO' => 'IOT', //British Indian Ocean Territory 'BN' => 'BRN', //Brunei 'BG' => 'BGR', //Bulgaria 'BF' => 'BFA', //Burkina Faso 'BI' => 'BDI', //Burundi 'KH' => 'KHM', //Cambodia 'CM' => 'CMR', //Cameroon 'CA' => 'CAN', //Canada 'CV' => 'CPV', //Cape Verde 'KY' => 'CYM', //Cayman Islands 'CF' => 'CAF', //Central African Republic 'TD' => 'TCD', //Chad 'CL' => 'CHL', //Chile 'CN' => 'CHN', //China 'CX' => 'CXR', //Christmas Island 'CC' => 'CCK', //Cocos (Keeling) Islands 'CO' => 'COL', //Colombia 'KM' => 'COM', //Comoros 'CG' => 'COG', //Congo 'CD' => 'COD', //Congo, Democratic Republic of the 'CK' => 'COK', //Cook Islands 'CR' => 'CRI', //Costa Rica 'CI' => 'CIV', //Côte d\'Ivoire 'HR' => 'HRV', //Croatia 'CU' => 'CUB', //Cuba 'CW' => 'CUW', //Curaçao 'CY' => 'CYP', //Cyprus 'CZ' => 'CZE', //Czech Republic 'DK' => 'DNK', //Denmark 'DJ' => 'DJI', //Djibouti 'DM' => 'DMA', //Dominica 'DO' => 'DOM', //Dominican Republic 'EC' => 'ECU', //Ecuador 'EG' => 'EGY', //Egypt 'SV' => 'SLV', //El Salvador 'GQ' => 'GNQ', //Equatorial Guinea 'ER' => 'ERI', //Eritrea 'EE' => 'EST', //Estonia 'ET' => 'ETH', //Ethiopia 'FK' => 'FLK', //Falkland Islands 'FO' => 'FRO', //Faroe Islands 'FJ' => 'FIJ', //Fiji 'FI' => 'FIN', //Finland 'FR' => 'FRA', //France 'GF' => 'GUF', //French Guiana 'PF' => 'PYF', //French Polynesia 'TF' => 'ATF', //French Southern Territories 'GA' => 'GAB', //Gabon 'GM' => 'GMB', //Gambia 'GE' => 'GEO', //Georgia 'DE' => 'DEU', //Germany 'GH' => 'GHA', //Ghana 'GI' => 'GIB', //Gibraltar 'GR' => 'GRC', //Greece 'GL' => 'GRL', //Greenland 'GD' => 'GRD', //Grenada 'GP' => 'GLP', //Guadeloupe 'GU' => 'GUM', //Guam 'GT' => 'GTM', //Guatemala 'GG' => 'GGY', //Guernsey 'GN' => 'GIN', //Guinea 'GW' => 'GNB', //Guinea-Bissau 'GY' => 'GUY', //Guyana 'HT' => 'HTI', //Haiti 'HM' => 'HMD', //Heard Island and McDonald Islands 'VA' => 'VAT', //Holy See (Vatican City State) 'HN' => 'HND', //Honduras 'HK' => 'HKG', //Hong Kong 'HU' => 'HUN', //Hungary 'IS' => 'ISL', //Iceland 'IN' => 'IND', //India 'ID' => 'IDN', //Indonesia 'IR' => 'IRN', //Iran 'IQ' => 'IRQ', //Iraq 'IE' => 'IRL', //Republic of Ireland 'IM' => 'IMN', //Isle of Man 'IL' => 'ISR', //Israel 'IT' => 'ITA', //Italy 'JM' => 'JAM', //Jamaica 'JP' => 'JPN', //Japan 'JE' => 'JEY', //Jersey 'JO' => 'JOR', //Jordan 'KZ' => 'KAZ', //Kazakhstan 'KE' => 'KEN', //Kenya 'KI' => 'KIR', //Kiribati 'KP' => 'PRK', //Korea, Democratic People\'s Republic of 'KR' => 'KOR', //Korea, Republic of (South) 'KW' => 'KWT', //Kuwait 'KG' => 'KGZ', //Kyrgyzstan 'LA' => 'LAO', //Laos 'LV' => 'LVA', //Latvia 'LB' => 'LBN', //Lebanon 'LS' => 'LSO', //Lesotho 'LR' => 'LBR', //Liberia 'LY' => 'LBY', //Libya 'LI' => 'LIE', //Liechtenstein 'LT' => 'LTU', //Lithuania 'LU' => 'LUX', //Luxembourg 'MO' => 'MAC', //Macao S.A.R., China 'MK' => 'MKD', //Macedonia 'MG' => 'MDG', //Madagascar 'MW' => 'MWI', //Malawi 'MY' => 'MYS', //Malaysia 'MV' => 'MDV', //Maldives 'ML' => 'MLI', //Mali 'MT' => 'MLT', //Malta 'MH' => 'MHL', //Marshall Islands 'MQ' => 'MTQ', //Martinique 'MR' => 'MRT', //Mauritania 'MU' => 'MUS', //Mauritius 'YT' => 'MYT', //Mayotte 'MX' => 'MEX', //Mexico 'FM' => 'FSM', //Micronesia 'MD' => 'MDA', //Moldova 'MC' => 'MCO', //Monaco 'MN' => 'MNG', //Mongolia 'ME' => 'MNE', //Montenegro 'MS' => 'MSR', //Montserrat 'MA' => 'MAR', //Morocco 'MZ' => 'MOZ', //Mozambique 'MM' => 'MMR', //Myanmar 'NA' => 'NAM', //Namibia 'NR' => 'NRU', //Nauru 'NP' => 'NPL', //Nepal 'NL' => 'NLD', //Netherlands 'AN' => 'ANT', //Netherlands Antilles 'NC' => 'NCL', //New Caledonia 'NZ' => 'NZL', //New Zealand 'NI' => 'NIC', //Nicaragua 'NE' => 'NER', //Niger 'NG' => 'NGA', //Nigeria 'NU' => 'NIU', //Niue 'NF' => 'NFK', //Norfolk Island 'MP' => 'MNP', //Northern Mariana Islands 'NO' => 'NOR', //Norway 'OM' => 'OMN', //Oman 'PK' => 'PAK', //Pakistan 'PW' => 'PLW', //Palau 'PS' => 'PSE', //Palestinian Territory 'PA' => 'PAN', //Panama 'PG' => 'PNG', //Papua New Guinea 'PY' => 'PRY', //Paraguay 'PE' => 'PER', //Peru 'PH' => 'PHL', //Philippines 'PN' => 'PCN', //Pitcairn 'PL' => 'POL', //Poland 'PT' => 'PRT', //Portugal 'PR' => 'PRI', //Puerto Rico 'QA' => 'QAT', //Qatar 'RE' => 'REU', //Reunion 'RO' => 'ROU', //Romania 'RU' => 'RUS', //Russia 'RW' => 'RWA', //Rwanda 'BL' => 'BLM', //Saint Barthélemy 'SH' => 'SHN', //Saint Helena 'KN' => 'KNA', //Saint Kitts and Nevis 'LC' => 'LCA', //Saint Lucia 'MF' => 'MAF', //Saint Martin (French part) 'SX' => 'SXM', //Sint Maarten / Saint Matin (Dutch part) 'PM' => 'SPM', //Saint Pierre and Miquelon 'VC' => 'VCT', //Saint Vincent and the Grenadines 'WS' => 'WSM', //Samoa 'SM' => 'SMR', //San Marino 'ST' => 'STP', //São Tomé and Príncipe 'SA' => 'SAU', //Saudi Arabia 'SN' => 'SEN', //Senegal 'RS' => 'SRB', //Serbia 'SC' => 'SYC', //Seychelles 'SL' => 'SLE', //Sierra Leone 'SG' => 'SGP', //Singapore 'SK' => 'SVK', //Slovakia 'SI' => 'SVN', //Slovenia 'SB' => 'SLB', //Solomon Islands 'SO' => 'SOM', //Somalia 'ZA' => 'ZAF', //South Africa 'GS' => 'SGS', //South Georgia/Sandwich Islands 'SS' => 'SSD', //South Sudan 'ES' => 'ESP', //Spain 'LK' => 'LKA', //Sri Lanka 'SD' => 'SDN', //Sudan 'SR' => 'SUR', //Suriname 'SJ' => 'SJM', //Svalbard and Jan Mayen 'SZ' => 'SWZ', //Swaziland 'SE' => 'SWE', //Sweden 'CH' => 'CHE', //Switzerland 'SY' => 'SYR', //Syria 'TW' => 'TWN', //Taiwan 'TJ' => 'TJK', //Tajikistan 'TZ' => 'TZA', //Tanzania 'TH' => 'THA', //Thailand 'TL' => 'TLS', //Timor-Leste 'TG' => 'TGO', //Togo 'TK' => 'TKL', //Tokelau 'TO' => 'TON', //Tonga 'TT' => 'TTO', //Trinidad and Tobago 'TN' => 'TUN', //Tunisia 'TR' => 'TUR', //Turkey 'TM' => 'TKM', //Turkmenistan 'TC' => 'TCA', //Turks and Caicos Islands 'TV' => 'TUV', //Tuvalu 'UG' => 'UGA', //Uganda 'UA' => 'UKR', //Ukraine 'AE' => 'ARE', //United Arab Emirates 'GB' => 'GBR', //United Kingdom 'US' => 'USA', //United States 'UM' => 'UMI', //United States Minor Outlying Islands 'UY' => 'URY', //Uruguay 'UZ' => 'UZB', //Uzbekistan 'VU' => 'VUT', //Vanuatu 'VE' => 'VEN', //Venezuela 'VN' => 'VNM', //Vietnam 'VG' => 'VGB', //Virgin Islands, British 'VI' => 'VIR', //Virgin Island, U.S. 'WF' => 'WLF', //Wallis and Futuna 'EH' => 'ESH', //Western Sahara 'YE' => 'YEM', //Yemen 'ZM' => 'ZMB', //Zambia 'ZW' => 'ZWE', //Zimbabwe ); $iso_code = isset($countries[$country]) ? $countries[$country] : $country; return $iso_code; } }