DatPilot: điểm sức khoẻ web (dùng được cả khi chưa kết nối), sao lưu thủ công tải về máy, * dán mã kết nối, xem trạng thái, ngắt kết nối. Mọi nút đều qua admin-post có nonce + quyền manage_options. */ defined( 'ABSPATH' ) || exit; class DATPCO_Admin { const PAGE = 'options-general.php?page=datpilot'; public static function boot() { add_action( 'admin_menu', function () { add_options_page( 'DatPilot', 'DatPilot', 'manage_options', 'datpilot', array( __CLASS__, 'page' ) ); } ); foreach ( array( 'save', 'disconnect', 'deep_scan', 'backup_download', 'backup_delete' ) as $a ) { add_action( 'admin_post_datpco_' . $a, array( __CLASS__, $a ) ); } add_action( 'wp_ajax_datpco_job', array( __CLASS__, 'ajax_job' ) ); add_action( 'admin_enqueue_scripts', array( __CLASS__, 'assets' ) ); add_filter( 'plugin_action_links_' . plugin_basename( DATPCO_FILE ), function ( $l ) { array_unshift( $l, 'Sức khoẻ web' ); return $l; } ); } /** JS của trang Cài đặt → DatPilot: chạy sao lưu/quét theo lát, hỏi lại trước khi xoá. Chỉ nạp ở trang này. */ public static function assets( $hook ) { if ( 'settings_page_datpilot' !== $hook ) { return; } wp_enqueue_script( 'datpco-admin', plugins_url( 'assets/admin.js', DATPCO_FILE ), array(), DATPCO_VERSION, true ); wp_localize_script( 'datpco-admin', 'datpcoAdmin', array( 'ajax' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'datpco_job' ), 'doneBackup' => admin_url( self::PAGE . '&datpco_msg=backup_ok' ), 'doneScan' => admin_url( self::PAGE . '&datpco_msg=scan_ok' ), ) ); } private static function cap() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'Không có quyền.', 403 ); } } private static function back( $msg ) { wp_safe_redirect( add_query_arg( 'datpco_msg', rawurlencode( $msg ), admin_url( self::PAGE ) ) ); exit; } public static function save() { self::cap(); check_admin_referer( 'datpco_save' ); $key = isset( $_POST['datpco_key'] ) ? sanitize_text_field( wp_unslash( $_POST['datpco_key'] ) ) : ''; $p = DATPCO_Auth::parse_key( $key ); $msg = 'ok'; if ( is_wp_error( $p ) ) { $msg = $p->get_error_message(); } else { $portal = ( defined( 'DATPCO_PORTAL' ) && DATPCO_PORTAL ) ? DATPCO_PORTAL : 'https://app.datpilot.com'; update_option( DATPCO_Auth::OPTION, array( 'site_id' => $p['site_id'], 'secret' => $p['secret'], 'portal' => $portal, 'connected' => time() ), false ); $h = DATPCO_Auth::hello(); if ( is_wp_error( $h ) ) { delete_option( DATPCO_Auth::OPTION ); $msg = $h->get_error_message(); } } self::back( $msg ); } public static function disconnect() { self::cap(); check_admin_referer( 'datpco_disconnect' ); delete_option( DATPCO_Auth::OPTION ); self::back( 'disconnected' ); } public static function deep_scan() { self::cap(); check_admin_referer( 'datpco_deep_scan' ); $r = DATPCO_Health::deep_scan(); self::back( is_wp_error( $r ) ? $r->get_error_message() : 'scanned' ); } /** Sao lưu thủ công chạy theo lát: trình duyệt gọi start rồi step liên tục tới khi xong. */ public static function ajax_job() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Không có quyền.' ), 403 ); } check_ajax_referer( 'datpco_job' ); $op = isset( $_POST['op'] ) ? sanitize_key( wp_unslash( $_POST['op'] ) ) : ''; $kind = isset( $_POST['kind'] ) && 'scan' === sanitize_key( wp_unslash( $_POST['kind'] ) ) ? 'scan' : 'backup'; if ( 'start' === $op ) { $r = DATPCO_Jobs::start( $kind, 'scan' === $kind ? array() : array( 'label' => 'Sao lưu thủ công trong wp-admin' ) ); } elseif ( 'step' === $op ) { $r = DATPCO_Jobs::step( isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : '' ); } else { $r = new WP_Error( 'datpco_op', 'Thao tác không hợp lệ.' ); } if ( is_wp_error( $r ) ) { wp_send_json_error( array( 'message' => $r->get_error_message(), 'code' => $r->get_error_code() ) ); } wp_send_json_success( $r ); } public static function backup_delete() { self::cap(); check_admin_referer( 'datpco_backup_delete' ); DATPCO_Backup::delete( isset( $_POST['id'] ) ? sanitize_text_field( wp_unslash( $_POST['id'] ) ) : '' ); self::back( 'backup_deleted' ); } public static function backup_download() { self::cap(); check_admin_referer( 'datpco_backup_download' ); $id = isset( $_POST['id'] ) ? sanitize_text_field( wp_unslash( $_POST['id'] ) ) : ''; $part = isset( $_POST['part'] ) && 'db' === sanitize_key( wp_unslash( $_POST['part'] ) ) ? 'db' : 'files'; $n = isset( $_POST['n'] ) ? max( 1, absint( $_POST['n'] ) ) : 1; if ( ! DATPCO_Backup::meta( $id ) || ( 'files' === $part && ! DATPCO_Backup::path( $id, 'files', $n ) ) ) { wp_die( 'Không thấy file sao lưu.', 404 ); } DATPCO_Backup::prepare(); while ( ob_get_level() ) { ob_end_clean(); } $name = sanitize_file_name( wp_parse_url( home_url(), PHP_URL_HOST ) . '-' . $id . ( 'db' === $part ? '.sql.gz' : sprintf( '-ma-nguon-%02d.zip', $n ) ) ); nocache_headers(); header( 'Content-Type: application/octet-stream' ); header( 'Content-Disposition: attachment; filename="' . $name . '"' ); DATPCO_Backup::stream( $id, $part, $n ); // CSDL: mọi đoạn nối lại, giải mã khi tải exit; } private static function notice( $msg ) { $known = array( 'ok' => array( 'success', 'Đã kết nối với DatPilot.' ), 'disconnected' => array( 'info', 'Đã ngắt kết nối.' ), 'scanned' => array( 'success', 'Đã quét sâu file lõi WordPress.' ), 'backup_ok' => array( 'success', 'Đã tạo bản sao lưu. Nên tải về máy để giữ một bản ngoài hosting.' ), 'backup_deleted' => array( 'info', 'Đã xoá bản sao lưu.' ), 'scan_ok' => array( 'success', 'Đã quét xong, kết quả ở mục Quét mã độc và spam SEO.' ), ); list( $type, $text ) = isset( $known[ $msg ] ) ? $known[ $msg ] : array( 'error', $msg ); echo '
' . esc_html( $text ) . '
' . count( $h['checks'] ) . ' mục kiểm tra, ' . count( $bad ) . ' mục cần xử lý. Phần kiểm tra chỉ đọc, không thay đổi gì trên web.
'; echo '' . ( $h['deep_at'] ? 'Quét sâu file lõi lần cuối: ' . esc_html( wp_date( 'd/m/Y H:i', $h['deep_at'] ) ) . '. ' : '' ) . '
| Mức | Vấn đề | Cách sửa |
|---|---|---|
| ' . esc_html( $sev[ $i['sev'] ] ) . ' | ' . esc_html( $i['title'] ) . ' ' . esc_html( $i['detail'] ) . ' | ' . esc_html( $i['fix'] )
. ' Nhờ DatPilot làm |
Không có vấn đề nào trong các mục đã kiểm.
'; } $deep = get_option( DATPCO_Health::LAST ); if ( is_array( $deep ) && ! empty( $deep['files'] ) ) { echo '' . esc_html( implode( "\n", $deep['files'] ) ) . '' . esc_html( $mode[ $errs['mode'] ] ?? $mode[''] ) . ' Không cần bật WP_DEBUG; chỉ ghi loại lỗi, file, dòng, trang bị lỗi; đã xoá email, số điện thoại, dữ liệu trong câu lỗi. Lỗi lưu trên hosting của bạn, chỉ gửi đi khi web đã kết nối DatPilot.
'; if ( $errs['errors'] ) { echo '| Lần cuối | Lỗi | Nghi do | Số lần |
|---|---|---|---|
' . esc_html( wp_date( 'd/m H:i', (int) $x['last'] ) ) . '' . esc_html( $x['uri'] ) . ' | ' . esc_html( $x['file'] . ':' . $x['line'] ) . '' . esc_html( $x['msg'] ) . ' | ' . esc_html( $x['slug'] ? $x['kind'] . ' ' . $x['slug'] . ( $x['version'] ? ' ' . $x['version'] : '' ) : ( 'core' === $x['kind'] ? 'lõi WordPress' : '' ) ) . ' | ' . (int) $x['n'] . ' |
Không có lỗi PHP nghiêm trọng nào.
'; } echo 'So file lõi WordPress và plugin tải từ wordpress.org với bản gốc, dò cửa hậu thường gặp trong theme, plugin trả phí, thư mục ảnh tải lên; tìm bài bị chèn spam cờ bạc, nhà cái, link ẩn. Chỉ đọc, không xoá hay sửa gì. Web lớn mất vài phút, giữ trang này mở tới khi xong.
'; echo ''; if ( is_array( $scan ) ) { $sp = is_array( $scan['spam'] ) ? $scan['spam'] : array(); echo '
Lần quét gần nhất ' . esc_html( wp_date( 'd/m/Y H:i', (int) $scan['at'] ) ) . ': ' . (int) $scan['files'] . ' file, ' . (int) $scan['plugins']['verified'] . ' plugin đối chiếu được với wordpress.org, ' . (int) $scan['plugins']['unverified'] . ' plugin chỉ dò dấu hiệu. ' . '' . (int) $scan['high'] . ' dấu hiệu nặng, ' . (int) $scan['med'] . ' dấu hiệu cần xem.
'; if ( $scan['hits'] ) { echo '| Mức | Ở đâu | Vì sao đáng ngờ |
|---|---|---|
| ' . esc_html( $sev[ $x['sev'] ] ) . ' | ' . esc_html( $x['path'] ) . ' | ' . esc_html( $x['why'] ) . ' |
Còn ' . (int) $scan['more'] . ' dấu hiệu nữa không hiện hết.
'; } } if ( ! empty( $sp['samples'] ) ) { echo 'Sao lưu cơ sở dữ liệu và thư mục wp-content (trừ ảnh tải lên, cache). Lưu trên hosting của bạn, giữ tối đa ' . (int) DATPCO_Backup::KEEP . ' bản; nên tải về máy một bản. Chạy từng phần nhỏ nên không bị hosting cắt giữa chừng; giữ trang này mở tới khi xong.
'; echo '' . ( $run ? 'Đang có việc ' . esc_html( $run['type'] ) . ' chạy dở.' : '' ) . '
'; $list = DATPCO_Backup::all(); if ( $list ) { echo '| Bản sao lưu | Dung lượng | |
|---|---|---|
| ' . esc_html( wp_date( 'd/m/Y H:i', (int) $b['created'] ) ) . ' ' . esc_html( $b['label'] ?? '' ) . ' | ' . esc_html( $size ) . ' | '; self::button( 'backup_download', 'Tải CSDL', array( 'id' => $b['id'], 'part' => 'db' ) ); $np = count( isset( $b['parts'] ) ? $b['parts'] : array() ); for ( $k = 1; $k <= $np; $k++ ) { self::button( 'backup_download', $np > 1 ? "Mã nguồn $k/$np" : 'Tải mã nguồn', array( 'id' => $b['id'], 'part' => 'files', 'n' => $k ) ); } self::button( 'backup_delete', 'Xoá', array( 'id' => $b['id'] ), 'button-link-delete button', 'Xoá bản sao lưu này?' ); echo ' |
Trạng thái: đã kết nối (mã web ' . esc_html( $c['site_id'] ) . ') từ ' . esc_html( wp_date( 'd/m/Y H:i', (int) ( isset( $c['connected'] ) ? $c['connected'] : time() ) ) ) . '.
DatPilot theo dõi web sập và báo cho bạn, gửi báo cáo sức khoẻ hằng tháng. DatPilot chỉ gửi được các lệnh đọc thông tin, điểm sức khoẻ, quét chỉ đọc, đọc lỗi PHP, xoá cache, tạo/xoá bản sao lưu (bản sao lưu nằm trên hosting của bạn, không gửi về DatPilot); mỗi lệnh có chữ ký riêng. Plugin này không cho DatPilot cài, bật, tắt, cập nhật plugin hay đổi cài đặt của web. Ngắt kết nối hoặc tắt plugin bất kỳ lúc nào là DatPilot không truy cập được nữa.
'; self::button( 'disconnect', 'Ngắt kết nối' ); } else { echo 'Kết nối miễn phí để DatPilot báo khi web sập (Telegram/email) và gửi báo cáo sức khoẻ hằng tháng. Dán mã kết nối lấy ở datpilot.com → Web của tôi. Chưa dán mã thì plugin không gửi gì ra ngoài.
'; echo ''; } echo '