2026-02-05 17:08:59 +03:00

420 lines
18 KiB
PHP

<?php
/**
* Queries the database for logs records.
*
* @package MainWP/Dashboard
*/
namespace MainWP\Dashboard\Module\Log;
use MainWP\Dashboard\MainWP_DB;
use MainWP\Dashboard\MainWP_DB_Client;
use MainWP\Dashboard\MainWP_Utility;
/**
* Class - Log_Query
*/
class Log_Query {
/**
* Hold the number of records found
*
* @var int
*/
public $found_records = 0;
/**
* Query records
*
* @param array $args Arguments to filter the records by.
*
* @return array Logs Records
*/
public function query( $args ) { //phpcs:ignore -- NOSONAR - complex method.
global $wpdb;
// To support none mainwp actions.
$log_id = isset( $args['log_id'] ) ? intval( $args['log_id'] ) : 0;
$site_id = isset( $args['wpid'] ) ? $args['wpid'] : 0; // int or array of int site ids.
$object_id = isset( $args['object_id'] ) ? sanitize_text_field( $args['object_id'] ) : '';
$where_extra = ''; // compatible.
$check_access = isset( $args['check_access'] ) ? $args['check_access'] : true;
$view = isset( $args['view'] ) ? sanitize_text_field( $args['view'] ) : '';
$join = '';
$where = '';
$count_only = ! empty( $args['count_only'] ) ? true : false;
$not_count = ! empty( $args['not_count'] ) ? true : false;
$mt_search = false;
if ( ! empty( $args['search'] ) ) {
$search_str = MainWP_DB::instance()->escape( $args['search'] );
// for searching.
if ( ! empty( $search_str ) ) {
$search_str = trim( $search_str );
// prepare search value for searching.
if ( ! empty( $search_str ) ) {
$where_search = ' AND ( wp.name LIKE "%' . $search_str . '%" OR lg.action LIKE "%' . $search_str . '%" OR sub_lg.action_display LIKE "%' . $search_str . '%" OR lg.log_id LIKE "%' . $search_str . '%" OR lg.user_id LIKE "%' . $search_str . '%" ';
$where_search .= ' OR lg.item LIKE "%' . $search_str . '%" ';
if ( 'events_list' === $view ) {
$where_search .= ' OR sub_lg.source LIKE "%' . $search_str . '%" ';
$where_search .= ' OR meta_view.user_login LIKE "%' . $search_str . '%" ';
$mt_search = true;
}
$where_search .= ') ';
$where .= $where_search;
}
}
}
if ( isset( $args['dismiss'] ) ) {
$where .= ' AND lg.dismiss = ' . ( ! empty( $args['dismiss'] ) ? 1 : 0 ) . ' ';
}
if ( ! empty( $args['groups_ids'] ) && is_array( $args['groups_ids'] ) ) {
$array_groups_ids = MainWP_Utility::array_numeric_filter( $args['groups_ids'] );
if ( ! empty( $array_groups_ids ) ) {
$groups_sites = MainWP_DB_Client::instance()->get_websites_by_group_ids( $array_groups_ids );
$array_website_ids = array();
if ( $groups_sites ) {
foreach ( $groups_sites as $website ) {
$array_website_ids[] = $website->id;
}
}
unset( $groups_sites );
if ( ! empty( $array_website_ids ) ) {
$where .= " AND lg.site_id IN ('" . implode( "','", $array_website_ids ) . "') ";
} else {
$where .= ' AND false ';
}
}
}
if ( ! empty( $args['client_ids'] ) && is_array( $args['client_ids'] ) ) {
$array_clients_ids = MainWP_Utility::array_numeric_filter( $args['client_ids'] );
if ( ! empty( $array_clients_ids ) ) {
$client_sites = MainWP_DB_Client::instance()->get_websites_by_client_ids( $array_clients_ids );
$array_website_ids = array();
if ( $client_sites ) {
foreach ( $client_sites as $website ) {
$array_website_ids[] = $website->id;
}
}
unset( $client_sites );
if ( ! empty( $array_website_ids ) ) {
$where .= " AND lg.site_id IN ('" . implode("','",$array_website_ids) . "') "; // phpcs:ignore -- ok.
} else {
$where .= ' AND false ';
}
}
}
$where_users_filter = '';
if ( ! empty( $args['usersfilter_sites_ids'] ) && is_array( $args['usersfilter_sites_ids'] ) ) {
$usersfilter_sites_ids = $args['usersfilter_sites_ids'];
$cond_users = array();
foreach ( $usersfilter_sites_ids as $user_filter ) {
if ( false !== strpos( $user_filter, '-' ) ) { // new users filter.
list( $uid, $sid, $is_dash_user ) = explode( '-', $user_filter );
if ( $is_dash_user ) {
$cond_users[] = ' lg.user_id = ' . (int) $uid . ' AND lg.connector != "non-mainwp-changes" '; // dashboard user does not need to check site ids.
} else {
$cond_users[] = ' lg.user_id = ' . (int) $uid . ' AND lg.site_id = ' . (int) $sid . ' AND lg.connector = "non-mainwp-changes" '; // child site user need to check site ids.
}
}
}
if ( ! empty( $cond_users ) ) {
$where_users_filter = ' AND ( ' . implode( ') OR (', $cond_users ) . ') ';
}
} elseif ( ! empty( $args['user_ids'] ) && is_array( $args['user_ids'] ) ) { // compatible.
$array_users_ids = MainWP_Utility::array_numeric_filter( $args['user_ids'] );
if ( ! empty( $array_users_ids ) ) {
$where .= " AND lg.user_id IN ('" . implode("','",$array_users_ids) . "') "; // phpcs:ignore -- ok.
}
}
if ( ! empty( $args['timestart'] ) && ! empty( $args['timestop'] ) ) {
$where .= $wpdb->prepare( ' AND `lg`.`created` >= %d AND `lg`.`created` <= %d', $args['timestart'], $args['timestop'] );
}
// available sources conds values: wp-admin-only|dashboard-only|empty.
if ( ! empty( $args['sources_conds'] ) ) {
if ( 'wp-admin-only' === $args['sources_conds'] ) {
$where .= ' AND `lg`.`connector` = "non-mainwp-changes" ';
} elseif ( 'dashboard-only' === $args['sources_conds'] ) {
$where .= ' AND `lg`.`connector` != "non-mainwp-changes" ';
}
}
if ( ! empty( $args['contexts'] ) ) {
$contexts_list = explode( ',', $args['contexts'] );
$contexts_list = array_map(
function ( $value ) {
return MainWP_DB::instance()->escape( $value );
},
(array) $contexts_list
);
$contexts_list = array_filter( $contexts_list );
if ( ! empty( $contexts_list ) ) {
$where .= ' AND lg.context IN ( "' . implode( '","', $contexts_list ) . '" ) ';
}
}
if ( ! empty( $args['sites_ids'] ) ) {
$where_site_ids = implode( ',', array_filter( array_map( 'intval', (array) $args['sites_ids'] ) ) );
if ( ! empty( $where_site_ids ) ) {
$where .= ' AND lg.site_id IN ( ' . $where_site_ids . ' ) ';
}
}
if ( ! empty( $args['events'] ) ) {
$events_list = array_map(
function ( $value ) {
return MainWP_DB::instance()->escape( $value );
},
(array) $args['events']
);
$events_list = array_filter( $events_list );
if ( ! empty( $events_list ) ) {
$where .= ' AND lg.action IN ( "' . implode( '","', $events_list ) . '" ) ';
}
}
/**
* PARSE PAGINATION PARAMS
*/
$limits = '';
$start = absint( $args['start'] );
$per_page = absint( $args['records_per_page'] );
if ( $per_page > 0 ) {
$limits = "LIMIT {$start}, {$per_page}";
}
// Show the recent records first by default.
$order = 'DESC';
if ( 'ASC' === strtoupper( $args['order'] ) ) {
$order = 'ASC';
}
/**
* PARSE ORDER PARAMS
*/
$orderable = array( 'site_id', 'name', 'url', 'user_id', 'item', 'created', 'connector', 'context', 'action', 'event', 'duration', 'state' );
// Default to sorting by.
$orderby = 'lg.created';
if ( in_array( $args['orderby'], $orderable, true ) ) {
if ( in_array( $args['orderby'], array( 'name', 'url' ) ) ) {
$orderby = sprintf( '%s.%s', 'meta_view', $args['orderby'] );
} elseif ( 'event' === $args['orderby'] ) {
$orderby = sprintf( '%s.%s', 'lg', 'action' );
} else {
$orderby = sprintf( '%s.%s', 'lg', $args['orderby'] );
}
}
if ( 'source' === $args['orderby'] ) {
$orderby = " ORDER BY
CASE
WHEN connector = 'non-mainwp-changes' THEN 2
ELSE 1
END " . $order;
} elseif ( 'log_object' === $args['orderby'] ) {
$orderby = sprintf( 'ORDER BY %s %s', $orderby, $order );
} else {
$orderby = sprintf( 'ORDER BY %s %s', $orderby, $order );
}
$where_actions = '';
if ( ! empty( $log_id ) ) {
$where_actions .= ' AND lg.log_id = ' . $log_id;
} else {
$sql_and = '';
if ( ! empty( $site_id ) ) {
if ( is_array( $site_id ) ) {
$site_ids = array_map( 'intval', $site_id );
$site_ids = array_filter( $site_ids );
if ( ! empty( $site_ids ) ) {
$site_ids = implode( ',', $site_ids );
$sql_and = ' AND ';
$where_actions .= $sql_and . ' lg.site_id IN ( ' . $site_ids . ' )';
}
} elseif ( is_numeric( $site_id ) ) {
$sql_and = ' AND ';
$where_actions .= $sql_and . ' lg.site_id = ' . intval( $site_id );
}
}
if ( ! empty( $object_id ) ) {
if ( empty( $sql_and ) ) {
$sql_and = ' AND ';
}
$where_actions .= $sql_and . ' lg.object_id = "' . esc_sql( $object_id ) . '" ';
}
}
if ( $check_access && 'api-view' !== $view ) {
$where_actions .= MainWP_DB::instance()->get_sql_where_allow_access_sites( 'wp' );
}
$where_dismiss = ! empty( $args['dismiss'] ) ? ' AND dismiss = 1 ' : ' AND dismiss = 0 ';
$where .= $where_actions . $where_extra . $where_dismiss;
/**
* PARSE FIELDS PARAMETER
*/
$selects = array();
$selects[] = 'lg.*';
if ( 'api-view' !== $view ) {
$selects[] = 'wp.url as url';
$selects[] = 'wp.name as log_site_name';
}
$selects[] = 'meta_view.*';
$select = implode( ', ', $selects );
if ( 'api-view' !== $view ) {
$join = ' LEFT JOIN ' . $wpdb->mainwp_tbl_wp . ' wp ON lg.site_id = wp.id ';
}
$mt_params = array();
if ( $mt_search ) {
$mt_params['user_login'] = true;
}
$join .= ' LEFT JOIN ' . $this->get_log_meta_view( $mt_params ) . ' meta_view ON lg.log_id = meta_view.view_log_id ';
if ( 'events_list' === $view ) {
$join .= ' LEFT JOIN ' . $this->get_sub_query_view() . ' sub_lg ON lg.log_id = sub_lg.sub_log_id ';
}
$recent_where = '';
$recent_query = '';
// list recent events.
if ( ! empty( $args['recent_number'] ) ) {
$recent_limits = ' LIMIT ' . intval( $args['recent_number'] );
$recent_query = "SELECT MAX( lg.created )
FROM $wpdb->mainwp_tbl_logs as lg
{$join}
WHERE `lg`.`connector` != 'compact' AND lg.dismiss = 0 ORDER BY lg.created DESC {$recent_limits}";
$recent_created = $wpdb->get_var( $recent_query ); //phpcs:ignore -- NOSONAR - ok.
$recent_where = ' AND lg.created <= ' . (int) $recent_created;
}
/**
* BUILD THE FINAL QUERY
*/
$query = "SELECT {$select}
FROM $wpdb->mainwp_tbl_logs as lg
{$join}
WHERE `lg`.`connector` != 'compact' {$where} {$recent_where} {$where_users_filter}
{$orderby}
{$limits}";
// Build result count query.
$count_query = "SELECT COUNT(*)
FROM $wpdb->mainwp_tbl_logs as lg
{$join}
WHERE `lg`.`connector` != 'compact' {$where} {$recent_where} {$where_users_filter}";
// Generate cache key for count query.
$cache_key = 'mainwp_logs_count_' . md5( serialize( $args ) ); // NOSONAR - MD5 used for cache key generation only, not cryptographic purposes.
if ( $count_only ) {
$cached_count = wp_cache_get( $cache_key, 'mainwp_logs' );
if ( false !== $cached_count ) {
return array(
'count' => $cached_count,
);
}
$count = absint( $wpdb->get_var( $count_query ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Count query is built with properly escaped and validated SQL fragments (WHERE clauses use escape(), intval(), and sanitize_text_field(); JOIN and recent_where are static or int-cast).
wp_cache_set( $cache_key, $count, 'mainwp_logs', HOUR_IN_SECONDS );
return array(
'count' => $count,
);
}
if ( ! empty( $args['dev_log_query'] ) ) {
//phpcs:disable Squiz.PHP.CommentedOutCode.Found,WordPress.PHP.DevelopmentFunctions
error_log( print_r( $args, true ) );
error_log( $recent_query );
error_log( $query );
error_log( $count_query );
//phpcs:enable Squiz.PHP.CommentedOutCode.Found,WordPress.PHP.DevelopmentFunctions
}
/**
* QUERY THE DATABASE FOR RESULTS
*/
$results = array(
'items' => $wpdb->get_results( $query ), // phpcs:ignore -- ok.
);
if ( ! $not_count ) {
$cached_count = wp_cache_get( $cache_key, 'mainwp_logs' );
if ( false !== $cached_count ) {
$results['count'] = $cached_count;
} else {
$count = absint( $wpdb->get_var( $count_query ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,PluginCheck.Security.DirectDB.UnescapedDBParameter -- Count query is built with properly escaped and validated SQL fragments (WHERE clauses use escape(), intval(), and sanitize_text_field(); JOIN and recent_where are static or int-cast).
wp_cache_set( $cache_key, $count, 'mainwp_logs', HOUR_IN_SECONDS );
$results['count'] = $count;
}
}
return $results;
}
/**
* Get logs meta database table view.
*
* @param array $params Params.
* @return string logs meta view.
*/
public function get_log_meta_view( $params = array() ) {
global $wpdb;
$view = '(SELECT intlog.log_id AS view_log_id, ';
$view .= '(SELECT meta_name.meta_value FROM ' . $wpdb->mainwp_tbl_logs_meta . ' meta_name WHERE meta_name.meta_log_id = intlog.log_id AND meta_name.meta_key = "name" LIMIT 1) AS meta_name, ';
$view .= '(SELECT user_meta_json.meta_value FROM ' . $wpdb->mainwp_tbl_logs_meta . ' user_meta_json WHERE user_meta_json.meta_log_id = intlog.log_id AND user_meta_json.meta_key = "user_meta_json" LIMIT 1) AS user_meta_json, ';
$view .= '(SELECT usermeta.meta_value FROM ' . $wpdb->mainwp_tbl_logs_meta . ' usermeta WHERE usermeta.meta_log_id = intlog.log_id AND usermeta.meta_key = "user_meta" LIMIT 1) AS usermeta, '; // compatible user_meta data.
if ( ! empty( $params['user_login'] ) ) {
$view .= '(SELECT user_login.meta_value FROM ' . $wpdb->mainwp_tbl_logs_meta . ' user_login WHERE user_login.meta_log_id = intlog.log_id AND user_login.meta_key = "user_login" LIMIT 1) AS user_login, ';
}
$view .= '(SELECT extra_info.meta_value FROM ' . $wpdb->mainwp_tbl_logs_meta . ' extra_info WHERE extra_info.meta_log_id = intlog.log_id AND extra_info.meta_key = "extra_info" LIMIT 1) AS extra_info ';
$view .= ' FROM ' . $wpdb->mainwp_tbl_logs . ' intlog)';
return $view;
}
/**
* Method get_sub_query to support seaching in events table.
*
* @return string sub query view.
*/
public function get_sub_query_view() {
global $wpdb;
$view = ' (SELECT sub_tbl.log_id AS sub_log_id, ';
$view .= ' CASE WHEN sub_tbl.connector = "non-mainwp-changes" THEN "WP Admin" ';
$view .= ' ELSE "Dashboard" ';
$view .= ' END AS source, ';
// to support searching on events column.
$view .= " CASE
WHEN sub_tbl.action = 'sync' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Sync Data', 'mainwp' ) ) . "'
WHEN sub_tbl.action = 'activate' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Activated', 'mainwp' ) ) . "'
WHEN sub_tbl.action = 'deactivate' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Deactivated', 'mainwp' ) ) . "'
WHEN sub_tbl.action = 'install' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Installed', 'mainwp' ) ) . "'
WHEN sub_tbl.action = 'updated' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Updated', 'mainwp' ) ) . "'
WHEN sub_tbl.action = 'delete' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Deleted', 'mainwp' ) ) . "'
WHEN sub_tbl.action = 'suspend' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Suspended', 'mainwp' ) ) . "'
ELSE sub_tbl.action
END AS action_display ";
$view .= ' FROM ' . $wpdb->mainwp_tbl_logs . ' sub_tbl) ';
return $view;
}
}