HEX
Server: LiteSpeed
System: Linux emerald.serverdnsnetwork.com 5.14.0-611.5.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Nov 11 08:09:09 EST 2025 x86_64
User: mahdetej (1135)
PHP: 8.1.34
Disabled: show_source, system, shell_exec, passthru, exec, popen, proc_open
Upload Files
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";
	}
}