HEX
Server: LiteSpeed
System: Linux cde4.duelhost.dk 4.18.0-553.34.1.lve.el8.x86_64 #1 SMP Thu Jan 9 16:30:32 UTC 2025 x86_64
User: dmhkjfau (1027)
PHP: 7.4.33
Disabled: exec,system,passthru,shell_exec,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/dmhkjfau/public_html/wp-content/plugins/wp-seopress/src/Actions/Admin/ModuleAdminHeader.php
<?php // phpcs:ignore

namespace SEOPress\Actions\Admin;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use SEOPress\Core\Hooks\ExecuteHooks;

/**
 * Enqueue the React-powered SEOPress admin header on every SEOPress page.
 *
 * Replaces the markup previously echoed by seopress_admin_header(). The PHP
 * function now outputs only the mount point and a hidden payload; this
 * module ships the React bundle that renders the breadcrumb, activity panel
 * trigger, and slide-in drawer (Help / Display / Notifications).
 *
 * @since 9.9.0
 */
class ModuleAdminHeader implements ExecuteHooks {

	/**
	 * Register hooks.
	 *
	 * @return void
	 */
	public function hooks() {
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
	}

	/**
	 * Enqueue the React admin header bundle on every SEOPress admin page.
	 *
	 * @return void
	 */
	public function enqueue() {
		$page = $this->getCurrentSeopressPage();
		if ( ! $page ) {
			return;
		}

		// White-label admin header hides the whole chrome.
		if ( defined( 'SEOPRESS_WL_ADMIN_HEADER' ) && false === SEOPRESS_WL_ADMIN_HEADER ) {
			return;
		}

		$asset_file = SEOPRESS_PLUGIN_DIR_PATH . 'public/admin/header.asset.php';
		$asset      = file_exists( $asset_file ) ? require $asset_file : array(
			'dependencies' => array( 'react', 'react-dom', 'wp-components', 'wp-element', 'wp-i18n' ),
			'version'      => SEOPRESS_VERSION,
		);

		wp_enqueue_style( 'wp-components' );

		$css_file = SEOPRESS_PLUGIN_DIR_PATH . 'public/admin/header.css';
		if ( file_exists( $css_file ) ) {
			$css_version = SEOPRESS_VERSION;
			if ( '{VERSION}' === $css_version || empty( $css_version ) ) {
				$css_version = substr( md5_file( $css_file ), 0, 12 );
			}

			wp_enqueue_style(
				'seopress-admin-header',
				SEOPRESS_URL_PUBLIC . '/admin/header.css',
				array( 'wp-components' ),
				$css_version
			);
		}

		wp_enqueue_script(
			'seopress-admin-header',
			SEOPRESS_URL_PUBLIC . '/admin/header.js',
			$asset['dependencies'],
			$asset['version'],
			true
		);

		wp_set_script_translations( 'seopress-admin-header', 'wp-seopress', WP_LANG_DIR . '/plugins' );

		wp_localize_script(
			'seopress-admin-header',
			'SEOPRESS_HEADER_DATA',
			array(
				'REST_URL'      => rest_url(),
				'NONCE'         => wp_create_nonce( 'wp_rest' ),
				'ADMIN_URL'     => admin_url(),
				'ASSETS_URL'    => SEOPRESS_URL_PUBLIC,
				'PLUGIN_URL'    => SEOPRESS_PLUGIN_DIR_URL,
				'BREADCRUMB'    => $this->getBreadcrumb( $page ),
				'IS_DASHBOARD'  => 'seopress-option' === $page,
				'HELP'          => $this->getHelpPayload(),
				'DISPLAY'       => 'seopress-option' === $page ? $this->getDisplayPayload() : null,
				'NOTIFICATIONS' => $this->getNotificationsPayload(),
				'TOP_BANNER'    => $this->getTopBannerPayload(),
			)
		);
	}

	/**
	 * Get the current SEOPress admin page slug, or false when not on one.
	 *
	 * @return string|false
	 */
	private function getCurrentSeopressPage() {
		if ( ! is_admin() ) {
			return false;
		}

		if ( ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			return false;
		}

		$page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( '' === $page || false === strpos( $page, 'seopress' ) ) {
			return false;
		}

		return $page;
	}

	/**
	 * Build the breadcrumb payload — "SEO / <page title>".
	 *
	 * @param string $page Current page slug.
	 * @return array
	 */
	private function getBreadcrumb( $page ) {
		$page_title = get_admin_page_title();

		return array(
			'rootLabel'   => __( 'SEO', 'wp-seopress' ),
			'rootUrl'     => admin_url( 'admin.php?page=seopress-option' ),
			'currentPage' => is_string( $page_title ) ? $page_title : '',
		);
	}

