<?php

use Aventura\Wprss\Core\Model\AdminAjaxNotice\NoticeInterface;
use Aventura\Wprss\Core\Model\AdminAjaxNotice\ServiceProvider;
use Dhii\Di\WritableContainerInterface;

define ('WPRSS_NOTICE_SERVICE_ID_PREFIX', WPRSS_SERVICE_ID_PREFIX . 'notice.', false);


    add_action( 'wp_ajax_wprss_hide_admin_notification', 'wprss_hide_admin_notification' );
    /**
     * JavaScript callback used to hide the administration notice when the 'Dismiss' anchor is clicked on the front end.
     *
     * @since 3.0
     */
    function wprss_hide_admin_notification() {

        // First, check the nonce to make sure it matches what we created when displaying the message.
        // If not, we won't do anything.
        if( wp_verify_nonce( $_REQUEST['nonce'], 'ajax-notification-nonce' ) ) {

            // If the update to the option is successful, send 1 back to the browser;
            // Otherwise, send 0.
            $general_settings = get_option( 'wprss_settings_notices' );
            $general_settings = true;

            if( update_option( 'wprss_settings_notices', $general_settings ) ) {
                die( '1' );
            } else {
                die( '0' );
            }
        }
    }



    /**
     * Checks if the addon notices option exists in the database, and creates it
     * if it does not.
     *
     * @return array The addon notices option
     * @since 3.4.2
     */
    function wprss_check_addon_notice_option() {
        $option = get_option( 'wprss_addon_notices' );
        if ( $option === FALSE ) {
            update_option( 'wprss_addon_notices', array() );
            return array();
        }
        return $option;
    }



    /**
     * This function is called through AJAX to dismiss a particular addon notification.
     *
     * @since 3.4.2
     */
    function wprss_dismiss_addon_notice() {
        check_admin_referer('wprss_admin_addon_ajax');
        $addon = isset($_POST['addon'])? $_POST['addon'] : null;
        if ($addon === null) {
            echo 'false';
            die;
        }
        $addon = strip_tags($addon);
        $notice = isset($_POST['notice'])? $_POST['notice'] : null;
        if ($notice === null) {
            echo 'false';
            die;
        }

        $notices = wprss_check_addon_notice_option();
        if (isset($notices[$addon])) {
            $notices[$addon] = array();
        }
        if (isset($notices[$addon][$addon])) {
            $notices[$addon][$notice] = '1';
        }
        update_option( 'wprss_addon_notices', $notices );
        echo 'true';

        die;
    }

    add_action( 'wp_ajax_wprss_dismiss_addon_notice', 'wprss_dismiss_addon_notice' );


/**
 * Responsible for tracking and outputting admin notices
 *
 * Usage:
 * Initialize by calling {@see init()} early, before `admin_init`. On `plugins_loaded`
 * is a good place.
 * Do not add notices conditionally. Instead, add them always, but specify
 * the `condition` index to {@see add_notice()}.
 *
 * @since 4.7.4
 */
class WPRSS_Admin_Notices {

	// How should a set of conditions be evaluated
	const CONDITION_TYPE_ALL = 'all'; // Requires all conditions to be true
	const CONDITION_TYPE_ANY = 'any'; // Requires one condition to be true
	const CONDITION_TYPE_NONE = 'none'; // Requires none of the conditions to be true
	const CONDITION_TYPE_ALMOST = 'almost'; // Requires at least one of the conditions to be false

	// What happens if a condition encounters an error
//	const CONDITION_ON_ERROR_STOP_FALSE = 'stop_false'; // Assume that the condition was not satisfied, and do not evaluate other conditions
//	const CONDITION_ON_ERROR_STOP_TRUE = 'stop_true'; // Assume that the condition was satisfied, and do not evaluate other conditions
//	const CONDITION_ON_ERROR_CONTINUE_FALSE = 'continue_false'; // Assume that the condition was not satisfied, and continue evaluating
//	const CONDITION_ON_ERROR_CONTINUE_TRUE = 'continue_true'; // Assume that the condition was satisfied, and continue evaluating
	const CONDITION_ON_ERROR_THROW_EXCEPTION = 'throw_exception'; // Just halt


	protected $_notices = array();
	protected $_setting_code;
	protected $_id_prefix;
	protected $_text_domain;
	protected $_notice_base_class;
	protected $_nonce_base_class;
	protected $_btn_close_base_class;
        protected $_dismiss_mode_class_prefix;


	/**
	 *
	 * @since 4.7.4
	 * @param null|array The settings of this instance.
	 *  Possible values are:
	 *		- 'setting_code': The code of the database setting used for storing and managing notices of this instance.
	 *			See {@link set_setting_code()}.
	 *			If a string is passed as data, this is what it is assumed to be.
	 *		- 'id_prefix': The prefix of all IDs generated by this instance.
	 *			See {@link set_id_prefix()}.
	 *		- 'text_domain': The text domain to use for translation.
	 *			See {@link set_text_domain()}.
	 *		- 'notice_base_class': The class for all notice elements.
	 *			See {@link set_notice_base_class()}.
	 *		- 'nonce_base_class': The class for all notice nonce elements.
	 *			See {@link set_nonce_base_class()}.
	 */
	public function __construct( $data = array() ) {
		if ( is_string( $data ) )
			$data = array( 'setting_code' => $data );

		if ( isset( $data['setting_code'] ) )
			$this->set_setting_code (  $data['setting_code'] );

		if ( isset( $data['id_prefix'] ) )
			$this->set_id_prefix( $data['id_prefix'] );

		if ( isset( $data['text_domain'] ) )
			$this->set_text_domain( $data['text_domain'] );

		// Common class for all notices
		if ( !isset( $data['notice_base_class'] ) )
			$data['notice_base_class'] = $this->prefix( 'admin-notice' );
		$this->set_notice_base_class( $data['notice_base_class'] );

		// Common class for all nonces
		if ( !isset( $data['nonce_base_class'] ) )
			$data['nonce_base_class'] = $this->prefix( 'admin-notice-nonce' );
		$this->set_nonce_base_class( $data['nonce_base_class'] );

		// Common class for all close buttons
		if ( !isset( $data['btn_close_base_class'] ) )
			$data['btn_close_base_class'] = $this->prefix( 'admin-notice-btn-close' );
		$this->set_btn_close_base_class( $data['btn_close_base_class'] );

                // Class prefix for dismiss mode
                if ( !isset( $data['dismiss_mode_class_prefix'] ) )
                        $data['dismiss_mode_class_prefix'] = 'dismiss-mode-';
                $this->set_dismiss_mode_class_prefix($data['dismiss_mode_class_prefix']);

		$this->_construct();
	}


