false, 'message' => __( 'Log entry not found.', 'activitypilot' ) ); } if ( ! self::can_rollback( $row ) ) { return array( 'success' => false, 'message' => __( 'This action is not rollback-eligible.', 'activitypilot' ) ); } $ctx = ! empty( $row['context'] ) ? json_decode( $row['context'], true ) : array(); if ( ! is_array( $ctx ) ) { $ctx = array(); } try { switch ( $row['action_type'] ) { case 'option_updated': $result = self::rollback_option( $row, $ctx ); break; case 'user_role_changed': $result = self::rollback_user_role( $row, $ctx ); break; case 'post_trashed': $result = self::rollback_post_trash( $row ); break; case 'plugin_activated': $result = self::rollback_plugin_activate( $row ); break; case 'plugin_deactivated': $result = self::rollback_plugin_deactivate( $row ); break; case 'theme_switched': $result = self::rollback_theme_switch( $row, $ctx ); break; default: $result = array( 'success' => false, 'message' => __( 'Unsupported action.', 'activitypilot' ) ); } } catch ( Throwable $e ) { $result = array( 'success' => false, 'message' => $e->getMessage() ); } if ( $result['success'] ) { APWPM_DB::update_log_fields( (int) $row['id'], array( 'rolled_back' => 1 ), array( '%d' ) ); $new_id = APWPM_Logger::log( array( 'action_type' => 'rollback_performed', 'object_type' => $row['object_type'], 'object_id' => (int) $row['object_id'], 'object_title' => (string) $row['object_title'], 'description' => sprintf( /* translators: 1: original action_type, 2: object title */ __( 'Rolled back "%1$s" on %2$s.', 'activitypilot' ), $row['action_type'], $row['object_title'] ), 'severity' => 'warning', 'context' => array( 'original_log_id' => (int) $row['id'], 'original_action' => $row['action_type'], 'rollback_outcome' => $result['message'], ), ) ); if ( $new_id ) { APWPM_DB::update_log_fields( (int) $new_id, array( 'rollback_of' => (int) $row['id'] ), array( '%d' ) ); } } return $result; } /* ---------------------------------------------------------------- */ protected static function rollback_option( $row, $ctx ) { if ( empty( $row['object_title'] ) ) { return array( 'success' => false, 'message' => __( 'Missing option name.', 'activitypilot' ) ); } if ( ! array_key_exists( 'old', $ctx ) ) { return array( 'success' => false, 'message' => __( 'Previous option value not captured.', 'activitypilot' ) ); } if ( '(complex)' === $ctx['old'] ) { return array( 'success' => false, 'message' => __( 'Previous value was too complex to capture safely.', 'activitypilot' ) ); } update_option( $row['object_title'], $ctx['old'] ); return array( 'success' => true, 'message' => __( 'Option value restored.', 'activitypilot' ) ); } protected static function rollback_user_role( $row, $ctx ) { $uid = (int) $row['object_id']; if ( ! $uid ) { return array( 'success' => false, 'message' => __( 'Missing user ID.', 'activitypilot' ) ); } $user = get_user_by( 'id', $uid ); if ( ! $user ) { return array( 'success' => false, 'message' => __( 'User no longer exists.', 'activitypilot' ) ); } $old_roles = isset( $ctx['old_roles'] ) ? (array) $ctx['old_roles'] : array(); if ( empty( $old_roles ) ) { return array( 'success' => false, 'message' => __( 'No previous role recorded.', 'activitypilot' ) ); } foreach ( (array) $user->roles as $r ) { $user->remove_role( $r ); } foreach ( $old_roles as $r ) { if ( is_string( $r ) && $r ) { $user->add_role( sanitize_key( $r ) ); } } return array( 'success' => true, 'message' => __( 'User roles restored.', 'activitypilot' ) ); } protected static function rollback_post_trash( $row ) { $pid = (int) $row['object_id']; if ( ! $pid ) { return array( 'success' => false, 'message' => __( 'Missing post ID.', 'activitypilot' ) ); } $ok = wp_untrash_post( $pid ); if ( ! $ok ) { return array( 'success' => false, 'message' => __( 'Untrash failed.', 'activitypilot' ) ); } return array( 'success' => true, 'message' => __( 'Post restored from trash.', 'activitypilot' ) ); } protected static function rollback_plugin_activate( $row ) { if ( empty( $row['object_title'] ) ) { return array( 'success' => false, 'message' => __( 'Missing plugin path.', 'activitypilot' ) ); } if ( ! function_exists( 'deactivate_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } deactivate_plugins( $row['object_title'] ); return array( 'success' => true, 'message' => __( 'Plugin deactivated.', 'activitypilot' ) ); } protected static function rollback_plugin_deactivate( $row ) { if ( empty( $row['object_title'] ) ) { return array( 'success' => false, 'message' => __( 'Missing plugin path.', 'activitypilot' ) ); } if ( ! function_exists( 'activate_plugin' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $result = activate_plugin( $row['object_title'] ); if ( is_wp_error( $result ) ) { return array( 'success' => false, 'message' => $result->get_error_message() ); } return array( 'success' => true, 'message' => __( 'Plugin reactivated.', 'activitypilot' ) ); } protected static function rollback_theme_switch( $row, $ctx ) { if ( empty( $ctx['old_stylesheet'] ) ) { return array( 'success' => false, 'message' => __( 'Previous theme not recorded.', 'activitypilot' ) ); } $old = (string) $ctx['old_stylesheet']; $theme = wp_get_theme( $old ); if ( ! $theme->exists() ) { return array( 'success' => false, 'message' => __( 'Previous theme is no longer installed.', 'activitypilot' ) ); } switch_theme( $old ); return array( 'success' => true, 'message' => __( 'Theme reverted.', 'activitypilot' ) ); } }