HEX
Server: Apache
System: Linux 244.240.109.208.host.secureserver.net 5.14.0-611.11.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Dec 3 09:47:37 EST 2025 x86_64
User: icsla (1002)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/icsla/public_html/wp-content/plugins/megamenu/classes/admin-notices.class.php
<?php
/**
 * Credit to PolyLang (https://polylang.pro)
 * https://plugins.trac.wordpress.org/browser/polylang/trunk/admin/admin-notices.php
 */

/**
 * A class to manage admin notices, displayed only to admins (based on
 * 'manage_options' capability) and only on dashboard, plugins, and
 * Max Mega Menu admin pages.
 *
 * Integrations may output notices on the {@see 'megamenu_admin_notices'} action
 * and use {@see Mega_Menu_Admin_Notices::output_persistent_dismissible_notice()}
 * with {@see Mega_Menu_Admin_Notices::clear_dismissed_notice()} for dismissal lifecycle.
 *
 * @since   3.0
 * @package MegaMenu
 */
class Mega_Menu_Admin_Notices {

	/**
	 * Stores the plugin options.
	 *
	 * @var array
	 */
	protected $options;

	/**
	 * Stores custom notices.
	 *
	 * @var string[]
	 */
	private static $notices = [];

	/**
	 * Constructor. Sets up actions for hiding and displaying notices.
	 *
	 * @since 3.0
	 */
	public function __construct() {
		add_action( 'admin_init', [ $this, 'hide_notice' ] );
		add_action( 'admin_notices', [ $this, 'display_notices' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_persistent_notice_dismiss_assets' ] );
		add_action( 'wp_ajax_megamenu_dismiss_admin_notice', [ $this, 'ajax_dismiss_admin_notice' ] );
	}

	/**
	 * Add a custom notice.
	 *
	 * @since 3.0
	 * @param string $name Notice name.
	 * @param string $html HTML content of the notice.
	 * @return void
	 */
	public static function add_notice( $name, $html ) {
		self::$notices[ $name ] = $html;
	}

	/**
	 * Get all registered custom notices.
	 *
	 * @since 3.0
	 * @return string[] Map of notice name to HTML content.
	 */
	public static function get_notices() {
		return self::$notices;
	}

	/**
	 * Has a notice been dismissed?
	 *
	 * @since 3.0
	 * @param string $notice Notice name.
	 * @return bool True if the notice has been dismissed, false otherwise.
	 */
	public static function is_dismissed( $notice ) {
		$dismissed = get_option( 'megamenu_dismissed_notices', [] );

		return in_array( $notice, $dismissed, true );
	}

	/**
	 * Should we display notices on this screen?
	 *
	 * @since 3.0
	 * @param  string $notice The notice name.
	 * @return bool True if the notice should be displayed on the current screen.
	 */
	protected function can_display_notice( $notice ) {
		$screen = get_current_screen();

		if ( empty( $screen ) ) {
			return false;
		}

		/**
		 * Filter admin notices which can be displayed
		 *
		 * @since 2.7.0
		 *
		 * @param bool   $display Whether the notice should be displayed or not.
		 * @param string $notice  The notice name.
		 */
		return apply_filters(
			'mmm_can_display_notice',
			in_array(
				$screen->id,
				[
					'dashboard',
					'plugins',
					'toplevel_page_maxmegamenu'
				]
			),
			$notice
		);
	}

	/**
	 * Stores a dismissed notice in the database.
	 *
	 * @since 3.0
	 * @param string $notice Notice name.
	 * @return void
	 */
	public static function dismiss( $notice ) {
		$dismissed = get_option( 'megamenu_dismissed_notices', [] );

		if ( ! in_array( $notice, $dismissed, true ) ) {
			$dismissed[] = $notice;
			update_option( 'megamenu_dismissed_notices', array_unique( $dismissed ) );
		}
	}

	/**
	 * Allowed HTML in optional notice wrappers (`before_content` / `after_paragraph`).
	 *
	 * @since 3.9
	 * @return array<string, array<string, bool>>
	 */
	public static function get_persistent_notice_fragment_kses() {
		$defaults = [
			'div'  => [
				'class' => true,
			],
			'span' => [
				'class'       => true,
				'aria-hidden' => true,
			],
			'p'    => [
				'class' => true,
			],
			'a'    => [
				'href'   => true,
				'class'  => true,
				'target' => true,
				'rel'    => true,
			],
		];

		/**
		 * Filters kses rules for HTML fragments wrapped around persistent notice paragraphs.
		 *
		 * @since 3.9
		 *
		 * @param array<string, array<string, bool>> $defaults Kses rules.
		 */
		return apply_filters( 'megamenu_admin_persistent_notice_fragment_kses', $defaults );
	}

	/**
	 * Enqueue script for AJAX-dismiss of persistent admin notices (review, integrations).
	 *
	 * @since 3.9
	 * @param string $hook Current admin screen hook suffix.
	 * @return void
	 */
	public function enqueue_persistent_notice_dismiss_assets( $hook ) {
		$on_mmm_screen = ( false !== strpos( $hook, 'maxmegamenu' ) );
		$on_review     = in_array( $hook, [ 'index.php', 'plugins.php', 'plugins-network.php' ], true );

		if ( ! $on_mmm_screen && ! $on_review ) {
			return;
		}

		if ( ! current_user_can( 'manage_options' ) && ! current_user_can( apply_filters( 'megamenu_options_capability', 'edit_theme_options' ) ) ) {
			return;
		}

		wp_enqueue_script(
			'megamenu-admin-dismiss-notices',
			plugins_url( 'js/admin/dismiss-notices.js', MEGAMENU_PATH . 'megamenu.php' ),
			[ 'jquery' ],
			defined( 'MEGAMENU_VERSION' ) ? MEGAMENU_VERSION : false,
			true
		);

		wp_localize_script(
			'megamenu-admin-dismiss-notices',
			'megamenuDismissNotices',
			[
				'ajaxUrl' => admin_url( 'admin-ajax.php' ),
			]
		);
	}

	/**
	 * Persist dismissal of a registered admin notice via AJAX.
	 *
	 * @since 3.9
	 * @return void
	 */
	public function ajax_dismiss_admin_notice() {
		check_ajax_referer( 'megamenu_dismiss_admin_notice', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) && ! current_user_can( apply_filters( 'megamenu_options_capability', 'edit_theme_options' ) ) ) {
			wp_send_json_error( null, 403 );
		}

		$notice = isset( $_POST['notice'] ) ? sanitize_key( wp_unslash( $_POST['notice'] ) ) : '';
		if ( '' === $notice ) {
			wp_send_json_error( null, 400 );
		}

		/**
		 * Notice keys allowed to be dismissed via AJAX / GET.
		 *
		 * @since 3.9
		 *
		 * @param string[] $keys Sanitized notice identifiers.
		 */
		$allowed = apply_filters( 'megamenu_dismissible_admin_notice_keys', [ 'review' ] );

		if ( ! is_array( $allowed ) || ! in_array( $notice, array_map( 'sanitize_key', $allowed ), true ) ) {
			wp_send_json_error( null, 400 );
		}

		self::dismiss( $notice );
		wp_send_json_success();
	}

	/**
	 * Handle a click on the dismiss button.
	 *
	 * @since 3.0
	 * @return void
	 */
	public function hide_notice() {
		if ( isset( $_GET['mmm-hide-notice'], $_GET['_mmm_notice_nonce'] ) ) {
			$notice = sanitize_key( wp_unslash( $_GET['mmm-hide-notice'] ) );
			check_admin_referer( $notice, '_mmm_notice_nonce' );
			self::dismiss( $notice );
			wp_safe_redirect( remove_query_arg( [ 'mmm-hide-notice', '_mmm_notice_nonce' ], wp_get_referer() ) );
			exit;
		}
	}

	/**
	 * Displays notices.
	 *
	 * @since 2.3.9
	 * @return void
	 */
	public function display_notices() {
		/**
		 * Fires when Max Mega Menu is about to output admin notices (before the built-in review notice).
		 *
		 * Use {@see Mega_Menu_Admin_Notices::output_persistent_dismissible_notice()} for core-style
		 * dismissible notices that persist dismissal via `megamenu_dismissed_notices`.
		 *
		 * @since 3.9
		 *
		 * @param Mega_Menu_Admin_Notices $admin_notices The notices manager instance.
		 */
		do_action( 'megamenu_admin_notices', $this );

		if ( ! $this->can_display_notice( 'review' ) ) {
			return;
		}

		if ( defined( 'MEGAMENU_PRO_VERSION' ) ) {
			return;
		}

		if ( $this->is_dismissed( 'review' ) ) {
			return;
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$install_date = get_option( 'megamenu_install_date' );

		if ( ! $install_date ) {
			return;
		}

		if ( time() > $install_date + ( 14 * DAY_IN_SECONDS ) ) {
			$this->review_notice();
		}
	}

	/**
	 * Remove a notice slug from `megamenu_dismissed_notices` so it can be shown again.
	 *
	 * @since 3.9
	 * @param string $notice Sanitized notice key (same as `mmm-hide-notice` / {@see dismiss()}).
	 * @return void
	 */
	public static function clear_dismissed_notice( $notice ) {
		$notice    = sanitize_key( $notice );
		$dismissed = get_option( 'megamenu_dismissed_notices', [] );

		if ( ! is_array( $dismissed ) || ! in_array( $notice, $dismissed, true ) ) {
			return;
		}

		$filtered = array_values( array_diff( $dismissed, [ $notice ] ) );

		if ( empty( $filtered ) ) {
			delete_option( 'megamenu_dismissed_notices' );
		} else {
			update_option( 'megamenu_dismissed_notices', $filtered );
		}
	}

	/**
	 * Default allowed inline HTML inside {@see output_persistent_dismissible_notice()} paragraphs.
	 *
	 * @since 3.9
	 * @return array<string, array<string, bool>>
	 */
	public static function get_persistent_notice_paragraph_kses() {
		$defaults = [
			'a'      => [
				'href'   => true,
				'target' => true,
				'rel'    => true,
			],
			'strong' => [],
			'em'     => [],
		];

		/**
		 * Filters allowed HTML inside persistent admin notice `<p>` content.
		 *
		 * @since 3.9
		 *
		 * @param array<string, array<string, bool>> $defaults Kses rules.
		 */
		return apply_filters( 'megamenu_admin_persistent_notice_paragraph_kses', $defaults );
	}

	/**
	 * Core-style dismissible admin notice with AJAX dismiss (persists via `megamenu_dismissed_notices`).
	 * Legacy GET `mmm-hide-notice` URLs are still handled in {@see hide_notice()}.
	 *
	 * @since 3.9
	 * @param string               $notice_type          One of `info`, `warning`, `error`, `success`.
	 * @param string               $paragraph_inner_html Message HTML for inside `<p>` (caller-escaped / trusted for kses).
	 * @param string               $dismiss_key          Notice key for storage / legacy query arg.
	 * @param array<string, mixed> $args {
	 *     Optional. Extra markup and classes.
	 *
	 *     @type string       $before_content     Markup after the opening `.notice` div, before `<p>`.
	 *     @type string       $after_paragraph    Markup after `</p>`, before the dismiss button.
	 *     @type string|array $wrapper_extra_classes Additional classes on the outer `.notice` div.
	 * }
	 * @return void
	 */
	public static function output_persistent_dismissible_notice( $notice_type, $paragraph_inner_html, $dismiss_key, $args = [] ) {
		$args = wp_parse_args(
			$args,
			[
				'before_content'        => '',
				'after_paragraph'       => '',
				'wrapper_extra_classes' => '',
			]
		);

		$allowed_types = [ 'info', 'warning', 'error', 'success' ];
		$notice_type   = sanitize_key( (string) $notice_type );
		if ( ! in_array( $notice_type, $allowed_types, true ) ) {
			$notice_type = 'info';
		}

		$dismiss_key = sanitize_key( $dismiss_key );

		$paragraph_kses = self::get_persistent_notice_paragraph_kses();
		$fragment_kses  = self::get_persistent_notice_fragment_kses();

		/**
		 * Filters whether a persistent admin notice should be printed.
		 *
		 * @since 3.9
		 *
		 * @param bool               $display              Whether to output markup.
		 * @param string             $notice_type          Notice class suffix.
		 * @param string             $dismiss_key          Dismiss / storage key.
		 * @param string             $paragraph_inner_html Inner HTML (escaped per caller).
		 * @param array<string,mixed> $args                Parsed arguments.
		 */
		if ( ! apply_filters( 'megamenu_should_output_persistent_admin_notice', true, $notice_type, $dismiss_key, $paragraph_inner_html, $args ) ) {
			return;
		}

		$extra_classes = $args['wrapper_extra_classes'];
		if ( is_array( $extra_classes ) ) {
			$extra_classes = implode( ' ', array_map( 'sanitize_html_class', $extra_classes ) );
		} else {
			$extra_classes = sanitize_text_field( (string) $extra_classes );
		}

		$classes = trim( 'notice notice-' . $notice_type . ' is-dismissible ' . $extra_classes );
		$nonce   = wp_create_nonce( 'megamenu_dismiss_admin_notice' );

		?>
		<div class="<?php echo esc_attr( $classes ); ?>">
			<?php
			if ( $args['before_content'] ) {
				echo wp_kses( $args['before_content'], $fragment_kses );
			}
			?>
			<p><?php echo wp_kses( $paragraph_inner_html, $paragraph_kses ); ?></p>
			<?php
			if ( $args['after_paragraph'] ) {
				echo wp_kses( $args['after_paragraph'], $fragment_kses );
			}
			?>
			<button
				type="button"
				class="notice-dismiss"
				data-megamenu-dismiss="<?php echo esc_attr( $dismiss_key ); ?>"
				data-megamenu-dismiss-nonce="<?php echo esc_attr( $nonce ); ?>"
			>
				<span class="screen-reader-text"><?php esc_html_e( 'Dismiss this notice.', 'megamenu' ); ?></span>
			</button>
		</div>
		<?php
	}

	/**
	 * Displays a notice asking for a review.
	 *
	 * @since 3.0
	 * @return void
	 */
	private function review_notice() {
		$review_processor = new WP_HTML_Tag_Processor( '<a>' . esc_html__( 'give us a 5 stars rating', 'megamenu' ) . '</a>' );
		if ( $review_processor->next_tag( 'a' ) ) {
			$review_processor->set_attribute( 'href', 'https://wordpress.org/support/plugin/megamenu/reviews/?rate=5#new-post' );
			$review_processor->set_attribute( 'target', '_blank' );
			$review_processor->set_attribute( 'rel', 'noopener noreferrer' );
		}
		$paragraph = sprintf(
			/* translators: %s: link to the plugin review form on WordPress.org */
			esc_html__( 'We have noticed that you have been using Max Mega Menu for some time. We hope you love it, and we would really appreciate it if you would %s.', 'megamenu' ),
			wp_kses(
				$review_processor->get_updated_html(),
				self::get_persistent_notice_paragraph_kses()
			)
		);
		self::output_persistent_dismissible_notice( 'info', $paragraph, 'review', [] );
	}
}