	/**
	 * Help panel payload — docs links, products and support.
	 *
	 * Mirrors the data the legacy admin-header used to render in PHP.
	 *
	 * @return array
	 */
	private function getHelpPayload() {
		// White-label "remove help links" (PRO): legacy hid .seopress-help /
		// .seopress-doc via CSS, which doesn't match the React header. Mirror
		// the option here so the Help button + panel are dropped entirely.
		if ( is_plugin_active( 'wp-seopress-pro/seopress-pro.php' )
			&& function_exists( 'seopress_pro_get_service' )
			&& method_exists( seopress_pro_get_service( 'OptionPro' ), 'getWhiteLabelHelpLinks' )
			&& '1' === seopress_pro_get_service( 'OptionPro' )->getWhiteLabelHelpLinks() ) {
			return null;
		}

		$docs = function_exists( 'seopress_get_docs_links' ) ? seopress_get_docs_links() : array();

		$guides = array();
		if ( ! empty( $docs['get_started'] ) && is_array( $docs['get_started'] ) ) {
			foreach ( $docs['get_started'] as $guide ) {
				if ( empty( $guide['link'] ) || empty( $guide['title'] ) || empty( $guide['desc'] ) || empty( $guide['ico'] ) ) {
					continue;
				}
				$guides[] = array(
					'iconUrl' => SEOPRESS_ASSETS_DIR . '/img/' . $guide['ico'] . '.svg',
					'title'   => (string) $guide['title'],
					'desc'    => (string) $guide['desc'],
					'link'    => (string) $guide['link'],
				);
			}
		}

		return array(
			'searchUrl'       => isset( $docs['website'] ) ? (string) $docs['website'] : '',
			'guides'          => $guides,
			'products'        => array(
				array(
					'label'   => 'SEOPress Free',
					'url'     => isset( $docs['support-free'] ) ? (string) $docs['support-free'] : '',
					'logoUrl' => SEOPRESS_ASSETS_DIR . '/img/support-seopress-free.svg',
				),
				array(
					'label'   => 'SEOPress PRO',
					'url'     => isset( $docs['support-pro'] ) ? (string) $docs['support-pro'] : '',
					'logoUrl' => SEOPRESS_ASSETS_DIR . '/img/support-seopress-pro.svg',
				),
				array(
					'label'   => 'SEOPress Insights',
					'url'     => isset( $docs['support-insights'] ) ? (string) $docs['support-insights'] : '',
					'logoUrl' => SEOPRESS_ASSETS_DIR . '/img/support-seopress-insights.svg',
				),
			),
			'translationsUrl' => isset( $docs['i18n'] ) ? (string) $docs['i18n'] : '',
			'updatesUrl'      => admin_url( 'update-core.php' ),
			'supportUrl'      => isset( $docs['support-tickets'] ) ? (string) $docs['support-tickets'] : '',
		);
	}

	/**
	 * Display panel payload — current state of the six dashboard
	 * "Hide ..." toggles plus a REST nonce.
	 *
	 * @return array
	 */
	private function getDisplayPayload() {
		$notice_option = seopress_get_service( 'NoticeOption' );
		$advanced      = get_option( 'seopress_advanced_option_name' );
		if ( ! is_array( $advanced ) ) {
			$advanced = array();
		}

		$bool = static function ( $value ) {
			return '1' === (string) $value;
		};

		return array(
			'toggles' => array(
				array(
					'key'     => 'notice-get-started',
					'label'   => __( 'Hide Get started?', 'wp-seopress' ),
					'checked' => $bool( $notice_option->getNoticeGetStarted() ),
				),
				array(
					'key'     => 'notice-tasks',
					'label'   => __( 'Hide SEO Suite?', 'wp-seopress' ),
					'checked' => $bool( $notice_option->getNoticeTasks() ),
				),
				array(
					'key'     => 'seopress-advanced-seo-tools',
					'label'   => __( 'Hide Site Overview?', 'wp-seopress' ),
					'checked' => $bool( isset( $advanced['seopress_advanced_appearance_seo_tools'] ) ? $advanced['seopress_advanced_appearance_seo_tools'] : '' ),
				),
				array(
					'key'     => 'seopress-advanced-notifications',
					'label'   => __( 'Hide Notifications Center?', 'wp-seopress' ),
					'checked' => $bool( isset( $advanced['seopress_advanced_appearance_notifications'] ) ? $advanced['seopress_advanced_appearance_notifications'] : '' ),
				),
				array(
					'key'     => 'notice-integrations',
					'label'   => __( 'Hide Integrations?', 'wp-seopress' ),
					'checked' => $bool( $notice_option->getNoticeIntegrations() ),
				),
			),
		);
	}

