<?php
/**
 * Base admin page class.
 *
 * Abstract class that makes it easy to create new admin pages.
 *
 * Most of WP Ultimo pages are implemented using this class, which means that the filters and hooks
 * listed below can be used to append content to all of our pages at once.
 *
 * @package WP_Ultimo
 * @subpackage Admin_Pages
 * @since 2.0.0
 */

namespace WP_Ultimo\Admin_Pages;

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

/**
 * Abstract class that makes it easy to create new admin pages.
 */
abstract class Base_Admin_Page {

	/**
     * @var bool
     */
	protected $edit;

	/**
     * Holds the ID for this page, this is also used as the page slug.
     *
     * @var string
     */
	protected $id;

	/**
	 * Is this a top-level menu or a submenu?
	 *
	 * @since 1.8.2
	 * @var string
	 */
	protected $type = 'menu';

	/**
	 * If this is a submenu, we need a parent menu to attach this to
	 *
	 * @since 1.8.2
	 * @var string
	 */
	protected $parent = 'wp-ultimo';

	/**
	 * Holds the list of action links.
	 * These are the ones displayed next to the title of the page. e.g. Add New.
	 *
	 * @since 1.8.2
	 * @var array
	 */
	public $action_links = array();

	/**
	 * Holds the page title
	 *
	 * @since 1.8.2
	 * @var string
	 */
	protected $title;

	/**
	 * Holds the menu label of the page, this is what we effectively use on the menu item
	 *
	 * @since 1.8.2
	 * @var string
	 */
	protected $menu_title;

	/**
	 * After we create the menu item using WordPress functions, we need to store the generated hook.
	 *
	 * @since 1.8.2
	 * @var string
	 */
	protected $page_hook;

	/**
	 * Menu position. This is only used for top-level menus
	 *
	 * @since 1.8.2
	 * @var integer
	 */
	protected $position;

	/**
	 * Dashicon to be used on the menu item. This is only used on top-level menus
	 *
	 * @since 1.8.2
	 * @var string
	 */
	protected $menu_icon;

	/**
	 * If this number is greater than 0, a badge with the number will be displayed alongside the menu title
	 *
	 * @since 1.8.2
	 * @var integer
	 */
	protected $badge_count = 0;

	/**
	 * If this is a top-level menu, we can need the option to rewrite the sub-menu
	 *
	 * @since 1.8.2
	 * @var boolean|string
	 */
	protected $submenu_title = false;

	/**
	 * Allows us to highlight another menu page, if this page has no parent page at all.
	 *
	 * @since 2.0.0
	 * @var bool|string
	 */
	protected $highlight_menu_slug = false;

	/**
	 * Should we hide admin notices on this page?
	 *
	 * @since 2.0.0
	 * @var boolean
	 */
	protected $hide_admin_notices = false;

	/**
	 * Should we force the admin menu into a folded state?
	 *
	 * @since 2.0.0
	 * @var boolean
	 */
	protected $fold_menu = false;

	/**
	 * Should we remove the default WordPress frame?
	 *
	 * When set to true, this will remove the admin top-bar and the admin menu.
	 *
	 * @since 2.0.0
	 * @var boolean
	 */
	protected $remove_frame = false;

	/**
	 * Holds the admin panels where this page should be displayed, as well as which capability to require.
	 *
	 * To add a page to the regular admin (wp-admin/), use: 'admin_menu' => 'capability_here'
	 * To add a page to the network admin (wp-admin/network), use: 'network_admin_menu' => 'capability_here'
	 * To add a page to the user (wp-admin/user) admin, use: 'user_admin_menu' => 'capability_here'
	 *
	 * @since 2.0.0
	 * @var array
	 */
	protected $supported_panels = array(
		'network_admin_menu' => 'manage_network',
	);

