<?php
/**
 * Matador Helper
 *
 * @link        https://matadorjobs.com/
 * @since       1.0.0
 *
 * @package     Matador Jobs Board
 * @subpackage  Bullhorn API
 * @author      Matador Software LLC, Jeremy Scott, Paul Bearne
 * @copyright   (c) 2017-2021 Matador Software, LLC
 *
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
 */

namespace matador;

// Exit if accessed directly or if parent class doesn't exist.
if ( ! defined( 'ABSPATH' ) || class_exists( 'Logger' ) ) {
	exit;
}

use DateTimeImmutable;
use matador\MatadorJobs\Core\Logger;
use matador\MatadorJobs\Core\Severity;
use NumberFormatter;

/**
 * Class Helper
 *
 * @since 3.0.0
 *
 * @final
 */
final class Helper {

	/**
	 * Get File Type From Path
	 *
	 * @since 3.0.0
	 * @since 3.8.7 Added handling for the Google Docs generated .docx files with double MIME-type strings
	 *
	 * @param string $file_path a path to the file
	 *
	 * @return boolean|array
	 */
	public static function get_file_type( $file_path = null ) {

		if ( null === $file_path ) {

			return false;
		}

		// Get file extension
		$mine_types = wp_get_mime_types();

		unset( $mine_types['swf'], $mine_types['exe'], $mine_types['htm|html'] );

		if ( empty( $file_path ) || is_array( $file_path ) ) {

		  Logger::entry( 'no-path-in-\matador\Helper::get_file_type', __( 'No file path provided in get_filetype().', 'matador-jobs' ), Severity::ERROR );

			return false;
		}

		add_filter( 'upload_mimes', [ __CLASS__, 'allowed_mime_types' ] );
		// @see docblock for `Helper::validate_mime_type_of_google_docs_docx_files()`
		add_filter( 'wp_check_filetype_and_ext', [ __CLASS__, 'validate_mime_type_of_google_docs_docx_files' ], 10, 2 );

		$file_type = wp_check_filetype_and_ext( $file_path, basename( $file_path ) );

		// @see docblock for `Helper::validate_mime_type_of_google_docs_docx_files()`
		remove_filter( 'wp_check_filetype_and_ext', [ __CLASS__, 'validate_mime_type_of_google_docs_docx_files' ] );
		remove_filter( 'upload_mimes', [ __CLASS__, 'allowed_mime_types' ] );

		switch ( strtolower( $file_type['type'] ) ) {
			case 'text/plain':
				$format = 'TEXT';
				break;
			case 'application/msword':
				$format = 'DOC';
				break;
			case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
				$format = 'DOCX';
				break;
			case 'application/pdf':
				$format = 'PDF';
				break;
			case 'text/rtf':
				$format = 'RTF';
				break;
			case 'text/html':
				$format = 'HTML';
				break;
			default:
				return false;
		}

		return array( $file_type['ext'], $format );
	}

	/**
	 * Validate MIME Type of Google Docs Generated Open Office Files
	 *
	 * Since early 2019, it has been observed that files downloaded to Open Office formats from Google Docs/Sheets/etc
	 * when MIME-type checked by libmagik in Unix/Linux will have a MIME-type string that is doubled. PHP language bug
	 * reports have explored this and determined it is upstream of PHP itself, and thus upstream of WordPress. Since
	 * Matador accepts .docx files, including those generated by Google Docs, this bug was causing files to fail
	 * MIME-type checks in the `wp_check_filetype_and_ext()` function, which returned false, and resulted in Matador not
	 * uploading the file to Bullhorn. Bullhorn, however, does accept these invalid files for processing (and they do
	 * open on Word) so for out intents they are valid.
	 *
	 * This function, which runs on the @wordpress-filter `wp_check_filetype_and_ext`, will detect these cases and
	 * return a valid extension and type when the doubled MIME-type string for .docx is deteced, allowing Matador to
	 * trust the file for processing to Bullhorn.
	 *
	 * @see https://bugs.php.net/bug.php?id=77784
	 *
	 * @since 3.8.7
	 *
	 * @param array $result
	 * @param string $file
	 *
	 * @return array
	 */
	public static function validate_mime_type_of_google_docs_docx_files( $result, $file ) {

		// This filter will run on all files checked by Matador, but if the file already has a result array with a valid
		// result array, exit early.
		if ( false !== $result['ext'] ) {

			return $result;
		}

		$wp_filetype = wp_check_filetype( $file );
		$ext         = $wp_filetype['ext'];
		$type        = $wp_filetype['type'];

		// We only need to run this filter when the file's extension is .docx. Otherwise exit early.
		if ( 'docx' !== strtolower( $wp_filetype['ext'] ) ) {

			return $result;
		}

		$finfo     = finfo_open( FILEINFO_MIME_TYPE );
		$real_mime = finfo_file( $finfo, $file );
		finfo_close( $finfo );

		$proper_mime_type_string = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
		$improperly_doubled_mime_type_string = $proper_mime_type_string . $proper_mime_type_string;

		// If the "Real Mime" detected by `finfo_file()` is the doubled MIME-type string, set the results array as if
		// the MIME-type were right for .docx.
		if ( $improperly_doubled_mime_type_string === $real_mime ) {
			$result['ext']  = $ext;
			$result['type'] = $type;
		}

		// If we got here, return the modified or unmodified $result array.
		return $result;
	}

	/**
	 * Matador Allowed MIME Types
	 *
	 * Modify the allowed MIME Types to be uploaded to Matador.
	 *
	 * @since 3.9.0
	 *
	 * @param $types
	 *
	 * @return void
	 */
	public static function allowed_mime_types( $types ) {

		// @todo: do stuff

		return $types;
	}

	/**
	 * Time From UTC to Local
	 *
	 * @param object $datetime
	 *
	 * @return object DateTime with TimeZone
	 */
	public static function utc_to_local( $datetime ) {
		$zone = get_option( 'timezone_string' ) ?: 'UTC';

		return $datetime->setTimezone( new \DateTimeZone( $zone ) );
	}

	/**
	 * Format Date
	 *
	 * Takes a time object and formats it to the 8601 standard
	 *
	 * @since 1.0.0
	 *
	 * @param string $datetime
	 *
	 * @return string
	 */
	public static function format_datetime_to_8601( $datetime ) {

		$datetime = self::utc_to_local( $datetime );

		return $datetime->format( 'c' );
	}

	/**
	 * Format Date : MYSQL
	 *
	 * Takes a time object and formats it to the 8601 standard
	 *
	 * @since 1.0.0
	 *
	 * @param string $
	 *
	 * @return DateTimeImmutable
	 */
	public static function format_datetime_to_mysql( $datetime ) {
		$datetime = self::utc_to_local( $datetime );

		return $datetime->format( 'Y-m-d H:i:s' );
	}

	/**
	 * Get Post by Bullhorn ID
	 *
	 * Searches the local jobs for a job with the _matador_source_id postmeta where _matador_source is Bullhorn and
	 * returns the job object
	 *
	 * @param integer $id id of Bullhorn Job to search
	 *
	 * @return object|boolean WP_Post|false
	 *
	 * @since 3.0.0
	 * @since 3.5.0 Updated to use new standard post meta _matador_source and _matador_source_id
	 */
	public static function get_post_by_bullhorn_id( $id ) {
		$args = array(
			'post_type'  => Matador::variable( 'post_type_key_job_listing' ),
			'number'     => 1,
			'meta_query' => array(
				'relation' => 'AND',
				array(
					'key'     => '_matador_source',
					'value'   => 'bullhorn',
					'compare' => '=',
				),
				array(
					'key'     => '_matador_source_id',
					'value'   => absint( $id ),
					'compare' => '=',
				),
			),
		);

		$job_post = get_posts( $args );

		if ( empty( $job_post ) ) {
			return false;
		}

		return $job_post[0];
	}

