<?php
/**
 * Handles limitations to post types, uploads and more.
 *
 * @package WP_Ultimo
 * @subpackage Limits
 * @since 2.0.0
 */

namespace WP_Ultimo\Limits;

// Exit if accessed directly
defined('ABSPATH') || exit;

/**
 * Handles limitations to post types, uploads and more.
 *
 * @since 2.0.0
 */
class Theme_Limits {

	use \WP_Ultimo\Traits\Singleton;

	/**
	 * List of themes that are not available.
	 *
	 * @since 2.0.0
	 * @var array
	 */
	protected $themes_not_available = [];

	/**
	 * Keep a cache of the results as the check is costly.
	 *
	 * @since 2.1.2
	 * @var null|false|string
	 */
	protected $forced_theme_stylesheet = null;

	/**
	 * Keep a cache of the template results as the check is costly.
	 *
	 * @since 2.1.2
	 * @var null|false|string
	 */
	protected $forced_theme_template = null;

	/**
	 * Runs on the first and only instantiation.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function init(): void {

		/**
		 * We need to bail if we're inside the WP CLI context and the
		 * `skip-plugins` flag is present.
		 *
		 * This is due to the fact that without WP Multisite WaaS being loaded,
		 * the functions and classes we'll need to perform any kind of proper
		 * checks won't be available. To validate if we're being loaded or not,
		 * we check for the function `wu_get_product`.
		 *
		 * @since 2.1.0
		 */
		if (wu_cli_is_plugin_skipped('wp-ultimo')) {
			return;
		}

