locations = array( 'dir' => $mod_log_dir, 'url' => MAINWP_MODULES_URL . 'logs/', 'inc_dir' => $mod_log_dir . 'includes/', 'class_dir' => $mod_log_dir . 'classes/', ); spl_autoload_register( array( $this, 'autoload' ) ); // Load helper functions. require_once $this->locations['inc_dir'] . 'functions.php'; // NOSONAR - WP compatible. $driver = new Log_DB_Driver_WPDB(); $this->db = new Log_DB( $driver ); $this->settings = new Log_Settings( $this ); // Load logger class. $this->log = new Log( $this ); // Load settings and connectors after widgets_init and before the default init priority. add_action( 'init', array( $this, 'init' ), 9 ); // Change DB driver after plugin loaded if any add-ons want to replace. add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 20 ); add_action( 'mainwp_delete_site', array( $this, 'hook_delete_site' ), 10, 3 ); // Load admin area classes. if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) { $this->admin = new Log_Admin( $this ); } elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) { $this->admin = new Log_Admin( $this, $driver ); } add_filter( 'mainwp_module_log_enable_insert_log_type', array( $this, 'hook_enable_insert_log_type' ), 10, 2 ); add_filter( 'mainwp_get_cron_jobs_init', array( $this, 'hook_get_cron_jobs_init' ), 10, 2 ); // on/off by change status of use wp cron option. add_filter( 'mainwp_module_logs_changes_logs_sync_params', array( $this, 'hook_changes_logs_sync_params' ), 10, 2 ); // on/off by change status of use wp cron option. add_filter( 'mainwp_module_logs_get_log_records', array( $this, 'hook_get_log_records' ), 10, 2 ); // Retrieves MainWP log records based on provided query parameters. if ( $this->is_enabled_auto_archive_logs() && ! empty( $this->settings->options['records_logs_ttl'] ) ) { add_action( 'mainwp_module_log_cron_job_auto_archive', array( $this, 'cron_module_log_auto_archive' ) ); } if ( $this->admin instanceof Log_Admin ) { if ( ! empty( $this->settings->options['enabled'] ) ) { add_action( 'mainwp_module_log_render_db_size_notice', array( $this->admin, 'render_logs_db_notice' ), 10, 2 ); } add_action( 'mainwp_module_log_render_db_update_notice', array( $this->admin, 'render_update_db_notice' ), 10, 1 ); } } /** * Autoloader for classes. * * @param string $class_name class name. */ public function autoload( $class_name ) { if ( ! preg_match( '/^(?P.+)\\\\(?P[^\\\\]+)$/', $class_name, $matches ) ) { return; } static $reflection; if ( empty( $reflection ) ) { $reflection = new \ReflectionObject( $this ); } if ( $reflection->getNamespaceName() !== $matches['namespace'] ) { return; } $autoload_name = $matches['autoload']; $autoload_dir = \trailingslashit( $this->locations['dir'] ); $load_dirs = array( 'classes' => 'class', 'pages' => 'page', 'widgets' => 'widget', ); foreach ( $load_dirs as $dir => $prefix ) { $dir = $dir . '/'; $autoload_path = sprintf( '%s%s%s-%s.php', $autoload_dir, $dir, $prefix, strtolower( str_replace( '_', '-', $autoload_name ) ) ); if ( is_readable( $autoload_path ) ) { require_once $autoload_path; // NOSONAR - WP compatible. return; } } } /** * Get internal connectors. */ public function get_internal_connectors() { return array( 'compact', ); } /** * Load Log_Connectors. * * @action init * * @uses \MainWP\Dashboard\Module\Log\Log_Connectors */ public function init() { if ( class_exists( '\MainWP\Dashboard\Module\Log\Log_Connectors' ) ) { // to fix fatal error in case class not found. $this->connectors = new Log_Connectors( $this ); } } /** * Getter for the version number. * * @return string */ public function get_version() { return static::VERSION; } /** * Change plugin database driver in case driver plugin loaded after logs. * * @uses \MainWP\Dashboard\Module\Log\Log_DB * @uses \MainWP\Dashboard\Module\Log\Log_DB_Driver_WPDB */ public function plugins_loaded() { // Load DB helper interface/class. $driver_class = '\MainWP\Dashboard\Module\Log\Log_DB_Driver_WPDB'; if ( class_exists( $driver_class ) ) { $driver = new $driver_class(); $this->db = new Log_DB( $driver ); } } /** * Method get_sync_actions_last_created(). * * Sync site changes logs data. * * @param int $site_id site id. * * @return bool */ public function get_sync_actions_last_created( $site_id ) { if ( ! isset( static::$last_log_created[ $site_id ] ) ) { $site_opts = MainWP_DB::instance()->get_website_options_array( $site_id, array( 'non_mainwp_changes_sync_last_created' ) ); if ( ! is_array( $site_opts ) ) { $site_opts = array(); } static::$last_log_created[ $site_id ] = isset( $site_opts['non_mainwp_changes_sync_last_created'] ) ? $site_opts['non_mainwp_changes_sync_last_created'] : 0; // to sure. if ( empty( static::$last_log_created[ $site_id ] ) ) { static::$last_log_created[ $site_id ] = time() - DAY_IN_SECONDS; MainWP_DB::instance()->update_website_option( $site_id, 'non_mainwp_changes_sync_last_created', static::$last_log_created[ $site_id ] ); } } return static::$last_log_created[ $site_id ]; } /** * Method sync_log_site_actions(). * * Sync site actions data. * * @param int $site_id site id. * @param array $sync_actions action data. * @param object $website website data. * * @return bool */ public function sync_log_site_actions( $site_id, $sync_actions, $website ) { // phpcs:ignore -- NOSONAR - complex. if ( empty( $sync_actions ) || ! is_array( $sync_actions ) ) { return false; } MainWP_Utility::array_sort_existed_keys( $sync_actions, 'created', SORT_NUMERIC ); $sync_last_created = (float) $this->get_sync_actions_last_created( $site_id ); $new_last_created = 0; foreach ( $sync_actions as $data ) { if ( ! is_array( $data ) || empty( $data['action_user'] ) || empty( $data['created'] ) ) { continue; } $item_created = round( (float) $data['created'], 4 ); // to fix float comparing issue. if ( $item_created <= $sync_last_created ) { continue; } if ( $new_last_created < $item_created ) { $new_last_created = $item_created; } $user_meta = array(); $meta_data = array(); $extra_info = false; if ( isset( $data['meta_data'] ) && is_array( $data['meta_data'] ) ) { $meta_data = $data['meta_data']; if ( isset( $meta_data['user_meta'] ) && is_array( $meta_data['user_meta'] ) ) { $user_meta = $meta_data['user_meta']; // to compatible old user_meta site changes actions data. unset( $meta_data['user_meta'] ); } elseif ( isset( $meta_data['meta_data'] ) && ! empty( $meta_data['meta_data'] ) && is_array( $meta_data['meta_data'] ) ) { $user_meta = $meta_data['meta_data']; // new meta_data site changes actions. unset( $meta_data['meta_data'] ); } $meta_data['user_meta_json'] = wp_json_encode( $user_meta ); if ( isset( $meta_data['extra_info'] ) && is_array( $meta_data['extra_info'] ) ) { $extra_info = $meta_data['extra_info']; } } $user_login = sanitize_text_field( wp_unslash( $data['action_user'] ) ); $sum = ''; if ( false !== $extra_info ) { $meta_data['extra_info'] = wp_json_encode( $extra_info ); $sum .= ! empty( $extra_info['name'] ) ? esc_html( $extra_info['name'] ) : ''; } else { $sum .= ! empty( $meta_data['name'] ) ? esc_html( $meta_data['name'] ) : ''; } $sum .= ' '; $sum .= 'wordpress' !== $data['context'] ? esc_html( ucfirst( rtrim( $data['context'], 's' ) ) ) : 'WordPress'; //phpcs:ignore -- wordpress text. if ( 'wordpress' === $data['context'] ) { $sum = 'WordPress'; } if ( isset( $user_meta['wp_user_id'] ) ) { $user_id = ! empty( $user_meta['wp_user_id'] ) ? sanitize_text_field( $user_meta['wp_user_id'] ) : 0; } elseif ( ! empty( $user_meta['user_id'] ) ) { // to compatible with old child actions data. $user_id = sanitize_text_field( $user_meta['user_id'] ); } elseif ( isset( $data['user_id'] ) && ! empty( $data['user_id'] ) ) { $user_id = $data['user_id']; // new sync changes logs data. } $actions_mapping = array( 'installed' => 'install', 'deleted' => 'delete', 'activated' => 'activate', 'deactivated' => 'deactivate', ); $contexts_mapping = array( 'plugins' => 'plugin', 'themes' => 'theme', 'wordpress' => 'core', ); $action = isset( $actions_mapping[ $data['action'] ] ) ? $actions_mapping[ $data['action'] ] : $data['action']; $context = isset( $contexts_mapping[ $data['context'] ] ) ? $contexts_mapping[ $data['context'] ] : $data['context']; $created = isset( $data['created'] ) ? (float) $data['created'] : microtime( true ); // to fix missing slug meta for theme context issue. if ( ( 'theme' === $context ) && empty( $meta_data['slug'] ) && is_array( $extra_info ) ) { if ( ! empty( $extra_info['slug'] ) ) { $meta_data['slug'] = $extra_info['slug']; } elseif ( empty( $meta_data['name'] ) && ! empty( $extra_info['name'] ) ) { $meta_data['name'] = $extra_info['name']; } } $record_mapping = array( 'site_id' => $site_id, 'user_id' => $user_id, 'user_login' => $user_login, 'created' => $created, 'item' => $sum, 'context' => $context, 'action' => $action, 'state' => 1, 'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : 0, // sanitize_text_field for seconds. 'meta' => $meta_data, ); do_action( 'mainwp_sync_site_log_install_actions', $website, $record_mapping ); } if ( $new_last_created ) { MainWP_DB::instance()->update_website_option( $site_id, 'non_mainwp_changes_sync_last_created', $new_last_created ); } return true; } /** * Method is_enabled_auto_archive_logs(). * * @return bool True|False Enabled auto archive log or not. */ public function is_enabled_auto_archive_logs() { return is_array( $this->settings->options ) && ! empty( $this->settings->options['enabled'] ) && ! empty( $this->settings->options['auto_archive'] ) ? true : false; } /** * Method hook_delete_site() * * @param mixed $site site object. * * @return bool result. */ public function hook_delete_site( $site ) { if ( empty( $site ) ) { return false; } return Log_DB_Helper::instance()->remove_logs_by( $site->id ); } /** * Method hook_enable_insert_log_type() * * @param bool $enabled Enable input value. * @param array $data Log data. * * @return bool True: Enable log. */ public function hook_enable_insert_log_type( $enabled, $data ) { if ( is_array( $data ) && ! empty( $data['log_type_id'] ) ) { return Log_Settings::is_action_log_enabled( $data['log_type_id'], 'changeslogs' ); } elseif ( is_array( $data ) && isset( $data['connector'] ) && isset( $data['context'] ) && isset( $data['action'] ) ) { if ( 'non-mainwp-changes' === $data['connector'] ) { return Log_Settings::is_action_log_enabled( $data['context'] . '_' . $data['action'], 'nonmainwpchanges' ); } elseif ( 'compact' !== $data['connector'] ) { return Log_Settings::is_action_log_enabled( $data['context'] . '_' . $data['action'], 'dashboard' ); } } return $enabled; } /** * Method hook_get_cron_jobs_init() * * @param array $init_jobs Jobs to init. * * @return array Init Jobs. */ public function hook_get_cron_jobs_init( $init_jobs ) { if ( $this->is_enabled_auto_archive_logs() ) { $init_jobs['mainwp_module_log_cron_job_auto_archive'] = 'daily'; } return $init_jobs; } /** * Method cron_module_log_auto_archive() */ public function cron_module_log_auto_archive() { $ttl = 3 * YEAR_IN_SECONDS; if ( is_array( $this->settings->options ) && isset( $this->settings->options['records_logs_ttl'] ) ) { $ttl = intval( $this->settings->options['records_logs_ttl'] ); } if ( $ttl ) { do_action( 'mainwp_log_action', 'Module Log :: Archive logs schedule start.', MainWP_Logger::LOGS_AUTO_PURGE_LOG_PRIORITY ); $time = time(); $before = $time - $ttl; Log_DB_Archive::instance()->archive_sites_changes( $before ); update_option( 'mainwp_module_log_last_time_auto_archive_logs', $time ); update_option( 'mainwp_module_log_next_time_auto_archive_logs', $time + $ttl ); } } /** * Method hook_changes_logs_sync_params() * * @param string $params Input value. * @param int $site_id Site id. * @param array $postdata Post data. * * @return string Empty or json encoded value. * * @since 5.5 */ public function hook_changes_logs_sync_params( $params, $site_id, $postdata = array() ) { // if it is not a manual sync data. $sync_logs = isset( $_POST['action'] ) && 'mainwp_syncsites' === $_POST['action'] ? true : false; //phpcs:ignore --ok. $sync_logs = apply_filters( 'mainwp_module_logs_sync_changes_log', $sync_logs ); if ( ! $sync_logs ) { return $params; } $last_created = Log_Changes_Logs_Helper::instance()->get_sync_changes_logs_last_created( $site_id ); $events_count = apply_filters( 'mainwp_module_log_changes_logs_sync_count', 100, $site_id, $postdata ); $disabled_changeslogs = Log_Settings::get_disabled_logs_type( 'changeslogs' ); $disabled_nonmainwp_actions = Log_Settings::get_disabled_logs_type( 'nonmainwpchanges' ); $log_settings = get_option( 'mainwp_module_log_settings', array() ); if ( ! is_array( $log_settings ) ) { $log_settings = array(); } return array( 'newer_than' => $last_created, 'events_count' => $events_count, 'ignore_sync_changes_logs' => ! empty( $disabled_changeslogs ) ? $disabled_changeslogs : -1, // -1 to prevent it removed nested empty array from http query builder. 'ignore_sync_nonmainwp_actions' => ! empty( $disabled_nonmainwp_actions ) ? $disabled_nonmainwp_actions : -1, 'child_logs_ttl' => ! empty( $log_settings['child_logs_ttl'] ) ? absint( $log_settings['child_logs_ttl'] ) : 7, 'child_logs_enabled' => ! empty( $log_settings['enabled'] ) ? 1 : 0, ); } /** * Method hook_get_log_records() * * Retrieves MainWP log records based on provided query parameters. * * Available $params arguments (all optional): * * Default values: * * array( * // LOG TARGETING * 'log_id' => 0, // Specific log ID. * 'wpid' => 0, // Site ID or array of site IDs. * * // ACCESS / VIEW * 'check_access' => true, // Apply access restrictions. * 'view' => '', // View mode: '', 'events_list', 'api-view'. * * // OPTIMIZATION * 'optimize' => false, // Enable optimized query. * 'optimize_with_meta' => false, // Fetch meta separately when optimized. * 'with_all_logs_meta' => false, // Load full logs meta. * * // RESULT MODE * 'count_only' => false, // Return count only. * 'not_count' => false, // Skip count query. * * // SEARCH / FILTERS * 'search' => '', // Search keyword. * 'dismiss' => null, // true|false|null. * * // GROUP / CLIENT FILTERS * 'groups_ids' => array(), // Filter by group IDs. * 'client_ids' => array(), // Filter by client IDs. * * // USER FILTERS * 'usersfilter_sites_ids' => array(), // Format: userId-siteId-isDashboardUser. * 'user_ids' => array(), // Legacy user filter. * * // TIME RANGE * 'timestart' => 0, // Start timestamp (seconds). * 'timestop' => 0, // End timestamp (seconds). * * // SOURCE FILTER * 'sources_conds' => '', // 'wp-admin-only'|'dashboard-only'. * * // CONTEXT / EVENTS * 'contexts' => '', // CSV contexts list. * 'sites_ids' => array(), // Explicit site IDs. * 'events' => array(), // Actions/events filter. * * // PAGINATION * 'start' => 0, // Offset. * 'records_per_page' => 0, // LIMIT size (0 = unlimited). * * // SORTING * 'order' => 'DESC', // ASC|DESC. * 'orderby' => 'created', // Sort column. * * // RECENT EVENTS * 'recent_number' => 0, // Limit to recent logs. * ) * * * @param mixed $input_val Default value. * @param array $params { * Optional. Log query parameters. * } * * @return array Results. * * @since 6.0.1 */ public function hook_get_log_records( $input_val, $params = array() ) { unset( $input_val ); // not used, just for hook compatibility. $defaults = array( // Search param. 'search' => null, 'search_field' => 'item', 'records_per_page' => get_option( 'posts_per_page', 20 ), 'paged' => 1, // Order. 'order' => 'desc', 'orderby' => 'date', ); $args = wp_parse_args( $params, $defaults ); return (array) $this->db->get_records( $args ); } /** * Method normalize_to_microseconds() * * @param mixed $time float time value or microsecords to check. * * @return int Normalize microseconds value. * * @since 5.5 */ public static function normalize_to_microseconds( $time ) { return ( $time < 1e12 ) ? (int) ( $time * 1000000 ) : (int) $time; } }