	/**
	 * Creates the page with the necessary hooks.
	 *
	 * @since 1.8.2
	 */
	public function __construct() {
		/*
		 * Adds the page to all the necessary admin panels.
		 */
		foreach ($this->supported_panels as $panel => $capability) {

			add_action($panel, array($this, 'add_menu_page'));

			add_action($panel, array($this, 'fix_subdomain_name'), 100);

		} // end foreach;

		/*
		 * Delegates further initializations to the child class.
		 */
		$this->init();

		/*
		 * Add forms
		 */
		add_action('plugins_loaded', array($this, 'register_forms'));

		/**
		 * Allow plugin developers to run additional things when pages are registered.
		 *
		 * Unlike the wu_page_load, which only runs when a specific page
		 * is being seen, this hook runs at registration for every admin page
		 * being added using WP Ultimo code.
		 *
		 * @since 2.0.0
		 * @param string $page_id The ID of this page.
		 * @return void
		 */
		do_action('wu_page_added', $this->id, $this->page_hook);

	} // end __construct;

	/**
	 * Returns the ID of the admin page.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_id() {

		return $this->id;

	} // end get_id;

	/**
	 * Returns the appropriate capability for a this page, depending on the context.
	 *
	 * @since 2.0.0
	 * @return string
	 */
	public function get_capability() {

		if (is_user_admin()) {

			return $this->supported_panels['user_admin_menu'];

		} elseif (is_network_admin()) {

			return $this->supported_panels['network_admin_menu'];

		} // end if;

		return $this->supported_panels['admin_menu'];

	} // end get_capability;

	/**
	 * Fix the subdomain name if an option (submenu title) is passed.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function fix_subdomain_name() {

		global $submenu;

		if ($this->get_submenu_title() && $this->type === 'menu' && isset($submenu[$this->id]) && $submenu[$this->id][0][3] === $this->get_title()) {

			$submenu[$this->id][0][0] = $this->get_submenu_title();

		} // end if;

	} // end fix_subdomain_name;

	/**
	 * Fix the highlight Menu.
	 *
	 * @since 2.0.0
	 * @param string $file Fix the menu highlight for menus without parent.
	 * @return string
	 */
	public function fix_menu_highlight($file) {

		global $plugin_page;

		if ($this->highlight_menu_slug && isset($_GET['page']) && $_GET['page'] === $this->get_id()) {

			$plugin_page = $this->highlight_menu_slug;

			$file = $this->highlight_menu_slug;

		} // end if;

		return $file;

	} // end fix_menu_highlight;

	/**
	 * Install the base hooks for developers
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function install_hooks() {

		/**
		 * Get the action links
		 */
		$this->action_links = $this->action_links();

		/**
		 * Allow plugin developers to add additional hooks to our pages.
		 *
		 * @since 1.8.2
		 * @since 2.0.4 Added third parameter: the page instance.
		 *
		 * @param string $id The ID of this page.
		 * @param string $page_hook The page hook of this page.
		 * @param self   $admin_page TThe page instance.
		 *
		 * @return void
		 */
		do_action('wu_page_load', $this->id, $this->page_hook, $this);

		/**
		 * Allow plugin developers to add additional hooks to our pages.
		 *
		 * @since 1.8.2
		 * @since 2.0.4 Added third parameter: the page instance.
		 *
		 * @param string $id The ID of this page.
		 * @param string $page_hook The page hook of this page.
		 * @param self   $admin_page TThe page instance.
		 *
		 * @return void
		 */
		do_action("wu_page_{$this->id}_load", $this->id, $this->page_hook, $this);

		/**
		 * Fixes menu highlights when necessary.
		 */
		add_filter('parent_file', array($this, 'fix_menu_highlight'), 99);