	/**
	 * Internal, parameter-less constructor.
	 *
	 * @since 4.7.4
	 */
	protected function _construct() {

	}


	/**
	 * Initializes the instance for use with WP.
	 *
	 * Essentially, this is what prepares it and hooks the handlers in.
	 *
	 * @since 4.7.4
	 * @uses-action admin_notice_before_init To expose the object before initialization
	 * @uses-action admin_notice_before_init To expose the object after initialization
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function init() {
		do_action( $this->prefix( 'admin_notice_before_init' ), $this );
		add_action( 'admin_notices', array( $this, 'output_allowed_notices' ) );
		do_action( $this->prefix( 'admin_notice_after_init' ), $this );

		return $this;
	}


        /**
         * Sets the prefix for dismiss mode class name.
         *
         * @since 4.11
         *
         * @param string $prefix The prefix.
         * @return $this This instance.
         */
        public function set_dismiss_mode_class_prefix($prefix) {
            $this->_dismiss_mode_class_prefix = trim($prefix);
            return $this;
        }


        /**
         * Sets the prefix for dismiss mode class name.
         *
         * @since 4.11
         *
         * @return string The prefix.
         */
        public function get_dismiss_mode_class_prefix() {
            return $this->_dismiss_mode_class_prefix;
        }

	/**
	 * Get the ID prefix, or a prefixed string.
	 *
	 * This function is also used internally by this class to prefix generated
	 * IDs that are specific to this instance.
	 * Currently, this prefix is used in HTML of the notices, and in names
	 * of hooks.
	 *
	 * @param null|string $string The string to prefix.
	 * @return string The prefix, or prefixed string
	 */
	public function prefix( $string = null ) {
		$prefix = (string)$this->_id_prefix;
		return is_null( $string ) ? $prefix : $prefix . $string;
	}


	/**
	 * Sets a prefix that will be added to IDs specific to this collection.
	 *
	 * @since 4.7.4
	 * @param string $prefix The prefix to set.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function set_id_prefix( $prefix ) {
		$this->_id_prefix = $prefix;
		return $this;
	}


	/**
	 * Set the name of the setting to store the notices in.
	 *
	 * @since 4.7.4
	 * @see get_setting_name()
	 * @param string $name The name of the notices setting to use.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function set_setting_code( $name ) {
		$this->_setting_code = $name;
		return $this;
	}


	/**
	 * Get the name of the notices setting.
	 *
	 * @since 4.7.4
	 * @see set_setting_name()
	 * @return string The name of the setting which stores notices and their states.
	 */
	public function get_setting_name() {
		return $this->_setting_code;
	}


	/**
	 * Retrieve the text domain that is used for translation by this instance.
	 *
	 * @since 4.7.4
	 * @return string The text domain.
	 */
	public function get_text_domain() {
		return $this->_text_domain;
	}


	/**
	 * Set the text domain that is used for translation by this instance.
	 *
	 * @since 4.7.4
	 * @param string $text_domain The text domain.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function set_text_domain( $text_domain ) {
		$this->_text_domain = $text_domain;
		return $this;
	}


	/**
	 * Get the class that is the base, common class for all notices' top HTML elements.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_base_class To modify return value.
	 * @return string The class common to all notices
	 */
	public function get_notice_base_class() {
		return apply_filters( $this->prefix( 'admin_notice_base_class' ), $this->_notice_base_class );
	}


	/**
	 * Set the class that will be the base, common class for all notices' top HTML elements.
	 *
	 * @since 4.7.4
	 * @param string $class The class name that will be common to all notices.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function set_notice_base_class( $class ) {
		$this->_notice_base_class = $class;
		return $this;
	}


	/**
	 * Get the class that is the base, common class for all notices' nonces' HTML elements.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_nonce_base_class To modify return value.
	 * @return string The class common to all nonces
	 */
	public function get_nonce_base_class() {
		return apply_filters( $this->prefix( 'admin_notice_nonce_base_class' ), $this->_nonce_base_class );
	}


	/**
	 * Set the class that will be the base, common class for all notices' nonces' HTML elements.
	 *
	 * @since 4.7.4
	 * @param string $class The class name that will be common to all nonces.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function set_nonce_base_class( $class ) {
		$this->_nonce_base_class = $class;
		return $this;
	}


	/**
	 * Get the class that is the base, common class for all notices' close buttons' HTML elements.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_btn_close_base_class To modify return value.
	 * @return string The class common to all close buttons
	 */
	public function get_btn_close_base_class() {
		return apply_filters( $this->prefix( 'admin_notice_btn_close_base_class' ), $this->_btn_close_base_class );
	}


