job_store = $job_store;
}
/**
* Add the single-site Tools page.
*/
public function register_site_menu() {
if ( is_multisite() ) {
return;
}
$hook = add_management_page(
__( '1Gbits Site Move Inspector', '1gbits-site-move-inspector' ),
__( 'Site Move Inspector', '1gbits-site-move-inspector' ),
'manage_options',
self::PAGE_SLUG,
array( $this, 'render_page' )
);
if ( is_string( $hook ) ) {
$this->page_hooks[] = $hook;
}
}
/**
* Add the multisite Network Settings page.
*/
public function register_network_menu() {
if ( ! is_multisite() ) {
return;
}
$hook = add_submenu_page(
'settings.php',
__( '1Gbits Site Move Inspector', '1gbits-site-move-inspector' ),
__( 'Site Move Inspector', '1gbits-site-move-inspector' ),
'manage_network_options',
self::PAGE_SLUG,
array( $this, 'render_page' )
);
if ( is_string( $hook ) ) {
$this->page_hooks[] = $hook;
}
}
/**
* Load local assets only on this plugin's page.
*
* @param string $hook_suffix Current admin hook.
*/
public function enqueue_assets( $hook_suffix ) {
if ( ! in_array( $hook_suffix, $this->page_hooks, true ) ) {
return;
}
wp_enqueue_style(
'ogsmi-admin',
OGSMI_PLUGIN_URL . 'assets/admin.css',
array(),
OGSMI_VERSION
);
wp_enqueue_script(
'ogsmi-admin',
OGSMI_PLUGIN_URL . 'assets/admin.js',
array(),
OGSMI_VERSION,
true
);
wp_localize_script(
'ogsmi-admin',
'ogsmiAdmin',
array(
'restRoot' => esc_url_raw( rest_url( OGSMI_REST_Controller::REST_NAMESPACE ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'activeJob' => $this->job_store->get_active_id( get_current_user_id() ),
'strings' => array(
'starting' => __( 'Preparing the inspection…', '1gbits-site-move-inspector' ),
'scanning' => __( 'Scanning filesystem metadata…', '1gbits-site-move-inspector' ),
'complete' => __( 'Inspection complete. Loading the report…', '1gbits-site-move-inspector' ),
'canceling' => __( 'Canceling the inspection…', '1gbits-site-move-inspector' ),
'canceled' => __( 'Inspection canceled. No site data was changed.', '1gbits-site-move-inspector' ),
'genericError' => __( 'The inspection could not continue. No site data was changed.', '1gbits-site-move-inspector' ),
/* translators: %s: number of files scanned. */
'filesScanned' => __( 'Files scanned: %s', '1gbits-site-move-inspector' ),
/* translators: %s: human-readable number of bytes scanned. */
'bytesScanned' => __( '%s scanned', '1gbits-site-move-inspector' ),
),
)
);
}
/**
* Render the scan form and latest short-lived report.
*/
public function render_page() {
if ( ! $this->current_user_can_scan() ) {
wp_die( esc_html__( 'You are not allowed to view this page.', '1gbits-site-move-inspector' ) );
}
$user_id = get_current_user_id();
$active_job = $this->job_store->get_active_id( $user_id );
$report = $this->job_store->get_latest_report( $user_id );
?>
render_report( $report ); ?>
current_user_can_scan() ) {
wp_die(
esc_html__( 'You are not allowed to export this report.', '1gbits-site-move-inspector' ),
esc_html__( 'Export denied', '1gbits-site-move-inspector' ),
array( 'response' => 403 )
);
}
check_admin_referer( 'ogsmi_export_report' );
$format = isset( $_POST['format'] ) ? sanitize_key( wp_unslash( $_POST['format'] ) ) : '';
if ( ! in_array( $format, array( 'json', 'txt' ), true ) ) {
wp_die(
esc_html__( 'The requested export format is not supported.', '1gbits-site-move-inspector' ),
esc_html__( 'Invalid export', '1gbits-site-move-inspector' ),
array( 'response' => 400 )
);
}
$report = $this->job_store->get_latest_report( get_current_user_id() );
if ( ! is_array( $report ) ) {
wp_die(
esc_html__( 'The temporary report expired. Run a new inspection.', '1gbits-site-move-inspector' ),
esc_html__( 'Report expired', '1gbits-site-move-inspector' ),
array( 'response' => 404 )
);
}
if ( 'json' === $format ) {
$content_type = 'application/json; charset=utf-8';
$content = OGSMI_Exporter::to_json( $report );
} else {
$content_type = 'text/plain; charset=utf-8';
$content = OGSMI_Exporter::to_text( $report );
}
$filename = sprintf( '1gbits-site-move-report-%s.%s', gmdate( 'Ymd-His' ), $format );
nocache_headers();
header( 'Content-Type: ' . $content_type );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
header( 'X-Content-Type-Options: nosniff' );
header( 'Content-Length: ' . strlen( $content ) );
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Purpose-built plain text or JSON download.
echo $content;
exit;
}
/**
* Render the report summary, checks, and admin-only inventory.
*
* @param array $report Internal report.
*/
private function render_report( array $report ) {
$summary = $report['summary'] ?? OGSMI_Report_Builder::summarize( $report );
$overall = $summary['overall'] ?? 'review_recommended';
$status = $this->overall_label( $overall );
$time = strtotime( $report['generated_at'] ?? '' );
?>
__( 'Critical', '1gbits-site-move-inspector' ),
'warning' => __( 'Warnings', '1gbits-site-move-inspector' ),
'unknown' => __( 'Unknown', '1gbits-site-move-inspector' ),
'pass' => __( 'Passed', '1gbits-site-move-inspector' ),
) as $key => $label
) :
?>
render_section( $section ); ?>
render_inventory( $report['inventory'] ?? array() ); ?>
render_exports(); ?>
check_status_label( $status ) ); ?>
__( 'Download TXT', '1gbits-site-move-inspector' ),
'json' => __( 'Download JSON', '1gbits-site-move-inspector' ),
) as $format => $label ) :
?>
__( 'High risk', '1gbits-site-move-inspector' ),
'review_recommended' => __( 'Review recommended', '1gbits-site-move-inspector' ),
'no_blockers' => __( 'No blockers detected', '1gbits-site-move-inspector' ),
);
return $labels[ $overall ] ?? $labels['review_recommended'];
}
/**
* Return a translatable check status label.
*
* @param string $status Status key.
* @return string
*/
private function check_status_label( $status ) {
$labels = array(
'pass' => __( 'Pass', '1gbits-site-move-inspector' ),
'warning' => __( 'Warning', '1gbits-site-move-inspector' ),
'critical' => __( 'Critical', '1gbits-site-move-inspector' ),
'unknown' => __( 'Unknown', '1gbits-site-move-inspector' ),
'not_applicable' => __( 'Not applicable', '1gbits-site-move-inspector' ),
);
return $labels[ $status ] ?? $labels['unknown'];
}
/**
* Apply site/network authorization consistently.
*
* @return bool
*/
private function current_user_can_scan() {
if ( is_multisite() ) {
return is_super_admin() && current_user_can( 'manage_network_options' );
}
return current_user_can( 'manage_options' );
}
}