This commit is contained in:
Javier Casares 2026-08-17 18:51:45 +00:00
commit 7f89e98bbc
18 changed files with 2227 additions and 1853 deletions

View file

@ -0,0 +1,209 @@
<?php
/**
* Manager plugin dependency notice.
*
* Media Audit ships without a self-updater; updates are delivered by the
* Manager (by ROBOTSTXT) plugin. This component nudges the administrator
* when Manager is missing: a dismissible notice on the Plugins page and a
* permanent inline notice on the plugin Settings pages.
*
* @package MediaRightsAudit\Admin
*/
namespace MediaRightsAudit\Admin;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Shows update-manager dependency notices.
*/
final class ManagerNotice {
/**
* Manager plugin folder slug.
*/
public const MANAGER_SLUG = 'robotstxt-manager';
/**
* Manager plugin page URL.
*/
public const MANAGER_URL = 'https://www.robotstxt.software/plugins/robotstxt-manager/';
/**
* User-meta key that records a dismissed notice (per user).
*/
private const DISMISS_KEY = 'robotstxt_mediaaudit_manager_notice_dismissed';
/**
* GET argument used by the dismiss link.
*/
private const DISMISS_ARG = 'robotstxt_mediaaudit_dismiss_manager_notice';
/**
* Nonce action for the dismiss link.
*/
private const DISMISS_ACTION = 'robotstxt_mediaaudit_dismiss_manager_notice';
/**
* Registers the admin hooks (site and network admin).
*
* @return void
*/
public static function register_hooks(): void {
add_action( 'admin_notices', array( __CLASS__, 'render_plugins_page_notice' ) );
add_action( 'network_admin_notices', array( __CLASS__, 'render_plugins_page_notice' ) );
add_action( 'admin_init', array( __CLASS__, 'handle_dismiss' ) );
}
/**
* Indicates whether the Manager plugin is installed and active.
*
* Checks site-level and network-wide activation, plus a filter so the
* Manager itself can report its own state.
*
* @return bool True when Manager is active.
*/
public static function is_manager_active(): bool {
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if ( ! function_exists( 'is_plugin_active' ) ) {
return false;
}
$active = false;
// Canonical main file first (fast path).
$basename = self::MANAGER_SLUG . '/' . self::MANAGER_SLUG . '.php';
if ( is_plugin_active( $basename ) || is_plugin_active_for_network( $basename ) ) {
$active = true;
}
// Cover any other main-file name inside the canonical folder.
if ( ! $active && function_exists( 'get_plugins' ) ) {
foreach ( array_keys( get_plugins( '/' . self::MANAGER_SLUG ) ) as $file ) {
if ( is_plugin_active( self::MANAGER_SLUG . '/' . $file ) ) {
$active = true;
break;
}
}
}
/**
* Filters whether the Manager (by ROBOTSTXT) plugin is active.
*
* @param bool $active Whether Manager is detected as active.
*/
return (bool) apply_filters( 'robotstxt_mediaaudit_manager_active', $active );
}
/**
* Renders the dismissible notice on the Plugins page when Manager is missing.
*
* @return void
*/
public static function render_plugins_page_notice(): void {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( ! $screen instanceof \WP_Screen || ! in_array( $screen->base, array( 'plugins', 'plugins-network' ), true ) ) {
return;
}
if ( self::is_manager_active() ) {
return;
}
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
if ( '1' === get_user_meta( get_current_user_id(), self::DISMISS_KEY, true ) ) {
return;
}
$dismiss_url = wp_nonce_url(
add_query_arg( self::DISMISS_ARG, 1 ),
self::DISMISS_ACTION
);
echo '<div class="notice notice-warning"><p>'
. wp_kses( self::message(), array( 'a' => array( 'href' => true ) ) )
. ' <a href="' . esc_url( $dismiss_url ) . '" class="button button-small">' . esc_html__( 'Dismiss', 'robotstxt-mediaaudit' ) . '</a>'
. '</p></div>';
}
/**
* Renders the permanent inline notice for the Settings pages (not dismissible).
*
* Used on both the site Settings page and the Network Settings page.
*
* @return void
*/
public static function render_settings_notice(): void {
if ( self::is_manager_active() ) {
return;
}
echo '<div class="notice notice-warning inline" style="margin:16px 0;">'
. '<p>' . wp_kses( self::message(), array( 'a' => array( 'href' => true ) ) ) . '</p>'
. '</div>';
}
/**
* Handles the dismiss link: verifies the nonce, stores the dismissal (PRG).
*
* Also clears a stored dismissal once Manager is active again, so the
* notice returns if Manager is ever removed.
*
* @return void
*/
public static function handle_dismiss(): void {
$user_id = get_current_user_id();
$dismissed = $user_id && get_user_meta( $user_id, self::DISMISS_KEY, true );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- presence check only; state changes require the nonce below.
if ( ! isset( $_GET[ self::DISMISS_ARG ] ) && ! $dismissed ) {
return;
}
if ( self::is_manager_active() ) {
if ( $dismissed ) {
delete_user_meta( $user_id, self::DISMISS_KEY );
}
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no state change without the nonce check below.
if ( ! isset( $_GET[ self::DISMISS_ARG ] ) ) {
return;
}
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
check_admin_referer( self::DISMISS_ACTION );
update_user_meta( $user_id, self::DISMISS_KEY, '1' );
wp_safe_redirect( remove_query_arg( array( self::DISMISS_ARG, '_wpnonce' ) ) );
exit;
}
/**
* Builds the shared notice message (translation with a link placeholder).
*
* The result must be passed through wp_kses() at output time.
*
* @return string Unescaped message HTML.
*/
private static function message(): string {
return sprintf(
/* translators: %s: Manager plugin page URL. */
__( 'To receive plugin updates, the plugin <a href="%s">Manager (by ROBOTSTXT)</a> must be installed and active.', 'robotstxt-mediaaudit' ),
esc_url( self::MANAGER_URL )
);
}
}