	/**
	 * Set the class that will be the base, common class for all notices' close buttons' HTML elements.
	 *
	 * @since 4.7.4
	 * @param string $class The class name that will be common to close buttons.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function set_btn_close_base_class( $class ) {
		$this->_btn_close_base_class = $class;
		return $this;
	}


	/**
	 * Adds an admin notice.
	 *
	 * - If 'id' is not passed, a unique ID will be auto-generated.
	 * - If 'nonce' is not passed, a nonce will be auto-generated based on the ID.
	 * - A 'condition' is one or more callbacks. If none are passed, the notice will be displayed on all admin pages.
	 * - A 'condition_type' is one of the CONDITION_TYPE_* class constants. By default, all conditions have to be true.
	 * - The 'class' index determinces what type of notice it is. Currently, the valid values are 'updated', 'error'  and 'update-nag'.
	 *		See https://codex.wordpress.org/Plugin_API/Action_Reference/admin_notices
	 * - The 'content' index is the literal content of the notice.
	 * - If 'btn_close_id' is not passed, it will be auto-generated based on the ID.
	 * - The 'btn_close_class' index determines the class that the close button will have, in addition to the default 'btn-close'.
	 * - The 'btn_close_content' index determines the literal content of the element of the close button. HTML allowed.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_add_before_normalize To allow pre-normalization modification of notice.
	 * @uses-filter admin_notice_add_before To allow post-normalization modification of notice.
	 * @uses-action admin_notice_add_after To expose data of added notice. This will not be fired if notice was not added.
	 * @param array $notice Data of the notice to add.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function add_notice( $notice ) {
		$notice = apply_filters( $this->prefix( 'admin_notice_add_before_normalize' ), $notice, $this );
		$notice = $this->normalize_notice_data( $notice );
		$notice = apply_filters( $this->prefix( 'admin_notice_add_before' ), $notice, $this );
		$this->set_notice( $notice );
		do_action( $this->prefix( 'admin_notice_add_after' ), $notice, $this );

		return $this;
	}


	/**
	 * Sets the data for a notice with the specified ID.
	 *
	 * No normalization or checks are made, except for the presence of an ID.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_set_before To alter the data of the notice before setting.
	 * @uses-action admin_notice_set_after To expose the data of the notice after setting.
	 * @param array $notice Data of the notice.
	 * @param null|string $id The ID of the notice. If set, overrides 'id' index in notice data.
	 * @return \WPRSS_Admin_Notices This instance.
	 * @throws Exception If ID is missing.
	 */
	public function set_notice( $notice, $id = null ) {
		$notice = apply_filters( $this->prefix( 'admin_notice_set_before' ), $notice, $id, $this );
		$id = isset( $notice['id'] ) ? $notice['id'] : $id;
		if ( is_null( $id ) )
			throw new Exception( 'Could not set admin notice: ID must be specified in either notice data, or as separate argument' );

		$this->_notices[ $id ] = $notice;
		do_action( $this->prefix( 'admin_notice_set_after' ), $notice, $id, $this );
		return $this;
	}


	/**
	 * Normalize data of a notice, adding defaults.
	 *
	 * Auto-generating 'id', 'nonce', 'btn_close_id', 'nonce_element_id', 'btn_close_id'.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_defaults Default values, before addin auto-generated values.
	 * @uses-filter admin_notice_defaults_autogenerated Default values, after adding auto-generated values.
	 * @param array $data The notice data to normalize
	 * @return array $data The normalized data of a notice
	 */
	public function normalize_notice_data( $data ) {
		$data = wp_parse_args( $data, apply_filters( $this->prefix( 'admin_notice_defaults' ), array(
			'id'					=> null, // ID of the notice. Unique for the notice in this collection.
			'nonce'					=> null, // Nonce for the notice. Prevents unauthorised manipulation.
			'condition'				=> array(), // These callbacks will decide whether or not the nonce is to be displayed
			'condition_type'		=> self::CONDITION_TYPE_ALL, // Which of the conditions have to be satisfied
			'conditon_on_error'		=> self::CONDITION_ON_ERROR_THROW_EXCEPTION,
			'is_active'				=> true, // Whether this notice should be assumed to be active, unless set otherwise
			'nonce_element_class'	=> $this->prefix( 'admin-notice-nonce' ), // HTML class for the element that contains the nonce
			'nonce_element_id'		=> null,
			'class'					=> '', // HTML class for the element of the notice
			'notice_type'			=> 'updated', // Type of the notice.
			'notice_element_class'	=> $this->prefix( 'admin-notice' ),
			'content'				=> '', // The content of this notice
			'btn_close_id'			=> null, // The HTML ID for the close button
			'btn_close_class'		=> 'btn-close', // The HTML class for the close button, in addition to default
			'btn_close_content'	 => __( 'Dismiss this notification', $this->get_text_domain() ), // The content of the close button. HTML allowed.
                        'dismiss_mode'          => NoticeInterface::DISMISS_MODE_AJAX
		)));

		// Auto-generate ID
		if ( is_null( $data['id'] ) )
			$data['id'] = $this->generate_unique_id( 'admin-notice-', $data );

		// Prefix ID
		$data['id'] = $this->prefix( $data['id'] );

		// Auto-generate nonce
		if ( is_null( $data['nonce'] ) && !is_null( $data['id'] ) ) {
            $me = $this;
			$data['nonce'] = wprss()->getAdminHelper()->createCommand(function() use ($me, $data) {
                $nonce = $me->generate_nonce_for_notice( $data['id'] );
                return $nonce;
            });
		}

		// Auto-generate nonce element ID
		if ( is_null( $data['nonce_element_id'] ) && !is_null( $data['id'] ) )
			$data['nonce_element_id'] = sprintf( '%1$s-nonce', $data['id'] );

		// Auto-generate close button ID
		if ( is_null( $data['btn_close_id'] ) && !is_null( $data['id'] ) )
			$data['btn_close_id'] = sprintf( 'close-%1$s', $data['id'] );

		return apply_filters( $this->prefix( 'admin_notice_defaults_autogenerated' ), $data );
	}


