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
mainwp / modules / logs / classes / class-log-db-helper.php

class-log-db-helper.php in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.1, at modules/logs/classes/class-log-db-helper.php

670 lines 22.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * MainWP Database Logs.
4 *
5 * This file handles all interactions with the Client DB.
6 *
7 * @package MainWP/Dashboard
8 */
9
10 namespace MainWP\Dashboard\Module\Log;
11
12 use MainWP\Dashboard\MainWP_DB;
13 use MainWP\Dashboard\MainWP_Utility;
14
15 // Exit if accessed directly.
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * Class Log_DB
22 *
23 * @package MainWP\Dashboard
24 */
25 class Log_DB_Helper extends MainWP_DB {
26
27 /**
28 * Protected static variable to hold the single instance of the class.
29 *
30 * @var mixed Default null
31 */
32 protected static $instance = null;
33
34 /**
35 * Return the single instance of the class.
36 *
37 * @return mixed $instance The single instance of the class.
38 */
39 public static function instance() {
40 if ( is_null( static::$instance ) ) {
41 static::$instance = new self();
42 }
43 return static::$instance;
44 }
45
46 /**
47 * Method get_log_by_id().
48 *
49 * @param int $log_id Log id.
50 *
51 * @return object|false log.
52 */
53 public function get_log_by_id( $log_id ) {
54 if ( empty( $log_id ) ) {
55 return false;
56 }
57 return $this->wpdb->get_row( $this->wpdb->prepare( 'SELECT * FROM ' . $this->table_name( 'wp_logs' ) . ' WHERE log_id = %d ', $log_id ) ); //phpcs:ignore -- ok.
58 }
59
60 /**
61 * Method update_log().
62 *
63 * @param array $data Log data.
64 *
65 * @return mixed
66 */
67 public function update_log( $data ) {
68 if ( empty( $data ) || ! is_array( $data ) || empty( $data['log_id'] ) ) {
69 return false;
70 }
71
72 $log_id = $data['log_id'];
73 unset( $data['log_id'] );
74
75 return $this->wpdb->update( $this->table_name( 'wp_logs' ), $data, array( 'log_id' => $log_id ) );
76 }
77
78 /**
79 * Method remove_logs_by().
80 *
81 * @param int $site_id Site ID.
82 *
83 * @return void.
84 */
85 public function remove_logs_by( $site_id ) { //phpcs:ignore -- NOSONAR -complex.
86 //phpcs:disable
87 $this->wpdb->query(
88 $this->wpdb->prepare(
89 'DELETE lo, me
90 FROM ' . $this->table_name( 'wp_logs' ) . ' lo
91 JOIN ' . $this->table_name( 'wp_logs_meta' ) . ' me ON lo.log_id = me.meta_log_id
92 WHERE lo.site_id = %d',
93 $site_id
94 )
95 );
96 //phpcs:enable
97 }
98
99 /**
100 * Method get_sites_options().
101 *
102 * @param array $sites_ids Site Ids array.
103 * @param array $opts Site options array.
104 *
105 * @return array
106 */
107 public function get_sites_options( $sites_ids, $opts = array() ) {
108
109 $where_opts = implode(
110 '" OR name ="',
111 array_map(
112 function ( $val ) {
113 return $this->escape( $val );
114 },
115 $opts
116 )
117 );
118
119 if ( empty( $where_opts ) ) {
120 return array();
121 }
122
123 $sql = sprintf(
124 'SELECT name,value,wpid FROM ' . $this->table_name( 'wp_options' ) . ' WHERE wpid IN ( %s ) AND ( name="' . $where_opts . '" )',
125 implode( ',', array_unique( $sites_ids ) )
126 );
127
128 return $this->wpdb->get_results( $sql ); //phpcs:ignore -- ok.
129 }
130
131 /**
132 * Method dismiss_all_changes().
133 *
134 * Handle dismiss all Network Activity data.
135 *
136 * Compatible method.
137 *
138 * @return mixed
139 */
140 public function dismiss_all_changes() {
141 return $this->wpdb->update( $this->table_name( 'wp_logs' ), array( 'dismiss' => 1 ), array( 'dismiss' => 0 ) );
142 }
143
144
145 /**
146 * Method count_events().
147 *
148 * @return mixed Count events.
149 */
150 public function count_events() {
151 return $this->wpdb->get_var( 'SELECT count(*) FROM ' . $this->table_name( 'wp_logs' ) . ' WHERE dismiss = 0 ' ); //phpcs:ignore -- ok.
152 }
153
154
155 /**
156 * Method get_logs_users().
157 *
158 * @return array Users list.
159 */
160 public function get_logs_users() { //phpcs:ignore -- NOSONAR -complex.
161 $where = MainWP_DB::instance()->get_sql_where_allow_access_sites( 'wp' );
162 $sql = 'SELECT lo.log_id, lo.site_id, lo.user_id, lo.user_login, lo.connector, wp.name, me.meta_log_id, me.meta_key, me.meta_value '
163 . ', CASE
164 WHEN connector != "non-mainwp-changes" THEN "dashboard"
165 ELSE "wpadmin"
166 END AS log_source '
167 . ' FROM ' . $this->table_name( 'wp_logs' ) . ' lo '
168 . ' LEFT JOIN ' . $this->table_name( 'wp' ) . ' wp ON lo.site_id = wp.id '
169 . ' LEFT JOIN ' . $this->table_name( 'wp_logs_meta' ) . ' me ON lo.log_id = me.meta_log_id '
170 . ' WHERE me.meta_key = "user_meta_json" '
171 . $where
172 . ' GROUP BY site_id, user_id, log_source ';
173
174 $users_sites_logs = $this->wpdb->get_results( $sql ); //phpcs:ignore -- ok.
175 if ( $users_sites_logs ) {
176 $logs_users = array();
177 $dash_users = array();
178 foreach ( $users_sites_logs as $item ) {
179 if ( ! empty( $item->site_id ) && ! empty( $item->name ) && ! empty( $item->meta_value ) ) {
180 $info = json_decode( $item->meta_value, true );
181 if ( is_array( $info ) ) {
182 if ( 'non-mainwp-changes' === $item->connector ) { // child site users ID, 0 is child site system user.
183 if ( ! empty( isset( $info['user_id'] ) ) && ! empty( $info['user_login'] ) ) {
184 $act_user = $info['user_login'];
185 } elseif ( isset( $info['action_user'] ) ) { // to compatible.
186 $act_user = $info['action_user'];
187 } else {
188 $act_user = $item->user_login;
189 }
190
191 if ( 'wp_cron' === $act_user ) {
192 $act_user = __( 'during WP Cron', 'mainwp' );
193 }
194
195 $logs_users[ $item->log_id ] = array(
196 'id' => $item->user_id,
197 'site_id' => $item->site_id,
198 'login' => $act_user,
199 'nicename' => isset( $info['display_name'] ) ? $info['display_name'] : $item->user_login,
200 'source' => ! empty( $item->name ) ? $item->name : '', // site name.
201 'is_dashboard_user' => 0,
202 );
203 } else { // dashboard users.
204 // to prevent add double dashboard users in the users selection.
205 if ( in_array( $item->user_id, $dash_users ) ) {
206 continue;
207 }
208
209 $user_login = '';
210
211 if ( ! empty( $item->user_login ) ) {
212 $user_login = $item->user_login;
213 } elseif ( ! empty( $info['user_login'] ) ) { // compatible user login value.
214 $user_login = $info['user_login'];
215 }
216
217 $dash_users[] = $item->user_id;
218
219 $nicename = $user_login;
220 if ( empty( $nicename ) ) {
221 if ( ! empty( $info['agent'] ) ) {
222 $nicename = $info['agent'];
223 if ( 'wp_cron' === $nicename ) {
224 $nicename = __( 'during WP Cron', 'mainwp' );
225 }
226 } else {
227 $nicename = 'N/A';
228 }
229 }
230 $logs_users[ $item->log_id ] = array(
231 'id' => (int) $item->user_id,
232 'site_id' => $item->site_id,
233 'login' => $user_login,
234 'nicename' => $nicename,
235 'source' => 'dashboard',
236 'is_dashboard_user' => 1,
237 );
238 }
239 }
240 }
241 }
242 return $logs_users;
243 }
244
245 return array();
246 }
247
248 /**
249 * Method get_logs_db_stats().
250 *
251 * @return array DB stats.
252 */
253 public function get_logs_db_stats() {
254 $sql_meta = 'SELECT meta_key, COUNT(*) AS total
255 FROM ' . $this->table_name( 'wp_logs_meta' ) . '
256 GROUP BY meta_key
257 ORDER BY total DESC';
258 $sql_total = 'SELECT COUNT(*) AS total FROM ' . $this->table_name( 'wp_logs' );
259 $sql_meta_total = 'SELECT COUNT(*) AS total FROM ' . $this->table_name( 'wp_logs_meta' );
260 return array(
261 'logs_count' => $this->wpdb->get_var( $sql_total ), //phpcs:ignore --NOSONAR -ok.
262 'logs_meta_count' => $this->wpdb->get_var( $sql_meta_total ), //phpcs:ignore --NOSONAR -ok.
263 'logs_meta_db_info' => $this->wpdb->get_results( $sql_meta ), //phpcs:ignore --NOSONAR -ok.
264 );
265 }
266
267 /**
268 * Method get_changes_logs_by().
269 *
270 * @param array $args Arguments.
271 *
272 * @return array DB stats.
273 */
274 public function get_changes_logs_by( $args ) { // phpcs:ignore -- NOSONAR - complex.
275
276 $site_id = isset( $args['wpid'] ) ? $args['wpid'] : 0; // int or array of int site ids.
277
278 $from_date = ! empty( $args['from_date'] ) ? sanitize_text_field( $args['from_date'] ) : gmdate( 'Y-m-d' );
279 $days_number = isset( $args['days_number'] ) ? intval( $args['days_number'] ) : 10;
280 $slug_value = isset( $args['slug'] ) ? sanitize_text_field( $args['slug'] ) : '';
281 $name_value = isset( $args['name'] ) ? sanitize_text_field( $args['name'] ) : ''; // supports in case slug are empty.
282 $type = isset( $args['type'] ) ? sanitize_text_field( $args['type'] ) : '';
283
284 $target_dt = ! empty( $args['target_date'] ) ? sanitize_text_field( $args['target_date'] ) : '';
285
286 if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
287 return array();
288 }
289
290 global $wpdb;
291
292 if ( ! empty( $target_dt ) ) {
293
294 $utc_start = MainWP_Utility::get_utc_timestamp_by_date( $target_dt );
295 $utc_end = MainWP_Utility::get_utc_timestamp_by_date( $target_dt, 1 );
296
297 $utc_start_micro = (int) $utc_start * 1000000;
298 $utc_end_micro = (int) $utc_end * 1000000;
299
300 $where_site = '';
301 $prepare_args = array( $type, $utc_start_micro, $utc_end_micro );
302
303 if ( ! empty( $site_id ) ) {
304 $where_site = ' AND i.site_id = %d';
305 $prepare_args[] = $site_id;
306 }
307
308 $query = '
309 SELECT i.*
310 FROM ' . $this->table_name( 'wp_logs' ) . ' i
311 WHERE i.context = %s
312 AND i.created >= %d
313 AND i.created < %d
314 ' . $where_site . '
315 ORDER BY i.created ASC
316 ';
317
318 $sql = $wpdb->prepare(
319 $query, //phpcs:ignore --ok.
320 ...$prepare_args
321 );
322
323 $items = $wpdb->get_results( $sql); //phpcs:ignore --ok.
324
325 $this->get_meta_items( $items );
326
327 return array(
328 'items' => $items,
329 'onward_time' => 0,
330 'total' => count( $items ),
331 'more_date' => '',
332 );
333
334 }
335
336 if ( empty( $site_id ) || ( empty( $slug_value ) && empty( $name_value ) ) || empty( $days_number ) ) {
337 return array();
338 }
339
340 $join_meta_cond = '';
341 $extra_prepare = array();
342
343 if ( 'plugin' === $type && ! empty( $slug_value ) ) {
344 $join_meta_cond = ' AND ( m.meta_key = "slug" AND ( m.meta_value = %s OR m.meta_value = %s ) ) ';
345 $extra_prepare[] = $slug_value;
346 $extra_prepare[] = $slug_value . '/' . basename( $slug_value, '.php' ) . '.php';
347 } elseif ( 'theme' === $type ) {
348 $join_meta_cond = ' AND ( ( m.meta_key = "slug" AND m.meta_value = %s ) OR ( m.meta_key = "name" AND m.meta_value = %s ) ) ';
349 $extra_prepare[] = $slug_value;
350 $extra_prepare[] = $name_value;
351 } else {
352 return array();
353 }
354
355 $onward = '
356 SELECT i.*
357 FROM ' . $this->table_name( 'wp_logs' ) . ' i
358 LEFT JOIN ' . $this->table_name( 'wp_logs_meta' ) . ' m
359 ON i.log_id = m.meta_log_id
360 ' . $join_meta_cond . '
361 WHERE i.site_id = %d
362 AND i.context = %s
363 ORDER BY i.created ASC
364 LIMIT 1;
365 ';
366
367 $onward_params = array_merge( array( $site_id, $type ), $extra_prepare );
368
369 $sql_onward = $wpdb->prepare(
370 $onward, //phpcs:ignore --ok.
371 ...$onward_params
372 );
373
374 $found = $wpdb->get_row( $sql_onward ); //phpcs:ignore --ok.
375
376 $count = '
377 SELECT count(*)
378 FROM ' . $this->table_name( 'wp_logs' ) . ' i
379 LEFT JOIN ' . $this->table_name( 'wp_logs_meta' ) . ' m
380 ON i.log_id = m.meta_log_id
381 ' . $join_meta_cond . '
382 WHERE i.site_id = %d
383 AND i.context = %s';
384
385 $count_params = array_merge( array( $site_id, $type ), $extra_prepare );
386
387 $sql_count = $wpdb->prepare(
388 $count, //phpcs:ignore --ok.
389 ...$count_params
390 );
391
392 $total_count = $wpdb->get_var( $sql_count ); //phpcs:ignore --ok.
393
394 global $wpdb;
395
396 $ctx = MainWP_Utility::get_time_context( $from_date );
397
398 $day_micros = $ctx['day_micros'];
399 $offset_micro = $ctx['offset_micro'];
400 $from_date_utc = $ctx['from_date_utc'];
401
402 $sql = "
403 SELECT
404 d.day_start,
405 i.*,
406 MAX(m.meta_value) as meta_value
407 FROM (
408 SELECT
409 ((FLOOR((created + %d) / %d) * %d) - %d) AS day_start
410 FROM {$this->table_name('wp_logs')}
411 WHERE site_id = %d
412 AND created < (UNIX_TIMESTAMP(%s) * 1000000)
413 GROUP BY day_start
414 ORDER BY day_start DESC
415 LIMIT %d
416 ) d
417 JOIN {$this->table_name('wp_logs')} i
418 ON i.created >= d.day_start
419 AND i.created < d.day_start + %d
420 AND i.site_id = %d
421 AND i.context = %s
422 LEFT JOIN {$this->table_name('wp_logs_meta')} m
423 ON i.log_id = m.meta_log_id
424 {$join_meta_cond}
425 WHERE 1=1
426 GROUP BY i.log_id, d.day_start
427 ORDER BY d.day_start DESC, i.created DESC
428 ;";
429
430 $prepare_params = array(
431 $offset_micro,
432 $day_micros,
433 $day_micros,
434 $offset_micro,
435 $site_id,
436 $from_date_utc,
437 $days_number,
438 $day_micros,
439 $site_id,
440 $type,
441 );
442
443 $prepare_params = array_merge( $prepare_params, $extra_prepare );
444
445 $query = $wpdb->prepare(
446 $sql, //phpcs:ignore --ok.
447 ...$prepare_params
448 );
449
450 $items = $wpdb->get_results( $query ); //phpcs:ignore --ok.
451
452 if ( $items ) {
453 $this->get_meta_items( $items );
454 }
455
456 if ( ! empty( $items ) && in_array( $type, array( 'plugin', 'theme' ) ) && ! empty( $slug_value ) ) {
457 $items = array_filter(
458 $items,
459 function ( $item ) use ( $slug_value, $type, $name_value ) {
460 $item_slug = '';
461 $item_name = '';
462
463 if ( ! empty( $item->meta['slug'] ) ) {
464 $item_slug = $item->meta['slug'];
465 }
466
467 if ( ! empty( $item->meta['name'] ) ) {
468 $item_name = $item->meta['name'];
469 }
470
471 if ( empty( $item_slug ) && ! empty( $item->extra_info ) ) {
472 $extra_data = json_decode( $item->extra_info, true );
473 if ( is_array( $extra_data ) && ! empty( $extra_data['slug'] ) ) {
474 $item_slug = $extra_data['slug'];
475 }
476 }
477
478 if ( 'theme' === $type ) {
479 if ( ! empty( $slug_value ) && ! empty( $item_slug ) && $item_slug === $slug_value ) {
480 return true;
481 }
482 if ( ! empty( $name_value ) && ! empty( $item_name ) && $item_name === $name_value ) {
483 return true;
484 }
485 return false;
486 }
487
488 if ( empty( $item_slug ) ) {
489 return false;
490 }
491
492 if ( false !== strpos( $item_slug, '/' ) ) {
493 $parts = explode( '/', $item_slug );
494 $normalized_item = $parts[0];
495 } else {
496 $normalized_item = basename( $item_slug, '.php' );
497 }
498
499 if ( false !== strpos( $slug_value, '/' ) ) {
500 $parts = explode( '/', $slug_value );
501 $normalized_search = $parts[0];
502 } else {
503 $normalized_search = basename( $slug_value, '.php' );
504 }
505
506 return $normalized_item === $normalized_search;
507 }
508 );
509
510 $items = array_values( $items );
511 }
512
513 $more_date = '';
514
515 $first_created = 0;
516
517 if ( ! empty( $items ) && ! empty( $found ) ) {
518 $first_created = $found ? intval( $found->created / 1000000 ) : 0;
519 $end = end( $items );
520 $end_created = $end && ! empty( $end->created ) ? $end->created / 1000000 : 0;
521
522 if ( $end_created && $first_created ) {
523 $more_date = gmdate( 'Y-m-d', (int) $end_created );
524 $more_date = gmdate( 'Y-m-d', (int) $first_created ) === $more_date ? '' : $more_date;
525 }
526 }
527
528 if ( ! empty( $more_date ) ) {
529 $more_date = gmdate( 'Y-m-d', strtotime( $more_date . ' -1 day' ) ); // previous day.
530 }
531
532 return array(
533 'items' => $items,
534 'onward_time' => $first_created,
535 'total' => $total_count ? $total_count : 0,
536 'more_date' => $more_date,
537 );
538 }
539
540
541 /**
542 * Get meta data of logs.
543 */
544 public function get_meta_items( &$items ) { // phpcs:ignore -- NOSONAR - complex.
545
546 global $wpdb;
547
548 if ( ! is_array( $items ) || empty( $items ) ) {
549 return;
550 }
551
552 $ids = array_map( 'absint', wp_list_pluck( $items, 'log_id' ) );
553
554 $start_slice = 0;
555 $max_slice = 100;
556 $count = count( $ids );
557
558 while ( $start_slice <= $count ) {
559 $slice_ids = array_slice( $ids, $start_slice, $max_slice );
560 $start_slice += $max_slice;
561
562 if ( ! empty( $slice_ids ) ) {
563
564 $sql_meta = sprintf(
565 'SELECT * FROM ' . $this->table_name( 'wp_logs_meta' ) . ' WHERE meta_log_id IN ( %s )',
566 implode( ',', $slice_ids )
567 );
568
569 $meta_records = $wpdb->get_results( $sql_meta ); //phpcs:ignore -- ok.
570 $ids_flip = array_flip( $ids );
571
572 if ( is_array( $meta_records ) ) {
573 foreach ( $meta_records as $meta_record ) {
574 if ( ! empty( $meta_record->meta_value ) ) {
575 if ( in_array( $meta_record->meta_key, array( 'user_meta_json', 'user_login', 'extra_info' ) ) ) {
576 $items[ $ids_flip[ $meta_record->meta_log_id ] ]->{$meta_record->meta_key} = $meta_record->meta_value;
577 } else {
578 if ( empty( $items[ $ids_flip[ $meta_record->meta_log_id ] ]->meta ) ) {
579 $items[ $ids_flip[ $meta_record->meta_log_id ] ]->meta = array();
580 }
581 $items[ $ids_flip[ $meta_record->meta_log_id ] ]->meta[ $meta_record->meta_key ] = $meta_record->meta_value;
582 }
583 }
584 }
585 }
586 }
587 }
588 }
589
590 /**
591 * Get db size.
592 *
593 * @return string Return current db size.
594 */
595 public function count_legacy_dismissed() {
596 return $this->wpdb->get_var( 'SELECT count(*) FROM ' . $this->table_name( 'wp_logs' ) . ' WHERE dismiss = 1 ' ); //phpcs:ignore -- ok.
597 }
598
599 /**
600 * Get db size.
601 *
602 * @return string Return current db size.
603 */
604 public function get_db_size() {
605 $cache_key = 'db_logs_size';
606 $cache_group = 'mainwp_module_log';
607
608 $size = wp_cache_get( $cache_key, $cache_group );
609
610 if ( false !== $size ) {
611 return $size;
612 }
613
614 $size = get_transient( 'mainwp_module_log_transient_db_logs_size' );
615
616 if ( false !== $size ) {
617 wp_cache_set( $cache_key, $size, $cache_group, 15 * MINUTE_IN_SECONDS );
618 return $size;
619 }
620
621 global $wpdb;
622
623 $sql = $wpdb->prepare(
624 'SELECT
625 ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2)
626 FROM INFORMATION_SCHEMA.TABLES
627 WHERE TABLE_SCHEMA = %s
628 AND (
629 table_name = %s
630 OR table_name = %s
631 OR table_name = %s
632 OR table_name = %s
633 )',
634 $wpdb->dbname,
635 $wpdb->mainwp_tbl_logs,
636 $wpdb->mainwp_tbl_logs_meta,
637 $this->table_name( 'wp_logs_archive' ),
638 $this->table_name( 'wp_logs_meta_archive' )
639 );
640
641 $dbsize_mb = (float) $wpdb->get_var( $sql ); // phpcs:ignore
642
643 set_transient( 'mainwp_module_log_transient_db_logs_size', $dbsize_mb, 15 * MINUTE_IN_SECONDS );
644 wp_cache_set( $cache_key, $dbsize_mb, $cache_group, 15 * MINUTE_IN_SECONDS );
645
646 return $dbsize_mb;
647 }
648
649 /**
650 * Get child logs db synced size.
651 *
652 * @return string Return current db size.
653 */
654 public function get_child_logs_db_size() {
655
656 $wp_table = esc_sql( $this->table_name( 'wp' ) );
657 $wp_opt_table = esc_sql( $this->table_name( 'wp_options' ) );
658
659 return $this->wpdb->get_results(
660 "
661 SELECT wp.id,wp.url,wp.name, COALESCE(o.value, 0) AS dbsize_activitylogs
662 FROM {$wp_table} wp
663 LEFT JOIN {$wp_opt_table} o
664 ON o.wpid = wp.id
665 AND o.name = 'dbsize_activitylogs'
666 "
667 );
668 }
669 }
670