Server IP : 104.21.14.48 / Your IP : 3.133.126.164 [ Web Server : Apache System : Linux b70eb322-3aee-0c53-7c82-0db91281f2c6.secureserver.net 6.1.90-1.el9.elrepo.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 2 12:09:22 EDT 2024 x86_64 User : root ( 0) PHP Version : 8.0.30.2 Disable Function : NONE Domains : 0 Domains MySQL : ON | cURL : ON | WGET : ON | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/wp-content/plugins/defender-security/src/controller/ |
Upload File : |
<?php /** * Handles all scan related actions. * * @package WP_Defender\Controller */ namespace WP_Defender\Controller; use ActionScheduler; use WP_Defender\Event; use WP_Defender\Admin; use Valitron\Validator; use Calotes\Component\Request; use Calotes\Component\Response; use WP_Defender\Controller\Quarantine; use WP_Defender\Component\Rate; use WP_Defender\Traits\Formats; use WP_Defender\Traits\Scan_Upsell; use WP_Defender\Model\Scan_Item; use WP_Defender\Behavior\WPMUDEV; use WP_Defender\Model\Scan as Model_Scan; use WP_Defender\Behavior\Scan\Core_Integrity; use WP_Defender\Model\Setting\Scan as Scan_Settings; use WP_Defender\Model\Notification\Malware_Report; use WP_Defender\Component\Config\Config_Hub_Helper; use WP_Defender\Helper\Analytics\Scan as Scan_Analytics; use WP_Defender\Model\Notification\Malware_Notification; use WP_Defender\Component\Quarantine as Quarantine_Component; /** * Contains methods for handling scans. */ class Scan extends Event { use Formats; use Scan_Upsell; /** * The slug identifier for this controller. * * @var string */ protected $slug = 'wdf-scan'; /** * The model for handling the data. * * @var \WP_Defender\Model\Setting\Scan */ protected $model; /** * Service for handling logic. * * @var \WP_Defender\Component\Scan */ protected $service; /** * Quarantine controller. * * @var Quarantine */ private $quarantine_controller; /** * Initializes the model and service, registers routes, and sets up scheduled events if the model is active. */ public function __construct() { $this->register_page( esc_html__( 'Malware Scanning', 'defender-security' ), $this->slug, array( &$this, 'main_view', ), $this->parent_slug ); $this->model = new Scan_Settings(); $this->service = wd_di()->get( \WP_Defender\Component\Scan::class ); if ( class_exists( 'WP_Defender\Controller\Quarantine' ) ) { $this->quarantine_controller = wd_di()->get( Quarantine::class ); } $this->register_routes(); add_action( 'defender_enqueue_assets', array( &$this, 'enqueue_assets' ) ); add_action( 'wp_ajax_defender_process_scan', array( &$this, 'process' ) ); add_action( 'wp_ajax_nopriv_defender_process_scan', array( &$this, 'process' ) ); add_action( 'defender/async_scan', array( &$this, 'process' ) ); // Clean up data after successful core update. add_action( '_core_updated_successfully', array( &$this, 'clean_up_data' ) ); global $pagenow; // since 2.6.2. if ( is_admin() && 'plugins.php' === $pagenow && apply_filters( 'wd_display_vulnerability_warnings', true ) ) { $this->service->display_vulnerability_warnings(); } // Schedule a time to clear completed action scheduler logs. if ( ! wp_next_scheduled( 'wpdef_clear_scan_logs' ) ) { wp_schedule_event( time(), 'weekly', 'wpdef_clear_scan_logs' ); } add_action( 'wpdef_clear_scan_logs', array( $this, 'clear_scan_logs' ) ); add_filter( 'heartbeat_nopriv_send', array( $this, 'nopriv_heartbeat' ), 10, 2 ); add_action( 'action_scheduler_completed_action', array( $this, 'scan_completed_analytics' ) ); } /** * Clean up data after core updating. * * @return void */ public function clean_up_data(): void { $this->service->clean_up(); } /** * Start a scan. * * @param Request $request Request object. * * @return Response * @defender_route * @defender_redirect */ public function start( Request $request ): Response { $model = Model_Scan::create(); if ( is_object( $model ) && ! is_wp_error( $model ) ) { $this->log( 'Initial ping self', 'scan.log' ); $this->scan_started_analytics( array( 'Triggered From' => 'Plugin', 'Scan Type' => 'Manual', ) ); $this->do_async_scan( 'scan' ); return new Response( true, array( 'status' => $model->status, 'status_text' => $model->get_status_text(), 'percent' => 0, ) ); } return new Response( false, array( 'message' => esc_html__( 'A scan is already in progress', 'defender-security' ), ) ); } /** * Use this for self ping, so it can both run in background and active mode with good performance. * * @return void * @defender_route * @is_public */ public function process() { if ( $this->service->has_lock() ) { $this->log( 'Fallback as already a process is running', 'scan.log' ); return; } // This creates file lock, for make sure only 1 process run as a time. $this->service->create_lock(); // Check if the ping is from self or not. $ret = $this->service->process(); $this->log( 'process done, queue for next', 'scan.log' ); if ( false === $ret ) { // Ping self. $this->log( 'Scan not done, pinging', 'scan.log' ); $this->service->remove_lock(); $this->process(); } else { $this->queue_to_sync_with_hub(); $this->service->remove_lock(); } } /** * Query status. * * @return Response * @defender_route * @defender_redirect */ public function status(): Response { $idle_scan = wd_di()->get( Model_Scan::class )->get_idle(); if ( is_object( $idle_scan ) ) { $this->service->update_idle_scan_status(); return new Response( false, $idle_scan->to_array() ); } $checksum_issue = get_site_option( Core_Integrity::ISSUE_CHECKSUMS, 'false' ); $checksum_scan = Model_Scan::get_core_check(); if ( 'false' !== $checksum_issue && is_object( $checksum_scan ) ) { $this->service->update_idle_scan_status_by_checksum_issue( $checksum_scan ); return new Response( false, $checksum_scan->to_array() ); } $scan = Model_Scan::get_active(); if ( is_object( $scan ) ) { return new Response( false, $scan->to_array() ); } $scan = Model_Scan::get_last(); if ( is_object( $scan ) && ! is_wp_error( $scan ) ) { return new Response( true, $scan->to_array() ); } return new Response( false, array( 'message' => esc_html__( 'Error during scanning', 'defender-security' ), ) ); } /** * Cancel current scan. * * @return Response * @defender_route * @defender_redirect */ public function cancel(): Response { $component = wd_di()->get( \WP_Defender\Component\Scan::class ); $component->cancel_a_scan(); $last = Model_Scan::get_last(); if ( is_object( $last ) && ! is_wp_error( $last ) ) { $last = $last->to_array(); } return new Response( true, array( 'scan' => $last, ) ); } /** * Track scan item action analytics. * * @param Scan_Item $scan_item Individual item of scan issues list. * @param string $intention What action is going to be executed. */ private function item_action_analytics( Scan_Item $scan_item, string $intention ) { $allowed_intentions = array( 'resolve', 'ignore', 'delete', 'unignore', 'quarantine', ); $event_name = 'def_threat_resolved'; if ( in_array( $intention, $allowed_intentions, true ) ) { $intention_desc = array( 'resolve' => 'Safe Repair', 'ignore' => 'Ignore', 'delete' => 'Delete', 'unignore' => 'Unignore', 'quarantine' => 'Safe Repair & Quarantine', ); $resolution_method = $intention_desc[ $intention ]; $threat_type = ''; if ( Scan_Item::TYPE_INTEGRITY === $scan_item->type ) { $threat_type = 'Unknown file in WordPress core'; } elseif ( Scan_Item::TYPE_PLUGIN_CHECK === $scan_item->type ) { $raw_data = $scan_item->raw_data; if ( isset( $raw_data['type'] ) && 'modified' === $raw_data['type'] ) { $threat_type = 'plugin file modified'; } } elseif ( Scan_Item::TYPE_VULNERABILITY === $scan_item->type ) { $threat_type = 'Vulnerability'; if ( 'resolve' === $intention ) { $resolution_method = 'Update'; } } elseif ( Scan_Item::TYPE_SUSPICIOUS === $scan_item->type ) { $threat_type = 'Suspicious function'; } $this->track_feature( $event_name, array( 'Resolution Method' => $resolution_method, 'Threat type' => $threat_type, ) ); } } /** * A central controller to pass any request from frontend to scan item. * * @param Request $request Request object. * * @return Response * @defender_route */ public function item_action( Request $request ): Response { $data = $request->get_data( array( 'id' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), 'intention' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'parent_action' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); $id = $data['id'] ?? false; $intention = $data['intention'] ?? false; if ( false === $id || false === $intention || ! in_array( $intention, array( 'pull_src', 'resolve', 'ignore', 'delete', 'unignore', 'quarantine', ), true ) ) { wp_die(); } $result = array(); $scan = Model_Scan::get_last(); if ( $scan instanceof Model_Scan ) { $item = $scan->get_issue( $id ); if ( is_object( $item ) && $item->has_method( $intention ) ) { if ( 'quarantine' === $intention ) { $result = $item->$intention( $data['parent_action'] ); } else { $result = $item->$intention(); } $this->item_action_analytics( $item, $intention ); if ( is_wp_error( $result ) ) { return new Response( false, array( 'message' => $result->get_error_message(), ) ); } elseif ( isset( $result['type_notice'] ) ) { return new Response( true, $result ); } elseif ( isset( $result['url'] ) ) { // Without message and interval args. return new Response( true, array( 'redirect' => $result['url'] ) ); } $this->queue_to_sync_with_hub(); // Refresh scan instance. $scan = Model_Scan::get_last(); if ( $scan instanceof Model_Scan ) { $result['scan'] = $scan->to_array(); $success = true; if ( isset( $result['success'] ) && false === $result['success'] ) { $success = false; } return new Response( $success, $result ); } } } return new Response( false, array() ); } /** * Process for bulk action. * There is no Update-intention because it is a lengthy process. There may not be enough execution time. * * @param Request $request Request object. * * @defender_route * @return Response */ public function bulk_action( Request $request ): Response { $data = $request->get_data( array( 'items' => array( 'type' => 'array', 'sanitize' => 'sanitize_text_field', ), 'bulk' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), ) ); $items = $data['items'] ?? array(); $intention = $data['bulk'] ?? false; if ( empty( $items ) || ! is_array( $items ) || false === $intention || ! in_array( $intention, array( 'ignore', 'unignore', 'delete' ), true ) ) { return new Response( false, array() ); } // Try to get Scan. $scan = Model_Scan::get_last(); if ( ! is_object( $scan ) ) { return new Response( false, array() ); } $is_delete = false; $delete_items = array(); $none_delete_items = array(); foreach ( $items as $id ) { if ( 'ignore' === $intention ) { $scan->ignore_issue( (int) $id ); } elseif ( 'unignore' === $intention ) { $scan->unignore_issue( (int) $id ); } elseif ( 'delete' === $intention ) { $item = $scan->get_issue( (int) $id ); // Work with every item. if ( is_object( $item ) && $item->has_method( $intention ) ) { $item_result = $item->delete(); if ( is_wp_error( $item_result ) ) { $none_delete_items[] = $item_result->get_error_message(); } elseif ( isset( $item_result['type_notice'] ) ) { return new Response( true, $item_result ); } elseif ( isset( $item_result['collect_type'] ) ) { $is_delete = true; $delete_items[] = $item_result['message']; } } else { return new Response( false, array() ); } } } $this->queue_to_sync_with_hub(); $result = array(); if ( ! empty( $none_delete_items ) ) { $result['message'] = sprintf( /* translators: %s: Vulnerability item(es) */ _n( 'Defender doesn\'t have enough permission to remove this file: %s', 'Defender doesn\'t have enough permission to remove these files: %s', count( $none_delete_items ), 'defender-security' ), '<pre>' . implode( PHP_EOL, $none_delete_items ) . '</pre>' ); } elseif ( $is_delete ) { $result['message'] = sprintf( /* translators: %s: Vulnerability item(es) */ esc_html__( '%s has (have) been deleted', 'defender-security' ), implode( ', ', $delete_items ) ); } // Refresh scan instance. $scan = Model_Scan::get_last(); $result['scan'] = $scan->to_array(); return new Response( empty( $none_delete_items ), $result ); } /** * Save settings. * * @param Request $request The request object containing new settings data. * * @return Response * @since 2.7.0 Add Scheduled Scanning to Malware settings and hide it on Malware Scanning - Reporting. * Also, the backward compatibility of settings for Scan and Malware_Report models. * @defender_route */ public function save_settings( Request $request ): Response { $data = $request->get_data_by_model( $this->model ); // Case#1: enable all child options, if parent and all child options are disabled, so that there is no notice when saving. if ( ! $data['integrity_check'] && ! $data['check_core'] && ! $data['check_plugins'] ) { $data['check_core'] = true; $data['check_plugins'] = true; } // Case#2: Suspicious code is activated BUT File change detection is deactivated then show the notice. if ( $data['scan_malware'] && ! $data['integrity_check'] ) { $response = array( 'type_notice' => 'info', 'message' => sprintf( /* translators: 1. Open tag. 2. Close tag. 3. Open tag. 4. Close tag. */ esc_html__( 'To reduce false-positive results, we recommend enabling %1$sFile change detection%2$s options for all scan types while the %3$sSuspicious code%4$s option is enabled.', 'defender-security' ), '<strong>', '</strong>', '<strong>', '</strong>' ), ); } else { // Prepare response message for usual successful case. $response = array( 'message' => esc_html__( 'Your settings have been updated.', 'defender-security' ), 'auto_close' => true, ); } // Additional cases are in the Scan model. $report_change = false; // If 'Scheduled Scanning' is checked then need to change Malware_Report. if ( true === $data['scheduled_scanning'] ) { $report = new Malware_Report(); $report_change = true; $report->frequency = $data['frequency']; $report->day = $data['day']; $report->day_n = $data['day_n']; $report->time = $data['time']; // Disable 'Scheduled Scanning'. } elseif ( true === $this->model->scheduled_scanning && false === $data['scheduled_scanning'] ) { $report = new Malware_Report(); $report_change = true; $report->status = \WP_Defender\Model\Notification::STATUS_DISABLED; } $before_import_schedule = $this->model->quarantine_expire_schedule; $this->model->import( $data ); if ( $this->model->validate() ) { if ( class_exists( 'WP_Defender\Component\Quarantine' ) ) { $quarantine_component = wd_di()->get( Quarantine_Component::class ); $quarantine_component->reschedule_file_expiry_cron( $before_import_schedule, $data['quarantine_expire_schedule'] ); } // Todo: need to disable Malware_Notification & Malware_Report if all scan settings are deactivated? $this->model->save(); // Save Report's changes. if ( $report_change ) { $report->save(); } Config_Hub_Helper::set_clear_active_flag(); return new Response( true, array_merge( $response, $this->data_frontend() ) ); } else { return new Response( false, array_merge( array( 'message' => $this->model->get_formatted_errors(), ), $this->data_frontend() ) ); } } /** * Get the issues mainly for pagination request. * * @param Request $request The request object. * * @return Response * @defender_route */ public function get_issues( Request $request ): Response { $data = $request->get_data( array( 'scenario' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'type' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'per_page' => array( 'type' => 'string', 'sanitize' => 'sanitize_text_field', ), 'paged' => array( 'type' => 'int', 'sanitize' => 'sanitize_text_field', ), ) ); // Validate the request. $v = new Validator( $data, array() ); $v->rule( 'required', array( 'scenario', 'type', 'per_page', 'paged' ) ); if ( ! $v->validate() ) { return new Response( false, array( 'message' => '', ) ); } $scan = Model_Scan::get_last(); $issues = $scan->to_array( $data['per_page'], $data['paged'], $data['type'] ); return new Response( true, array( 'issue' => $issues['issues_items'], 'ignored' => $issues['ignored_items'], 'paging' => $issues['paging'], 'count' => $issues['count'], ) ); } /** * Handle notice. * Send the notice to the admin dashboard of the site. * * @param Request $request Request object. * * @return Response Response object. * @defender_route */ public function handle_notice( Request $request ): Response { update_site_option( Rate::SLUG_FOR_BUTTON_RATE, true ); return new Response( true, array() ); } /** * Handle postponed notice. * Reset counters for postponed notice. * * @param Request $request Request object. * * @return Response Response object. * @defender_route */ public function postpone_notice( Request $request ): Response { Rate::reset_counters(); return new Response( true, array() ); } /** * Handle refuse notice. * Send the refuse notice to the admin dashboard of the site. * * @param Request $request Request object. * * @return Response Response object. * @defender_route */ public function refuse_notice( Request $request ): Response { update_site_option( Rate::SLUG_FOR_BUTTON_THANKS, true ); return new Response( true, array() ); } /** * Render main page. * * @return void */ public function main_view(): void { $this->render( 'main' ); } /** * Enqueues scripts and styles for this page. * Only enqueues assets if the page is active. */ public function enqueue_assets() { if ( ! $this->is_page_active() ) { return; } wp_localize_script( 'def-scan', 'scan', $this->data_frontend() ); wp_enqueue_script( 'def-scan' ); wp_enqueue_script( 'clipboard' ); $this->enqueue_main_assets(); wp_enqueue_script( 'def-codemirror', defender_asset_url( '/assets/js/vendor/codemirror/codemirror.js' ), array(), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-xml', defender_asset_url( '/assets/js/vendor/codemirror/xml/xml.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-clike', defender_asset_url( '/assets/js/vendor/codemirror/clike/clike.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-css', defender_asset_url( '/assets/js/vendor/codemirror/css/css.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-javascript', defender_asset_url( '/assets/js/vendor/codemirror/javascript/javascript.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-htmlmixed', defender_asset_url( '/assets/js/vendor/codemirror/htmlmixed/htmlmixed.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-php', defender_asset_url( '/assets/js/vendor/codemirror/php/php.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-merge', defender_asset_url( '/assets/js/vendor/codemirror/merge/merge.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-diff-match-patch', defender_asset_url( '/assets/js/vendor/diff-match-patch.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-annotatescrollbar', defender_asset_url( '/assets/js/vendor/codemirror/scroll/annotatescrollbar.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-simplescrollbars', defender_asset_url( '/assets/js/vendor/codemirror/scroll/simplescrollbars.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-searchcursor', defender_asset_url( '/assets/js/vendor/codemirror/search/searchcursor.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_script( 'def-codemirror-matchonscrollbars', defender_asset_url( '/assets/js/vendor/codemirror/search/matchesonscrollbar.js' ), array( 'def-codemirror' ), DEFENDER_VERSION, true ); wp_enqueue_style( 'def-codemirror', defender_asset_url( '/assets/js/vendor/codemirror/codemirror.css' ), array(), DEFENDER_VERSION ); wp_enqueue_style( 'def-codemirror-dracula', defender_asset_url( '/assets/js/vendor/codemirror/dracula.css' ), array( 'def-codemirror' ), DEFENDER_VERSION ); wp_enqueue_style( 'def-codemirror-merge', defender_asset_url( '/assets/js/vendor/codemirror/merge/merge.css' ), array( 'def-codemirror' ), DEFENDER_VERSION ); wp_enqueue_style( 'def-codemirror-matchonscrollbars', defender_asset_url( '/assets/js/vendor/codemirror/search/matchesonscrollbar.css' ), array( 'def-codemirror' ), DEFENDER_VERSION ); wp_enqueue_style( 'def-codemirror-simplescrollbars', defender_asset_url( '/assets/js/vendor/codemirror/scroll/simplescrollbars.css' ), array( 'def-codemirror' ), DEFENDER_VERSION ); } /** * Converts the current object state to an array. * * @return array The array representation of the object. */ public function to_array(): array { $scan = Model_Scan::get_active(); $last = Model_Scan::get_last(); if ( ! is_object( $scan ) && ! is_object( $last ) ) { $scan = null; } else { $scan = is_object( $scan ) ? $scan->to_array() : $last->to_array(); } return array_merge( array( 'scan' => $scan, 'report' => array( 'enabled' => true, 'frequency' => 'weekly', ), ), $this->dump_routes_and_nonces() ); } /** * Removes settings for all submodules. */ public function remove_settings(): void { ( new Scan_Settings() )->delete(); } /** * Delete all the data & the cache. */ public function remove_data(): void { delete_site_option( Model_Scan::IGNORE_INDEXER ); delete_site_option( Core_Integrity::ISSUE_CHECKSUMS ); } /** * Provides data for the frontend. * * @return array An array of data for the frontend. */ public function data_frontend(): array { $scan = Model_Scan::get_active(); $last = Model_Scan::get_last(); $per_page = 10; $paged = 1; if ( ! is_object( $scan ) && ! is_object( $last ) ) { $scan = null; } else { $scan = is_object( $scan ) ? $scan->to_array( $per_page, $paged ) : $last->to_array( $per_page, $paged ); } $settings = new Scan_Settings(); $report = wd_di()->get( Malware_Report::class ); $report_text = esc_html__( 'Automatic scans are disabled', 'defender-security' ); if ( $settings->scheduled_scanning && isset( $settings->frequency ) ) { $report_text = sprintf( /* translators: 1. Line break tag. 2. Frequency value. */ esc_html__( 'Automatic scans are %1$srunning %2$s', 'defender-security' ), '<br/>', $settings->frequency ); } // Prepare additional data. if ( wd_di()->get( Admin::class )->is_wp_org_version() ) { $scan_array = Rate::what_scan_notice_display(); $misc = array( 'rating_is_displayed' => ! Rate::was_rate_request() && ! empty( $scan_array['text'] ), 'rating_text' => $scan_array['text'], 'rating_type' => $scan_array['slug'], ); } else { $misc = array( 'days_of_week' => $this->get_days_of_week(), 'times_of_day' => $this->get_times(), 'timezone_text' => sprintf( /* translators: %s - timezone, %s - time */ esc_html__( 'Your timezone is set to %1$s, so your current time is %2$s.', 'defender-security' ), '<strong>' . wp_timezone_string() . '</strong>', '<strong>' . wp_date( 'H:i' ) . '</strong>' ), 'show_notice' => ! $settings->scheduled_scanning && 'scheduled_scanning' === defender_get_data_from_request( 'enable', 'g' ), 'rating_is_displayed' => false, 'rating_text' => '', 'rating_type' => '', ); } // Todo: add logic for deactivated scan settings. Maybe display some notice. $data = array( 'scan' => $scan, 'settings' => $settings->export(), 'report' => $report_text, 'active_tools' => array( 'integrity_check' => $settings->integrity_check, 'check_known_vuln' => $settings->check_known_vuln, 'scan_malware' => $settings->scan_malware, 'scheduled_scanning' => $settings->scheduled_scanning, ), 'notification' => $report->to_string(), 'next_run' => $report->get_next_run_as_string(), 'misc' => $misc, ); if ( class_exists( 'WP_Defender\Controller\Quarantine' ) ) { $data['quarantine'] = $this->quarantine_controller->data_frontend(); } return array_merge( $data, $this->dump_routes_and_nonces() ); } /** * Imports data into the model. * * @param array $data Data to be imported into the model. */ public function import_data( array $data ) { $model = $this->model; if ( empty( $data ) ) { $model->scheduled_scanning = false; $model->frequency = 'weekly'; $model->day_n = '1'; $model->day = 'sunday'; $model->time = '4:00'; $model->save(); } else { $model->import( $data ); if ( $model->validate() ) { $model->save(); } } } /** * Checks if any scan is active. * * @param bool $is_pro Indicates if the product is a pro version. * * @return bool True if any scan is active, false otherwise. */ private function is_any_active( bool $is_pro ): bool { $settings = new Scan_Settings(); $file_change_check = $settings->is_checked_any_file_change_types(); if ( $is_pro ) { // Pro version. Check all parent types. return $file_change_check || $settings->check_known_vuln || $settings->scan_malware; } else { // Free version. Check the 'File change detection' type because only it's available with nested types. return $file_change_check; } } /** * Exports strings. * * @return array An array of strings. */ public function export_strings(): array { $strings = array(); $is_pro = ( new WPMUDEV() )->is_pro(); if ( $this->is_any_active( $is_pro ) ) { $strings[] = esc_html__( 'Active', 'defender-security' ); } else { $strings[] = esc_html__( 'Inactive', 'defender-security' ); } $scan_report = new Malware_Report(); $scan_notification = new Malware_Notification(); if ( 'enabled' === $scan_notification->status ) { $strings[] = esc_html__( 'Email notifications active', 'defender-security' ); } if ( $is_pro && 'enabled' === $scan_report->status ) { $strings[] = sprintf( /* translators: %s: Frequency value. */ esc_html__( 'Email reports sending %s', 'defender-security' ), $scan_report->frequency ); } elseif ( ! $is_pro ) { $strings[] = sprintf( /* translators: %s: Html for Pro-tag. */ esc_html__( 'Email report inactive %s', 'defender-security' ), '<span class="sui-tag sui-tag-pro">Pro</span>' ); } return $strings; } /** * Generates configuration strings based on the provided configuration and * whether the product is a pro version. * * @param array $config Configuration data. * @param bool $is_pro Indicates if the product is a pro version. * * @return array Returns an array of configuration strings. */ public function config_strings( array $config, bool $is_pro ): array { $strings = array(); $strings[] = $this->service->is_any_scan_active( $config, $is_pro ) ? esc_html__( 'Active', 'defender-security' ) : esc_html__( 'Inactive', 'defender-security' ); if ( 'enabled' === $config['notification'] ) { $strings[] = esc_html__( 'Email notifications active', 'defender-security' ); } if ( $is_pro && 'enabled' === $config['report'] ) { $strings[] = sprintf( /* translators: %s: Frequency value. */ esc_html__( 'Email reports sending %s', 'defender-security' ), $config['frequency'] ); } elseif ( ! $is_pro ) { $strings[] = sprintf( /* translators: %s: Html for Pro-tag. */ esc_html__( 'Email report inactive %s', 'defender-security' ), '<span class="sui-tag sui-tag-pro">Pro</span>' ); } return $strings; } /** * Triggers the asynchronous scan. * * @param string $type Denotes type of the scan from the following 4 possible values: scan, install, hub or report. * * @return void */ public function do_async_scan( string $type ): void { wd_di()->get( Model_Scan::class )->delete_idle(); // Delete the slug from the previous scan. delete_site_option( Core_Integrity::ISSUE_CHECKSUMS ); as_enqueue_async_action( 'defender/async_scan', array( 'type' => $type, ), 'defender' ); } /** * Clear completed action scheduler logs. * * @return void * @since 2.6.5 */ public function clear_scan_logs(): void { $scan_component = wd_di()->get( \WP_Defender\Component\Scan::class ); $result = $scan_component::clear_logs(); if ( isset( $result['error'] ) ) { $this->log( 'WP CRON Error : ' . $result['error'], 'scan.log' ); } } /** * When user session is expired and scan is running, then don't login via heartbeat modal. * * @param array $response The no-priv Heartbeat response. * @param string $screen_id The screen id. * * @return mixed * @since 3.11.0 */ public function nopriv_heartbeat( $response, $screen_id ) { if ( false !== strpos( $screen_id, $this->slug ) ) { $scan = Model_Scan::get_active(); if ( is_object( $scan ) ) { $response['wp-auth-check'] = true; } } return $response; } /** * Triggers and send analytics data on scan started. * * @param array $extra_data Extra data. * * @return void */ public function scan_started_analytics( array $extra_data ) { $scan_analytics = wd_di()->get( Scan_Analytics::class ); $analytics_data = $scan_analytics->scan_started( $this->model ); $this->track_feature( $analytics_data['event'], array_merge( $analytics_data['data'], $extra_data ) ); } /** * Triggers and send analytics data on scan completed. * * @param int $action_id Action ID. * * @return void */ public function scan_completed_analytics( $action_id ) { if ( 'defender' === ActionScheduler::store()->fetch_action( $action_id )->get_group() ) { $scan_analytics = wd_di()->get( Scan_Analytics::class ); $scan_model = wd_di()->get( Model_Scan::class ); $analytics_data = $scan_analytics->scan_completed( $scan_model ); $this->track_feature( $analytics_data['event'], $analytics_data['data'] ); } } }