		add_filter('submenu_file', array($this, 'fix_menu_highlight'), 99);

	} // end install_hooks;

	/**
	 * Get the badge value, to append to the menu item title.
	 *
	 * @since 1.8.2
	 * @return string
	 */
	public function get_badge() {

		$markup = '&nbsp;<span class="update-plugins count-%s">
      <span class="update-count">%s</span>
    </span>';

		return $this->badge_count >= 1 ? sprintf($markup, $this->badge_count, $this->badge_count) : '';

	} // end get_badge;

	/**
	 * Displays the page content.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	final public function display() {

		/**
		 * 'Hack-y' solution for the customer facing title problem... but good enough for now.
		 *
		 * @todo review when possible.
		 */
		add_filter('wp_ultimo_render_vars', function($vars) {

			$vars['page_title'] = $this->get_title();

			return $vars;

		});

		/**
		 * Allow plugin developers to add additional content before we print the page.
		 *
		 * @since 1.8.2
		 * @param string $this->id The id of this page.
		 * @return void
		 */
		do_action('wu_page_before_render', $this->id, $this);

		/**
		 * Allow plugin developers to add additional content before we print the page.
		 *
		 * @since 1.8.2
		 * @param string $this->id The id of this page.
		 * @return void
		 */
		do_action("wu_page_{$this->id}_before_render", $this->id, $this);

		/*
		 * Calls the output function.
		 */
		$this->output();

		/**
		 * Allow plugin developers to add additional content after we print the page
		 *
		 * @since 1.8.2
		 * @param string $this->id The id of this page
		 * @return void
		 */
		do_action('wu_page_after_render', $this->id, $this);

		/**
		 * Allow plugin developers to add additional content after we print the page
		 *
		 * @since 1.8.2
		 * @param string $this->id The id of this page
		 * @return void
		 */
		do_action("wu_page_{$this->id}_after_render", $this->id, $this);

	} // end display;

	/**
	 * Get the menu item, with the badge if necessary.
	 *
	 * @since 1.8.2
	 * @return string
	 */
	public function get_menu_label() {

		return $this->get_menu_title() . $this->get_badge();

	} // end get_menu_label;

	/**
	 * Adds the menu items using default WordPress functions and handles the side-effects
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function add_menu_page() {

		/**
		 * Create the admin page or sub-page
		 */
		$this->page_hook = $this->type === 'menu' ? $this->add_toplevel_menu_page() : $this->add_submenu_page();

		/**
		 * Add the default hooks
		 */
		$this->enqueue_default_hooks();

	} // end add_menu_page;

	/**
	 * Adds top-level admin page.
	 *
	 * @since 1.8.2
	 * @return string Page hook generated by WordPress.
	 */
	public function add_toplevel_menu_page() {

		if (wu_request('id')) {

			$this->edit = true;

		} // end if;

		return add_menu_page(
		$this->get_title(),
		$this->get_menu_label(),
		$this->get_capability(),
		$this->id,
		array($this, 'display'),
		$this->menu_icon,
		$this->position
		);

	} // end add_toplevel_menu_page;

	/**
	 * Adds sub-pages.
	 *
	 * @since 1.8.2
	 * @return string Page hook generated by WordPress.
	 */
	public function add_submenu_page() {

		if (wu_request('id')) {

			$this->edit = true;

		} // end if;

		return add_submenu_page(
		$this->parent,
		$this->get_title(),
		$this->get_menu_label(),
		$this->get_capability(),
		$this->id,
		array($this, 'display')
		);

	} // end add_submenu_page;

	/**
	 * Adds WP Ultimo branding to this page, if that's the case.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function add_branding() {

		if (apply_filters('wp_ultimo_remove_branding', false) === false) {

			add_action('in_admin_header', array($this, 'brand_header'));

			add_action('wu_header_right', array($this, 'add_container_toggle'));

			add_action('in_admin_footer', array($this, 'brand_footer'));

			add_filter('admin_footer_text', '__return_empty_string', 1000);

			add_filter('update_footer', '__return_empty_string', 1000);

		} // end if;

	} // end add_branding;

	/**
	 * Adds the Jumper trigger to the admin top pages.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function add_container_toggle() {

		wu_get_template('ui/container-toggle', array(
			'page' => $this,
		));

	} // end add_container_toggle;

	/**
	 * Adds the WP Ultimo branding header.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function brand_header() {

		wu_get_template('ui/branding/header', array(
			'page' => $this,
		));

	} // end brand_header;

	/**
	 * Adds the WP Ultimo branding footer.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function brand_footer() {

		wu_get_template('ui/branding/footer', array(
			'page' => $this,
		));

	} // end brand_footer;

	/**
	 * Injects our admin classes to the admin body classes.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function add_admin_body_classes() {

		add_action('admin_body_class', function($classes) {

			if ($this->hide_admin_notices) {

				$classes .= ' wu-hide-admin-notices';

			} // end if;

			if ($this->fold_menu) {

				$classes .= ' folded';

			} // end if;

			if ($this->remove_frame) {

				$classes .= ' wu-remove-frame folded';

			} // end if;

			if (is_network_admin()) {

				$classes .= ' wu-network-admin';

			} // end if;

			return "$classes wu-page-{$this->id} wu-styling hover:wu-styling first:wu-styling odd:wu-styling";

		});

	} // end add_admin_body_classes;

	/**
	 * Register the default hooks.
	 *
	 * @todo: this does not need to run on every page.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	final public function enqueue_default_hooks() {

		if ($this->page_hook) {

			add_action("load-$this->page_hook", array($this, 'install_hooks'));

			add_action("load-$this->page_hook", array($this, 'page_loaded'));

			add_action("load-$this->page_hook", array($this, 'hooks'));

			add_action("load-$this->page_hook", array($this, 'register_scripts'), 10);

			add_action("load-$this->page_hook", array($this, 'screen_options'), 10);

			add_action("load-$this->page_hook", array($this, 'register_widgets'), 20);

			add_action("load-$this->page_hook", array($this, 'add_admin_body_classes'), 20);

			/*
			 * Add the page to WP Ultimo branding (aka top-bar and footer)
			 */
			if (is_network_admin()) {

				add_action("load-$this->page_hook", array($this, 'add_branding'));

			} // end if;

			/**
			 * Allow plugin developers to add additional hooks
			 *
			 * @since 1.8.2
			 * @param string
			 */
			do_action('wu_enqueue_extra_hooks', $this->page_hook);

		} // end if;

	} // end enqueue_default_hooks;

	/**
	 * Returns an array with the title links.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function get_title_links() {

		if (wu_get_documentation_url($this->get_id(), false)) {

			$this->action_links[] = array(
				'url'   => wu_get_documentation_url($this->get_id()),
				'label' => __('Documentation'),
				'icon'  => 'wu-open-book',
			);

		} // end if;

		/**
		 * Allow plugin developers, and ourselves, to add action links to our edit pages
		 *
		 * @since 1.8.2
		 * @param WU_Page_Edit $this This instance
		 * @return array
		 */
		return apply_filters('wu_page_get_title_links', $this->action_links, $this);

	} // end get_title_links;

	/**
	 * Allows child classes to register their own title links.
	 *
	 * @since 2.0.0
	 * @return array
	 */
	public function action_links() {

		return array();

	} // end action_links;

	/**
	 * Allow child classes to add further initializations.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function init() {} // end init;

	/**
	 * Allow child classes to add further initializations, but only after the page is loaded.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function page_loaded() {} // end page_loaded;

	/**
	 * Allow child classes to add hooks to be run once the page is loaded.
	 *
	 * @see https://codex.wordpress.org/Plugin_API/Action_Reference/load-(page)
	 * @since 1.8.2
	 * @return void
	 */
	public function hooks() {} // end hooks;

	/**
	 * Allow child classes to add screen options; Useful for pages that have list tables.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function screen_options() {} // end screen_options;

	/**
	 * Allow child classes to register scripts and styles that can be loaded on the output function, for example.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function register_scripts() {} // end register_scripts;

	/**
	 * Allow child classes to register widgets, if they need them.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	public function register_widgets() {} // end register_widgets;

	/**
	 * Allow child classes to register forms, if they need them.
	 *
	 * @since 2.0.0
	 * @return void
	 */
	public function register_forms() {} // end register_forms;

	/**
	 * Returns the title of the page. Must be declared on the child classes.
	 *
	 * @since 2.0.0
	 * @return string Title of the page.
	 */
	abstract public function get_title();

	/**
	 * Returns the title of menu for this page. Must be declared on the child classes.
	 *
	 * @since 2.0.0
	 * @return string Menu label of the page.
	 */
	abstract public function get_menu_title();

	/**
	 * Allows admins to rename the sub-menu (first item) for a top-level page.
	 *
	 * @since 2.0.0
	 * @return string False to use the title menu or string with sub-menu title.
	 */
	public function get_submenu_title() {

		return false;

	} // end get_submenu_title;

	/**
	 * Every child class should implement the output method to display the contents of the page.
	 *
	 * @since 1.8.2
	 * @return void
	 */
	abstract public function output(); // end output;

} // end class Base_Admin_Page;
