<?php
/**
 * Matador Job Taxonomies
 *
 * A class to contain functions and helpers that register taxonomies for the Job Listing.
 *
 * @link        https://matadorjobs.com/
 * @since       3.0.0
 *
 * @package     Matador Jobs
 * @subpackage  Core
 * @author      Matador Software LLC, Jeremy Scott, Paul Bearne
 * @copyright   (c) 2017-2021 Matador Software, LLC
 *
 * @license     https://opensource.org/licenses/GPL-3.0 GNU General Public License version 3
 */

namespace matador;

use WP_Query;

/**
 * Class Job Taxonomies
 *
 * @final
 *
 * @since  3.0.0
 */
final class Job_Taxonomies {

	/**
	 * Class Constructor
	 * @since  3.0.0
	 */
	public function __construct() {
		add_action( 'init', array( $this, 'register_taxonomies' ) );
		add_action( 'init', array( $this, 'maybe_recount_terms' ) );
		add_action( 'pre_get_posts', [ __CLASS__, 'disable_feed' ], 10 );
		add_filter( 'taxonomy_template_hierarchy', [ $this, 'taxonomy_template_hierarchy' ], 99 );
	}

	/**
	 * Taxonomies
	 *
	 * @since 3.9.7
	 */
	public static function taxonomies(): array {

		$taxonomies = [];

		// Instantiate the Category Taxonomy
		$taxonomies['category'] = [
			'key'    => 'matador-categories',
			'single' => _x( 'category', 'Job Category Singular Name.', 'matador-jobs' ),
			'plural' => _x( 'categories', 'Job Category Plural Name.', 'matador-jobs' ),
			'args'   => array(
				'public'             => true,
				'show_ui'            => true,
				'show_in_menu'       => true,
				'show_in_nav_menus'  => true,
				'show_tagcloud'      => true,
				'show_in_quick_edit' => true,
				'show_admin_column'  => true,
				'hierarchical'       => false,
			),
		];

		// Instantiate the Location Taxonomy
		$taxonomies['location'] = [
			'key'    => 'matador-locations',
			'single' => _x( 'location', 'Job Location Singular Name.', 'matador-jobs' ),
			'plural' => _x( 'locations', 'Job Location Plural Name.', 'matador-jobs' ),
			'args'   => array(
				'show_admin_column' => true,
			),
		];

		// Instantiate the Type Taxonomy
		$taxonomies['type'] = [
			'key'    => 'matador-types',
			'single' => _x( 'type', 'Job Type Singular Name.', 'matador-jobs' ),
			'plural' => _x( 'types', 'Job Type Plural Name.', 'matador-jobs' ),
		];

		// BEGIN: Backwards Compatibility for 3.9.7
		//
		// With Matador Jobs 3.0.0 and forward, taxonomies were created by modifying the Variables array for taxonomies.
		// With WordPress 6.7 and 6.8, the implementation of Just In Time translations sped up WordPress loading time
		// but made the load order more fickle. Taxonomies had to move out of Variables. To make it so integrations that
		// read the old Variable via `Matador::variable('job_taxonomies');`, a filter on the now ghost variable is
		// called that reads this function. To make it so old filters that added taxonomies to the variable to work, we
		// need to read those too. So this block removes the "read ghost variable" filter, reads the other filters,
		// removes them, then re-adds the "read ghost variable" filter. This makes everything (in theory) work with full
		// backwards compatibility
		//
		// Step 1: in the event this function is called by the "read ghost variable" filter, remove it so we don't get
		// stuck in a loop.
		remove_filter( 'matador_variable_job_taxonomies', [ 'matador\Variables', '_deprecated_variables_397_job_taxonomies' ], 5 );
		// Step 2: get the array of other functions from the $wp_filter global, and remove them
		if ( has_filter( 'matador_variable_job_taxonomies' ) ) {
			global $wp_filter;
			foreach ( $wp_filter['matador_variable_job_taxonomies'] as $priority => $filters ) {
				foreach ( $filters as $filter ) {
					if ( is_array( $filter['function'] ) ) {
						// Remove the filter
						remove_filter( 'matador_variable_job_taxonomies', $filter['function'], $priority );
						add_filter( 'matador_job_taxonomies', $filter['function'], $priority );
					}
				}
			}
		}
		// Step 3: restore the "read ghost variable" filter
		add_filter( 'matador_variable_job_taxonomies', [ 'matador\Variables', '_deprecated_variables_397_job_taxonomies' ], 5, 1 );
		// END: Backwards Compatibility for 3.9.7

		/**
		 * Matador Job Taxonomies
		 *
		 * @wordpress-filter
		 *
		 * A filter to allow other plugins to add their own taxonomies to the job listing post type.
		 *
		 * @since 3.0.0
		 *
		 * @param array $taxonomies An array of taxonomy args
		 *
		 * @return array
		 */
		$taxonomies = apply_filters( 'matador_job_taxonomies', $taxonomies );

		foreach ( array_keys( $taxonomies ) as $taxonomy ) {
			$slug_key = 'taxonomy_slug_' . $taxonomy;

			$taxonomies[ $taxonomy ]['slug'] = Matador::setting( $slug_key ) ?: 'matador-' . $taxonomy;
		}

		return $taxonomies;
	}