	/**
	 * Removes a notice from this collection.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_remove_before To modify the notice ID that will be removed. Returning falsy value prevents removal.
	 * @uses-action admin_notice_remove_after To expose notice ID after removal.
	 * @param array|int $notice A notice, or notice ID.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function remove_notice( $notice ) {
		if ( is_array( $notice ) )
			$notice = isset( $notice['id'] ) ? $notice['id'] : null;

		if ( is_null( $notice ) )
			return $this;

		if ( !$this->has_notice( $notice ) )
			return $this;

		$notice = apply_filters( $this->prefix( 'admin_notice_remove_before' ), $notice, $this );
		if( !$notice ) return $this;

		$this->_remove_notice ( $notice, $this->_notices);
		do_action( $this->prefix( 'admin_notice_remove_after' ), $notice, $this );

		return $this;
	}


	/**
	 * Removes a notice by ID from the supplied array.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_internal_remove_before To modify notice before removal. Returning falsy value prevents removal.
	 * @uses-action admin_notice_internal_remove_after To expose data of notice after removal.
	 * @param array|string $notice A notice, or notice ID
	 * @param array $array The array, from which to remove the notice.
	 * @return \WPRSS_Admin_Notices This instance.
	 * @throws Exception If no ID specified.
	 */
	protected function _remove_notice( $notice, &$array = null ) {
		if ( is_array( $notice ) )
			$notice = isset( $notice['id'] ) ? $notice['id'] : null;

		if ( is_null( $notice ) )
			throw new Exception( 'Could not remove notice: an ID must be specified' );

		if ( is_null( $array ) )
			$array = &$this->_notices;

		if ( !array_key_exists( $notice, $array ) )
			return $this;

		$notice = apply_filters( $this->prefix( 'admin_notice_internal_remove_before' ), $notice, $array, $this );
		if ( !$notice ) return $this;

		unset( $array[ $notice ] );
		do_action( $this->prefix( 'admin_notice_internal_remove_after' ), $notice, $array, $this );

		return $this;
	}


	/**
	 * Checks whether a notice already exists.
	 *
	 * @since 4.7.4
	 * @param array|int $notice A notice, or notice ID to check for.
	 * @return boolean True if notice already exists; false otherwise.
	 */
	public function has_notice( $notice ) {
		if ( is_array( $notice ) )
			$notice = isset( $notice['id'] ) ? $notice['id'] : null;

		return array_key_exists( $notice , $this->_notices );
	}


	/**
	 * Get all notices, or a notice with the specified ID.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_get_all To modify all notices returned.
	 * @uses-filter admin_notice_get To modify single returned notice.
	 * @param null|string $id The ID of a notice to retrieve.
	 * @param null $default What to return if notice not found.
	 * @return array Retrieve all or one notice. See {@link normalize_notice_data()} for data keys.
	 */
	public function get_notices( $id = null, $default = null ) {
		if ( is_null( $id ) )
			return apply_filters( $this->prefix( 'admin_notice_get_all' ), $this->_notices, $this );

		return apply_filters( $this->prefix( 'admin_notice_get' ),
				isset( $this->_notices[ $id ] ) ? $this->_notices[ $id ] : $default,
				$id,
				$default,
				$this );
	}


	/**
	 * Get all notices that are active.
	 *
	 * @since 4.7.4
	 * @see is_notice_active()
	 * @return array Notices that are currently active;
	 */
	public function get_active_notices( $is_default_active = null ) {
		if ( is_null( $is_default_active ) )
			$is_default_active = true;

		$active_notices = array();
		foreach ( $this->get_notices() as $_id => $_notice ) {
			if ( $this->is_notice_active( $_notice, $is_default_active ) )
				$active_notices[ $_id ] = $_notice;
		}

		return $active_notices;
	}


	/**
	 * Determine whether the specified notice is active.
	 *
	 * If data array is passed, it's 'is_active' key will be used as default.
	 * Otherwise, data will be retrieved by ID and compared to database.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_is_active To modify return value. Used in several places.
	 * @param array|string $notice Notice or notice ID.
	 * @param boolean $default What to return if no notice state data exists.
	 * @return boolean Whether or not the specified notice is active.
	 * @throws Exception If ID not specified.
	 */
	public function is_notice_active( $notice, $default = null ) {
		// State if no state provided
		if ( is_null( $default ) )
			$default = true;

		// If ID passed, retrieve the notice
		if ( !is_array( $notice ) )
			$notice = $this->get_notices( $notice, array() );
		// Last resort defaults
		$id = isset( $notice['id'] ) ? $notice['id'] : null;
		$is_active_default = isset( $notice['is_active'] ) ? (bool)$notice['is_active'] : $default;

		if ( is_null( $id ) )
			throw new Exception( 'Could not determine notice state: ID must be specified' );

		// Settings from DB
		$settings = $this->get_notices_settings( $id );

		// If no state, assume default
		if ( !isset( $settings['is_active'] ) )
			return apply_filters( $this->prefix( 'admin_notice_is_active' ), $is_active_default, $id, $this );

		return apply_filters( $this->prefix( 'admin_notice_is_active' ), (bool)$settings['is_active'], $id, $this );
	}


	/**
	 * Set notice active state.
	 *
	 * @since 4.7.4
	 * @param array|string $notice Notice data or ID.
	 * @param null|boolean $is_active If true, notice state will be set to active; if false - to inactive. Default: true.
	 */
	public function set_notice_active( $notice, $is_active = null ) {
		if ( is_null( $is_active ) )
			$is_active = true;

		$this->set_notices_settings( $notice, (bool)$is_active );
		return $this;
	}


	/**
	 * Gets all notices that pass their conditions according to the condition type.
	 *
	 * Allowed notices are also only active ones. Inactive notices are not evaluated.
	 *
	 * @since 4.7.4
	 * @see is_notice_allowed()
	 * @uses-filter admin_notice_all_allowed To modify return value.
	 * @return array Allowed notices.
	 */
	public function get_allowed_notices() {
		$allowed_notices = array();
		foreach ( $this->get_active_notices() as $_id => $_notice ) {
			if ( $this->is_notice_allowed( $_notice) )
				$allowed_notices[ $_id ] = $_notice;
		}

		return apply_filters( $this->prefix( 'admin_notice_all_allowed' ), $allowed_notices, $this );
	}