		add_action('wu_sunrise_loaded', [$this, 'load_limitations']);
	}

	/**
	 * Apply limitations if they are available.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function load_limitations(): void {

		if (wu_get_current_site()->has_limitations()) {
			add_filter('stylesheet', [$this, 'force_active_theme_stylesheet']);

			add_filter('template', [$this, 'force_active_theme_template']);

			add_filter('allowed_themes', [$this, 'add_extra_available_themes']);

			add_filter('site_allowed_themes', [$this, 'add_extra_available_themes']);

			add_filter('wp_prepare_themes_for_js', [$this, 'maybe_remove_activate_button']);

			add_action('admin_enqueue_scripts', [$this, 'hacky_remove_activate_button']);

			add_action('admin_footer-themes.php', [$this, 'modify_backbone_template']);

			add_action('customize_changeset_save_data', [$this, 'prevent_theme_activation_on_customizer'], 99, 2);
		}
	}

	/**
	 * Prevents sub-site admins from switching to locked themes inside the customizer.
	 *
	 * @since 2.0.10
	 *
	 * @param array $data The changeset array being saved.
	 * @param array $context The context array with tons of info about the current customizer session.
	 * @return array|void
	 */
	public function prevent_theme_activation_on_customizer($data, $context) {

		if (wu_get_current_site()->has_limitations() === false) {
			return $data;
		}

		$pending_theme_switch = $context['manager']->is_theme_active() === false;

		if (false === $pending_theme_switch) {
			return $data;
		}

		$new_theme = $context['manager']->theme()->stylesheet;

		$theme_limitations = wu_get_current_site()->get_limitations()->themes;

		if ($theme_limitations->allowed($new_theme, 'not_available')) {
			$response = [
				'code'    => 'not-available',
				'message' => __('This theme is not available on your current plan.', 'wp-multisite-waas'),
			];

			wp_send_json($response, 'not-available');

			exit;
		}

		return $data;
	}

	/**
	 * Removes the activate button from not available themes.
	 *
	 * This uses a very hack-y approach due to a bug on WordPress
	 * Core. The problem is that the WP code assumes that no one
	 * with the capability of activating themes would be unable
	 * to activate a theme (in cases of external factors for example).
	 *
	 * @todo Send patch to WordPress core.
	 * @since 2.0.0
	 * @return void
	 */
	public function hacky_remove_activate_button(): void {

		global $pagenow;

		if ( ! function_exists('wu_generate_upgrade_to_unlock_url')) {
			return;
		}

		if ('themes.php' !== $pagenow) {
			return;
		}

		$membership = wu_get_current_site()->get_membership();

		if ( ! $membership) {
			return;
		}

		$upgrade_button = wu_generate_upgrade_to_unlock_button(
			__('Upgrade to unlock', 'wp-multisite-waas'),
			[
				'module'  => 'themes',
				'type'    => 'EXTENSION',
				'classes' => 'button',
			]
		);

		wp_localize_script(
			'theme',
			'wu_theme_settings',
			[
				'themes_not_available' => $this->themes_not_available,
				'replacement_message'  => $upgrade_button,
			]
		);
	}

	/**
	 * Modifies the default WordPress theme page template.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function modify_backbone_template(): void { // phpcs:disable ?>

		<script type="text/javascript">

			if (typeof wu_theme_settings !== 'undefined') {

				let content = document.getElementById("tmpl-theme").innerHTML;

				content = content.replace(new RegExp('(<a class="button activate".*<\/a>)', 'g'), '<# if ( !wu_theme_settings.themes_not_available.includes(data.id) ) { #>$1<# } else { #> {{{ wu_theme_settings.replacement_message.replace("EXTENSION", data.id) }}} <# } #>');

				document.getElementById("tmpl-theme").innerHTML = content;

			}

		</script>

		<?php // phpcs:enable
	}

	/**
	 * Checks if a theme needs to have the activate button removed.
	 *
	 * @since 2.0.0
	 *
	 * @param array $themes The list of themes available.
	 * @return array
	 */
	public function maybe_remove_activate_button($themes) {

		if (is_main_site()) {
			return $themes;
		}

		$theme_limitations = wu_get_current_site()->get_limitations()->themes;

		foreach ($themes as $stylesheet => &$data) {
			$data['notAvailable'] = false;

			if ($theme_limitations->allowed($stylesheet, 'not_available')) {
				$data['actions']['activate'] = '';

				/*
				 * Hack solution due to core WP
				 * not allowing us to filter out the button.
				 */
				$data['notAvailable'] = true;

				/*
				 * Adds to the not available list
				 * for our hack-y solution.
				 */
				$this->themes_not_available[] = $stylesheet;
			}
		}

		return $themes;
	}

	/**
	 * Force the activation of one particularly selected theme.
	 *
	 * @since 2.0.0
	 *
	 * @param string $stylesheet The default theme being used.
	 * @return string
	 */
	public function force_active_theme_stylesheet($stylesheet) {

		if (is_main_site()) {
			return $stylesheet;
		}

		$forced_stylesheet = $this->get_forced_theme_stylesheet();

		return $forced_stylesheet ?: $stylesheet;
	}

	/**
	 * Force the activation of one particularly selected theme.
	 *
	 * @since 2.1.2
	 *
	 * @param string $template The default theme being used.
	 * @return string
	 */
	public function force_active_theme_template($template) {

		if (is_main_site()) {
			return $template;
		}

		$forced_template = $this->get_forced_theme_template();

		return $forced_template ?: $template;
	}

	/**
	 * Deactivates the plugins that people are not allowed to use.
	 *
	 * @since 2.0.0
	 *
	 * @param array $themes Array with the plugins activated.
	 * @return array
	 */
	public function add_extra_available_themes($themes) {
		/*
		 * Bail on network admin =)
		 */
		if (is_network_admin()) {
			return $themes;
		}

		$theme_limitations = wu_get_current_site()->get_limitations()->themes;

		$_themes = $theme_limitations->get_all_themes();

		foreach ($_themes as $theme_stylesheet) {
			$should_appear = $theme_limitations->allowed($theme_stylesheet, 'visible');

			if ( ! $should_appear && isset($themes[ $theme_stylesheet ])) {
				unset($themes[ $theme_stylesheet ]);
			} elseif ($should_appear && ! isset($themes[ $theme_stylesheet ])) {
				$themes[] = $theme_stylesheet;
			}
		}

		return $themes;
	}

	/**
	 * Get the stylesheet of the theme that is forced to be active.
	 *
	 * @since 2.1.2
	 *
	 * @return string|bool The stylesheet of the theme that is forced to be active or false.
	 */
	protected function get_forced_theme_stylesheet() {

		if (null === $this->forced_theme_stylesheet) {
			$this->forced_theme_stylesheet = wu_get_current_site()->get_limitations()->themes->get_forced_active_theme();
		}

		return $this->forced_theme_stylesheet;
	}

	/**
	 * Get the template of the theme that is forced to be active.
	 *
	 * @since 2.1.2
	 *
	 * @return string|bool The template of the theme that is forced to be active or false.
	 */
	protected function get_forced_theme_template() {

		if (null === $this->forced_theme_template) {
			$stylesheet = $this->get_forced_theme_stylesheet();

			$this->forced_theme_template = $stylesheet ? wp_get_theme($stylesheet)->get_template() : false;
		}

		return $this->forced_theme_template;
	}
}