<?php
/**
 * Handles the registration and display of admin notices, including storage and retrieval of individual EM_Admin_Notice notice objects. 
 * @since 5.8.2.0
 */
class EM_Admin_Notices {
	
	/**
	 * Flag for whether or not to add dismissable notice JS to admin page footer.
	 * @var boolean
	 */
	public static $js_footer = false;
	
	/**
	 * Initialize EM Admin Notices by adding the relevant hooks. 
	 */
	public static function init(){
		add_action('admin_notices', 'EM_Admin_Notices::admin_notices');
		add_action('wp_ajax_em_dismiss_admin_notice', 'EM_Admin_Notices::dismiss_admin_notice');
		if( is_multisite() ){
			add_action('admin_notices', 'EM_Admin_Notices::network_admin_notices');
			add_action('network_admin_notices', 'EM_Admin_Notices::network_admin_notices');
			add_action('wp_ajax_em_dismiss_network_admin_notice', 'EM_Admin_Notices::dismiss_admin_notice');
		}
	}
	
	/**
	 * Adds an admin notice to the site. If $network is set to true, notice will be saved at network level.
	 * If a string is provided as $EM_Admin_Notice, it will be considered as a notice requiring a hook to ouptut anything.
	 * If a notice with an identical key is provided, it will overwrite the previously stored notice.
	 * When adding a notice that all users will see and can dismiss, it's recommended you use a hook to build the EM_Admin_Notice object, to avoid storing unecessary data in the DB
	 * @param EM_Admin_Notice|string $EM_Admin_Notice
	 * @param boolean $network
	 * @return boolean Returns true if added successfully, false if not or if the exact same record exists.
	 */
	public static function add( $EM_Admin_Notice, $network = false ){
		$network = $network && is_multisite(); //make sure we are actually in multisite!
		if( is_string($EM_Admin_Notice) ){
		    $EM_Admin_Notice = new EM_Admin_Notice( $EM_Admin_Notice );
		    $hook_notice = true;
		}
		if( !$EM_Admin_Notice->name ) return false;
		//get options data
		$data = $network ? get_site_option('dbem_data') : em_get_option('dbem_data');
		$data = empty($data) ? array() : maybe_unserialize($data);
		if( !is_array($data)) $data = array();
		$notices_data = $network ? get_site_option('dbem_admin_notices') : em_get_option('dbem_admin_notices');
		$notices_data = empty($notices_data) ? array() : maybe_unserialize($notices_data);
		if( !is_array($notices_data)) $notices_data = array(); //we store the data regarldess of whether a message will require a hook, since it contains location and caps considtions
		//start building data
		$notices = !empty($data['admin_notices']) ? $data['admin_notices'] : array();
		$notices[$EM_Admin_Notice->name] = !empty($EM_Admin_Notice->when) ? $EM_Admin_Notice->when : 0;
		if( empty($hook_notice) ){ //we only skip this if simply a key is provided initially in $EM_Admin_Notice
            $notices_data[$EM_Admin_Notice->name] = $EM_Admin_Notice->to_array();
        }
		if( !empty($notices) ){
			$data['admin_notices'] = $notices;
			$update_notices =  $network ? update_site_option('dbem_data', $data) : update_option('dbem_data', $data);
			$update_notices_data = true;
			if( !empty($notices_data) ){
				$update_notices_data =  $network ? update_site_option('dbem_admin_notices', $notices_data) : update_option('dbem_admin_notices', $notices_data, false);
			}
			return $update_notices && $update_notices_data;
		}
		return false;
	}
	
	/**
	 * Remove an admin notice. If $network is true, then a network-level admin notice will be removed.
	 * @param string $notice_key
	 * @param string $network
	 * @return boolean Returns true if successfully deleted, false if there's an error or if there's nothing to delete. 
	 */
	public static function remove( $notice_key, $network = false ){
		$network = $network && is_multisite(); //make sure we are actually in multisite!
		$data = $network ? get_site_option('dbem_data') : em_get_option('dbem_data');
		if( !empty($data['admin_notices']) && isset($data['admin_notices'][$notice_key])){
			unset($data['admin_notices'][$notice_key]);
			if( empty($data['admin_notices']) ) unset($data['admin_notices']);
			$result = $update_notices_data = $network ? update_site_option('dbem_data', $data) : update_option('dbem_data', $data);
			$notices_data = $network ? get_site_option('dbem_admin_notices') : em_get_option('dbem_admin_notices');
			if( !empty($notices_data[$notice_key]) ){
				unset($notices_data[$notice_key]);
				if( empty($notices_data) ){
					$update_notices_data =  $network ? delete_site_option('dbem_admin_notices') : delete_option('dbem_admin_notices');
				}else{
					$update_notices_data =  $network ? update_site_option('dbem_admin_notices', $notices_data) : update_option('dbem_admin_notices', $notices_data, false);
				}
			}
			return $result && $update_notices_data;
		}
		return false;
	}
	
	/**
	 * Adds admin notice to network rather than specific blog. Equivalent to self::add( $EM_Admin_Notice, true );
	 * @see EM_Admin_Notices::add()
	 */
	public static function network_add( $EM_Admin_Notice ){ return self::add( $EM_Admin_Notice, true ); }
	
