$data Event data. * @return int|false */ public static function log_event( $data ) { global $wpdb; $defaults = array( 'event_type' => 'manual_note', 'object_type' => '', 'object_name' => '', 'object_slug' => '', 'old_version' => '', 'new_version' => '', 'notes' => '', 'created_by' => get_current_user_id(), ); $data = wp_parse_args( $data, $defaults ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $inserted = $wpdb->insert( AMAR_Install::get_events_table(), array( 'event_type' => sanitize_key( $data['event_type'] ), 'object_type' => sanitize_text_field( $data['object_type'] ), 'object_name' => sanitize_text_field( $data['object_name'] ), 'object_slug' => sanitize_text_field( $data['object_slug'] ), 'old_version' => sanitize_text_field( $data['old_version'] ), 'new_version' => sanitize_text_field( $data['new_version'] ), 'notes' => sanitize_textarea_field( $data['notes'] ), 'created_by' => absint( $data['created_by'] ), 'created_at' => current_time( 'mysql' ), ), array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s' ) ); return false === $inserted ? false : (int) $wpdb->insert_id; } /** * Log completed upgrader actions. * * @param WP_Upgrader $upgrader Upgrader instance. * @param array $hook_extra Extra hook data. */ public static function log_upgrader_complete( $upgrader, $hook_extra ) { if ( empty( $hook_extra['action'] ) || 'update' !== $hook_extra['action'] ) { return; } if ( ! empty( $hook_extra['bulk'] ) && ! empty( $hook_extra['plugins'] ) && is_array( $hook_extra['plugins'] ) ) { foreach ( $hook_extra['plugins'] as $plugin_file ) { self::log_plugin_update( $plugin_file ); } return; } if ( ! empty( $hook_extra['plugin'] ) ) { self::log_plugin_update( $hook_extra['plugin'] ); return; } if ( ! empty( $hook_extra['theme'] ) ) { self::log_theme_update( $hook_extra['theme'] ); return; } if ( ! empty( $hook_extra['type'] ) && 'core' === $hook_extra['type'] ) { self::log_core_update(); } } /** * Log a plugin update. * * @param string $plugin_file Plugin basename. */ private static function log_plugin_update( $plugin_file ) { if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file; if ( ! file_exists( $plugin_path ) ) { return; } $plugin_data = get_plugin_data( $plugin_path, false, false ); self::log_event( array( 'event_type' => 'plugin_update', 'object_type' => 'plugin', 'object_name' => isset( $plugin_data['Name'] ) ? $plugin_data['Name'] : $plugin_file, 'object_slug' => $plugin_file, 'new_version' => isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : '', 'notes' => __( 'Plugin updated.', 'agency-maintenance-activity-reports' ), ) ); } /** * Log a theme update. * * @param string $theme_slug Theme stylesheet slug. */ private static function log_theme_update( $theme_slug ) { $theme = wp_get_theme( $theme_slug ); if ( ! $theme->exists() ) { return; } self::log_event( array( 'event_type' => 'theme_update', 'object_type' => 'theme', 'object_name' => $theme->get( 'Name' ), 'object_slug' => $theme_slug, 'new_version' => $theme->get( 'Version' ), 'notes' => __( 'Theme updated.', 'agency-maintenance-activity-reports' ), ) ); } /** * Log a WordPress core update. */ private static function log_core_update() { global $wp_version; self::log_event( array( 'event_type' => 'core_update', 'object_type' => 'core', 'object_name' => 'WordPress', 'object_slug' => 'wordpress', 'new_version' => $wp_version, 'notes' => __( 'WordPress core updated.', 'agency-maintenance-activity-reports' ), ) ); } /** * Log plugin activation. * * @param string $plugin_file Plugin basename. * @param bool $network_wide Network activation flag. */ public static function log_plugin_activated( $plugin_file, $network_wide ) { unset( $network_wide ); if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file; if ( ! file_exists( $plugin_path ) ) { return; } $plugin_data = get_plugin_data( $plugin_path, false, false ); self::log_event( array( 'event_type' => 'plugin_activated', 'object_type' => 'plugin', 'object_name' => isset( $plugin_data['Name'] ) ? $plugin_data['Name'] : $plugin_file, 'object_slug' => $plugin_file, 'new_version' => isset( $plugin_data['Version'] ) ? $plugin_data['Version'] : '', 'notes' => __( 'Plugin activated.', 'agency-maintenance-activity-reports' ), ) ); } /** * Log plugin deactivation. * * @param string $plugin_file Plugin basename. * @param bool $network_wide Network deactivation flag. */ public static function log_plugin_deactivated( $plugin_file, $network_wide ) { unset( $network_wide ); if ( ! function_exists( 'get_plugin_data' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file; if ( ! file_exists( $plugin_path ) ) { $name = $plugin_file; } else { $plugin_data = get_plugin_data( $plugin_path, false, false ); $name = isset( $plugin_data['Name'] ) ? $plugin_data['Name'] : $plugin_file; } self::log_event( array( 'event_type' => 'plugin_deactivated', 'object_type' => 'plugin', 'object_name' => $name, 'object_slug' => $plugin_file, 'notes' => __( 'Plugin deactivated.', 'agency-maintenance-activity-reports' ), ) ); } /** * Log theme switch. * * @param string $new_name New theme name. * @param WP_Theme $new_theme New theme object. * @param WP_Theme $old_theme Old theme object. */ public static function log_theme_switched( $new_name, $new_theme, $old_theme ) { unset( $new_name ); self::log_event( array( 'event_type' => 'theme_switched', 'object_type' => 'theme', 'object_name' => $new_theme->get( 'Name' ), 'object_slug' => $new_theme->get_stylesheet(), 'old_version' => $old_theme->get( 'Name' ), 'new_version' => $new_theme->get( 'Version' ), 'notes' => sprintf( /* translators: 1: old theme name, 2: new theme name */ __( 'Theme switched from %1$s to %2$s.', 'agency-maintenance-activity-reports' ), $old_theme->get( 'Name' ), $new_theme->get( 'Name' ) ), ) ); } /** * Get events for a date range. * * @param string $start_date Start date (Y-m-d). * @param string $end_date End date (Y-m-d). * @param int $limit Max rows. * @return array */ public static function get_events( $start_date, $end_date, $limit = 500 ) { global $wpdb; $table = AMAR_Install::get_events_table(); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} WHERE created_at >= %s AND created_at <= %s ORDER BY created_at DESC LIMIT %d", $start_date . ' 00:00:00', $end_date . ' 23:59:59', absint( $limit ) ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter return is_array( $rows ) ? $rows : array(); } /** * Get recent events for dashboard. * * @param int $limit Max rows. * @return array */ public static function get_recent_events( $limit = 10 ) { global $wpdb; $table = AMAR_Install::get_events_table(); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} ORDER BY created_at DESC LIMIT %d", absint( $limit ) ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter return is_array( $rows ) ? $rows : array(); } /** * Count events in a date range. * * @param string $start_date Start date (Y-m-d). * @param string $end_date End date (Y-m-d). * @return int */ public static function count_events( $start_date, $end_date ) { global $wpdb; $table = AMAR_Install::get_events_table(); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE created_at >= %s AND created_at <= %s", $start_date . ' 00:00:00', $end_date . ' 23:59:59' ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter return (int) $count; } /** * Delete an event by ID. * * @param int $event_id Event ID. * @return bool */ public static function delete_event( $event_id ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $deleted = $wpdb->delete( AMAR_Install::get_events_table(), array( 'id' => absint( $event_id ) ), array( '%d' ) ); return false !== $deleted; } /** * Human-readable label for an event type. * * @param string $event_type Event type slug. * @return string */ public static function get_event_type_label( $event_type ) { $labels = array( 'core_update' => __( 'Core update', 'agency-maintenance-activity-reports' ), 'plugin_update' => __( 'Plugin update', 'agency-maintenance-activity-reports' ), 'theme_update' => __( 'Theme update', 'agency-maintenance-activity-reports' ), 'plugin_activated' => __( 'Plugin activated', 'agency-maintenance-activity-reports' ), 'plugin_deactivated' => __( 'Plugin deactivated', 'agency-maintenance-activity-reports' ), 'theme_switched' => __( 'Theme switched', 'agency-maintenance-activity-reports' ), 'manual_note' => __( 'Manual note', 'agency-maintenance-activity-reports' ), ); return isset( $labels[ $event_type ] ) ? $labels[ $event_type ] : ucwords( str_replace( '_', ' ', $event_type ) ); } }