	/**
	 * Top banner promotion payload.
	 *
	 * Mirrors inc/admin/promotions/top-banner.php. The promotion comes from
	 * PromotionService->getPromotion('top_banner'). Returns null when there
	 * is nothing to render (white-label, no promo, or service missing).
	 *
	 * @return array|null
	 */
	private function getTopBannerPayload() {
		if ( is_plugin_active( 'wp-seopress-pro/seopress-pro.php' ) ) {
			$toggle_service = seopress_get_service( 'ToggleOption' );
			if ( method_exists( $toggle_service, 'getToggleWhiteLabel' ) && '1' === $toggle_service->getToggleWhiteLabel() ) {
				return null;
			}
		}

		$service = seopress_get_service( 'PromotionService' );
		if ( ! method_exists( $service, 'getPromotion' ) ) {
			return null;
		}

		$promotion = $service->getPromotion( 'top_banner' );
		if ( ! $promotion ) {
			return null;
		}

		$content = isset( $promotion['content'] ) && is_array( $promotion['content'] ) ? $promotion['content'] : array();

		return array(
			'id'              => isset( $promotion['id'] ) ? (string) $promotion['id'] : '',
			'icon'            => isset( $content['icon'] ) ? (string) $content['icon'] : '',
			'title'           => isset( $content['title'] ) ? (string) $content['title'] : '',
			'body'            => isset( $content['body'] ) ? (string) $content['body'] : '',
			'ctaUrl'          => isset( $content['cta_url'] ) ? (string) $content['cta_url'] : '',
			'ctaText'         => isset( $content['cta_text'] ) ? (string) $content['cta_text'] : '',
			'buttonStyle'     => isset( $promotion['styling']['button_style'] ) ? (string) $promotion['styling']['button_style'] : 'primary',
			'dismissible'     => ! empty( $promotion['dismissible'] ),
			'dismissDuration' => isset( $promotion['dismiss_duration_days'] ) ? (int) $promotion['dismiss_duration_days'] : 30,
			'dismissNonce'    => wp_create_nonce( 'seopress_dismiss_promotion_nonce' ),
			'ajaxUrl'         => admin_url( 'admin-ajax.php' ),
		);
	}

	/**
	 * Notifications panel payload.
	 *
	 * Renders each notification server-side via the existing
	 * Notifications->renderNotification() so the rich HTML (icons, CTAs,
	 * severity badges) is preserved without re-implementing the renderer in
	 * React. The React panel injects the strings through
	 * dangerouslySetInnerHTML.
	 *
	 * @return array
	 */
	private function getNotificationsPayload() {
		$service = seopress_get_service( 'Notifications' );

		$severity = $service->getSeverityNotification( 'all' );
		$total    = isset( $severity['total'] ) ? (int) $severity['total'] : 0;

		$items = method_exists( $service, 'generateAllNotifications' ) ? $service->generateAllNotifications() : array();
		$items = method_exists( $service, 'orderByImpact' ) ? $service->orderByImpact( $items ) : $items;

		$active = array();
		$hidden = array();
		if ( is_array( $items ) ) {
			foreach ( $items as $key => $item ) {
				// generateAllNotifications() appends an `impact` summary
				// (counts per severity) at the end of the array. It is not
				// a notification — skip it so it doesn't get rendered as an
				// empty card and inflate the hidden count by one.
				if ( 'impact' === $key ) {
					continue;
				}
				if ( ! is_array( $item ) ) {
					continue;
				}
				if ( empty( $item['title'] ) ) {
					continue;
				}
				$html = method_exists( $service, 'renderNotification' ) ? (string) $service->renderNotification( $item ) : '';
				if ( '' === $html ) {
					continue;
				}
				if ( ! empty( $item['status'] ) ) {
					$active[] = $html;
				} else {
					$hidden[] = $html;
				}
			}
		}

		return array(
			'total'         => $total,
			'activeItems'   => $active,
			'hiddenItems'   => $hidden,
			'emptyIconUrl'  => SEOPRESS_ASSETS_DIR . '/img/ico-notifications.svg',
			'hiddenIconUrl' => SEOPRESS_ASSETS_DIR . '/img/ico-notifications-hidden.svg',
			'hideNonce'     => wp_create_nonce( 'seopress_hide_notices_nonce' ),
			'ajaxUrl'       => admin_url( 'admin-ajax.php' ),
		);
	}
}