> $exporters Registered exporters. * @return array> */ public static function register_exporter( array $exporters ): array { $exporters['spinx-quick-migrate-audit-log'] = array( 'exporter_friendly_name' => __( 'SPINX Quick Migrate audit log', 'spinx-quick-migrate' ), 'callback' => array( __CLASS__, 'export' ), ); return $exporters; } /** * Register the audit-log eraser. * * @param array> $erasers Registered erasers. * @return array> */ public static function register_eraser( array $erasers ): array { $erasers['spinx-quick-migrate-audit-log'] = array( 'eraser_friendly_name' => __( 'SPINX Quick Migrate audit log', 'spinx-quick-migrate' ), 'callback' => array( __CLASS__, 'erase' ), ); return $erasers; } /** * Export the audit entries that belong to an email address. * * @param string $email_address Requester email. * @param int $page 1-based page. * @return array{data:array>,done:bool} */ public static function export( string $email_address, int $page = 1 ): array { $rows = self::rows_for( $email_address, $page, false ); $data = array(); foreach ( $rows as $row ) { $data[] = array( 'group_id' => 'spinx-quick-migrate-audit-log', 'group_label' => __( 'SPINX Quick Migrate audit log', 'spinx-quick-migrate' ), 'item_id' => 'spinx-quick-migrate-audit-' . (int) $row['id'], 'data' => array( array( 'name' => __( 'Operation', 'spinx-quick-migrate' ), 'value' => (string) $row['operation_type'], ), array( 'name' => __( 'Status', 'spinx-quick-migrate' ), 'value' => (string) $row['status'], ), array( 'name' => __( 'Date', 'spinx-quick-migrate' ), 'value' => (string) $row['created_at'], ), array( 'name' => __( 'Email', 'spinx-quick-migrate' ), 'value' => (string) $row['user_email'], ), array( 'name' => __( 'Role', 'spinx-quick-migrate' ), 'value' => (string) $row['user_role'], ), array( 'name' => __( 'IP address', 'spinx-quick-migrate' ), 'value' => (string) $row['ip_address'], ), array( 'name' => __( 'Browser user agent', 'spinx-quick-migrate' ), 'value' => (string) $row['user_agent'], ), ), ); } return array( 'data' => $data, 'done' => count( $rows ) < self::PER_PAGE, ); } /** * Anonymize the audit entries that belong to an email address. The entries * themselves (what ran, when, result) are kept as an operational record; the * personal fields (user ID, email, IP address, user agent) are removed. * * @param string $email_address Requester email. * @param int $page 1-based page (always 1: anonymized rows no longer match). * @return array{items_removed:bool,items_retained:bool,messages:array,done:bool} */ public static function erase( string $email_address, int $page = 1 ): array { unset( $page ); $response = array( 'items_removed' => false, 'items_retained' => false, 'messages' => array(), 'done' => true, ); $table = self::table(); if ( '' === $table ) { return $response; } global $wpdb; $rows = self::rows_for( $email_address, 1, true ); foreach ( $rows as $row ) { $updated = $wpdb->update( $table, array( 'user_id' => 0, 'user_email' => '', 'ip_address' => '', 'user_agent' => '', ), array( 'id' => (int) $row['id'] ), array( '%d', '%s', '%s', '%s' ), array( '%d' ) ); if ( $updated ) { $response['items_removed'] = true; } } $response['done'] = count( $rows ) < self::PER_PAGE; return $response; } /** * Suggested privacy-policy text. */ public static function add_policy_content(): void { if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) { return; } $content = '

' . esc_html__( 'When an administrator runs an export, backup or find & replace with SPINX Quick Migrate, the plugin records an audit log entry with the user’s ID, email address, role, IP address and browser user agent, together with the operation details. This information is stored in this site’s database for accountability and troubleshooting and is not sent to any third party. It can be exported or erased with the WordPress personal data tools.', 'spinx-quick-migrate' ) . '

'; wp_add_privacy_policy_content( 'SPINX Quick Migrate', wp_kses_post( $content ) ); } /** * Audit rows belonging to an email address (by stored email, or by the * matching user's ID). * * @param string $email_address Email address. * @param int $page 1-based page. * @param bool $first_page Always read the first page (eraser: processed rows drop out). * @return array> */ private static function rows_for( string $email_address, int $page, bool $first_page ): array { $table = self::table(); $email = sanitize_email( $email_address ); if ( '' === $table || '' === $email ) { return array(); } $user = get_user_by( 'email', $email ); $user_id = $user ? (int) $user->ID : -1; $offset = $first_page ? 0 : ( max( 1, $page ) - 1 ) * self::PER_PAGE; global $wpdb; $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT id, user_email, user_role, operation_type, status, ip_address, user_agent, created_at FROM %i WHERE user_email = %s OR user_id = %d ORDER BY id ASC LIMIT %d OFFSET %d', $table, $email, $user_id, self::PER_PAGE, $offset ), ARRAY_A ); return is_array( $rows ) ? $rows : array(); } /** * Audit table name, or '' when it doesn't exist yet. */ private static function table(): string { $logger = new Audit_Logger(); if ( ! $logger->table_ready() ) { return ''; } global $wpdb; return $wpdb->prefix . 'spinxqm_audit_log'; } }