	/**
	 * Resume or CV Helper
	 *
	 * @since 3.0.0
	 * @since 3.7.3 Added 'resume_or_cv' filter
	 *
	 * @return mixed|void
	 */
	public static function resume_or_cv() {
		$setting = Matador::setting( 'resume_or_cv' );
		switch ( $setting ) {
			case 'cv':
				$label = esc_html__( 'Curriculum Vitae', 'matador-jobs' );
				break;
			case 'cv_abbr':
				$label = esc_html__( 'CV', 'matador-jobs' );
				break;
			case 'resume':
			default:
				$label = esc_html__( 'Resume', 'matador-jobs' );
		}

		/**
		 * Filter: Resume or CV
		 *
		 * Allows filtering of the word selected for the 'employment summary' document.
		 *
		 * @since 3.7.3
		 *
		 * @param string $label
		 * @return string
		 */
		return apply_filters( 'matador_resume_or_cv', $label );
	}

	/**
	 * Get Bullhorn ID from WPID
	 *
	 * Fetches the bullhorn ID of the job or returns false.
	 *
	 * @since  3.0.0
	 * @since  3.5.0 Now verifies source is 'bullhorn' from _matador_source prior to returning id
	 *
	 * @param  array $id ID of job (optional)
	 *
	 * @return int|bool    Job ID or false.
	 */
	public static function the_job_bullhorn_id( $id = null ) {
		$id = is_int( $id ) ? $id : get_the_id();

		if ( 'bullhorn' !== get_post_meta( $id, '_matador_source', true ) ) {

			return false;
		}

		return get_post_meta( $id, '_matador_source_id', true );
	}

	private static $categories;

	/**
	 * @return array|null|string
	 */
	public static function get_categories( $id = null ) {
		if ( null === $id ) {
			$id = get_the_id();
		}
		if ( null === self::$categories || ! isset( self::$categories[ $id ] ) ) {
			$names      = $slug = array();
			$category_taxonomy = Job_Taxonomies::registered_taxonomy( 'category' );

			if ( $category_taxonomy && isset( $category_taxonomy['key'] ) ) {
				$categories = wp_get_post_terms( $id, $category_taxonomy['key'] );
			} else {
				$categories = [];
			}

			if ( $categories && ! is_wp_error( $categories ) ) {
				foreach ( $categories as $category ) {
					$names[] = ucwords( $category->name );
					$slug[]  = $category_taxonomy['key'] . '-' . $category->slug;
				}
			}

			self::$categories[ $id ] = array( $names, $slug );
		}

		return self::$categories[ $id ];
	}


	private static $locations;

	/**
	 * @return array|null|string
	 */
	public static function get_locations( $id = null ) {
		if ( null === $id ) {
			$id = get_the_id();
		}
		if ( null === self::$locations || ! isset( self::$locations[ $id ] ) ) {
			$names      = $slug = array();

			$location_taxonomy = Job_Taxonomies::registered_taxonomy( 'location' );

			if ( $location_taxonomy && isset( $location_taxonomy['key'] ) ) {
				$locations = wp_get_post_terms( $id, $location_taxonomy['key'] );
			} else {
				$locations = [];
			}

			if ( $locations && ! is_wp_error( $locations ) ) {
				foreach ( $locations as $location ) {
					$names[] = ucwords( $location->name );
					$slug[]  = $location_taxonomy['key'] . '-' . $location->slug;
				}
			}
			self::$locations[ $id ] = array( $names, $slug );
		}

		return self::$locations[ $id ];
	}

	public static function get_nopaging_url() {
		$current_url = $_SERVER['REQUEST_URI'];

		$pattern      = '/page\\/[0-9]+\\//i';
		$nopaging_url = preg_replace( $pattern, '', $current_url );

		// we should never pass the matador-apply on in the URL as this is a one time call
		$nopaging_url = remove_query_arg( 'matador-apply', $nopaging_url );

		return $nopaging_url;
	}

	/**
	 * @param array $array
	 * @param int|string $position
	 * @param mixed $insert
	 *
	 * @return array
	 */
	public static function array_insert( $array, $position, $insert ) {

		$position = self::array_position( $array, $position );

		$insert = is_array( $insert ) ? $insert : array( $insert );

		// array_splice will index the array.
		array_splice( $array, $position + 1, 0, $insert );

		return $array;

	}

	/**
	 * @param array $array
	 * @param int|string $position
	 *
	 * @return array
	 * @todo make it so it reindex only on numeric arrays
	 */
	public static function array_remove( $array, $position ) {

		$position = self::array_position( $array, $position );

		if ( array_key_exists( $position, $array ) ) {

			unset( $array[ $position ] );

		} else {

			foreach ( array_keys( $array, $position, true ) as $key ) {

				unset( $array[ $key ] );
			}
		}

		return array_values( $array );

	}

	/**
	 * @param array $array
	 * @param int|string $position
	 * @param mixed $insert
	 *
	 * @return array
	 */
	public static function array_replace( $array, $position, $insert ) {

		$position = self::array_position( $array, $position );

		$array = self::array_insert( $array, $position, $insert );

		$array = self::array_remove( $array, $position );

		return $array;

	}

	public static function array_position( $array, $position ) {
		if ( ! is_int( $position ) ) {

			if ( array_key_exists( $position, $array ) ) {

				$position = array_search( $position, array_keys( $array ), true );

			} else {

				$position = array_search( $position, $array, true );

			}
		}

		return $position;
	}

	public static function build_attributes( $attributes_array ) {

		$attributes = array();

		if ( empty( $attributes_array ) || ! is_array( $attributes_array ) ) {
			return '';
		}

		foreach ( $attributes_array as $attribute => $value ) {
			if ( is_bool( $value ) || 'true' === $value ) {
				$attributes[] = $attribute;
			} else {
				$attributes[] = sprintf( '%1$s="%2$s"', $attribute, $value );
			}
		}

		return implode( ' ', $attributes );
	}

	public static function form_field_args( $args = null, $field = null ) {

		if ( empty( $args ) || empty( $field ) ) {
			return false;
		}

		// Add all potential indexes to the array
		// to prevent undefined index errors.
		$args = wp_parse_args( $args, array(
			'type'        => null,
			'template'    => null,
			'name'        => null,
			'default'     => null,
			'label'       => null,
			'sublabel'    => null,
			'description' => null,
			'options'     => null,
			'attributes'  => array(),
			'class'       => array(),
			'value'       => null,
			'sanitize'    => null,
		) );

		// We need to pass the key to the template
		$args['name'] = $field;

		// Determine Form Template (Default, or Check for Specific)
		$template = ! empty( $args['template'] ) ? $args['template'] : $args['type'];

		// Some fields need a default, even if the database has no value for it.
		if ( 'toggle' === $args['type'] ) {
			$args['default'] = 0;
		}

		if ( ! is_array( $args['class'] ) ) {
			$args['class'] = (array) $args['class'];
		}

		// Add Classes
		$args['class'] = array_merge( array(
			'matador-field-group',
			'matador-field-' . $field,
			'matador-field-template-' . $template,
			'matador-field-type-' . $args['type'],
		), $args['class'] );

		// Clean Up a Bit
		unset( $args['template'], $args['sanitize'], $args['supports'] );

		return array( $args, $template );
	}