	/**
	 * Checks if the specified notice is allowed.
	 *
	 * To determine that, the notice's conditions are evaluated according
	 * to the condition type.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_is_allowed To modify return value.
	 * @param array|string $notice Notice or notice ID.
	 * @return bool Whether or not the specified notice passed it's conditions to be allowed.
	 * @throws Exception If ID not specified.
	 */
	public function is_notice_allowed( $notice ) {
		if ( !is_array( $notice ) )
			$notice = $this->get_notices( $notice );

		$conditions = isset( $notice['condition'] ) ? $notice['condition'] : array();
		$condition_type = isset( $notice['condition_type'] ) ? $notice['condition_type'] : self::CONDITION_TYPE_ALL;
		$is_allowed = $this->evaluate_conditions( $conditions, $condition_type, array( $notice ) );
		return apply_filters( $this->prefix( 'admin_notice_is_allowed' ), $is_allowed, $notice );
	}


	/**
	 * Generates a nonce for a notice ID.
	 *
	 * @since 4.7.4
	 * @see wp_create_nonce()
	 * @see generate_nonce_code()
	 * @uses-filter admin_notice_nonce_for_notice
	 * @param array|string $notice Notice or notice ID.
	 * @return string The nonce.
	 * @throws Exception If ID not specified.
	 */
	public function generate_nonce_for_notice( $notice ) {
		if ( is_array( $notice ) )
			$notice = isset( $notice['id'] ) ? $notice['id'] : null;

		if ( is_null( $notice ) )
			throw new Exception( 'Could not get nonce for notice: notice ID must be specified' );

		$nonce_code = $this->generate_nonce_code( $notice );
		$nonce = wp_create_nonce( $nonce_code );

		return apply_filters( $this->prefix( 'admin_notice_nonce_for_notice' ), $nonce, $notice, $nonce_code, $this );
	}


	/**
	 * Generates a code that is used to generate a nonce for a notice.
	 *
	 * @since 4.7.4
	 * @see wp_create_nonce()
	 * @see generate_nonce_for_notice()
	 * @uses-filter admin_notice_nonce_code To modify return value.
	 * @param array|string $notice Notice or notice ID;
	 * @return string Code (action) for the nonce.
	 * @throws Exception If nonce ID not specified.
	 */
	public function generate_nonce_code( $notice ) {
		if ( is_array( $notice ) )
			$notice = isset( $notice['id'] ) ? $notice['id'] : null;

		if ( is_null( $notice ) )
			throw new Exception( 'Could not generate nonce code for notice: notice ID must be specified' );

		return apply_filters( $this->prefix( 'admin_notice_nonce_code' ), sprintf( '%1$s-nonce', $notice ), $notice, $this );
	}


	/**
	 * Evaluates a condition or group of conditions based on the condition type.
	 *
	 * A condition is a callable that returns true or false (no type checking is done here).
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_conditions_evaluated To modify the return value.
	 * @uses-filter admin_notice_condition_result To alter the result of each condition's evaluated.
	 * @param array|callable $conditions A callable or an array of callables.
	 * @param string $condition_type One of the CONDITION_TYPE_* class constants. Default: CONDITION_TYPE_ALL.
	 * @param array $args These args will be passed to the condition callable.
	 * @return boolean Whether or not the conditions evaluate according to the condition type.
	 * @throws Exception If a condition cannot be called.
	 */
	public function evaluate_conditions( $conditions, $condition_type = self::CONDITION_TYPE_ALL, $args = array() ) {
		$event_name = $this->prefix( 'admin_notice_conditions_evaluated' );
		$result = true; // By default, evaluation passes
		if ( empty( $conditions ) ) return apply_filters ( $event_name, $result, $condition_type, $this ); // Unconditional ;)
		if ( !is_array( $conditions ) ) $conditions = array($conditions); // Normalizing

		foreach ( $conditions as $_idx => $_condition ) {
			$func = is_array( $_condition ) && isset( $_condition['func'] )
					? $_condition['func']
					: $_condition;
			$args = array_merge( // Appending our args to the passed args
						array_values( isset( $_condition['args'] ) ? (array)$_condition['args'] : array() ),
						array_values( $args ));

			if ( !is_callable( $func ) )
				throw new Exception ( sprintf( 'Could not evaluate condition %1$d: condition must contain a callable', $_idx ) );

			$_value = call_user_func_array( $func, $args );
			$_value = apply_filters( $this->prefix( 'admin_notice_condition_result' ), $_value, $_idx, $condition_type, $_condition, $args, $this );
			switch ( $condition_type ) {
				case self::CONDITION_TYPE_ANY: // At least one must be true
					if ( (bool)$_value ) return apply_filters ( $event_name, true, $condition_type, $this );
					$result = false;
					break;

				case self::CONDITION_TYPE_ALMOST: // At least one must be false
					if ( !(bool)$_value ) return  apply_filters ( $event_name, true, $condition_type, $this );
					$result = false;
					break;

				case self::CONDITION_TYPE_NONE: // All must be false
					if ( (bool)$_value ) return apply_filters ( $event_name, false, $condition_type, $this );
					$result = true;
					break;

				default:
				case self::CONDITION_TYPE_ALL: // All must be true
					if ( !(bool)$_value ) return apply_filters ( $event_name, false, $condition_type, $this );
					$result = true;
					break;
			}
		}

		return apply_filters ( $event_name, $result, $condition_type, $this );
	}


