PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | modules/logs/classes/class-log-manager.php +426 -10 5.1 → 6.2.1 View file →
@@ -8,10 +8,17 @@
8 8 */
9 9
10 10 namespace MainWP\Dashboard\Module\Log;
11 11
12 -use MainWP\Dashboard\MainWP_Execution_Helper;
12 +use MainWP\Dashboard\MainWP_DB;
13 +use MainWP\Dashboard\MainWP_Utility;
14 +use MainWP\Dashboard\MainWP_Logger;
13 15
16 +// Exit if accessed directly.
17 +if ( ! defined( 'ABSPATH' ) ) {
18 + exit;
19 +}
20 +
14 21 /**
15 22 * Class Log_Manager
16 23 *
17 24 * @package MainWP\Dashboard
@@ -32,15 +39,8 @@
32 39 * */
33 40 public $admin;
34 41
35 42 /**
36 - * MainWP_Execution_Helper
37 - *
38 - * @var \MainWP\Dashboard\MainWP_Execution_Helper class.
39 - * */
40 - public $executor;
41 -
42 - /**
43 43 * Holds Instance of settings object
44 44 *
45 45 * @var Log_Settings
46 46 */
@@ -74,8 +74,16 @@
74 74 * */
75 75 public $install;
76 76
77 77 /**
78 + * Holds the last log created time.
79 + *
80 + * @var array
81 + */
82 + private static $last_log_created = array();
83 +
84 +
85 + /**
78 86 * Locations.
79 87 *
80 88 * @var array URLs and Paths used by the plugin.
81 89 */
@@ -121,9 +129,8 @@
121 129 require_once $this->locations['inc_dir'] . 'functions.php'; // NOSONAR - WP compatible.
122 130
123 131 $driver = new Log_DB_Driver_WPDB();
124 132 $this->db = new Log_DB( $driver );
125 - $this->executor = MainWP_Execution_Helper::instance();
126 133 $this->settings = new Log_Settings( $this );
127 134
128 135 // Load logger class.
129 136 $this->log = new Log( $this );
@@ -133,8 +140,10 @@
133 140
134 141 // Change DB driver after plugin loaded if any add-ons want to replace.
135 142 add_action( 'plugins_loaded', array( $this, 'plugins_loaded' ), 20 );
136 143
144 + add_action( 'mainwp_delete_site', array( $this, 'hook_delete_site' ), 10, 3 );
145 +
137 146 // Load admin area classes.
138 147 if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
139 148 $this->admin = new Log_Admin( $this );
140 149 } elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) {
@@ -139,10 +148,27 @@
139 148 $this->admin = new Log_Admin( $this );
140 149 } elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) {
141 150 $this->admin = new Log_Admin( $this, $driver );
142 151 }
152 +
153 + add_filter( 'mainwp_module_log_enable_insert_log_type', array( $this, 'hook_enable_insert_log_type' ), 10, 2 );
154 + 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.
155 + 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.
156 + 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.
157 +
158 + if ( $this->is_enabled_auto_archive_logs() && ! empty( $this->settings->options['records_logs_ttl'] ) ) {
159 + add_action( 'mainwp_module_log_cron_job_auto_archive', array( $this, 'cron_module_log_auto_archive' ) );
160 + }
161 +
162 + if ( $this->admin instanceof Log_Admin ) {
163 + if ( ! empty( $this->settings->options['enabled'] ) ) {
164 + add_action( 'mainwp_module_log_render_db_size_notice', array( $this->admin, 'render_logs_db_notice' ), 10, 2 );
165 + }
166 + add_action( 'mainwp_module_log_render_db_update_notice', array( $this->admin, 'render_update_db_notice' ), 10, 1 );
167 + }
143 168 }
144 169
170 +
145 171 /**
146 172 * Autoloader for classes.
147 173 *
148 174 * @param string $class_name class name.
@@ -197,11 +223,14 @@
197 223 *
198 224 * @uses \MainWP\Dashboard\Module\Log\Log_Connectors
199 225 */
200 226 public function init() {
201 - $this->connectors = new Log_Connectors( $this );
227 + if ( class_exists( '\MainWP\Dashboard\Module\Log\Log_Connectors' ) ) { // to fix fatal error in case class not found.
228 + $this->connectors = new Log_Connectors( $this );
229 + }
202 230 }
203 231
232 +
204 233 /**
205 234 * Getter for the version number.
206 235 *
207 236 * @return string
@@ -223,6 +252,393 @@
223 252 if ( class_exists( $driver_class ) ) {
224 253 $driver = new $driver_class();
225 254 $this->db = new Log_DB( $driver );
226 255 }
256 + }
257 +
258 + /**
259 + * Method get_sync_actions_last_created().
260 + *
261 + * Sync site changes logs data.
262 + *
263 + * @param int $site_id site id.
264 + *
265 + * @return bool
266 + */
267 + public function get_sync_actions_last_created( $site_id ) {
268 +
269 + if ( ! isset( static::$last_log_created[ $site_id ] ) ) {
270 + $site_opts = MainWP_DB::instance()->get_website_options_array( $site_id, array( 'non_mainwp_changes_sync_last_created' ) );
271 +
272 + if ( ! is_array( $site_opts ) ) {
273 + $site_opts = array();
274 + }
275 +
276 + static::$last_log_created[ $site_id ] = isset( $site_opts['non_mainwp_changes_sync_last_created'] ) ? $site_opts['non_mainwp_changes_sync_last_created'] : 0;
277 +
278 + // to sure.
279 + if ( empty( static::$last_log_created[ $site_id ] ) ) {
280 + static::$last_log_created[ $site_id ] = time() - DAY_IN_SECONDS;
281 + MainWP_DB::instance()->update_website_option( $site_id, 'non_mainwp_changes_sync_last_created', static::$last_log_created[ $site_id ] );
282 + }
283 + }
284 +
285 + return static::$last_log_created[ $site_id ];
286 + }
287 +
288 +
289 + /**
290 + * Method sync_log_site_actions().
291 + *
292 + * Sync site actions data.
293 + *
294 + * @param int $site_id site id.
295 + * @param array $sync_actions action data.
296 + * @param object $website website data.
297 + *
298 + * @return bool
299 + */
300 + public function sync_log_site_actions( $site_id, $sync_actions, $website ) { // phpcs:ignore -- NOSONAR - complex.
301 +
302 + if ( empty( $sync_actions ) || ! is_array( $sync_actions ) ) {
303 + return false;
304 + }
305 +
306 + MainWP_Utility::array_sort_existed_keys( $sync_actions, 'created', SORT_NUMERIC );
307 +
308 + $sync_last_created = (float) $this->get_sync_actions_last_created( $site_id );
309 +
310 + $new_last_created = 0;
311 +
312 + foreach ( $sync_actions as $data ) {
313 + if ( ! is_array( $data ) || empty( $data['action_user'] ) || empty( $data['created'] ) ) {
314 + continue;
315 + }
316 +
317 + $item_created = round( (float) $data['created'], 4 ); // to fix float comparing issue.
318 +
319 + if ( $item_created <= $sync_last_created ) {
320 + continue;
321 + }
322 +
323 + if ( $new_last_created < $item_created ) {
324 + $new_last_created = $item_created;
325 + }
326 +
327 + $user_meta = array();
328 + $meta_data = array();
329 + $extra_info = false;
330 +
331 + if ( isset( $data['meta_data'] ) && is_array( $data['meta_data'] ) ) {
332 + $meta_data = $data['meta_data'];
333 + if ( isset( $meta_data['user_meta'] ) && is_array( $meta_data['user_meta'] ) ) {
334 + $user_meta = $meta_data['user_meta']; // to compatible old user_meta site changes actions data.
335 + unset( $meta_data['user_meta'] );
336 + } elseif ( isset( $meta_data['meta_data'] ) && ! empty( $meta_data['meta_data'] ) && is_array( $meta_data['meta_data'] ) ) {
337 + $user_meta = $meta_data['meta_data']; // new meta_data site changes actions.
338 + unset( $meta_data['meta_data'] );
339 + }
340 + $meta_data['user_meta_json'] = wp_json_encode( $user_meta );
341 + if ( isset( $meta_data['extra_info'] ) && is_array( $meta_data['extra_info'] ) ) {
342 + $extra_info = $meta_data['extra_info'];
343 + }
344 + }
345 +
346 + $user_login = sanitize_text_field( wp_unslash( $data['action_user'] ) );
347 +
348 + $sum = '';
349 + if ( false !== $extra_info ) {
350 + $meta_data['extra_info'] = wp_json_encode( $extra_info );
351 + $sum .= ! empty( $extra_info['name'] ) ? esc_html( $extra_info['name'] ) : '';
352 + } else {
353 + $sum .= ! empty( $meta_data['name'] ) ? esc_html( $meta_data['name'] ) : '';
354 + }
355 + $sum .= ' ';
356 + $sum .= 'wordpress' !== $data['context'] ? esc_html( ucfirst( rtrim( $data['context'], 's' ) ) ) : 'WordPress'; //phpcs:ignore -- wordpress text.
357 +
358 + if ( 'wordpress' === $data['context'] ) {
359 + $sum = 'WordPress';
360 + }
361 +
362 + if ( isset( $user_meta['wp_user_id'] ) ) {
363 + $user_id = ! empty( $user_meta['wp_user_id'] ) ? sanitize_text_field( $user_meta['wp_user_id'] ) : 0;
364 + } elseif ( ! empty( $user_meta['user_id'] ) ) { // to compatible with old child actions data.
365 + $user_id = sanitize_text_field( $user_meta['user_id'] );
366 + } elseif ( isset( $data['user_id'] ) && ! empty( $data['user_id'] ) ) {
367 + $user_id = $data['user_id']; // new sync changes logs data.
368 + }
369 +
370 + $actions_mapping = array(
371 + 'installed' => 'install',
372 + 'deleted' => 'delete',
373 + 'activated' => 'activate',
374 + 'deactivated' => 'deactivate',
375 + );
376 +
377 + $contexts_mapping = array(
378 + 'plugins' => 'plugin',
379 + 'themes' => 'theme',
380 + 'wordpress' => 'core',
381 + );
382 +
383 + $action = isset( $actions_mapping[ $data['action'] ] ) ? $actions_mapping[ $data['action'] ] : $data['action'];
384 + $context = isset( $contexts_mapping[ $data['context'] ] ) ? $contexts_mapping[ $data['context'] ] : $data['context'];
385 +
386 + $created = isset( $data['created'] ) ? (float) $data['created'] : microtime( true );
387 +
388 + // to fix missing slug meta for theme context issue.
389 + if ( ( 'theme' === $context ) && empty( $meta_data['slug'] ) && is_array( $extra_info ) ) {
390 + if ( ! empty( $extra_info['slug'] ) ) {
391 + $meta_data['slug'] = $extra_info['slug'];
392 + } elseif ( empty( $meta_data['name'] ) && ! empty( $extra_info['name'] ) ) {
393 + $meta_data['name'] = $extra_info['name'];
394 + }
395 + }
396 +
397 + $record_mapping = array(
398 + 'site_id' => $site_id,
399 + 'user_id' => $user_id,
400 + 'user_login' => $user_login,
401 + 'created' => $created,
402 + 'item' => $sum,
403 + 'context' => $context,
404 + 'action' => $action,
405 + 'state' => 1,
406 + 'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : 0, // sanitize_text_field for seconds.
407 + 'meta' => $meta_data,
408 + );
409 +
410 + do_action( 'mainwp_sync_site_log_install_actions', $website, $record_mapping );
411 + }
412 +
413 + if ( $new_last_created ) {
414 + MainWP_DB::instance()->update_website_option( $site_id, 'non_mainwp_changes_sync_last_created', $new_last_created );
415 + }
416 +
417 + return true;
418 + }
419 +
420 + /**
421 + * Method is_enabled_auto_archive_logs().
422 + *
423 + * @return bool True|False Enabled auto archive log or not.
424 + */
425 + public function is_enabled_auto_archive_logs() {
426 + return is_array( $this->settings->options ) && ! empty( $this->settings->options['enabled'] ) && ! empty( $this->settings->options['auto_archive'] ) ? true : false;
427 + }
428 +
429 + /**
430 + * Method hook_delete_site()
431 + *
432 + * @param mixed $site site object.
433 + *
434 + * @return bool result.
435 + */
436 + public function hook_delete_site( $site ) {
437 + if ( empty( $site ) ) {
438 + return false;
439 + }
440 + return Log_DB_Helper::instance()->remove_logs_by( $site->id );
441 + }
442 +
443 + /**
444 + * Method hook_enable_insert_log_type()
445 + *
446 + * @param bool $enabled Enable input value.
447 + * @param array $data Log data.
448 + *
449 + * @return bool True: Enable log.
450 + */
451 + public function hook_enable_insert_log_type( $enabled, $data ) {
452 + if ( is_array( $data ) && ! empty( $data['log_type_id'] ) ) {
453 + return Log_Settings::is_action_log_enabled( $data['log_type_id'], 'changeslogs' );
454 + } elseif ( is_array( $data ) && isset( $data['connector'] ) && isset( $data['context'] ) && isset( $data['action'] ) ) {
455 + if ( 'non-mainwp-changes' === $data['connector'] ) {
456 + return Log_Settings::is_action_log_enabled( $data['context'] . '_' . $data['action'], 'nonmainwpchanges' );
457 + } elseif ( 'compact' !== $data['connector'] ) {
458 + return Log_Settings::is_action_log_enabled( $data['context'] . '_' . $data['action'], 'dashboard' );
459 + }
460 + }
461 + return $enabled;
462 + }
463 +
464 + /**
465 + * Method hook_get_cron_jobs_init()
466 + *
467 + * @param array $init_jobs Jobs to init.
468 + *
469 + * @return array Init Jobs.
470 + */
471 + public function hook_get_cron_jobs_init( $init_jobs ) {
472 + if ( $this->is_enabled_auto_archive_logs() ) {
473 + $init_jobs['mainwp_module_log_cron_job_auto_archive'] = 'daily';
474 + }
475 + return $init_jobs;
476 + }
477 +
478 + /**
479 + * Method cron_module_log_auto_archive()
480 + */
481 + public function cron_module_log_auto_archive() {
482 + $ttl = 3 * YEAR_IN_SECONDS;
483 + if ( is_array( $this->settings->options ) && isset( $this->settings->options['records_logs_ttl'] ) ) {
484 + $ttl = intval( $this->settings->options['records_logs_ttl'] );
485 + }
486 + if ( $ttl ) {
487 + do_action( 'mainwp_log_action', 'Module Log :: Archive logs schedule start.', MainWP_Logger::LOGS_AUTO_PURGE_LOG_PRIORITY );
488 + $time = time();
489 + $before = $time - $ttl;
490 + Log_DB_Archive::instance()->archive_sites_changes( $before );
491 + update_option( 'mainwp_module_log_last_time_auto_archive_logs', $time );
492 + update_option( 'mainwp_module_log_next_time_auto_archive_logs', $time + $ttl );
493 + }
494 + }
495 +
496 + /**
497 + * Method hook_changes_logs_sync_params()
498 + *
499 + * @param string $params Input value.
500 + * @param int $site_id Site id.
501 + * @param array $postdata Post data.
502 + *
503 + * @return string Empty or json encoded value.
504 + *
505 + * @since 5.5
506 + */
507 + public function hook_changes_logs_sync_params( $params, $site_id, $postdata = array() ) {
508 +
509 + // if it is not a manual sync data.
510 + $sync_logs = isset( $_POST['action'] ) && 'mainwp_syncsites' === $_POST['action'] ? true : false; //phpcs:ignore --ok.
511 + $sync_logs = apply_filters( 'mainwp_module_logs_sync_changes_log', $sync_logs );
512 +
513 + if ( ! $sync_logs ) {
514 + return $params;
515 + }
516 +
517 + $last_created = Log_Changes_Logs_Helper::instance()->get_sync_changes_logs_last_created( $site_id );
518 + $events_count = apply_filters( 'mainwp_module_log_changes_logs_sync_count', 100, $site_id, $postdata );
519 +
520 + $disabled_changeslogs = Log_Settings::get_disabled_logs_type( 'changeslogs' );
521 + $disabled_nonmainwp_actions = Log_Settings::get_disabled_logs_type( 'nonmainwpchanges' );
522 +
523 + $log_settings = get_option( 'mainwp_module_log_settings', array() );
524 + if ( ! is_array( $log_settings ) ) {
525 + $log_settings = array();
526 + }
527 +
528 + return array(
529 + 'newer_than' => $last_created,
530 + 'events_count' => $events_count,
531 + 'ignore_sync_changes_logs' => ! empty( $disabled_changeslogs ) ? $disabled_changeslogs : -1, // -1 to prevent it removed nested empty array from http query builder.
532 + 'ignore_sync_nonmainwp_actions' => ! empty( $disabled_nonmainwp_actions ) ? $disabled_nonmainwp_actions : -1,
533 + 'child_logs_ttl' => ! empty( $log_settings['child_logs_ttl'] ) ? absint( $log_settings['child_logs_ttl'] ) : 7,
534 + 'child_logs_enabled' => ! empty( $log_settings['enabled'] ) ? 1 : 0,
535 + );
536 + }
537 +
538 + /**
539 + * Method hook_get_log_records()
540 + *
541 + * Retrieves MainWP log records based on provided query parameters.
542 + *
543 + * Available $params arguments (all optional):
544 + *
545 + * Default values:
546 + * <code>
547 + * array(
548 + * // LOG TARGETING
549 + * 'log_id' => 0, // Specific log ID.
550 + * 'wpid' => 0, // Site ID or array of site IDs.
551 + *
552 + * // ACCESS / VIEW
553 + * 'check_access' => true, // Apply access restrictions.
554 + * 'view' => '', // View mode: '', 'events_list', 'api-view'.
555 + *
556 + * // OPTIMIZATION
557 + * 'optimize' => false, // Enable optimized query.
558 + * 'optimize_with_meta' => false, // Fetch meta separately when optimized.
559 + * 'with_all_logs_meta' => false, // Load full logs meta.
560 + *
561 + * // RESULT MODE
562 + * 'count_only' => false, // Return count only.
563 + * 'not_count' => false, // Skip count query.
564 + *
565 + * // SEARCH / FILTERS
566 + * 'search' => '', // Search keyword.
567 + * 'dismiss' => null, // true|false|null.
568 + *
569 + * // GROUP / CLIENT FILTERS
570 + * 'groups_ids' => array(), // Filter by group IDs.
571 + * 'client_ids' => array(), // Filter by client IDs.
572 + *
573 + * // USER FILTERS
574 + * 'usersfilter_sites_ids' => array(), // Format: userId-siteId-isDashboardUser.
575 + * 'user_ids' => array(), // Legacy user filter.
576 + *
577 + * // TIME RANGE
578 + * 'timestart' => 0, // Start timestamp (seconds).
579 + * 'timestop' => 0, // End timestamp (seconds).
580 + *
581 + * // SOURCE FILTER
582 + * 'sources_conds' => '', // 'wp-admin-only'|'dashboard-only'.
583 + *
584 + * // CONTEXT / EVENTS
585 + * 'contexts' => '', // CSV contexts list.
586 + * 'sites_ids' => array(), // Explicit site IDs.
587 + * 'events' => array(), // Actions/events filter.
588 + *
589 + * // PAGINATION
590 + * 'start' => 0, // Offset.
591 + * 'records_per_page' => 0, // LIMIT size (0 = unlimited).
592 + *
593 + * // SORTING
594 + * 'order' => 'DESC', // ASC|DESC.
595 + * 'orderby' => 'created', // Sort column.
596 + *
597 + * // RECENT EVENTS
598 + * 'recent_number' => 0, // Limit to recent logs.
599 + * )
600 + * </code>
601 + *
602 + * @param mixed $input_val Default value.
603 + * @param array $params {
604 + * Optional. Log query parameters.
605 + * }
606 + *
607 + * @return array Results.
608 + *
609 + * @since 6.0.1
610 + */
611 + public function hook_get_log_records( $input_val, $params = array() ) {
612 +
613 + unset( $input_val ); // not used, just for hook compatibility.
614 +
615 + $defaults = array(
616 + // Search param.
617 + 'search' => null,
618 + 'search_field' => 'item',
619 + 'records_per_page' => get_option( 'posts_per_page', 20 ),
620 + 'paged' => 1,
621 + // Order.
622 + 'order' => 'desc',
623 + 'orderby' => 'date',
624 + );
625 +
626 + $args = wp_parse_args( $params, $defaults );
627 +
628 + return (array) $this->db->get_records( $args );
629 + }
630 +
631 +
632 + /**
633 + * Method normalize_to_microseconds()
634 + *
635 + * @param mixed $time float time value or microsecords to check.
636 + *
637 + * @return int Normalize microseconds value.
638 + *
639 + * @since 5.5
640 + */
641 + public static function normalize_to_microseconds( $time ) {
642 + return ( $time < 1e12 ) ? (int) ( $time * 1000000 ) : (int) $time;
227 643 }
228 644 }