robotstxt-mediaaudit/includes/Core/Plugin.php
2026-08-17 18:51:45 +00:00

300 lines
8.2 KiB
PHP

<?php
/**
* Core plugin bootstrap class.
*
* @package MediaRightsAudit\Core
*/
namespace MediaRightsAudit\Core;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use MediaRightsAudit\Admin\AlertsPage;
use MediaRightsAudit\Admin\AttachmentDetailPage;
use MediaRightsAudit\Admin\AuditPage;
use MediaRightsAudit\Admin\ManagerNotice;
use MediaRightsAudit\Admin\Settings;
use MediaRightsAudit\Admin\ToolsPage;
use MediaRightsAudit\CLI\Command;
use MediaRightsAudit\External\AbstractProvider;
use MediaRightsAudit\External\ExternalScanner;
use MediaRightsAudit\External\GoogleVisionProvider;
use MediaRightsAudit\External\PicDefenseProvider;
use MediaRightsAudit\External\TinEyeProvider;
use MediaRightsAudit\Internal\AttachmentIndexer;
use MediaRightsAudit\Internal\UsageScanner;
use MediaRightsAudit\Privacy\DataEraser;
use MediaRightsAudit\Privacy\DataExporter;
/**
* Registers all hooks and initialises the plugin subsystems.
*/
class Plugin {
/**
* Alerts page controller.
*
* @var AlertsPage
*/
private AlertsPage $alerts_page;
/**
* Settings page handler.
*
* @var Settings
*/
private Settings $settings;
/**
* Audit list page controller.
*
* @var AuditPage
*/
private AuditPage $audit_page;
/**
* Tools & Status page controller.
*
* @var ToolsPage
*/
private ToolsPage $tools_page;
/**
* Attachment detail page controller.
*
* @var AttachmentDetailPage
*/
private AttachmentDetailPage $detail_page;
/**
* Initialises dependencies.
*/
public function __construct() {
$this->alerts_page = new AlertsPage();
$this->settings = new Settings();
$this->audit_page = new AuditPage();
$this->tools_page = new ToolsPage();
$this->detail_page = new AttachmentDetailPage();
}
/**
* Attaches all WordPress hooks.
*
* @return void
*/
public function run(): void {
add_action( 'init', array( $this, 'load_textdomain' ) );
add_action( 'admin_menu', array( $this, 'register_admin_pages' ) );
add_action( 'admin_init', array( $this->settings, 'register' ) );
add_action( 'admin_init', array( $this, 'check_db_version' ) );
add_action( AttachmentIndexer::AS_HOOK, array( AttachmentIndexer::class, 'process_scheduled_batch' ) );
add_action( UsageScanner::AS_HOOK, array( UsageScanner::class, 'process_scheduled_batch' ) );
add_action( ExternalScanner::AS_HOOK, array( ExternalScanner::class, 'process_scheduled_batch' ) );
add_action( 'wp_ajax_mra_run_batch', array( $this->tools_page, 'ajax_run_batch' ) );
add_filter( 'mra/external/providers', array( $this, 'register_providers' ) );
add_filter( 'wp_privacy_personal_data_exporters', array( DataExporter::class, 'register' ) );
add_filter( 'wp_privacy_personal_data_erasers', array( DataEraser::class, 'register' ) );
ManagerNotice::register_hooks();
$this->audit_page->register_hooks();
$this->register_cli_commands();
}
/**
* Loads the plugin text domain.
*
* @return void
*/
public function load_textdomain(): void {
load_plugin_textdomain(
'robotstxt-mediaaudit',
false,
dirname( plugin_basename( ROBOTSTXT_MEDIAAUDIT_PLUGIN_FILE ) ) . '/languages'
);
}
/**
* Registers admin menu pages.
*
* @return void
*/
public function register_admin_pages(): void {
// In central mode, replace the full admin UI with a placeholder notice.
if ( is_multisite() && 'central' === get_site_option( 'robotstxt_mediaaudit_network_mode', 'per_site' ) ) {
add_menu_page(
__( 'Media Audit', 'robotstxt-mediaaudit' ),
__( 'Media Audit', 'robotstxt-mediaaudit' ),
'edit_others_posts',
'robotstxt-mediaaudit',
array( $this, 'render_central_mode_notice' ),
'dashicons-camera',
11
);
return;
}
$suffix = add_menu_page(
__( 'Media Audit', 'robotstxt-mediaaudit' ),
__( 'Media Audit', 'robotstxt-mediaaudit' ),
'edit_others_posts',
'robotstxt-mediaaudit',
array( $this->audit_page, 'render' ),
'dashicons-camera',
11
);
$this->audit_page->set_hook_suffix( $suffix );
// Rename the auto-created first submenu entry.
add_submenu_page(
'robotstxt-mediaaudit',
__( 'Media Audit', 'robotstxt-mediaaudit' ),
__( 'Media Audit', 'robotstxt-mediaaudit' ),
'edit_others_posts',
'robotstxt-mediaaudit',
array( $this->audit_page, 'render' )
);
$alerts_suffix = add_submenu_page(
'robotstxt-mediaaudit',
__( 'Alerts', 'robotstxt-mediaaudit' ),
__( 'Alerts', 'robotstxt-mediaaudit' ),
'edit_others_posts',
'robotstxt-mediaaudit-alerts',
array( $this->alerts_page, 'render' )
);
if ( is_string( $alerts_suffix ) ) {
$this->alerts_page->set_hook_suffix( $alerts_suffix );
}
$tools_suffix = add_submenu_page(
'robotstxt-mediaaudit',
__( 'Tools', 'robotstxt-mediaaudit' ),
__( 'Tools', 'robotstxt-mediaaudit' ),
'manage_options',
'robotstxt-mediaaudit-tools',
array( $this->tools_page, 'render' )
);
if ( is_string( $tools_suffix ) ) {
$this->tools_page->set_hook_suffix( $tools_suffix );
}
add_submenu_page(
'robotstxt-mediaaudit',
__( 'Settings', 'robotstxt-mediaaudit' ),
__( 'Settings', 'robotstxt-mediaaudit' ),
'manage_options',
'robotstxt-mediaaudit-settings',
array( $this->settings, 'render' )
);
add_submenu_page(
'', // hidden from menu.
__( 'Attachment Report', 'robotstxt-mediaaudit' ),
'',
'edit_posts',
'robotstxt-mediaaudit-detail',
array( $this->detail_page, 'render' )
);
}
/**
* Runs pending DB migrations when the stored schema version is behind the constant.
*
* @return void
*/
public function check_db_version(): void {
$raw = get_option( 'robotstxt_mediaaudit_db_version', '0.0.0' );
$stored = is_string( $raw ) ? $raw : '0.0.0';
if ( ROBOTSTXT_MEDIAAUDIT_DB_VERSION !== $stored ) {
Database::run_migrations();
update_option( 'robotstxt_mediaaudit_db_version', ROBOTSTXT_MEDIAAUDIT_DB_VERSION );
}
}
/**
* Registers built-in external providers via the mra/external/providers filter.
*
* @param mixed $providers Accumulated provider list.
*
* @return array<\MediaRightsAudit\External\AbstractProvider>
*/
public function register_providers( $providers ): array {
$list = array();
if ( is_array( $providers ) ) {
foreach ( $providers as $p ) {
if ( $p instanceof AbstractProvider ) {
$list[] = $p;
}
}
}
$list[] = new GoogleVisionProvider();
$list[] = new TinEyeProvider();
if ( $this->is_provider_configured( 'picdefense' ) ) {
$list[] = new PicDefenseProvider();
}
return $list;
}
/**
* Returns whether a provider has its credentials configured.
*
* Checks provider-specific option keys in the plugin settings.
*
* @param string $slug Provider slug (e.g. 'picdefense').
*
* @return bool
*/
private function is_provider_configured( string $slug ): bool {
$opts = Settings::get_effective_settings();
if ( 'picdefense' === $slug ) {
$uid = $opts['picdefense_user_id'] ?? '';
$key = $opts['picdefense_api_key'] ?? '';
return is_string( $uid ) && '' !== trim( $uid ) && is_string( $key ) && '' !== trim( $key );
}
return false;
}
/**
* Renders the central-mode placeholder shown on each site's admin pages.
*
* Replaces the full per-site admin UI when the network is operating in
* central mode, directing users to the network admin panel instead.
*
* @return void
*/
public function render_central_mode_notice(): void {
$network_url = network_admin_url( 'admin.php?page=robotstxt-mediaaudit-network' );
?>
<div class="wrap">
<h1><?php esc_html_e( 'Media Audit', 'robotstxt-mediaaudit' ); ?></h1>
<div class="notice notice-info" style="margin-top:1em;">
<p>
<?php esc_html_e( 'This plugin is managed at the network level. Media data for this site is collected and stored here, but viewed and managed from the Network Admin panel.', 'robotstxt-mediaaudit' ); ?>
</p>
<p>
<a href="<?php echo esc_url( $network_url ); ?>" class="button button-primary">
<?php esc_html_e( 'Go to Network Media Audit →', 'robotstxt-mediaaudit' ); ?>
</a>
</p>
</div>
</div>
<?php
}
/**
* Registers WP-CLI commands when running in CLI context.
*
* @return void
*/
private function register_cli_commands(): void {
if ( defined( 'WP_CLI' ) && WP_CLI ) {
\WP_CLI::add_command( 'mra', Command::class );
}
}
}