	/**
	 * Get settings for all notices, or just one.
	 *
	 * It appears that options are already being cached by WP.
	 * Also, some notices in the returned array may not be registered for display,
	 * in which case they will not be displayed. And vice-versa: some of the registered
	 * notices will not have any settings associated with them, in which case
	 * defaults are assumed. See {@link is_notice_active()} for information.
	 * The settings contain states, not notice information.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_get_notices_settings_all To modify settings of all notices.
	 * @uses-filter admin_notice_get_notices_settings To modify settings of just one notice. May have been modified by admin_notice_get_notices_settings_all.
	 * @param null|string $id Notice ID
	 * @param null|mixed $default What to return if no settings for notice. Default: empty array.
	 * @return array An array, where key is notice ID, and value is boolean indicating whether or not it is active.
	 */
	public function get_notices_settings( $id = null, $default = null ) {
		if( is_null( $default ) )
			$default = array();

		$settings = apply_filters( $this->prefix( 'admin_notice_get_notices_settings_all' ),
				get_option( $this->get_setting_name(), array() ),
				$this );

		if ( is_null( $id ) )
			return $settings;

		// Normalize
		$settings = isset( $settings[ $id ] ) ? $settings[ $id ] : $default;
		$settings = $this->normalize_notice_data_from_db( $settings );

		return apply_filters( $this->prefix( 'admin_notice_get_notices_settings' ),
				$settings,
				$id,
				$default,
				$this );
	}


	/**
	 *
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_set_settings_before To modify what gets saved. Also see {@link prepare_notice_data_for_db()}.
	 * @uses-action admin_notice_set_settings_after To expose data that has been saved.
	 * @param string|array $notice The notice data, or notice ID, to save.
	 * @param null|array|boolean $settings The settings, or just the active state, to save.
	 * @return \WPRSS_Admin_Notices This instance.
	 * @throws Exception If an ID is nowhere to be found. How to save? :S
	 */
	public function set_notices_settings( $notice, $settings = null ) {
		// Normalizing notice data
		if ( !is_array( $notice ) )
			$notice = array( 'id' => $notice );
		// If using just the notice data to save everything
		if ( is_null( $settings ) )
			$settings = $notice;
		// If saving just the active state
		if ( is_bool( $settings ) )
			$settings = array( 'is_active' => $settings );

		// Making sure notice ID isn't overwritten
		if( isset( $settings['id'] ) )
			unset( $settings['id'] );

		// Merging the data together to get all data to save
		$settings = wp_parse_args( $settings, $notice );

		// Making sure that an ID ultimately exists
		if ( !isset( $settings['id'] ) )
			throw new Exception( 'Could not set notice settings: ID must be specified' );

		$id = $settings['id'];
		$db_settings = $this->get_notices_settings( $id );

		// Merge again to only update what is in the database
		$settings = wp_parse_args( $settings, $db_settings );

		// Get all settings data
		$all_settings = $this->get_notices_settings();
		// Set and finally save
		$settings = apply_filters( $this->prefix( 'admin_notice_set_settings_before' ), $settings, $id, $this );
		$settings = $this->prepare_notice_data_for_db( $settings );
		$all_settings[ $id ] = $settings;
		$this->set_notices_settings_all( $all_settings );
		do_action( $this->prefix( 'admin_notice_set_settings_after' ), $settings, $id, $this );

		return $this;
	}


	/**
	 * Saves the data of all specified notices to the database.
	 *
	 * The passed data will replace all data currently stored in the option.
	 *
	 * @since 4.7.4
	 * @see get_setting_name()
	 * @uses-filter admin_notice_set_settings_all_before To modify what gets saved.
	 * @uses-action admin_notice_set_settings_all_after To expose saved data.
	 * @param array $settings An array containing data of all notices.
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function set_notices_settings_all( $settings ) {
		$settings = apply_filters( $this->prefix( 'admin_notice_set_settings_all_before' ), $settings, $this );
		update_option( $this->get_setting_name(), $settings );
		do_action( $this->prefix( 'admin_notice_set_settings_all_after' ), $settings, $this );

		return $this;
	}


	/**
	 * Normalize a single notice's data that was returned from the database.
	 *
	 * @since4.8.2
	 * @uses-filter admin_notice_normalize_notice_data_from_db To modify the return value.
	 * @param null|mixed|array $data The individual notice's data to normalize.
	 * @return array The notice data returned from the database.
	 */
	public function normalize_notice_data_from_db( $data ) {
		if ( is_null( $data ) )
			$data = array();

		if ( !is_array( $data ) )
			$data = array( 'is_active' => (bool)$data );

		return apply_filters( $this->prefix( 'admin_notice_normalize_notice_data_from_db' ), $data, $this );
	}


	/**
	 * Prepares data of a single notice to be saved to the database.
	 *
	 * Is responsible for preserving only allowed fields, and adding some
	 * required ones, if necessary and possible.
	 *
	 * @since4.8.2
	 * @uses-filter admin_notice_prepare_notice_data_for_db To modify the resulting prepared data.
	 * @param array $data The data to prepare.
	 * @return array The data that should be saved to the database.
	 */
	public function prepare_notice_data_for_db( $data ) {
		$prepared_data = array();
		if ( isset( $data['is_active'] ) )
			$prepared_data['is_active'] = (bool)$data['is_active'];

		return apply_filters( $this->prefix( 'admin_notice_prepare_notice_data_for_db' ), $prepared_data, $data, $this );
	}


	/**
	 * Generates a unique ID.
	 *
	 * This ID will be unique to this collection.
	 *
	 * @since 4.7.4
	 * @see uniqid()
	 * @uses-filter admin_notice_generate_unique_id To allow modification of ID.
	 * @param string $prefix The prefix to give to the generated ID.
	 * @return string A notice ID unique to this instance in the scope of this collection.
	 */
	public function generate_unique_id( $prefix = '', $data = null ) {
		do {
			$id = is_null($data) ? uniqid( $prefix ) : $prefix . $this->hash($data);
		} while ( $this->has_notice( $id ) );

		return apply_filters( $this->prefix( 'admin_notice_generate_unique_id' ), $id, $prefix, $this );
	}

    public function hash($data) {
        return md5(serialize($data));
    }


