File: /home/mahdetej/www/wp-content/plugins/Melli-shild/admin/class-melli-shield-admin.php
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
class Melli_Shield_Admin {
private $ui;
public function __construct() {
$this->ui = Melli_Shield_Admin_UI::instance();
add_action( 'admin_init', array( $this, 'handle_exports' ) );
add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
}
public function admin_body_class( $classes ) {
if ( Melli_Shield_Config::is_admin_screen() ) {
$classes .= ' melli-shield-admin';
}
return $classes;
}
public function add_menu_pages() {
$icon = 'data:image/svg+xml;base64,' . base64_encode(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#a7f3d0" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>'
);
$parent = Melli_Shield_Config::PAGE_DASHBOARD;
$title = __( 'ملی شیلد', 'melli-shield' );
$this->ensure_menu_hook_prefix( $parent );
add_menu_page( $title, $title, 'manage_options', $parent, array( $this, 'render_dashboard' ), $icon, 80 );
$this->ensure_menu_hook_prefix( $parent );
$subs = array(
Melli_Shield_Config::PAGE_DASHBOARD => __( 'داشبورد', 'melli-shield' ),
Melli_Shield_Config::PAGE_SCANNER => __( 'اسکنر', 'melli-shield' ),
Melli_Shield_Config::PAGE_PERFORMANCE => __( 'عملکرد', 'melli-shield' ),
Melli_Shield_Config::PAGE_SETTINGS => __( 'تنظیمات', 'melli-shield' ),
Melli_Shield_Config::PAGE_LOG => __( 'گزارش', 'melli-shield' ),
Melli_Shield_Config::PAGE_WHITELIST => __( 'لیست سفید', 'melli-shield' ),
Melli_Shield_Config::PAGE_HELP => __( 'راهنما', 'melli-shield' ),
);
foreach ( $subs as $slug => $label ) {
add_submenu_page( $parent, $label, $label, 'manage_options', $slug, array( $this, $this->renderer_for( $slug ) ) );
}
$this->repair_registered_plugin_pages( $parent );
}
/**
* Force ASCII hook prefix before menu registration (WP may use sanitize_title of Persian title).
*
* @param string $parent Parent menu slug.
*/
private function ensure_menu_hook_prefix( $parent ) {
global $admin_page_hooks;
$admin_page_hooks[ $parent ] = $parent;
}
/**
* Re-map $_registered_pages when submenu hooks were registered with a wrong parent prefix.
* Changing $admin_page_hooks after registration alone causes "Sorry, you are not allowed".
*
* @param string $parent Parent menu slug.
*/
private function repair_registered_plugin_pages( $parent ) {
global $_registered_pages, $admin_page_hooks;
$wrong_prefixes = Melli_Shield_Config::admin_menu_parent_prefixes();
if ( isset( $admin_page_hooks[ $parent ] ) && $admin_page_hooks[ $parent ] !== $parent ) {
$wrong_prefixes[] = $admin_page_hooks[ $parent ];
}
$wrong_prefixes = array_unique(
array_diff( $wrong_prefixes, array( $parent ) )
);
$this->ensure_menu_hook_prefix( $parent );
$repaired = 0;
foreach ( Melli_Shield_Config::admin_page_slugs() as $slug ) {
$right_hook = $parent . '_page_' . $slug;
foreach ( $wrong_prefixes as $wrong ) {
$wrong_hook = $wrong . '_page_' . $slug;
if ( isset( $_registered_pages[ $wrong_hook ] ) && ! isset( $_registered_pages[ $right_hook ] ) ) {
$_registered_pages[ $right_hook ] = $_registered_pages[ $wrong_hook ];
unset( $_registered_pages[ $wrong_hook ] );
++$repaired;
}
}
if ( ! isset( $_registered_pages[ $right_hook ] ) ) {
$_registered_pages[ $right_hook ] = true;
++$repaired;
}
}
Melli_Shield_Debug::log(
'menu_pages_repaired',
array(
'parent' => $parent,
'wrong_prefixes' => array_values( $wrong_prefixes ),
'entries_repaired' => $repaired,
)
);
}
private function renderer_for( $slug ) {
$map = array(
Melli_Shield_Config::PAGE_DASHBOARD => 'render_dashboard',
Melli_Shield_Config::PAGE_SCANNER => 'render_scanner',
Melli_Shield_Config::PAGE_PERFORMANCE => 'render_performance',
Melli_Shield_Config::PAGE_SETTINGS => 'render_settings',
Melli_Shield_Config::PAGE_LOG => 'render_log',
Melli_Shield_Config::PAGE_WHITELIST => 'render_whitelist',
Melli_Shield_Config::PAGE_HELP => 'render_help',
);
return isset( $map[ $slug ] ) ? $map[ $slug ] : 'render_dashboard';
}
public function enqueue_assets( $hook ) {
$is_screen = Melli_Shield_Config::is_admin_screen( $hook );
Melli_Shield_Debug::log(
'admin_enqueue',
array(
'hook' => $hook,
'page' => $this->current_admin_page(),
'is_screen' => $is_screen,
'screen_id' => function_exists( 'get_current_screen' ) && get_current_screen() ? get_current_screen()->id : null,
)
);
if ( ! $is_screen ) {
return;
}
$css_path = MELLI_SHIELD_PLUGIN_DIR . 'assets/css/admin.css';
$css_url = MELLI_SHIELD_PLUGIN_URL . 'assets/css/admin.css';
wp_enqueue_style(
'melli-shield-admin-css',
$css_url,
array(),
Melli_Shield_Config::asset_version( 'assets/css/admin.css' )
);
// Fallback: direct output if another plugin dequeued our handle.
add_action( 'admin_head', array( $this, 'print_css_fallback' ), 5 );
add_action( 'admin_head', array( $this, 'print_fonts' ), 1 );
add_action( 'admin_head', array( $this, 'print_config' ), 2 );
add_action( 'admin_footer', array( $this, 'print_js' ), 1 );
Melli_Shield_Debug::log(
'assets_registered',
array(
'page' => $this->current_admin_page(),
'css_url' => $css_url,
'css_exists' => file_exists( $css_path ),
)
);
}
/**
* Optional Google Fonts (skipped in national/offline when block_fonts is on).
*/
public function print_fonts() {
if ( Melli_Shield_Utils::get_setting( 'block_fonts', 1 ) && Melli_Shield_Utils::is_blocking_active() ) {
return;
}
echo '<link rel="preconnect" href="https://fonts.googleapis.com" />' . "\n";
echo '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />' . "\n";
echo '<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" media="all" />' . "\n";
}
/**
* Output admin.css if wp_enqueue was blocked or dequeued.
*/
public function print_css_fallback() {
if ( wp_style_is( 'melli-shield-admin-css', 'done' ) || wp_style_is( 'melli-shield-admin-css', 'enqueued' ) ) {
return;
}
$v = Melli_Shield_Config::asset_version( 'assets/css/admin.css' );
$url = MELLI_SHIELD_PLUGIN_URL . 'assets/css/admin.css?ver=' . $v;
Melli_Shield_Debug::log( 'css_fallback_printed', array( 'url' => $url ) );
echo '<link rel="stylesheet" id="melli-shield-admin-css" href="' . esc_url( $url ) . '" media="all" />' . "\n";
}
public function print_config() {
echo '<script type="text/javascript" id="melli-shield-config">' . "\n";
echo 'var melli_shield_ajax = ' . wp_json_encode( $this->ajax_config() ) . ';' . "\n";
echo '</script>' . "\n";
}
public function print_js() {
$base = MELLI_SHIELD_PLUGIN_URL . 'assets/js/';
$util_v = Melli_Shield_Config::asset_version( 'assets/js/melli-shield-util.js' );
$admin_v = Melli_Shield_Config::asset_version( 'assets/js/admin.js' );
$page = $this->current_admin_page();
echo '<script src="' . esc_url( $base . 'melli-shield-util.js?ver=' . $util_v ) . '"></script>' . "\n";
echo '<script src="' . esc_url( $base . 'admin.js?ver=' . $admin_v ) . '"></script>' . "\n";
if ( Melli_Shield_Config::PAGE_SCANNER === $page ) {
$scan_v = Melli_Shield_Config::asset_version( 'assets/js/scanner.js' );
echo '<script src="' . esc_url( $base . 'scanner.js?ver=' . $scan_v ) . '"></script>' . "\n";
}
if ( Melli_Shield_Config::PAGE_PERFORMANCE === $page ) {
$perf_v = Melli_Shield_Config::asset_version( 'assets/js/performance.js' );
echo '<script src="' . esc_url( $base . 'performance.js?ver=' . $perf_v ) . '"></script>' . "\n";
}
}
/**
* @return string Current ?page= slug or empty.
*/
private function current_admin_page() {
if ( ! isset( $_GET['page'] ) ) {
return '';
}
return sanitize_text_field( wp_unslash( $_GET['page'] ) );
}
/**
* @return array<string,mixed>
*/
private function ajax_config() {
return array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( Melli_Shield_Config::NONCE_ACTION ),
'plugin_url' => MELLI_SHIELD_PLUGIN_URL,
'strings' => array(
'confirm_mode' => __( 'آیا از تغییر حالت مطمئن هستید؟', 'melli-shield' ),
'scanning' => __( 'در حال اسکن...', 'melli-shield' ),
'scanning_files' => __( 'اسکن فایلها', 'melli-shield' ),
'scanning_db' => __( 'اسکن پایگاهداده', 'melli-shield' ),
'scan_complete' => __( 'اسکن کامل شد.', 'melli-shield' ),
'scan_found' => __( '%total% URL خارجی در %domains% دامنه یافت شد.', 'melli-shield' ),
'confirm_clear' => __( 'آیا از پاک کردن نتایج اسکن مطمئن هستید؟', 'melli-shield' ),
'no_results' => __( 'هنوز نتیجهای ثبت نشده.', 'melli-shield' ),
'error' => __( 'خطایی رخ داد.', 'melli-shield' ),
'pause' => __( 'توقف موقت', 'melli-shield' ),
'resume' => __( 'ادامه', 'melli-shield' ),
'paused_status' => __( 'متوقف شده', 'melli-shield' ),
'confirm_stop' => __( 'اسکن متوقف شود؟ نتایج تا این لحظه حفظ میشوند.', 'melli-shield' ),
'scan_stopped' => __( 'اسکن متوقف شد.', 'melli-shield' ),
'scan_stopped_detail' => __( 'توقف در %completed% از %total% دسته.', 'melli-shield' ),
),
);
}
public function admin_bar_status( $wp_admin_bar ) {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$mode = Melli_Shield_Utils::get_mode();
$dots = array(
'normal' => '#16a34a',
'national' => '#d97706',
'offline' => '#dc2626',
);
$color = isset( $dots[ $mode ] ) ? $dots[ $mode ] : '#999';
$wp_admin_bar->add_node( array(
'id' => 'melli-shield-status',
'title' => '<span style="color:' . esc_attr( $color ) . '">●</span> ' .
esc_html__( 'ملی شیلد', 'melli-shield' ) . ': ' .
esc_html( Melli_Shield_Utils::get_mode_label() ),
'href' => Melli_Shield_Config::admin_url(),
) );
$subs = array(
Melli_Shield_Config::PAGE_DASHBOARD => __( 'داشبورد', 'melli-shield' ),
Melli_Shield_Config::PAGE_SETTINGS => __( 'تنظیمات', 'melli-shield' ),
Melli_Shield_Config::PAGE_LOG => __( 'گزارش', 'melli-shield' ),
Melli_Shield_Config::PAGE_PERFORMANCE => __( 'عملکرد', 'melli-shield' ),
);
foreach ( $subs as $slug => $label ) {
$wp_admin_bar->add_node( array(
'parent' => 'melli-shield-status',
'id' => 'msh-bar-' . $slug,
'title' => $label,
'href' => Melli_Shield_Config::admin_url( $slug ),
) );
}
if ( ! is_admin() ) {
$wp_admin_bar->add_node( array(
'parent' => 'melli-shield-status',
'id' => 'melli-shield-analyze-page',
'title' => __( 'تحلیل این صفحه', 'melli-shield' ),
'href' => '#melli-shield-analyze',
'meta' => array(
'class' => 'melli-shield-analyze-btn melli-shield-analyze-btn',
),
) );
}
}
public function handle_exports() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( isset( $_POST['melli_shield_export_log'] ) || isset( $_POST['darkshield_export_log'] ) ) {
if ( $this->verify_nonce( 'melli_shield_log_actions' ) || $this->verify_nonce( 'darkshield_log_actions' ) ) {
$this->export_log_csv();
}
}
if ( isset( $_POST['melli_shield_export_whitelist'] ) || isset( $_POST['darkshield_export_whitelist'] ) ) {
if ( $this->verify_nonce( 'melli_shield_whitelist_action' ) || $this->verify_nonce( 'melli_shield_whitelist_action' ) ) { // phpcs:ignore
$this->export_whitelist_txt();
}
}
}
private function export_log_csv() {
global $wpdb;
$table = Melli_Shield_Config::log_table();
if ( ! Melli_Shield_Utils::table_exists( $table ) ) {
return;
}
$where = array( '1=1' );
$args = array();
$filters = array(
'log_domain' => 'domain LIKE %s',
'log_type' => 'type = %s',
'log_source' => 'source = %s',
'log_mode' => 'mode = %s',
);
foreach ( $filters as $param => $sql ) {
$val = $this->input( $param );
if ( '' !== $val ) {
$where[] = $sql;
$args[] = ( 'log_domain' === $param ) ? '%' . $wpdb->esc_like( $val ) . '%' : $val;
}
}
$status = $this->input( 'log_status' );
if ( '' !== $status ) {
$where[] = 'blocked = %d';
$args[] = (int) $status;
}
foreach ( array( 'log_date_from' => '>=', 'log_date_to' => '<=' ) as $param => $op ) {
$val = $this->input( $param );
if ( '' !== $val ) {
$where[] = "DATE(created_at) {$op} %s";
$args[] = $val;
}
}
$sql = "SELECT * FROM {$table} WHERE " . implode( ' AND ', $where ) . ' ORDER BY created_at DESC';
$rows = ! empty( $args ) ? $wpdb->get_results( $wpdb->prepare( $sql, $args ) ) : $wpdb->get_results( $sql );
while ( ob_get_level() > 0 ) {
ob_end_clean();
}
nocache_headers();
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=melli-shield-log-' . gmdate( 'Y-m-d-His' ) . '.csv' );
$out = fopen( 'php://output', 'w' );
fprintf( $out, chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF ) );
fputcsv( $out, array( 'ID', 'URL', 'Domain', 'Type', 'Source', 'Mode', 'Blocked', 'Date' ) );
foreach ( (array) $rows as $r ) {
fputcsv( $out, array( $r->id, $r->url, $r->domain, $r->type, $r->source, $r->mode, $r->blocked ? 'Yes' : 'No', $r->created_at ) );
}
fclose( $out );
exit;
}
private function export_whitelist_txt() {
$wl = new Melli_Shield_Whitelist();
while ( ob_get_level() > 0 ) {
ob_end_clean();
}
nocache_headers();
header( 'Content-Type: text/plain; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=melli-shield-whitelist-' . gmdate( 'Y-m-d' ) . '.txt' );
echo implode( "\n", $wl->export() );
exit;
}
private function input( $key ) {
if ( isset( $_POST[ $key ] ) ) {
return sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
}
if ( isset( $_GET[ $key ] ) ) {
return sanitize_text_field( wp_unslash( $_GET[ $key ] ) );
}
return '';
}
private function verify_nonce( $action ) {
return isset( $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), $action );
}
public function render_dashboard() {
include MELLI_SHIELD_PLUGIN_DIR . 'admin/views/page-dashboard.php';
}
public function render_scanner() {
include MELLI_SHIELD_PLUGIN_DIR . 'admin/views/page-scanner.php';
}
public function render_performance() {
include MELLI_SHIELD_PLUGIN_DIR . 'admin/views/page-performance.php';
}
public function render_settings() {
include MELLI_SHIELD_PLUGIN_DIR . 'admin/views/page-settings.php';
}
public function render_log() {
include MELLI_SHIELD_PLUGIN_DIR . 'admin/views/page-log.php';
}
public function render_whitelist() {
include MELLI_SHIELD_PLUGIN_DIR . 'admin/views/page-whitelist.php';
}
public function render_help() {
include MELLI_SHIELD_PLUGIN_DIR . 'admin/views/page-help.php';
}
public function enqueue_frontend_perf() {
if ( is_admin() || ! current_user_can( 'manage_options' ) || ! is_admin_bar_showing() ) {
return;
}
add_action( 'wp_footer', array( $this, 'print_frontend_perf_script' ), 999 );
}
public function print_frontend_perf_script() {
$config = array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( Melli_Shield_Config::NONCE_ACTION ),
);
echo '<script id="melli-shield-front-config">' . "\n";
echo 'var melli_shield_front_perf = ' . wp_json_encode( $config ) . ';' . "\n";
echo '</script>' . "\n";
$url = MELLI_SHIELD_PLUGIN_URL . 'assets/js/frontend-perf.js?ver=' . MELLI_SHIELD_VERSION;
echo '<script src="' . esc_url( $url ) . '"></script>' . "\n";
}
}