	public static function comma_separated_string_to_escaped_array( $string ) {
		$array = array_map( 'trim', explode( ',', $string ) );

		return self::array_values_escaped( $array );
	}

	public static function array_values_escaped( $array ) {
		foreach ( $array as &$item ) {
			$item = esc_attr( $item );
		}

		return $array;
	}

	/**
	 * Is Associative Array
	 *
	 * Runs check on array to see if it is associative (not indexed/sequential).
	 *
	 * @see https://stackoverflow.com/a/173479
	 *
	 * @since 3.8.0
	 *
	 * @param array $array
	 *
	 * @return bool
	 */
	public static function is_associative_array( array $array ) {

		// Quick Check #1: An Empty Array is an Indexed/Sequential Array
		if ( $array === [] ) {

			return false;
		}

		// Quick Check #2. isset() returns true if the index exists with a non-null value,
		// array_key_exists() returns true if the index exists with a null value. Since isset()
		// is faster, run that first.
		// @see https://www.php.net/manual/en/function.array-key-exists.php#107786
		if ( ! ( isset( $array[0] ) || array_key_exists( 0, $array ) ) ) {

			return false;
		}

		// If we passed the quick check and have a 0 index, lets check the rest
		return array_keys( $array ) !== range( 0, count( $array ) - 1);
	}

	/**
	 * Camelcase to Kebabcase
	 *
	 * camel_case_is_cool vs kebab-case-is-cool
	 *
	 * @since 3.8.0
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function camelcase_to_kebabcase( $string ) {
	// public static function camelcase_to_kebabcase( string $string ): string {

		return strtolower( preg_replace( '/([A-Z])/', '-$1', lcfirst( $string ) ) );
	}

	/**
	 * Get Bullhorn Client Cluster URL
	 *
	 * @since 3.1.0
	 * @since 3.8.10 Modified to use the Bullhorn Cluster ID option, which tracks last-known cluster from login, as
	 *               opposed to the old fully-formed URL from previous iterations. Output will be unchanged.
	 *
	 * @return string
	 */
	public static function get_client_cluster_url() {

		$bullhorn_cluster_id = Matador::setting( 'bullhorn_api_cluster_id' );

		if ( $bullhorn_cluster_id ) {

			return 'https://cls' . $bullhorn_cluster_id . '.bullhornstaffing.com/';
		}

		return '';
	}