	/**
	 * Removes admin notice from network rather than specific blog. Equivalent to self::remove( $EM_Admin_Notice, true );
	 * @see EM_Admin_Notices::remove()
	 */
	public static function network_remove( $notice_key ){  return self::remove( $notice_key, true ); }
	
	/**
	 * Output the admin notices we need to output now. If $network is true, MultiSite network messages will be output.
	 * @param string $network
	 */
	public static function admin_notices( $network = false ){
		$notices = array();
		$data = $network ? get_site_option('dbem_data') : em_get_option('dbem_data');
		$possible_notices = is_array($data) && !empty($data['admin_notices']) ? $data['admin_notices'] : array();
		//we may have something to show, so we make sure that there's something to show right now
		foreach( $possible_notices as $key => $val ){
			//to avoid extra loading etc. we weed out time-based notices that aren't triggered right now 
			if( empty($val) || ($val > 0 && $val < time()) ){
				//we have a match, so we add this to $notices
				$notices[$key] = self::get_notice($key, $network);
			}
		}
		self::output( $notices, $network );
	}
	
	public static function get_notice( $key, $network = false ){
		//build notice object
		$notice_data = $network ? get_site_option('dbem_admin_notices') : em_get_option('dbem_admin_notices');
		if( empty($notice_data[$key]) || !is_array($notice_data[$key]) ){
			$notice = array('name'=>$key, 'network'=>$network);
		}else{
			$notice = $notice_data[$key];
			$notice['network'] = $network;
		}
		return new EM_Admin_Notice($notice);
	}
	
	/**
	 * Outputs admin notices at network level, same as EM_Admin_Notices::admin_notices(true)
	 * @see EM_Admin_Notices::admin_notices()
	 */
	public static function network_admin_notices(){ self::admin_notices(true); }
	
	/**
	 * Outputs admin notices and calls the dismissable JS to be output at footer of admin page.
	 * If $network is true, only MultiSite network-level notices will be shown.
	 * @param array $notices
	 * @param boolean $network
	 */
	public static function output( $notices, $network = false ){
		foreach( $notices as $EM_Admin_Notice ){
			//output the notice if meant to
			if( $EM_Admin_Notice->can_show() ){
				if( $EM_Admin_Notice->output() ) self::$js_footer = true;
			}
		}
		if( self::$js_footer ){
			add_action('admin_footer', 'EM_Admin_Notices::admin_footer');
		}
	}
	
	/**
	 * If called via AJAX, the notice will be removed. 
	 */
	public static function dismiss_admin_notice(){
		if( empty($_REQUEST['notice']) ) return;
		if( empty($_REQUEST['nonce']) || !wp_verify_nonce($_REQUEST['nonce'], $_REQUEST['action'].$_REQUEST['notice'].get_current_user_id() ) ) die('Invalid Nonce');
		$key = $_REQUEST['notice'];
		$network = $_REQUEST['action'] == 'em_dismiss_network_admin_notice';
		//get the notice
		$EM_Admin_Notice = self::get_notice($key, $network);
		if( $EM_Admin_Notice->is_user_notice() ){
			//user-specific notices are flagged on the user-level
			$user_id = get_current_user_id();
			$dismissed_notices = get_user_meta( $user_id, '_em_dismissed_notices', true);
			$dismissed_notices = is_array($dismissed_notices) ? $dismissed_notices : array(); 
			if( !in_array($EM_Admin_Notice->name, $dismissed_notices) ){
				$dismissed_notices[] = $EM_Admin_Notice->name;
				$result = update_user_meta( $user_id, '_em_dismissed_notices', $dismissed_notices);
			}
		}else{
			$result = self::remove($_REQUEST['notice'], $network);
		}
		if( !empty($_REQUEST['redirect']) ){
			wp_safe_redirect( wp_get_referer() );
			exit();
		}
		echo !empty($result) ? 'Thou art dismissed!' : 'Thou shall not pass!';
		exit();
	}
	
	/**
	 * Outputs JS for dismissing notices. 
	 */
	public static function admin_footer(){
		?>
		<script type="text/javascript">
		document.addEventListener('DOMContentLoaded', function(){
			document.querySelectorAll('.em-admin-notice').forEach( function( notice ){
				notice.addEventListener('click', function( e ){
					if( e.target.matches('button.notice-dismiss') ) {
						e.preventDefault();
						fetch( notice.dataset.url ).then( (response) => {
							if ( response.ok ) {
								return response.text();
							} else {
								console.log( 'Could not dismiss admin notice die to not OK repsonse: %o', response );
								response.reject();
							}
						}, {mode : 'no-cors'} ).then( (response) => {
							console.log( 'Admin notice dismissed OK with response: %s', response );
						}).catch( (error) => {
							console.log( 'Could not dismiss admin notice, error is %o', error );
						});
						return false;
					}
				});
			});
		});
		</script>
		<?php
	}
}
include('em-admin-notice.php');
EM_Admin_Notices::init();