hook_suffix = add_menu_page(
__( 'Aardwolf Security Scanner', 'aardwolf-security-scanner' ),
__( 'Security Scanner', 'aardwolf-security-scanner' ),
AARDWOLF_SC_CAPABILITY,
'aardwolf-security-scanner',
array( $this, 'render_page' ),
self::menu_icon(),
81
);
}
/**
* Map a logo variant key to its official brand asset filename.
*
* @return array
*/
protected static function logo_files() {
return array(
'horizontal-blue' => 'aardwolf-full-logo-horizontal-blue.svg',
'horizontal-white' => 'aardwolf-full-logo-horizontal-white.svg',
'vertical-blue' => 'aardwolf-full-logo-vertical-blue.svg',
'vertical-white' => 'aardwolf-full-logo-vertical-white.svg',
'mark-blue' => 'aardwolf-mark-blue.svg',
'mark-white' => 'aardwolf-mark-white.svg',
);
}
/**
* URL of an official Aardwolf Security logo asset.
*
* @param string $variant One of the logo_files() keys.
* @return string
*/
public static function logo_url( $variant ) {
$files = self::logo_files();
$file = isset( $files[ $variant ] ) ? $files[ $variant ] : $files['horizontal-blue'];
return AARDWOLF_SC_URL . 'assets/img/' . $file;
}
/**
* Admin menu icon — the official wolf mark (white), which reads well on the
* default dark admin sidebar.
*
* Returned as a base64 SVG data URI (not a URL): WordPress only renders menu
* icons at the standard 20px when they are data URIs — a plain
URL
* is drawn at the SVG's intrinsic size, which made the wolf appear oversized
* next to the other menu icons.
*
* @return string
*/
public static function menu_icon() {
$file = AARDWOLF_SC_PATH . 'assets/img/aardwolf-mark-white.svg';
if ( is_readable( $file ) ) {
$svg = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( $svg ) {
return 'data:image/svg+xml;base64,' . base64_encode( $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}
}
// Fallback to a core dashicon if the asset is missing.
return 'dashicons-shield-alt';
}
/**
* Add a quick "Scan" link on the Plugins screen.
*
* @param array $links Existing action links.
* @return array
*/
public function action_links( $links ) {
$url = add_query_arg( 'page', 'aardwolf-security-scanner', admin_url( 'admin.php' ) );
$links[] = '' . esc_html__( 'Run Scan', 'aardwolf-security-scanner' ) . '';
return $links;
}
/**
* Enqueue CSS/JS only on our own admin page.
*
* @param string $hook Current admin page hook.
*/
public function enqueue_assets( $hook ) {
if ( $hook !== $this->hook_suffix ) {
return;
}
wp_enqueue_style(
'aardwolf-sc-admin',
AARDWOLF_SC_URL . 'assets/css/admin.css',
array(),
AARDWOLF_SC_VERSION
);
wp_enqueue_script(
'aardwolf-sc-admin',
AARDWOLF_SC_URL . 'assets/js/admin.js',
array( 'jquery' ),
AARDWOLF_SC_VERSION,
true
);
wp_localize_script(
'aardwolf-sc-admin',
'AardwolfSC',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'aardwolf_sc_scan' ),
'strings' => array(
'scanning' => __( 'Scanning your site…', 'aardwolf-security-scanner' ),
'error' => __( 'The scan could not be completed. Please try again.', 'aardwolf-security-scanner' ),
'confirm' => __( 'Run a fresh security scan now?', 'aardwolf-security-scanner' ),
),
)
);
}
/**
* AJAX handler that runs a scan and returns rendered HTML + summary.
*/
public function ajax_scan() {
// Capability and nonce gate.
if ( ! current_user_can( AARDWOLF_SC_CAPABILITY ) ) {
wp_send_json_error( array( 'message' => __( 'You do not have permission to run a scan.', 'aardwolf-security-scanner' ) ), 403 );
}
check_ajax_referer( 'aardwolf_sc_scan', 'nonce' );
$scanner = new Aardwolf_Scanner();
$payload = $scanner->run();
ob_start();
$this->render_results( $payload );
$html = ob_get_clean();
wp_send_json_success(
array(
'summary' => $payload['summary'],
'html' => $html,
'time' => $this->format_time( $payload['timestamp'] ),
)
);
}
/**
* Render the main admin page shell.
*/
public function render_page() {
if ( ! current_user_can( AARDWOLF_SC_CAPABILITY ) ) {
wp_die( esc_html__( 'You do not have permission to view this page.', 'aardwolf-security-scanner' ) );
}
$last = Aardwolf_Scanner::get_last_scan();
include AARDWOLF_SC_PATH . 'templates/dashboard.php';
}
/**
* Render the results block (used both on page load and via AJAX).
*
* @param array $payload Scan payload.
*/
public function render_results( $payload ) {
if ( empty( $payload ) || empty( $payload['results'] ) ) {
return;
}
include AARDWOLF_SC_PATH . 'templates/results.php';
}
/**
* Human friendly timestamp in the site's timezone.
*
* @param int $timestamp Unix time.
* @return string
*/
protected function format_time( $timestamp ) {
if ( ! $timestamp ) {
return '';
}
return wp_date(
get_option( 'date_format' ) . ' ' . get_option( 'time_format' ),
$timestamp
);
}
/**
* Map a status to a human label. Static so templates can reuse it.
*
* @param string $status Status constant.
* @return string
*/
public static function status_label( $status ) {
switch ( $status ) {
case Aardwolf_Check_Result::STATUS_PASS:
return __( 'Pass', 'aardwolf-security-scanner' );
case Aardwolf_Check_Result::STATUS_WARN:
return __( 'Warning', 'aardwolf-security-scanner' );
case Aardwolf_Check_Result::STATUS_FAIL:
return __( 'Fail', 'aardwolf-security-scanner' );
default:
return __( 'Info', 'aardwolf-security-scanner' );
}
}
/**
* Map a severity to a human label.
*
* @param string $severity Severity constant.
* @return string
*/
public static function severity_label( $severity ) {
switch ( $severity ) {
case Aardwolf_Check_Result::SEVERITY_HIGH:
return __( 'High', 'aardwolf-security-scanner' );
case Aardwolf_Check_Result::SEVERITY_MEDIUM:
return __( 'Medium', 'aardwolf-security-scanner' );
case Aardwolf_Check_Result::SEVERITY_LOW:
return __( 'Low', 'aardwolf-security-scanner' );
default:
return __( 'Info', 'aardwolf-security-scanner' );
}
}
}