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 . '%" '; $where_search .= ' OR ( CASE '; $where_search .= " WHEN lg.action = 'sync' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Sync Data', 'mainwp' ) ) . "' "; $where_search .= " WHEN lg.action = 'activate' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Activated', 'mainwp' ) ) . "' "; $where_search .= " WHEN lg.action = 'deactivate' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Deactivated', 'mainwp' ) ) . "' "; $where_search .= " WHEN lg.action = 'install' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Installed', 'mainwp' ) ) . "' "; $where_search .= " WHEN lg.action = 'updated' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Updated', 'mainwp' ) ) . "' "; $where_search .= " WHEN lg.action = 'delete' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Deleted', 'mainwp' ) ) . "' "; $where_search .= " WHEN lg.action = 'suspend' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Suspended', 'mainwp' ) ) . "' "; $where_search .= ' ELSE lg.action '; $where_search .= ' END ) LIKE "%' . $search_str . '%" '; $where_search .= ' 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 ( CASE '; $where_search .= " WHEN lg.connector = 'non-mainwp-changes' THEN 'WP Admin' "; $where_search .= " ELSE 'Dashboard' "; $where_search .= ' END ) LIKE "%' . $search_str . '%" '; $where_search .= ' OR lg.user_login LIKE "%' . $search_str . '%" '; } $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'] ) ) { $timestart_us = (int) $args['timestart'] * 1000000; $timestop_us = (int) $args['timestop'] * 1000000; $where .= $wpdb->prepare( ' AND `lg`.`created` >= %d AND `lg`.`created` <= %d', $timestart_us, $timestop_us ); } // 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', $args['orderby'] ); } elseif ( 'event' === $args['orderby'] || 'action' === $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 ); } } } $where .= $where_actions . $where_extra; /** * PARSE FIELDS PARAMETER */ $selects = array(); $selects[] = 'lg.*'; if ( 'api-view' !== $view ) { $selects[] = 'wp.url as url'; $selects[] = 'wp.name as log_site_name'; } if ( 'api-view' !== $view ) { $join = ' INNER JOIN ' . $wpdb->mainwp_tbl_wp . ' wp ON lg.site_id = wp.id '; // Improve query. if ( $check_access && 'api-view' !== $view ) { $join .= MainWP_DB::instance()->get_sql_where_allow_access_sites( 'wp' ); // Example: AND wp.is_staging = 0 or empty. } } $mt_params = array(); $optimize_get_meta = false; if ( ! $optimize_get_dt ) { $join_meta .= ' LEFT JOIN ' . $this->get_log_meta_view( $mt_params ) . ' meta_view ON lg.log_id = meta_view.log_id '; } elseif ( $opti_with_meta ) { $optimize_get_meta = true; } if ( 'events_list' === $view ) { $select_view .= $this->select_fields_view(); } $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} {$join_meta} WHERE `lg`.`connector` != 'compact' 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; } if ( ! empty( $recent_where ) ) { $orderby = ''; $limits = ''; } if ( ! empty( $join_meta ) ) { // Improve select. $selects[] = 'meta_view.meta_name'; $selects[] = 'meta_view.user_meta_json'; $selects[] = 'meta_view.usermeta'; $selects[] = 'meta_view.extra_info'; $selects[] = 'lg.log_id as view_log_id'; // deprecate compatible. } $select = implode( ', ', $selects ); /** * BUILD THE FINAL QUERY */ $query = "SELECT {$select}{$select_view} FROM $wpdb->mainwp_tbl_logs as lg {$join} {$join_meta} WHERE `lg`.`connector` != 'compact' {$where} {$where_users_filter} {$recent_where} {$orderby} {$limits}"; // Build result count query. // Join meta, join sub for search conditionals if existed. $count_query = "SELECT COUNT(*) FROM $wpdb->mainwp_tbl_logs as lg {$join}"; if ( ! empty( $search_str ) ) { $count_query .= "{$join_meta}"; } $count_query .= " WHERE `lg`.`connector` != 'compact' {$where} {$where_users_filter} {$recent_where} "; if ( ! empty( $recent_query ) ) { MainWP_DB::instance()->log_system_query( $args, $recent_query, $this ); } if ( ! $count_only ) { MainWP_DB::instance()->log_system_query( $args, $query, $this ); } if ( $count_only || ! $not_count ) { MainWP_DB::instance()->log_system_query( $args, $count_query, $this ); } // 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,WordPress.DB.PreparedSQL.NotPrepared -- 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, ); } $items = $wpdb->get_results( $query ); // phpcs:ignore -- ok. if ( ( ( $optimize_get_dt && $optimize_get_meta ) || $with_logs_meta ) && $items ) { $ids = array_map( 'absint', wp_list_pluck( $items, 'log_id' ) ); $start_slice = 0; $max_slice = 100; $count = count( $ids ); while ( $start_slice <= $count ) { $slice_ids = array_slice( $ids, $start_slice, $max_slice ); $start_slice += $max_slice; if ( ! empty( $slice_ids ) ) { $sql_meta = sprintf( "SELECT * FROM $wpdb->mainwp_tbl_logs_meta WHERE meta_log_id IN ( %s )", implode( ',', $slice_ids ) ); $meta_records = $wpdb->get_results( $sql_meta ); //phpcs:ignore -- ok. $ids_flip = array_flip( $ids ); if ( is_array( $meta_records ) ) { foreach ( $meta_records as $meta_record ) { if ( ! empty( $meta_record->meta_value ) ) { // compatible format. if ( in_array( $meta_record->meta_key, array( 'user_meta_json', 'user_login', 'extra_info' ) ) ) { $items[ $ids_flip[ $meta_record->meta_log_id ] ]->{$meta_record->meta_key} = $meta_record->meta_value; } else { if ( empty( $items[ $ids_flip[ $meta_record->meta_log_id ] ]->meta ) ) { $items[ $ids_flip[ $meta_record->meta_log_id ] ]->meta = array(); } $items[ $ids_flip[ $meta_record->meta_log_id ] ]->meta[ $meta_record->meta_key ] = $meta_record->meta_value; } } } } } } } $sites_opts = array(); // get sites meta data. if ( $items ) { $ids = array_map( 'absint', wp_list_pluck( $items, 'site_id' ) ); $start_slice = 0; $max_slice = 100; $count = count( $ids ); while ( $start_slice <= $count ) { $slice_ids = array_slice( $ids, $start_slice, $max_slice ); $start_slice += $max_slice; if ( ! empty( $slice_ids ) ) { $wp_opts = $with_logs_meta ? array( 'site_info', 'cust_site_icon_info', 'favi_icon' ) : array( 'site_info' ); $opts_records = Log_DB_Helper::instance()->get_sites_options( $slice_ids, $wp_opts ); if ( is_array( $opts_records ) ) { foreach ( $opts_records as $opt_record ) { if ( ! isset( $sites_opts[ $opt_record->wpid ] ) ) { $sites_opts[ $opt_record->wpid ] = array(); } if ( ! empty( $opt_record->value ) ) { $values = $opt_record->value; if ( 'site_info' === $opt_record->name ) { $values = json_decode( $values, true ); if ( ! is_array( $values ) ) { $values = array(); } } $sites_opts[ $opt_record->wpid ][ $opt_record->name ] = $values; } } } } } } /** * QUERY THE DATABASE FOR RESULTS */ $results = array( 'items' => $items, 'sites_opts' => $sites_opts, ); 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,WordPress.DB.PreparedSQL.NotPrepared -- NOSONAR 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. * * @return string logs meta view. */ public function get_log_meta_view() { global $wpdb; // Improve select. return " ( SELECT meta_log_id AS log_id, MAX(CASE WHEN meta_key = 'name' THEN meta_value END) AS meta_name, MAX(CASE WHEN meta_key = 'user_meta_json' THEN meta_value END) AS user_meta_json, MAX(CASE WHEN meta_key = 'user_meta' THEN meta_value END) AS usermeta, MAX(CASE WHEN meta_key = 'extra_info' THEN meta_value END) AS extra_info FROM " . $wpdb->mainwp_tbl_logs_meta . " WHERE meta_key IN ('name', 'user_meta_json', 'user_meta', 'extra_info') GROUP BY meta_log_id ) "; } /** * Method get_sub_query to support seaching in events table. * deprecated @since 6.0. * * @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; } /** * Method select_fields_view to support seaching in events table. * * @return string sub query view. */ public function select_fields_view() { return ", CASE WHEN lg.connector = 'non-mainwp-changes' THEN 'WP Admin' ELSE 'Dashboard' END AS source, CASE WHEN lg.action = 'sync' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Sync Data', 'mainwp' ) ) . "' WHEN lg.action = 'activate' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Activated', 'mainwp' ) ) . "' WHEN lg.action = 'deactivate' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Deactivated', 'mainwp' ) ) . "' WHEN lg.action = 'install' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Installed', 'mainwp' ) ) . "' WHEN lg.action = 'updated' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Updated', 'mainwp' ) ) . "' WHEN lg.action = 'delete' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Deleted', 'mainwp' ) ) . "' WHEN lg.action = 'suspend' THEN '" . MainWP_DB::instance()->escape( esc_html__( 'Suspended', 'mainwp' ) ) . "' ELSE lg.action END AS action_display "; } }