<?php

    /**

     * @package     Freemius

     * @copyright   Copyright (c) 2015, Freemius, Inc.

     * @license     https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3

     * @since       1.0.7

     */



    if ( ! defined( 'ABSPATH' ) ) {

        exit;

    }



    class FS_Admin_Notice_Manager {

        /**

         * @since 1.2.2

         *

         * @var string

         */

        protected $_module_unique_affix;

        /**

         * @var string

         */

        protected $_id;

        /**

         * @var string

         */

        protected $_title;

        /**

         * @var array[string]array

         */

        private $_notices = array();

        /**

         * @var FS_Key_Value_Storage

         */

        private $_sticky_storage;

        /**

         * @var FS_Logger

         */

        protected $_logger;

        /**

         * @since 2.0.0

         * @var int The ID of the blog that is associated with the current site level admin notices.

         */

        private $_blog_id = 0;

        /**

         * @since 2.0.0

         * @var bool

         */

        private $_is_network_notices;



        /**

         * @var FS_Admin_Notice_Manager[]

         */

        private static $_instances = array();



        /**

         * @param string $id

         * @param string $title

         * @param string $module_unique_affix

         * @param bool   $is_network_and_blog_admins           Whether or not the message should be shown both on

         *                                                     network and blog admin pages.

         * @param bool   $network_level_or_blog_id Since 2.0.0

         *

         * @return \FS_Admin_Notice_Manager

         */

        static function instance(

            $id,

            $title = '',

            $module_unique_affix = '',

            $is_network_and_blog_admins = false,

            $network_level_or_blog_id = false

        ) {

            if ( $is_network_and_blog_admins ) {

                $network_level_or_blog_id = true;

            }



            $key = strtolower( $id );



            if ( is_multisite() ) {

                if ( true === $network_level_or_blog_id ) {

                    $key .= ':ms';

                } else if ( is_numeric( $network_level_or_blog_id ) && $network_level_or_blog_id > 0 ) {

                    $key .= ":{$network_level_or_blog_id}";

                } else {

                    $network_level_or_blog_id = get_current_blog_id();



                    $key .= ":{$network_level_or_blog_id}";

                }

            }



            if ( ! isset( self::$_instances[ $key ] ) ) {

                self::$_instances[ $key ] = new FS_Admin_Notice_Manager(

                    $id,

                    $title,

                    $module_unique_affix,

                    $is_network_and_blog_admins,

                    $network_level_or_blog_id

                );

            }



            return self::$_instances[ $key ];

        }



        /**

         * @param string $id

         * @param string $title

         * @param string $module_unique_affix

         * @param bool   $is_network_and_blog_admins Whether or not the message should be shown both on network and

         *                                             blog admin pages.

         * @param bool|int $network_level_or_blog_id

         */

        protected function __construct(

            $id,

            $title = '',

            $module_unique_affix = '',

            $is_network_and_blog_admins = false,

            $network_level_or_blog_id = false

        ) {

            $this->_id                  = $id;

            $this->_logger              = FS_Logger::get_logger( WP_FS__SLUG . '_' . $this->_id . '_data', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );

            $this->_title               = ! empty( $title ) ? $title : '';

            $this->_module_unique_affix = $module_unique_affix;

            $this->_sticky_storage      = FS_Key_Value_Storage::instance( 'admin_notices', $this->_id, $network_level_or_blog_id );



            if ( is_multisite() ) {

                $this->_is_network_notices = ( true === $network_level_or_blog_id );



                if ( is_numeric( $network_level_or_blog_id ) ) {

                    $this->_blog_id = $network_level_or_blog_id;

                }

            } else {

                $this->_is_network_notices = false;

            }



            $is_network_admin = fs_is_network_admin();

            $is_blog_admin    = fs_is_blog_admin();



            if ( ( $this->_is_network_notices && $is_network_admin ) ||

                 ( ! $this->_is_network_notices && $is_blog_admin ) ||

                ( $is_network_and_blog_admins && ( $is_network_admin || $is_blog_admin ) )

            ) {

                if ( 0 < count( $this->_sticky_storage ) ) {

                    $ajax_action_suffix = str_replace( ':', '-', $this->_id );



                    // If there are sticky notices for the current slug, add a callback

                    // to the AJAX action that handles message dismiss.

                    add_action( "wp_ajax_fs_dismiss_notice_action_{$ajax_action_suffix}", array(

                        &$this,

                        'dismiss_notice_ajax_callback'

                    ) );



                    foreach ( $this->_sticky_storage as $msg ) {

                        // Add admin notice.

                        $this->add(

                            $msg['message'],

                            $msg['title'],

                            $msg['type'],

                            true,

                            $msg['id'],

                            false,

                            isset( $msg['wp_user_id'] ) ? $msg['wp_user_id'] : null,

                            ! empty( $msg['plugin'] ) ? $msg['plugin'] : null,

                            $is_network_and_blog_admins,

                            isset( $msg['dismissible'] ) ?

                                $msg['dismissible'] :

                                null

                        );

                    }

                }

            }

        }



        /**

         * Remove sticky message by ID.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.7

         *

         */

        function dismiss_notice_ajax_callback() {

            check_admin_referer( 'fs_dismiss_notice_action' );



            if ( ! is_numeric( $_POST['message_id'] ) ) {

                $this->_sticky_storage->remove( $_POST['message_id'] );

            }



            wp_die();

        }



        /**

         * Rendered sticky message dismiss JavaScript.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.7

         */

        static function _add_sticky_dismiss_javascript() {

            $sticky_admin_notice_js_template_name = 'sticky-admin-notice-js.php';



            if ( ! file_exists( fs_get_template_path( $sticky_admin_notice_js_template_name ) ) ) {

                return;

            }



            $params = array();

            fs_require_once_template( $sticky_admin_notice_js_template_name, $params );

        }



        private static $_added_sticky_javascript = false;



        /**

         * Hook to the admin_footer to add sticky message dismiss JavaScript handler.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.7

         */

        private static function has_sticky_messages() {

            if ( ! self::$_added_sticky_javascript ) {

                add_action( 'admin_footer', array( 'FS_Admin_Notice_Manager', '_add_sticky_dismiss_javascript' ) );

            }

        }



        /**

         * Handle admin_notices by printing the admin messages stacked in the queue.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.4

         *

         */

        function _admin_notices_hook() {

            if ( function_exists( 'current_user_can' ) &&

                 ! current_user_can( 'manage_options' )

            ) {

                // Only show messages to admins.

                return;

            }



            foreach ( $this->_notices as $id => $msg ) {

                if ( isset( $msg['wp_user_id'] ) && is_numeric( $msg['wp_user_id'] ) ) {

                    if ( get_current_user_id() != $msg['wp_user_id'] ) {

                        continue;

                    }

                }



                /**

                 * Added a filter to control the visibility of admin notices.

                 *

                 * Usage example:

                 *

                 *     /**

                 *      * @param bool  $show

                 *      * @param array $msg {

                 *      *     @var string $message The actual message.

                 *      *     @var string $title An optional message title.

                 *      *     @var string $type The type of the message ('success', 'update', 'warning', 'promotion').

                 *      *     @var string $id The unique identifier of the message.

                 *      *     @var string $manager_id The unique identifier of the notices manager. For plugins it would be the plugin's slug, for themes - `<slug>-theme`.

                 *      *     @var string $plugin The product's title.

                 *      *     @var string $wp_user_id An optional WP user ID that this admin notice is for.

                 *      * }

                 *      *

                 *      * @return bool

                 *      *\/

                 *      function my_custom_show_admin_notice( $show, $msg ) {

                 *          if ('trial_promotion' != $msg['id']) {

                 *              return false;

                 *          }

                 *

                 *          return $show;

                 *      }

                 *

                 *      my_fs()->add_filter( 'show_admin_notice', 'my_custom_show_admin_notice', 10, 2 );

                 *

                 * @author Vova Feldman

                 * @since 2.2.0

                 */

                $show_notice = call_user_func_array( 'fs_apply_filter', array(

                    $this->_module_unique_affix,

                    'show_admin_notice',

                    $this->show_admin_notices(),

                    $msg

                ) );



                if ( true !== $show_notice ) {

                    continue;

                }



                fs_require_template( 'admin-notice.php', $msg );



                if ( $msg['sticky'] ) {

                    self::has_sticky_messages();

                }

            }

        }



        /**

         * Enqueue common stylesheet to style admin notice.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.7

         */

        function _enqueue_styles() {

            fs_enqueue_local_style( 'fs_common', '/admin/common.css' );

        }



        /**

         * Check if the current page is the Gutenberg block editor.

         *

         * @author Vova Feldman (@svovaf)

         * @since  2.2.3

         *

         * @return bool

         */

        function is_gutenberg_page() {

            if ( function_exists( 'is_gutenberg_page' ) &&

                 is_gutenberg_page()

            ) {

                // The Gutenberg plugin is on.

                return true;

            }



            $current_screen = get_current_screen();



            if ( method_exists( $current_screen, 'is_block_editor' ) &&

                 $current_screen->is_block_editor()

            ) {

                // Gutenberg page on 5+.

                return true;

            }



            return false;

        }



        /**

         * Check if admin notices should be shown on page. E.g., we don't want to show notices in the Visual Editor.

         *

         * @author Xiaheng Chen (@xhchen)

         * @since  2.4.2

         *

         * @return bool

         */

        function show_admin_notices() {

            global $pagenow;



            if ( 'about.php' === $pagenow ) {

                // Don't show admin notices on the About page.

                return false;

            }



            if ( $this->is_gutenberg_page() ) {

                // Don't show admin notices in Gutenberg (visual editor).

                return false;

            }



            return true;

        }



        /**

         * Add admin message to admin messages queue, and hook to admin_notices / all_admin_notices if not yet hooked.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.4

         *

         * @param string      $message

         * @param string      $title

         * @param string      $type

         * @param bool        $is_sticky

         * @param string      $id Message ID

         * @param bool        $store_if_sticky

         * @param number|null $wp_user_id

         * @param string|null $plugin_title

         * @param bool        $is_network_and_blog_admins Whether or not the message should be shown both on network

         *                                                and blog admin pages.

         * @param bool|null   $is_dismissible

         * @param array       $data

         *

         * @uses   add_action()

         */

        function add(

            $message,

            $title = '',

            $type = 'success',

            $is_sticky = false,

            $id = '',

            $store_if_sticky = true,

            $wp_user_id = null,

            $plugin_title = null,

            $is_network_and_blog_admins = false,

            $is_dismissible = null,

            $data = array()

        ) {

            $notices_type = $this->get_notices_type();



            if ( empty( $this->_notices ) ) {

                if ( ! $is_network_and_blog_admins ) {

                    add_action( $notices_type, array( &$this, "_admin_notices_hook" ) );

                } else {

                    add_action( 'network_admin_notices', array( &$this, "_admin_notices_hook" ) );

                    add_action( 'admin_notices', array( &$this, "_admin_notices_hook" ) );

                }



                add_action( 'admin_enqueue_scripts', array( &$this, '_enqueue_styles' ) );

            }



            if ( '' === $id ) {

                $id = md5( $title . ' ' . $message . ' ' . $type );

            }



            $message_object = array(

                'message'     => $message,

                'title'       => $title,

                'type'        => $type,

                'sticky'      => $is_sticky,

                'id'          => $id,

                'manager_id'  => $this->_id,

                'plugin'      => ( ! is_null( $plugin_title ) ? $plugin_title : $this->_title ),

                'wp_user_id'  => $wp_user_id,

                'dismissible' => $is_dismissible,

                'data'        => $data

            );



            if ( $is_sticky && $store_if_sticky ) {

                $this->_sticky_storage->{$id} = $message_object;

            }



            $this->_notices[ $id ] = $message_object;

        }



        /**

         * @author Vova Feldman (@svovaf)

         * @since  1.0.7

         *

         * @param string|string[] $ids

         * @param bool            $store

         */

        function remove_sticky( $ids, $store = true ) {

            if ( ! is_array( $ids ) ) {

                $ids = array( $ids );

            }



            foreach ( $ids as $id ) {

                // Remove from sticky storage.

                $this->_sticky_storage->remove( $id, $store );



                if ( isset( $this->_notices[ $id ] ) ) {

                    unset( $this->_notices[ $id ] );

                }

            }

        }



        /**

         * Check if sticky message exists by id.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.9

         *

         * @param $id

         *

         * @return bool

         */

        function has_sticky( $id ) {

            return isset( $this->_sticky_storage[ $id ] );

        }



        /**

         * Adds sticky admin notification.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.7

         *

         * @param string      $message

         * @param string      $id Message ID

         * @param string      $title

         * @param string      $type

         * @param number|null $wp_user_id

         * @param string|null $plugin_title

         * @param bool        $is_network_and_blog_admins Whether or not the message should be shown both on network

         *                                                and blog admin pages.

         * @param bool        $is_dimissible

         * @param array       $data

         */

        function add_sticky( $message, $id, $title = '', $type = 'success', $wp_user_id = null, $plugin_title = null, $is_network_and_blog_admins = false, $is_dimissible = true, $data = array() ) {

            if ( ! empty( $this->_module_unique_affix ) ) {

                $message = fs_apply_filter( $this->_module_unique_affix, "sticky_message_{$id}", $message );

                $title   = fs_apply_filter( $this->_module_unique_affix, "sticky_title_{$id}", $title );

            }



            $this->add( $message, $title, $type, true, $id, true, $wp_user_id, $plugin_title, $is_network_and_blog_admins, $is_dimissible, $data );

        }



        /**

         * Retrieves the data of an sticky notice.

         *

         * @author Leo Fajardo (@leorw)

         * @since  2.4.3

         *

         * @param string $id Message ID.

         *

         * @return array|null

         */

        function get_sticky( $id ) {

            return isset( $this->_sticky_storage->{$id} ) ?

                $this->_sticky_storage->{$id} :

                null;

        }



        /**

         * Clear all sticky messages.

         *

         * @author Vova Feldman (@svovaf)

         * @since  1.0.8

         *

         * @param bool $is_temporary @since 2.5.1

         */

        function clear_all_sticky( $is_temporary = false ) {

            if ( $is_temporary ) {

                $this->_notices = array();

            } else {

                $this->_sticky_storage->clear_all();

            }

        }



        #--------------------------------------------------------------------------------

        #region Helper Method

        #--------------------------------------------------------------------------------



        /**

         * @author Vova Feldman (@svovaf)

         * @since  2.0.0

         *

         * @return string

         */

        private function get_notices_type() {

            return $this->_is_network_notices ?

                'network_admin_notices' :

                'admin_notices';

        }



        #endregion

    }