	/**
	 * Generate the HTML for all allowed notices, sequentially.
	 *
	 * @since 4.7.4
	 * @return string The rendered HTML.
	 */
	public function render_allowed_notices() {
		$output = '';
		foreach ( $this->get_allowed_notices() as $_id => $_notice ) {
			$output .= $this->render_notice( $_notice );
		}

		return $output;
	}


	/**
	 * Directly output the rendered HTML of all allowed notices.
	 *
	 * @since 4.7.4
	 * @return \WPRSS_Admin_Notices This instance.
	 */
	public function output_allowed_notices() {
		echo $this->render_allowed_notices();
		return $this;
	}


	/**
	 * Generate the HTML of a notice.
	 *
	 * @since 4.7.4
	 * @uses-filter admin_notice_render_before To allow modification of notice data before rendering.
	 * @uses-action admin_notice_render_after To allow injection inside the notice HTML.
	 * @uses-filter admin_notice_rendered To allow modification of rendered HTML.
	 * @param array|string $id Notice, or notice ID.
	 * @return string The HTML output of the notice.
	 * @throws Exception If no notice found for ID.
	 */
	public function render_notice( $id ) {
		$notice = is_array( $id )
				? $id
				: $this->get_notices( $id );

		if ( !$notice )
			throw new Exception( sprintf( 'Could not render notice: no notice found for ID "%1$s"', $id ) );

        $helper = wprss()->getAdminHelper();

		ob_start();
		$notice = apply_filters( $this->prefix( 'admin_notice_render_before' ), $notice, $this );

        $noticeId = esc_attr($notice['id']);
		$content = $notice['content'];

		$type = $notice['notice_type'];
		$baseClass = $this->get_notice_base_class();
		$elemClass = $notice['notice_element_class'];
		$dismissMode = $notice['dismiss_mode'];
		$fullClass = esc_attr("{$type} {$elemClass} {$baseClass} dismiss-mode-{$dismissMode}");

        $nonce = $notice['nonce'];
        $nonceId = esc_attr($notice['nonce_element_id']);
        $nonceElemClass = $notice['nonce_element_class'];
        $nonceBaseClass = $this->get_nonce_base_class();
        $nonceFullClass = esc_attr("hidden {$nonceElemClass} {$nonceBaseClass}");
		?>

		<div id="<?= $noticeId ?>" class="<?= $fullClass ?>">
			<div class="notice-inside">
				<?= $content ?>
			</div>
            <?php if ($dismissMode !== NoticeInterface::DISMISS_MODE_NONE):
                $btnId = esc_attr($notice['btn_close_id']);
                $btnContent = $notice['btn_close_content'];
                $btnElemClass = $notice['btn_close_class'];
                $btnBaseClass = $this->get_btn_close_base_class();
                $btnFullClass = esc_attr("{$btnBaseClass} {$btnElemClass}");
                ?>
                <a href="javascript:void(0);" id="<?= $btnId ?>" style="float:right;" class="<?= $btnFullClass ?>">
                    <?= $btnContent ?>
                </a>
            <?php endif ?>
            <span id="<?= $nonceId ?>" class="<?= $nonceFullClass ?>">
                <?= $helper->resolveValue($nonce) ?>
            </span>
		</div>

		<?php

		do_action($this->prefix('admin_notice_render_after'), $notice, $this);
        $output = ob_get_clean();

        return apply_filters($this->prefix('admin_notice_rendered'), $output, $notice, $this);
	}


	/**
	 * Used to hide a notice, typically responding to a frontend event.
	 *
	 * @since 4.7.4
	 * @param array|string $notice Notice or notice ID.
	 * @param string $nonce The nonce from the frontend.
	 * @return \WPRSS_Admin_Notices This instance.
	 * @throws Exception If no notice ID specified, or no notice found for it,
	 *	or specified nonce does not belong to the notice, or the nonce is not right.
	 */
	public function hide_notice( $notice, $nonce ) {
		if ( is_array( $notice ) )
			$notice = isset( $notice['id'] ) ? $notice['id'] : null;

        $notice_id = $notice;
		if ( is_null( $notice ) )
			throw new Exception('Could not hide notice: Notice ID must be specified');
		if ( is_null( $nonce ) )
			throw new Exception('Could not hide notice: nonce must be specified');
		if ( !($notice = $this->get_notices( $notice ) ) )
			throw new Exception( sprintf( 'Could not hide notice: No notice found for ID "%1$s"', $notice_id ) );

		// Is it the right nonce?
        $noticeNonce = wprss()->getAdminHelper()->resolveValue($notice['nonce']);
		if ( $noticeNonce !== $nonce )
			throw new Exception( sprintf( 'Could not hide notice: Nonce "%1$s" does not belong to notice "%2$s"', $nonce, $notice_id ) );

		// Verify nonce
		if( !wp_verify_nonce( $nonce, $this->generate_nonce_code( $notice ) ) )
			throw new Exception( sprintf( 'Could not hide notice: Nonce "%1$s" is incorrect', $nonce ) );

		wprss_admin_notice_get_collection()->set_notice_active( $notice, false );

		return $this;
	}
}


// This should initialize the notice collection before anything can use it
add_action( 'init', 'wprss_admin_notice_get_collection', 9 );

// Trigger the component to initialize
add_action('wprss_pre_init', function() {
    wprss_wp_container()->get(\WPRSS_SERVICE_ID_PREFIX.'admin_ajax_notices');
});


/**
 * Returns the singleton, plugin-wide instane of the admin notices controller.
 * Initializes it if necessary.
 *
 * @since 4.7.4
 * @staticvar WPRSS_Admin_Notices $collection The singleton instance.
 * @return \WPRSS_Admin_Notices|null The singleton instance of notice collection, or null if it is unavailable.
 */