	/**
	 * Registered Taxonomies
	 *
	 * @since 3.4.0
	 *
	 * @return array
	 */
	public static function taxonomies_list(): array {
		return array_keys( self::taxonomies() );
	}

	/**
	 * Registered Taxonomy
	 *
	 * @since 3.4.0
	 *
	 * @param string $taxonomy The taxonomy key.
	 *
	 * @return array
	 */
	public static function registered_taxonomy( string $taxonomy ): array {

		if ( ! array_key_exists( strtolower( $taxonomy ), self::taxonomies() ) ) {

			return [];
		}

		return self::taxonomies()[ $taxonomy ];
	}

	/**
	 * Taxonomy Constructor
	 * @since  3.0.0
	 */
	public function register_taxonomies(): void {
		foreach ( self::taxonomies() as $key => $taxonomy ) {
			register_taxonomy( $taxonomy['key'], Matador::variable( 'post_type_key_job_listing' ), self::taxonomy_args( $key, $taxonomy ) );
			add_action( 'after-' . $taxonomy['key'] . '-table', array( $this, 'add_recount_link' ) );
		}
	}

	/**
	 * Taxonomy Args
	 *
	 * A taxonomy args are super important. While the key info for a taxonomy
	 * can be passed via the variable array (and filtered there), here is where
	 * we compare the variable array (if any) to the WP required defaults and
	 * build a complete new taxonomy. Before the array is passed back, it is
	 * filtered again, giving extension builders many ways to affect the construction
	 * of a taxonomy in Matador.
	 *
	 * @since 3.0.0
	 *
	 * @param string $key
	 * @param array $taxonomy
	 *
	 * @return array
	 */
	private static function taxonomy_args( $key, $taxonomy ) {

		if ( ! $taxonomy ) {
			die( 'Internal Error: Taxonomies::taxonomy_args called improperly without arguments.' );
			//throw new Exception( 'internal', 'Internal Error: Taxonomies::taxonomy_args called improperly without arguments.' );
		}

		$slug = isset( $taxonomy['slug'] ) ? $taxonomy['slug'] : $taxonomy['key'];

		$default_args = array(
			'labels'             => self::taxonomy_labels( $key, $taxonomy['single'], $taxonomy['plural'] ),
			'public'             => true,
			'show_ui'            => true,
			'show_in_menu'       => true,
			'show_in_nav_menus'  => true,
			'show_tagcloud'      => false,
			'show_in_quick_edit' => true,
			'show_admin_column'  => false,
			'show_in_rest'       => true,
			'hierarchical'       => false,
			'rewrite'            => self::taxonomy_rewrites( $key, $slug ),
			'sort'               => 'None',
		);

		$variable_args = ( ! empty( $taxonomy['args'] ) ) ? $taxonomy['args'] : array();

		$args = wp_parse_args( $variable_args, $default_args );

		/**
		 * Dynamic Filter: Taxonomy Args
		 *
		 * @since 3.0.0
		 *
		 * @param array $args
		 *
		 * @return array
		 */
		$args = apply_filters( "matador_taxonomy_args_{$key}", $args );

		/**
		 * Filter: Taxonomy Args
		 *
		 * @since 3.3.0
		 *
		 * @param array $args
		 * @param string $key
		 *
		 * @return array
		 */
		$args = apply_filters( 'matador_taxonomy_args', $args, $key );

		return $args;
	}