	public static function jobs_fields_string_to_array( $array ) {
		if ( ! is_string( $array ) ) {
			return $array;
		}

		$array  = array_map( 'trim', explode( ',', $array ) );
		$return = array();

		foreach ( $array as $component ) {
			$parts = explode( '|', $component );

			if ( 2 <= count( $parts ) ) {

				$return[ esc_attr( $parts[0] ) ] = esc_html( $parts[1] );
			} else {

				// convert camelcase to words with spaces
				$matches = array();
				preg_match_all( '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $component, $matches );
				$ret = $matches[0];
				foreach ( $ret as &$match ) {
					$match = ( strtoupper( $match ) === $match ) ? strtolower( $match ) : lcfirst( $match );
				}
				$title = implode( ' ', $ret );

				// convert dashes to spaces
				$title = str_replace( '-', ' ', $title );

				// convert underscores to spaces
				$title = str_replace( '_', ' ', $title );

				// make words uppercase
				$title = ucwords( $title );

				// assign title as the key to field
				$return[ esc_attr( $component ) ] = esc_html( $title );
			}
		}

		return $return;
	}

	/**
	 * Matador Deprecated Notice
	 *
	 * Helper to produce the text for a Deprecated Notice
	 *
	 * @param string $type
	 * @param string $old
	 * @param string $new
	 */
	public static function deprecated_notice( $type = '', $old = '', $new = '' ) {

		if ( ! ( $type && $old && $new ) ) {
			return;
		}

		if ( $new ) {
			// Translators: placeholder 1 is the 'type' of deprecated item, ie: a function. Placeholder 2 is the old
			// Translators: item name. Placeholder 3 is its replacement, if necessary.
			$notice = __(
				'Your theme or extension is using the deprecated "%2$s" %1$s. Please update the %1$s to "%3$s". We will remove this %1$s in a future version.',
				'matador-jobs'
			);
		} else {
			// Translators: placeholder 1 is the 'type' of deprecated item, ie: a function. Placeholder 2 is the old
			// Translators: item name.
			$notice = __(
				'Your theme or extension is using the deprecated "%2$s" %1$s. Please consider an alternative solution. We will remove this %1$s in a future version.',
				'matador-jobs'
			);
		}

		if ( current_user_can( 'edit_posts' ) ) {
			echo '<div class="matador-deprecated-notice"><h4>'
				. esc_html__( 'Deprecation Notice', 'matador-jobs' ) . '</h4><p>'
				. esc_html( sprintf( $notice, $type, $old, $new ) ) . '</p><p>'
				. esc_html__( 'Only you, a logged in user who can edit content, can see this notice.', 'matador-jobs' )
				. '</p></div>';
		}

		Logger::entry( "matador-deprecated-{$type}-notice", sprintf( $notice, $type, $old, $new ), Severity::NOTICE );
	}

	/**
	 * Escape Lucene Search Parameter
	 *
	 * Helper escaped reserved characters out of a Lucene Search Query parameter.
	 *
	 * @since 3.4.0
	 *
	 * @param string $string
	 * @return string
	 */
	public static function escape_lucene_string( $string ) {

		$string = self::convert_quote_marks( $string );

		$reserved = '/\+|-|&|\||!|\(|\)|\{|\}|\[|\]|\^|"|~|\*|\?|\:|\\\/';

		$escaped = preg_replace( $reserved, '\\\\\\0', $string );

		if ( preg_match( '/ /', $escaped ) || strlen( $string ) != strlen( $escaped ) ) {
			return $escaped;
		}

		return $string;
	}

	/**
	 * Convert Quote Marks to Single or Double Quote
	 *
	 * Helper to convert various forms of single and double quotes in UTF-8 to the Unicode ' or " characters. Used in
	 * various string sanitization calls.
	 *
	 * Thanks to Walter Tross: https://stackoverflow.com/questions/20025030/convert-all-types-of-smart-quotes-with-php
	 *
	 * @since 3.4.0
	 *
	 * @param string $string
	 * @return string
	 */
	public static function convert_quote_marks( $string ) {
		$chr_map = array(
			// Windows codepage 1252
			"\xC2\x82"     => "'", // U+0082⇒U+201A single low-9 quotation mark
			"\xC2\x84"     => '"', // U+0084⇒U+201E double low-9 quotation mark
			"\xC2\x8B"     => "'", // U+008B⇒U+2039 single left-pointing angle quotation mark
			"\xC2\x91"     => "'", // U+0091⇒U+2018 left single quotation mark
			"\xC2\x92"     => "'", // U+0092⇒U+2019 right single quotation mark
			"\xC2\x93"     => '"', // U+0093⇒U+201C left double quotation mark
			"\xC2\x94"     => '"', // U+0094⇒U+201D right double quotation mark
			"\xC2\x9B"     => "'", // U+009B⇒U+203A single right-pointing angle quotation mark

			// Regular Unicode     // U+0022 quotation mark (")
			// U+0027 apostrophe     (')
			"\xC2\xAB"     => '"', // U+00AB left-pointing double angle quotation mark
			"\xC2\xBB"     => '"', // U+00BB right-pointing double angle quotation mark
			"\xE2\x80\x98" => "'", // U+2018 left single quotation mark
			"\xE2\x80\x99" => "'", // U+2019 right single quotation mark
			"\xE2\x80\x9A" => "'", // U+201A single low-9 quotation mark
			"\xE2\x80\x9B" => "'", // U+201B single high-reversed-9 quotation mark
			"\xE2\x80\x9C" => '"', // U+201C left double quotation mark
			"\xE2\x80\x9D" => '"', // U+201D right double quotation mark
			"\xE2\x80\x9E" => '"', // U+201E double low-9 quotation mark
			"\xE2\x80\x9F" => '"', // U+201F double high-reversed-9 quotation mark
			"\xE2\x80\xB9" => "'", // U+2039 single left-pointing angle quotation mark
			"\xE2\x80\xBA" => "'", // U+203A single right-pointing angle quotation mark
		);
		$chr     = array_keys( $chr_map ); // but: for efficiency you should
		$rpl     = array_values( $chr_map ); // pre-calculate these two arrays

		return str_replace( $chr, $rpl, html_entity_decode( $string, ENT_QUOTES, 'UTF-8' ) );
	}

	/**
	 * Get User IP
	 *
	 * Returns the IP address of the current visitor
	 *
	 * Credit: Easy Digital Downloads edd_get_ip() https://easydigitaldownloads.com/
	 *
	 * @since 3.4.0
	 *
	 * @return string $ip User's IP address
	 */
	public static function get_user_ip() {

		$ip = '127.0.0.1';

		if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
			//check ip from share internet
			$ip = $_SERVER['HTTP_CLIENT_IP'];
		} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
			// to check ip is pass from proxy
			// can include more than 1 ip, first is the public one
			$ip = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
			$ip = trim( $ip[0] );
		} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
			$ip = $_SERVER['REMOTE_ADDR'];
		}

		// Fix potential CSV returned from $_SERVER variables
		$ip_array = explode( ',', $ip );
		$ip_array = array_map( 'trim', $ip_array );

		return apply_filters( 'matador_get_user_ip', $ip_array[0] );
	}

	/**
	 * Bullhorn Timestamp to PHP DateTimeImmutable
	 *
	 * Bullhorn saves the time as a Java timestamp, or Epoch in Milliseconds. PHP generally works in seconds, and this
	 * convert that into a PHP DateTimeImmutable.
	 *
	 * @since 3.0.0
	 *
	 * @param int $timestamp
	 *
	 * @return DateTimeImmutable
	 */
	public static function bullhorn_timestamp_to_datetime( $timestamp = null ) {

		if ( null === $timestamp ) {
			return null;
		}

		$microtime = number_format( ceil( $timestamp / 1000 ), 2, '.', '' );

		return DateTimeImmutable::createFromFormat( 'U.u', $microtime );
	}

	/**
	 * Get Domain MD5
	 *
	 * Makes an MD5-hashed string of the site domain. Used to track the URL of the site before changes due to migration
	 * and search-and-replace.
	 *
	 * @since 3.7.0
	 *
	 * @return string
	 */
	public static function get_domain_md5() {
		return md5( get_option( 'siteurl' ) );
	}

	/**
	 * Object to Associative Array
	 *
	 * @since 3.8.0
	 *
	 * @param $data
	 *
	 * @return array
	 */
	public static function object_to_array( $data ) {
		if ( is_array( $data ) || is_object( $data ) ) {
			$result = [];
			foreach ( $data as $key => $value ) {
				$result[ $key ] = ( is_array( $data ) || is_object( $data ) ) ? self::object_to_array( $value ) : $value;
			}

			return $result;
		}

		return $data;
	}

	/**
	 * Is URL Safe String
	 *
	 * This checks to see if the string provided is a URL-safe string that would not require conversion to URL
	 * entities. Helpful in some GET calls.
	 *
	 * @since 3.8.0
	 *
	 * @param string $string
	 *
	 * @return bool
	 */
	public static function is_url_safe_string( $string ) {

		$regex = '/[\%\[\]{}\|\\\^\?\/]/';

		return ! preg_match( $regex, $string );
	}

	public static function get_nonce_from_http_headers() {

		$nonce = '';

		if ( isset( $_SERVER['X-Wp-Nonce'] ) ) {
			$nonce = $_SERVER['X-Wp-Nonce'];
		} elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
			$nonce = $_SERVER['HTTP_X_WP_NONCE'];
		} elseif (function_exists('getallheaders')) {
			$requestHeaders = getallheaders();
			$requestHeaders = array_combine( array_map('strtolower', array_keys( $requestHeaders ) ), array_values( $requestHeaders ) );

			if ( isset( $requestHeaders['x-wp-nonce'])) {
				$nonce = $requestHeaders['x-wp-nonce'];
			}
		}

		return trim( $nonce );
	}

	public static function verify_nonce_from_http_headers( $nonce = '' ) {
	// public static function verify_nonce_from_http_headers( string $nonce = '' ) : bool {

		if ( ! is_string( $nonce ) && empty( $nonce ) ) {

			return false;
		}

		$header = self::get_nonce_from_http_headers();

		if ( ! $header || ! wp_verify_nonce( $header, $nonce ) ) {

			return false;
		}

		return true;
	}

	/**
	 * Is JSON
	 *
	 * Checks if the string valid JSON.
	 *
	 * @since 3.8.0
	 *
	 * @param $string
	 *
	 * @return bool
	 */
	public static function is_json( $string ) {

		json_decode( $string );

		return json_last_error() === JSON_ERROR_NONE;
	}

	/**
	 * Is Site a Staging Site
	 *
	 * Props to team at Jetpack from whom we brought over this code.
	 *
	 * Note: Contains filters from the Jetpack codebase.
	 *
	 * @author Automattic, Jetpack Team
	 *
	 * @since 3.8.0 (in Matador)
	 *
	 * @return bool
	 */
	public static function is_local_site() {

		// Is the the Jetpack code, so hey, they can keep it maintained for us
		if ( method_exists( 'Jetpack', 'is_local_site' ) ) {
			return Jetpack::is_local_site();
		}

		// If not present, fallback to our (Matador's) maybe out of date copy of the code:

		// Check for localhost and sites using an IP only first.
		$is_local = site_url() && false === strpos( site_url(), '.' );

		// @todo Remove function_exists when the package has a documented minimum WP version.
		// Use Core's environment check, if available. Added in 5.5.0 / 5.5.1 (for `local` return value).
		if ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) {
			$is_local = true;
		}

		// Then check for usual usual domains used by local dev tools.
		$known_local = array(
			'#\.local$#i',
			'#\.localhost$#i',
			'#\.test$#i',
			'#\.docksal$#i',      // Docksal.
			'#\.docksal\.site$#i', // Docksal.
			'#\.dev\.cc$#i',       // ServerPress.
			'#\.lndo\.site$#i',    // Lando.
		);

		if ( ! $is_local ) {
			foreach ( $known_local as $url ) {
				if ( preg_match( $url, site_url() ) ) {
					$is_local = true;
					break;
				}
			}
		}

		/**
		 * Filters is_local_site check.
		 *
		 * @since 3.8.0 (@since 1.3.0 JetPack)
		 *
		 * @param bool $is_local If the current site is a local site.
		 */
		return apply_filters( 'jetpack_is_local_site', $is_local );
	}

	/**
	 * Is Site a Staging Site
	 *
	 * Props to team at Jetpack from whom we brought over this code.
	 *
	 * Note: Contains filters from the Jetpack codebase.
	 *
	 * @author Automattic, Jetpack Team
	 *
	 * @since 3.8.0 (in Matador)
	 *
	 * @return bool
	 */
	public static function is_staging_site() {

		// Is the the Jetpack code, so hey, they can keep it maintained for us
		if ( method_exists( 'Jetpack', 'is_staging_site' ) ) {
			return Jetpack::is_staging_site();
		}

		// If not present, fallback to our (Matador's) maybe out of date copy of the code:

		// @todo Remove function_exists when the package has a documented minimum WP version.
		// Core's wp_get_environment_type allows for a few specific options. We should default to bowing out gracefully for anything other than production or local.
		$is_staging = function_exists( 'wp_get_environment_type' ) && ! in_array( wp_get_environment_type(), array( 'production', 'local' ), true );

		$known_staging = array(
			'urls'      => array(
				'#\.staging\.wpengine\.com$#i', // WP Engine.
				'#\.staging\.kinsta\.com$#i',   // Kinsta.com.
				'#\.kinsta\.cloud$#i',          // Kinsta.com.
				'#\.stage\.site$#i',            // DreamPress.
				'#\.newspackstaging\.com$#i',   // Newspack.
				'#\.pantheonsite\.io$#i',       // Pantheon.
				'#\.flywheelsites\.com$#i',     // Flywheel.
				'#\.flywheelstaging\.com$#i',   // Flywheel.
				'#\.cloudwaysapps\.com$#i',     // Cloudways.
				'#\.azurewebsites\.net$#i',     // Azure.
				'#\.wpserveur\.net$#i',         // WPServeur.
				'#\-liquidwebsites\.com$#i',    // Liquidweb.
			),
			'constants' => array(
				'IS_WPE_SNAPSHOT',      // WP Engine.
				'KINSTA_DEV_ENV',       // Kinsta.com.
				'WPSTAGECOACH_STAGING', // WP Stagecoach.
				'JETPACK_STAGING_MODE', // Generic.
				'WP_LOCAL_DEV',         // Generic.
			),
		);

		/**
		 * Filters the flags of known staging sites.
		 *
		 * @wordpress-filter
		 *
		 * @since-matador 3.8.0 (@since JetPack 3.9.0)
		 *
		 * @param array $known_staging {
		 *     An array of arrays that each are used to check if the current site is staging.
		 *     @type array $urls      URLs of staging sites in regex to check against site_url.
		 *     @type array $constants PHP constants of known staging/developement environments.
		 *  }
		 */
		$known_staging = apply_filters( 'jetpack_known_staging', $known_staging );

		if ( isset( $known_staging['urls'] ) ) {
			foreach ( $known_staging['urls'] as $url ) {
				if ( preg_match( $url, wp_parse_url( site_url(), PHP_URL_HOST ) ) ) {
					$is_staging = true;
					break;
				}
			}
		}

		if ( isset( $known_staging['constants'] ) ) {
			foreach ( $known_staging['constants'] as $constant ) {
				if ( defined( $constant ) && constant( $constant ) ) {
					$is_staging = true;
				}
			}
		}

		// Last, let's check if sync is erroring due to an IDC. If so, set the site to staging mode.
		if ( ! $is_staging && method_exists( 'Automattic\\Jetpack\\Identity_Crisis', 'validate_sync_error_idc_option' ) && \Automattic\Jetpack\Identity_Crisis::validate_sync_error_idc_option() ) {
			$is_staging = true;
		}

		/**
		 * Filters is_staging_site check.
		 *
		 * @wordpress-filter
		 *
		 * @since-matador 3.8.0 (@since 3.9.0 JetPack)
		 *
		 * @param bool $is_staging If the current site is a staging site.
		 */
		return apply_filters( 'jetpack_is_staging_site', $is_staging );
	}

	/**
	 * Get Currency Codes from ISO Currencies Table XML File
	 *
	 * @see https://www.six-group.com/en/products-services/financial-information/data-standards.html#scrollTo=maintenance-agency
	 *
	 * @since 3.8.4
	 *
	 * @return array
	 */
	public static function get_currency_codes() : array {

		$xml = wp_remote_get( Matador::$path . 'assets/xml/iso-currencies.xml' );

    if ( is_wp_error( $xml ) ) {

			Logger::entry( 'matador_import_get_currency_file_error', esc_html__( 'Failed to get the iso-currencies.xml file', 'matador-jobs' ), Severity::ERROR, (array) $xml );

			$output = [
				'USD' => [ 'country' => 'United States', 'currency' => 'US Dollar' ],
				'EUR' => [ 'country' => 'European Union', 'currency' => 'Euro' ],
				'GBP' => [ 'country' => 'United Kingdom', 'currency' => 'Pound Sterling' ],
				'AUD' => [ 'country' => 'Australia', 'currency' => 'Australian Dollar' ],
				'CAD' => [ 'country' => 'Canada', 'currency' => 'Canadian Dollar' ],
				'CHF' => [ 'country' => 'Switzerland', 'currency' => 'Swiss Franc' ],
				'CNY' => [ 'country' => 'China', 'currency' => 'Chinese Yuan Renminbi' ],
				'HKD' => [ 'country' => 'Hong Kong', 'currency' => 'Hong Kong Dollar'],
				'JPY' => [ 'country' => 'Japan', 'currency' => 'Japanese Yen' ],
			];

		} else {

			$xml = wp_remote_retrieve_body( $xml );

			$data = json_decode( wp_json_encode( (array) simplexml_load_string( $xml ) ), 1 );

			$output = [];

			foreach ( $data['CcyTbl']['CcyNtry'] as $country ) {

        if ( empty( $country['Ccy'] ) || empty( $country['CtryNm'] ) || empty( $country['CcyNm'] ) ) {
					continue;
				}

				$output[ $country['Ccy'] ] = [ 'country' => $country['CtryNm'], 'currency' => $country['CcyNm'] ];
			}
		}

		/**
		 * @wordpress-filter `matador_get_currency_codes`
     * Get Currency Codes
		 *
		 * @since 3.9.5
		 *
		 * @param array $output in form of [ 'USD' => [ 'country' => 'United States', 'currency' => 'US Dollar' ] ].
     * @return array
		 */
		return apply_filters( 'matador_get_currency_codes', $output );
	}

	/**
	 * Format Currency
	 *
	 * Uses the NumberFormatter to format the currency for the locale, or returns the number unmodified when the `intl`
	 * PHP module is not installed (which provides the NumberFormatter class).
	 *
	 * @since 3.8.7
	 *
	 * @param int|float $number Can be integer, float, or string.
	 * @param string $currency
	 *
	 * @return string
	 */
	public static function format_currency( $number, $currency ) {

		if ( class_exists( 'NumberFormatter' ) ) {

			$formatter = new NumberFormatter( get_locale(), NumberFormatter::CURRENCY );

			$formatted = $formatter->formatCurrency( $number, $currency );

			if ( $number >= 1000 ) {

				$to_remove = $formatter->getSymbol( NumberFormatter::DECIMAL_SEPARATOR_SYMBOL ) . '00';

				if ( str_contains( $formatted, $to_remove ) ) {
					$formatted = str_replace( $to_remove, '', $formatted );
				}
			}

			return $formatted;
		}

		return $number;
	}

	/**
	 * Get Countries
	 *
	 * Get translated Countries in WordPress. Runs output through a filter before returning to allow for customization
	 * for other purposes/integrations, as needed. Original code by thenoahmason, updated for new ISO 3166-1 values and
	 * filter names, translations by Matador US, LP.
	 *
	 * @see https://gist.github.com/DHS/1340150
	 *
	 * @author thenoahmason https://gist.github.com/theenoahmason
	 * @author Matador US, LP
	 *
	 * @since 3.8.16
	 *
	 * @return array[]
	 */
	public static function get_countries(): array {

		$countries = [
			'AF' => __( 'Afghanistan', 'matador-jobs' ),
			'AL' => __( 'Albania', 'matador-jobs' ),
			'AX' => __( 'Åland Islands', 'matador-jobs' ),
			'DZ' => __( 'Algeria', 'matador-jobs' ),
			'AS' => __( 'American Samoa', 'matador-jobs' ),
			'AD' => __( 'Andorra', 'matador-jobs' ),
			'AO' => __( 'Angola', 'matador-jobs' ),
			'AI' => __( 'Anguilla', 'matador-jobs' ),
			'AQ' => __( 'Antarctica', 'matador-jobs' ),
			'AG' => __( 'Antigua and Barbuda', 'matador-jobs' ),
			'AR' => __( 'Argentina', 'matador-jobs' ),
			'AM' => __( 'Armenia', 'matador-jobs' ),
			'AW' => __( 'Aruba', 'matador-jobs' ),
			'AU' => __( 'Australia', 'matador-jobs' ),
			'AT' => __( 'Austria', 'matador-jobs' ),
			'AZ' => __( 'Azerbaijan', 'matador-jobs' ),
			'BS' => __( 'Bahamas', 'matador-jobs' ),
			'BH' => __( 'Bahrain', 'matador-jobs' ),
			'BD' => __( 'Bangladesh', 'matador-jobs' ),
			'BB' => __( 'Barbados', 'matador-jobs' ),
			'BY' => __( 'Belarus', 'matador-jobs' ),
			'BE' => __( 'Belgium', 'matador-jobs' ),
			'BZ' => __( 'Belize', 'matador-jobs' ),
			'BJ' => __( 'Benin', 'matador-jobs' ),
			'BM' => __( 'Bermuda', 'matador-jobs' ),
			'BT' => __( 'Bhutan', 'matador-jobs' ),
			'BO' => __( 'Bolivia', 'matador-jobs' ),
			'BQ' => __( 'Bonaire, Sint Eustatius and Saba', 'matador-jobs' ),
			'BA' => __( 'Bosnia and Herzegovina', 'matador-jobs' ),
			'BW' => __( 'Botswana', 'matador-jobs' ),
			'BV' => __( 'Bouvet Island', 'matador-jobs' ),
			'BR' => __( 'Brazil', 'matador-jobs' ),
			'IO' => __( 'British Indian Ocean Territory', 'matador-jobs' ),
			'BN' => __( 'Brunei', 'matador-jobs' ),
			'BG' => __( 'Bulgaria', 'matador-jobs' ),
			'BF' => __( 'Burkina Faso', 'matador-jobs' ),
			'BI' => __( 'Burundi', 'matador-jobs' ),
			'CV' => __( 'Cabo Verde', 'matador-jobs' ),
			'KH' => __( 'Cambodia', 'matador-jobs' ),
			'CM' => __( 'Cameroon', 'matador-jobs' ),
			'CA' => __( 'Canada', 'matador-jobs' ),
			'KY' => __( 'Cayman Islands', 'matador-jobs' ),
			'CF' => __( 'Central African Republic', 'matador-jobs' ),
			'TD' => __( 'Chad', 'matador-jobs' ),
			'CL' => __( 'Chile', 'matador-jobs' ),
			'CN' => __( 'China', 'matador-jobs' ),
			'CX' => __( 'Christmas Island', 'matador-jobs' ),
			'CC' => __( 'Cocos [Keeling] Islands', 'matador-jobs' ),
			'CO' => __( 'Colombia', 'matador-jobs' ),
			'KM' => __( 'Comoros', 'matador-jobs' ),
			'CG' => __( 'Congo - Brazzaville', 'matador-jobs' ),
			'CD' => __( 'Congo - Kinshasa', 'matador-jobs' ),
			'CK' => __( 'Cook Islands', 'matador-jobs' ),
			'CR' => __( 'Costa Rica', 'matador-jobs' ),
			'CI' => __( 'Côte d’Ivoire', 'matador-jobs' ),
			'HR' => __( 'Croatia', 'matador-jobs' ),
			'CU' => __( 'Cuba', 'matador-jobs' ),
			'CW' => __( 'Curaco', 'matador-jobs' ),
			'CY' => __( 'Cyprus', 'matador-jobs' ),
			'CZ' => __( 'Czechia', 'matador-jobs' ),
			'DK' => __( 'Denmark', 'matador-jobs' ),
			'DJ' => __( 'Djibouti', 'matador-jobs' ),
			'DM' => __( 'Dominica', 'matador-jobs' ),
			'DO' => __( 'Dominican Republic', 'matador-jobs' ),
			'EC' => __( 'Ecuador', 'matador-jobs' ),
			'EG' => __( 'Egypt', 'matador-jobs' ),
			'SV' => __( 'El Salvador', 'matador-jobs' ),
			'GQ' => __( 'Equatorial Guinea', 'matador-jobs' ),
			'ER' => __( 'Eritrea', 'matador-jobs' ),
			'EE' => __( 'Estonia', 'matador-jobs' ),
			'SZ' => __( 'Eswatini', 'matador-jobs' ),
			'ET' => __( 'Ethiopia', 'matador-jobs' ),
			'FK' => __( 'Falkland Islands', 'matador-jobs' ),
			'FO' => __( 'Faroe Islands', 'matador-jobs' ),
			'FJ' => __( 'Fiji', 'matador-jobs' ),
			'FI' => __( 'Finland', 'matador-jobs' ),
			'FR' => __( 'France', 'matador-jobs' ),
			'GF' => __( 'French Guiana', 'matador-jobs' ),
			'PF' => __( 'French Polynesia', 'matador-jobs' ),
			'TF' => __( 'French Southern Territories', 'matador-jobs' ),
			'GA' => __( 'Gabon', 'matador-jobs' ),
			'GM' => __( 'Gambia', 'matador-jobs' ),
			'GE' => __( 'Georgia', 'matador-jobs' ),
			'DE' => __( 'Germany', 'matador-jobs' ),
			'GH' => __( 'Ghana', 'matador-jobs' ),
			'GI' => __( 'Gibraltar', 'matador-jobs' ),
			'GR' => __( 'Greece', 'matador-jobs' ),
			'GL' => __( 'Greenland', 'matador-jobs' ),
			'GD' => __( 'Grenada', 'matador-jobs' ),
			'GP' => __( 'Guadeloupe', 'matador-jobs' ),
			'GU' => __( 'Guam', 'matador-jobs' ),
			'GT' => __( 'Guatemala', 'matador-jobs' ),
			'GG' => __( 'Guernsey', 'matador-jobs' ),
			'GN' => __( 'Guinea', 'matador-jobs' ),
			'GW' => __( 'Guinea-Bissau', 'matador-jobs' ),
			'GY' => __( 'Guyana', 'matador-jobs' ),
			'HT' => __( 'Haiti', 'matador-jobs' ),
			'HM' => __( 'Heard Island and McDonald Islands', 'matador-jobs' ),
			'VA' => __( 'Holy See (Vatican)', 'matador-jobs' ),
			'HN' => __( 'Honduras', 'matador-jobs' ),
			'HK' => __( 'Hong Kong SAR China', 'matador-jobs' ),
			'HU' => __( 'Hungary', 'matador-jobs' ),
			'IS' => __( 'Iceland', 'matador-jobs' ),
			'IN' => __( 'India', 'matador-jobs' ),
			'ID' => __( 'Indonesia', 'matador-jobs' ),
			'IR' => __( 'Iran', 'matador-jobs' ),
			'IQ' => __( 'Iraq', 'matador-jobs' ),
			'IE' => __( 'Ireland', 'matador-jobs' ),
			'IM' => __( 'Isle of Man', 'matador-jobs' ),
			'IL' => __( 'Israel', 'matador-jobs' ),
			'IT' => __( 'Italy', 'matador-jobs' ),
			'JM' => __( 'Jamaica', 'matador-jobs' ),
			'JP' => __( 'Japan', 'matador-jobs' ),
			'JE' => __( 'Jersey', 'matador-jobs' ),
			'JO' => __( 'Jordan', 'matador-jobs' ),
			'KZ' => __( 'Kazakhstan', 'matador-jobs' ),
			'KE' => __( 'Kenya', 'matador-jobs' ),
			'KI' => __( 'Kiribati', 'matador-jobs' ),
			'KP' => __( 'North Korea', 'matador-jobs' ),
			'KR' => __( 'South Korea', 'matador-jobs' ),
			'KW' => __( 'Kuwait', 'matador-jobs' ),
			'KG' => __( 'Kyrgyzstan', 'matador-jobs' ),
			'LA' => __( 'Laos', 'matador-jobs' ),
			'LV' => __( 'Latvia', 'matador-jobs' ),
			'LB' => __( 'Lebanon', 'matador-jobs' ),
			'LS' => __( 'Lesotho', 'matador-jobs' ),
			'LR' => __( 'Liberia', 'matador-jobs' ),
			'LY' => __( 'Libya', 'matador-jobs' ),
			'LI' => __( 'Liechtenstein', 'matador-jobs' ),
			'LT' => __( 'Lithuania', 'matador-jobs' ),
			'LU' => __( 'Luxembourg', 'matador-jobs' ),
			'MO' => __( 'Macau SAR China', 'matador-jobs' ),
			'MG' => __( 'Madagascar', 'matador-jobs' ),
			'MW' => __( 'Malawi', 'matador-jobs' ),
			'MY' => __( 'Malaysia', 'matador-jobs' ),
			'MV' => __( 'Maldives', 'matador-jobs' ),
			'ML' => __( 'Mali', 'matador-jobs' ),
			'MT' => __( 'Malta', 'matador-jobs' ),
			'MH' => __( 'Marshall Islands', 'matador-jobs' ),
			'MQ' => __( 'Martinique', 'matador-jobs' ),
			'MR' => __( 'Mauritania', 'matador-jobs' ),
			'MU' => __( 'Mauritius', 'matador-jobs' ),
			'YT' => __( 'Mayotte', 'matador-jobs' ),
			'MX' => __( 'Mexico', 'matador-jobs' ),
			'FM' => __( 'Micronesia', 'matador-jobs' ),
			'MD' => __( 'Moldova', 'matador-jobs' ),
			'MC' => __( 'Monaco', 'matador-jobs' ),
			'MN' => __( 'Mongolia', 'matador-jobs' ),
			'ME' => __( 'Montenegro', 'matador-jobs' ),
			'MS' => __( 'Montserrat', 'matador-jobs' ),
			'MA' => __( 'Morocco', 'matador-jobs' ),
			'MZ' => __( 'Mozambique', 'matador-jobs' ),
			'MM' => __( 'Myanmar [Burma]', 'matador-jobs' ),
			'NA' => __( 'Namibia', 'matador-jobs' ),
			'NR' => __( 'Nauru', 'matador-jobs' ),
			'NP' => __( 'Nepal', 'matador-jobs' ),
			'NL' => __( 'Netherlands', 'matador-jobs' ),
			'NC' => __( 'New Caledonia', 'matador-jobs' ),
			'NZ' => __( 'New Zealand', 'matador-jobs' ),
			'NI' => __( 'Nicaragua', 'matador-jobs' ),
			'NE' => __( 'Niger', 'matador-jobs' ),
			'NG' => __( 'Nigeria', 'matador-jobs' ),
			'NU' => __( 'Niue', 'matador-jobs' ),
			'NF' => __( 'Norfolk Island', 'matador-jobs' ),
			'MK' => __( 'North Macedonia', 'matador-jobs' ),
			'MP' => __( 'Northern Mariana Islands', 'matador-jobs' ),
			'NO' => __( 'Norway', 'matador-jobs' ),
			'OM' => __( 'Oman', 'matador-jobs' ),
			'PK' => __( 'Pakistan', 'matador-jobs' ),
			'PW' => __( 'Palau', 'matador-jobs' ),
			'PS' => __( 'Palestinian Territories', 'matador-jobs' ),
			'PA' => __( 'Panama', 'matador-jobs' ),
			'PG' => __( 'Papua New Guinea', 'matador-jobs' ),
			'PY' => __( 'Paraguay', 'matador-jobs' ),
			'PE' => __( 'Peru', 'matador-jobs' ),
			'PH' => __( 'Philippines', 'matador-jobs' ),
			'PN' => __( 'Pitcairn Islands', 'matador-jobs' ),
			'PL' => __( 'Poland', 'matador-jobs' ),
			'PT' => __( 'Portugal', 'matador-jobs' ),
			'PR' => __( 'Puerto Rico', 'matador-jobs' ),
			'QA' => __( 'Qatar', 'matador-jobs' ),
			'RE' => __( 'Reunion', 'matador-jobs' ),
			'RO' => __( 'Romania', 'matador-jobs' ),
			'RU' => __( 'Russia', 'matador-jobs' ),
			'RW' => __( 'Rwanda', 'matador-jobs' ),
			'BL' => __( 'Saint Barthélemy', 'matador-jobs' ),
			'SH' => __( 'Saint Helena', 'matador-jobs' ),
			'KN' => __( 'Saint Kitts and Nevis', 'matador-jobs' ),
			'LC' => __( 'Saint Lucia', 'matador-jobs' ),
			'MF' => __( 'Saint Martin', 'matador-jobs' ),
			'PM' => __( 'Saint Pierre and Miquelon', 'matador-jobs' ),
			'VC' => __( 'Saint Vincent and the Grenadines', 'matador-jobs' ),
			'WS' => __( 'Samoa', 'matador-jobs' ),
			'SM' => __( 'San Marino', 'matador-jobs' ),
			'ST' => __( 'Sao Tome and Principe', 'matador-jobs' ),
			'SA' => __( 'Saudi Arabia', 'matador-jobs' ),
			'SN' => __( 'Senegal', 'matador-jobs' ),
			'RS' => __( 'Serbia', 'matador-jobs' ),
			'SC' => __( 'Seychelles', 'matador-jobs' ),
			'SL' => __( 'Sierra Leone', 'matador-jobs' ),
			'SG' => __( 'Singapore', 'matador-jobs' ),
			'SX' => __( 'Sint Maarten', 'matador-jobs' ),
			'SK' => __( 'Slovakia', 'matador-jobs' ),
			'SI' => __( 'Slovenia', 'matador-jobs' ),
			'SB' => __( 'Solomon Islands', 'matador-jobs' ),
			'SO' => __( 'Somalia', 'matador-jobs' ),
			'ZA' => __( 'South Africa', 'matador-jobs' ),
			'SS' => __( 'South Sudan', 'matador-jobs' ),
			'ES' => __( 'Spain', 'matador-jobs' ),
			'LK' => __( 'Sri Lanka', 'matador-jobs' ),
			'SD' => __( 'Sudan', 'matador-jobs' ),
			'SR' => __( 'Suriname', 'matador-jobs' ),
			'SJ' => __( 'Svalbard and Jan Mayen', 'matador-jobs' ),
			'SE' => __( 'Sweden', 'matador-jobs' ),
			'CH' => __( 'Switzerland', 'matador-jobs' ),
			'SY' => __( 'Syria', 'matador-jobs' ),
			'TW' => __( 'Taiwan', 'matador-jobs' ),
			'TJ' => __( 'Tajikistan', 'matador-jobs' ),
			'TZ' => __( 'Tanzania', 'matador-jobs' ),
			'TH' => __( 'Thailand', 'matador-jobs' ),
			'TL' => __( 'Timor-Leste', 'matador-jobs' ),
			'TG' => __( 'Togo', 'matador-jobs' ),
			'TK' => __( 'Tokelau', 'matador-jobs' ),
			'TO' => __( 'Tonga', 'matador-jobs' ),
			'TT' => __( 'Trinidad and Tobago', 'matador-jobs' ),
			'TN' => __( 'Tunisia', 'matador-jobs' ),
			'TR' => __( 'Turkey', 'matador-jobs' ),
			'TM' => __( 'Turkmenistan', 'matador-jobs' ),
			'TC' => __( 'Turks and Caicos Islands', 'matador-jobs' ),
			'TV' => __( 'Tuvalu', 'matador-jobs' ),
			'UG' => __( 'Uganda', 'matador-jobs' ),
			'UA' => __( 'Ukraine', 'matador-jobs' ),
			'AE' => __( 'United Arab Emirates', 'matador-jobs' ),
			'GB' => __( 'United Kingdom', 'matador-jobs' ),
			'US' => __( 'United States', 'matador-jobs' ),
			'UM' => __( 'US Minor Outlying Islands', 'matador-jobs' ),
			'UY' => __( 'Uruguay', 'matador-jobs' ),
			'UZ' => __( 'Uzbekistan', 'matador-jobs' ),
			'VU' => __( 'Vanuatu', 'matador-jobs' ),
			'VE' => __( 'Venezuela', 'matador-jobs' ),
			'VN' => __( 'Vietnam', 'matador-jobs' ),
			'VI' => __( 'Virgin Islands (US)', 'matador-jobs' ),
			'WF' => __( 'Wallis and Futuna', 'matador-jobs' ),
			'EH' => __( 'Western Sahara', 'matador-jobs' ),
			'YE' => __( 'Yemen', 'matador-jobs' ),
			'ZM' => __( 'Zambia', 'matador-jobs' ),
			'ZW' => __( 'Zimbabwe', 'matador-jobs' ),
		];

		/**
		 * Filter the countries before returning
		 *
		 * @wordpress-filter
		 *
		 * @since 3.8.16
		 *
		 * @param array $countries Array of country code with translated name. ATS integrations may need to reformat
		 *                         this array to use as country values.
		 * @return array
		 */
		return apply_filters( 'matador_jobs_general_countries', $countries );
	}

	/**
	 * Person Name Suffixes
	 *
	 * This function was added to provide an alternate method to reference and modify the array of name suffixes. It was
	 * made necessary by just-in-time translation features introduced in WordPress 6.7 and 6.8.
	 *
	 * @since 3.9.7
	 *
	 * @return array
	 */
	public static function name_suffixes(): array {

		$suffixes = [
			_x( 'jr', 'Person name suffix', 'matador-jobs' ),
			_x( 'sr', 'Person name suffix', 'matador-jobs' ),
			_x( 'ii', 'Person name suffix', 'matador-jobs' ),
			_x( 'iii', 'Person name suffix', 'matador-jobs' ),
			_x( 'iv', 'Person name suffix', 'matador-jobs' ),
			_x( 'v', 'Person name suffix', 'matador-jobs' ),
			_x( 'vi', 'Person name suffix', 'matador-jobs' ),
			_x( 'vii', 'Person name suffix', 'matador-jobs' ),
			_x( '1st', 'Person name suffix', 'matador-jobs' ),
			_x( '2nd', 'Person name suffix', 'matador-jobs' ),
			_x( '3rd', 'Person name suffix', 'matador-jobs' ),
			_x( '4th', 'Person name suffix', 'matador-jobs' ),
			_x( '5th', 'Person name suffix', 'matador-jobs' ),
			_x( '6th', 'Person name suffix', 'matador-jobs' ),
			_x( '7th', 'Person name suffix', 'matador-jobs' ),
		];

		/**
		 * Matador Configuration Person Name Suffixes
		 * @wordpress-filter `matador_configuration_name_suffixes`
		 *
		 * @since 3.9.7
		 *
		 * @param array $suffixes Array of suffixes, translated.
		 */
		return apply_filters( 'matador_configuration_name_suffixes', $suffixes );
	}

	/**
	 * Allowed Files Types
	 *
	 * This function was added to provide an alternate method to reference and modify the string describing allowed file
	 * types. The change was made necessary by just-in-time translation features introduced in WordPress 6.7 and 6.8.
	 *
	 * @since 3.9.7
	 *
	 * @return string
	 */
	public static function allowed_file_types(): string {

		// Get the array of allowed extensions
		$allowed_file_types = Matador::variable( 'accepted_file_extensions' );

		// strip period, make uppercase.
		$allowed_file_types = array_map( function( $value ) {

			return strtoupper( str_replace( '.', '', $value ) );
		}, $allowed_file_types );

		// In case we need it, the translated word we will use for "and".
		$and = _x( 'and', 'word to finalize a list of allowed file types, ie: "docx, and pdf"', 'matador-jobs' );

		// If there are more than one, combine them into a string with punctuation and "and".
		if ( count( $allowed_file_types ) > 1 ) {

			// Pop the last entry.
			$last = array_pop( $allowed_file_types );

			// If the popped array has more than one left, combine with a comma and space, then add "and" and the final entry.
			if ( count( $allowed_file_types ) > 1 ) {
				$string = implode( ', ', $allowed_file_types ) . ', ' . $and . ' ' . $last;
			// If the popped array has only one left, combine it with a simple "and".
			} else {
				$string = implode( " $and ", $allowed_file_types );
			}
		// If there is only one, just pop the value.
		} else {
			$string = $allowed_file_types[0];
		}

		/**
		 * Matador Allowed File Types String
		 *
		 * @since 3.9.7
		 *
		 * @param string $string             The generated human-readable string of allowed file types.
		 * @param array  $allowed_file_types Don't like ours? Generate your own!
		 * @param string $and                But don't go alone, have our translated "and" to help you.
		 *
		 * @return string
		 */
		return apply_filters( 'matador_configuration_allowed_file_types_string', $string, Matador::variable( 'accepted_file_extensions' ), $and );
	}
}