function wprss_admin_notice_get_collection() {
	static $collection = null;

	if ( is_null( $collection ) && is_admin() ) {
        $collection = wprss_wp_container()->get(\WPRSS_SERVICE_ID_PREFIX.'admin_ajax_notice_controller');
        add_action( \WPRSS_EVENT_PREFIX.'admin_exclusive_scripts_styles', 'wprss_admin_notices_collection_enqueue_scripts' );
	}

	return $collection;
}

/**
 * Enqueues the scripts for a notice collection.
 *
 * @since 4.7.8
 */
function wprss_admin_notices_collection_enqueue_scripts() {
    $notices = wprss()->getAdminAjaxNotices();
    $notices->_registerAssets();
    $notices->enqueueAssets();
}

/**
 * Centralizes access to the name of the AJAX action handler for dismissing admin notices.
 *
 * This is necessary for configuration of the frontend.
 *
 * @since 4.7.4
 * @uses-filter wprss_admin_notice_action_code To modify return value.
 * @return string The action code
 */
function wprss_admin_notice_get_action_code() {
	return apply_filters( 'wprss_admin_notice_action_code', 'wprss_admin_notice_hide' );
}


/**
 * Adds a notice to be displayed on top of an admin page.
 *
 * @since 4.7.4
 * @param array $notice Data of the notice
 * @return bool|WP_Error True if notice added, false if collection unavailable, or WP_Error if something went wrong.
 */
function wprss_admin_notice_add( $notice ) {
	try {
		if ( !($collection = wprss_admin_notice_get_collection()) )
			return false;

		$collection->add_notice( $notice );
	} catch ( Exception $e ) {
		return new WP_Error( 'could_not_add_admin_notice', $e->getMessage() );
	}

	return true;
}


add_action( sprintf( 'wp_ajax_%1$s', wprss_admin_notice_get_action_code() ), 'wprss_admin_notice_hide' );
/**
 * This is what handles the AJAX action of dismissing admin notices.
 *
 * @see WPRSS_Admin_Notices::hide_notice()
 * @since 4.7.4
 */
function wprss_admin_notice_hide() {
    $notice_id = isset($_REQUEST['notice_id']) ? $_REQUEST['notice_id'] : null;
    $notice_id = filter_var($notice_id, FILTER_DEFAULT);

    $nonce = isset($_REQUEST['nonce']) ? $_REQUEST['nonce'] : null;
    $nonce = filter_var($nonce, FILTER_DEFAULT);

	try {
        wprss_admin_notice_get_collection()->hide_notice($notice_id, $nonce);
    } catch (Exception $e) {
        // Failure
        echo esc_html($e->getMessage());
        exit();
    }

	// Success
    exit('1');
}


/**
 * Check whether the current page is related to WP RSS Aggregator.
 *
 * @since 4.7.4
 * @uses-filter wprss_is_wprss_page To modify return value.
 * @global string $typenow Post type of the current page.
 * @return boolean True if the current page is a WPRSS-related page; false otherwise.
 */
function wprss_is_wprss_page() {
	global $typenow;

    if (!is_admin()) {
        return false;
    }

    $postType = $typenow;
	if ( empty( $postType ) && isset( $_GET['post'] ) && !empty( $_GET['post'] ) ) {
        $post = get_post(filter_var($_GET['post'], FILTER_SANITIZE_NUMBER_INT));
        if ( $post !== NULL && !is_wp_error( $post ) )
            $postType = $post->post_type;
	}

    $filterFlags = FILTER_FLAG_STRIP_BACKTICK | FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH;
    if (empty($postType) && isset($_GET['post_type'])) {
        $postType = filter_var(trim($_GET['post_type']), FILTER_SANITIZE_STRING, $filterFlags);
    }

    $pagenow = isset($_GET['page']) ? filter_var($_GET['page'], FILTER_SANITIZE_STRING, $filterFlags) : null;
    $wprss_post_types = apply_filters('wprss_post_types', array(
        'wprss_feed',
        'wprss_feed_item',
        'wprss_blacklist',
    ));
    $wprss_pages = apply_filters('wprss_page_slugs', array(
        'wprss-aggregator',
        'wprss-aggregator-settings',
        'wprss-import-export-settings',
        'wprss-debugging',
        'wprss-addons',
        'wprss-welcome',
        'wprss-help',
        'wpra-intro',
        'wpra-update',
    ));

	$is_wprss_post = in_array($postType, $wprss_post_types, true);
    $is_wprss_page = in_array($pagenow, $wprss_pages, true);
	return apply_filters( 'wprss_is_wprss_page',  $is_wprss_post || $is_wprss_page );
}


/**
 * Check whether the currently logged in user can manage WP options.
 *
 * This normally describes the administrator.
 *
 * @since 4.7.4
 * @uses-filter wprss_user_can_manage_options To modify return value.
 * @return bool True if the currently logged in user has the 'manage_options' privilege; false otherwise.
 */
function wprss_user_can_manage_options() {
	return apply_filters( 'wprss_user_can_manage_options', current_user_can( 'manage_options' ) );
}

// Adds the AJAX notice service provider to the core container
add_filter(WPRSS_EVENT_PREFIX .'core_container_init', function(WritableContainerInterface $container) {
    $noticeProvider = wprss_core_admin_ajax_notices_service_provider();
    $container->register($noticeProvider);
});

/**
 * Retrieves the service provider that provides notice service definitions.
 *
 * @since 4.11
 *
 * @staticvar ServiceProvider $provider
 * @return ServiceProvider
 */
function wprss_core_admin_ajax_notices_service_provider()
{
    static $provider = null;

    if(is_null($provider)) {
        $provider = new ServiceProvider(array(
            'notice_service_id_prefix'  => \WPRSS_NOTICE_SERVICE_ID_PREFIX,
            'service_id_prefix'         => \WPRSS_SERVICE_ID_PREFIX,
            'event_prefix'              => \WPRSS_EVENT_PREFIX,
        ));
    }

    return $provider;
}
