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' ); } } }