	/**
	 * Taxonomy Labels
	 *
	 * Uses the passed args to generate default taxonomy labels.
	 *
	 * @since 3.0.0
	 *
	 * @param string $key
	 * @param string $singular
	 * @param string $plural
	 *
	 * @return array
	 */
	private static function taxonomy_labels( $key, $singular, $plural ) {
		if ( ! $singular || ! $plural ) {
			die( 'Internal Error: Taxonomies::taxonomy_labels called without arguments.' );
			//throw new Exception( 'internal', 'Internal Error: Taxonomies::taxonomy_labels called without arguments.' );
		}

		$labels = array(
			// Translators: Placeholder for Uppercased Taxonomy Name (Plural)
			'name'                       => esc_html( sprintf( _x( 'Job %1$s', 'Uppercased Taxonomy Name Plural, eg: "Job Categories"', 'matador-jobs' ), ucfirst( $plural ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Singular)
			'singular_name'              => esc_html( sprintf( _x( 'Job %1$s', 'Uppercased Taxonomy Name Singular, eg: "Job Category"', 'matador-jobs' ), ucfirst( $singular ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Plural)
			'menu_name'                  => esc_html( sprintf( __( 'Job %1$s', 'matador-jobs' ), ucfirst( $plural ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Plural)
			'all_items'                  => esc_html( sprintf( __( 'All %1$s', 'matador-jobs' ), ucfirst( $plural ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Singular)
			'edit_item'                  => esc_html( sprintf( __( 'Edit Job %1$s', 'matador-jobs' ), ucfirst( $singular ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Singular)
			'view_item'                  => esc_html( sprintf( __( 'View Job %1$s', 'matador-jobs' ), ucfirst( $singular ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Singular)
			'update_item'                => esc_html( sprintf( __( 'Update Job %1$s', 'matador-jobs' ), ucfirst( $singular ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Singular)
			'add_new_item'               => esc_html( sprintf( __( 'Add New %1$s', 'matador-jobs' ), ucfirst( $singular ) ) ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Singular)
			'new_item_name'              => esc_html( sprintf( __( 'New %1$s Name', 'matador-jobs' ), ucfirst( $singular ) ) ),
			'separate_items_with_commas' => esc_html__( 'Separate each with commas', 'matador-jobs' ),
			'add_or_remove_items'        => esc_html__( 'Add or Remove', 'matador-jobs' ),
			// Translators: Placeholder for Taxonomy Name (Plural)
			'choose_from_most_used'      => esc_html( sprintf( __( 'Choose from the most common %1$s', 'matador-jobs' ), $plural ) ),
			'popular_items'              => esc_html__( 'Popular', 'matador-jobs' ),
			'search_items'               => esc_html__( 'Search', 'matador-jobs' ),
			// Translators: Placeholder for Uppercased Taxonomy Name (Plural)
			'not_found'                  => esc_html( sprintf( __( 'No %1$s Found', 'matador-jobs' ), ucfirst( $plural ) ) ),
		);

		/**
		 * Dynamic Filter: Taxonomy Labels
		 *
		 * @since 3.0.0
		 *
		 * @param array $args
		 *
		 * @return array
		 */
		$labels = apply_filters( "matador_taxonomy_labels_{$key}", $labels );

		/**
		 * Filter: Taxonomy Labels
		 *
		 * @since 3.3.0
		 *
		 * @param array $args
		 * @param string $key
		 *
		 * @return array
		 */
		$labels = apply_filters( 'matador_taxonomy_labels', $labels, $key );

		return $labels;
	}

	/**
	 * Build Taxonomy Labels
	 *
	 * Uses the passed labels to generate all default taxonomy rewrite rules.
	 *
	 * @since 3.0.0
	 *
	 * @param string $key
	 * @param $slug
	 *
	 * @return array
	 */
	private static function taxonomy_rewrites( $key, $slug ) {

		if ( ! $key || ! $slug ) {
			die( 'Internal Error: Taxonomies::taxonomy_labels called without arguments.' );
			//throw new Exception( 'internal', 'Internal Error: Taxonomies::taxonomy_labels called without arguments.' );
		}

		/**
		 * Filter: Rewrites Taxonomy "Has Front"
		 *
		 * Filters the "front" of the rewrite for the taxonomy. Default is {$job_slug}/
		 *
		 * @var bool
		 * @var string $key
		 *
		 * @since   3.3.0
		 */
		if ( apply_filters( 'matador_rewrites_taxonomy_has_front', true, $key ) ) {
			$front = Matador::variable( 'post_type_slug_job_listing' );
		}

		$front = isset( $front ) ? trailingslashit( $front ) : '';

		$rewrites = array(
			'slug'         => $front . $slug,
			'with_front'   => false,
			'hierarchical' => false,
		);

		/**
		 * Dynamic Filter: Rewrites Taxonomy Args
		 *
		 * Filters the array of args passed to the 'rewrite' argument of register_taxonomy()
		 *
		 * @since   3.3.0
		 *
		 * @var array
		 * @var string $key
		 */
		$rewrites = apply_filters( "matador_rewrites_taxonomy_{$key}", $rewrites );

		/**
		 * Filter: Rewrites Taxonomy Args
		 *
		 * Filters the array of args passed to the 'rewrite' argument of register_taxonomy()
		 *
		 * @since   3.3.0
		 *
		 * @var array
		 * @var string $key
		 */
		$rewrites = apply_filters( 'matador_rewrites_taxonomy', $rewrites, $key );

		return $rewrites;
	}

	/**
	 * Disable Core WordPress XML Feeds for Job Taxonomy Archives
	 *
	 * RSS is hardly used anymore and when it comes to publishing jobs and there are many bespoke XML feeds. To help
	 * users curate their syndication, we disable certain core feeds. To re-enable, unhook this function by calling
	 * `remove_action( 'pre_get_posts', [ 'matador\Job_Taxonomies', 'disable_feed' ], 10 );`
	 *
	 * @since 3.9.0
	 *
	 * @param WP_Query $query
	 *
	 * @return void
	 */
	public static function disable_feed( WP_Query $query ): void {

		if ( ! $query->is_feed() ) {

			return;
		}

		$is_matador_taxonomy = false;

		// loop through all the taxonomies and see if any of them match
		foreach ( self::taxonomies_list() as $taxonomy ) {
			if ( array_key_exists( $taxonomy, $query->query_vars ) ) {
				$is_matador_taxonomy = true;
			}
		}

		if ( ! $is_matador_taxonomy ) {

			return;
		}

		/**
		 * Matador Jobs Taxonomies Disabled Feeds
		 *
		 * List of default feeds disabled by Matador. To re-enable one or a few, use this filter.
		 *
		 * @wordpress-filter `matador_taxonomies_disabled_feeds`
		 *
		 * @since 3.9.0
		 *
		 * @param array $disabled_feeds An array of the default feeds to disable.
		 *
		 * @return array
		 */
		$disabled_feeds = apply_filters( 'matador_taxonomies_disabled_feeds', [
			'feed',
			'rss',
			'rss2',
			'rdf',
			'atom'
		] );

		if ( ! in_array( $query->get( 'feed' ), $disabled_feeds ) ) {

			return;
		}

		if ( current_user_can( 'edit_posts' ) ) {
			wp_die( __( 'This feed is disabled by Matador Jobs for Job Taxonomy archives.', 'matador-jobs' ) );
		} else {
			wp_die( __( 'This feed is unavailable.', 'matador-jobs' ) );
		}
	}

	/**
	 * Add Matador Job Listings Archive to Taxonomy Template Archive Options
	 *
	 * @since 3.9.0
	 *
	 * @param array $templates An indexed array of template file names in the heirarchy.
	 *
	 * @return array
	 */
	public static function taxonomy_template_hierarchy( array $templates ): array {

		// If the first template in the hirearchy is looking for a file that contains `taxonomy-matador`,
		// we must be looking for a Matador Taxonomy template.
		if ( str_contains( $templates[0], 'taxonomy-matador' ) ) {

			// Therefore, before the final fallback of `archive.php`, add `archive-matador-job-listings.php`
			$last        = array_pop( $templates );
			$templates[] = 'archive-matador-job-listings.php';
			$templates[] = $last;
		}

		return $templates;
	}

	/**
	 * Maybe Recount Taxonomy Terms
	 *
	 * @since 3.7.0
	 *
	 * @return void
	 */
	public static function maybe_recount_terms() {

		if ( ! is_admin() && ! current_user_can( 'editor' ) ) {

			return;
		}
		// phpcs:ignore WordPress.Security.NonceVerification -- Nonce check.
		if ( ! isset( $_REQUEST['recount_terms'] ) ) {

			return;
		}

		foreach ( self::taxonomies() as $taxonomy ) {

			$get_terms_args = array(
				'taxonomy'   => $taxonomy['key'],
				'fields'     => 'ids',
				'hide_empty' => false,
			);

			$update_terms = get_terms( $get_terms_args );

			wp_update_term_count_now( $update_terms, $taxonomy['key'] );
		}

		wp_safe_redirect( remove_query_arg( 'recount_terms' ) );
	}

	/**
	 * Term Recount Button
	 *
	 * Adds a term recount button to each
	 *
	 * @since 3.7.0
	 *
	 * @uses filter after-{$taxonomy}-table
	 *
	 * @param $taxonomy
	 *
	 * @return void
	 */
	public static function add_recount_link( $taxonomy ) {

		if ( ! is_admin() && ! current_user_can( 'editor' ) ) {

			return;
		}

		$label = get_taxonomy( $taxonomy )->labels->name;

		printf(
			'<a class="button matador-admin-recount-terms-button" href="%1$s">%2$s</a>',
			add_query_arg( 'recount_terms', 'true' ),
			// Translators: Placeholder for name of Taxonomy (already translated)
			sprintf( __( 'Recount %s', 'matador-jobs' ), $label )
		);
	}

	/**
	 * Taxonomy Key Helper
	 *
	 * Prepend the namespace, then substring it to 32 chars (WP Taxonomy key max length).
	 *
	 * @since 3.8.0
	 *
	 * @param string $raw
	 *
	 * @return string
	 */
	public static function taxonomy_key( string $raw ) {
	// public static function taxonomy_key( string $raw ): string {

		return substr( 'matador-' . $raw, 0, 32 );
	}
}
