164 lines
4.8 KiB
PHP
164 lines
4.8 KiB
PHP
<?php
|
|
/**
|
|
* GDPR personal data eraser.
|
|
*
|
|
* @package MediaRightsAudit\Privacy
|
|
*/
|
|
|
|
namespace MediaRightsAudit\Privacy;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Erases Media Audit data for a given user via the WordPress privacy tools.
|
|
*
|
|
* Registered via the wp_privacy_personal_data_erasers filter. Removes all rows
|
|
* from mra_media_index, mra_media_usage, and mra_external_results that belong
|
|
* to attachments uploaded by the requested user.
|
|
*
|
|
* Erasure is a hard delete: once removed, the data cannot be recovered. The
|
|
* attachment itself (wp_posts row) is not touched — only plugin-specific data.
|
|
*/
|
|
class DataEraser {
|
|
|
|
/**
|
|
* Registers this eraser with the WordPress privacy API.
|
|
*
|
|
* Hooked to wp_privacy_personal_data_erasers.
|
|
*
|
|
* @param array<string, mixed> $erasers Accumulated eraser list.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function register( array $erasers ): array {
|
|
$erasers['robotstxt-mediaaudit'] = array(
|
|
'eraser_friendly_name' => __( 'Media Audit', 'robotstxt-mediaaudit' ),
|
|
'callback' => array( self::class, 'erase' ),
|
|
);
|
|
return $erasers;
|
|
}
|
|
|
|
/**
|
|
* Erases Media Audit data for the user identified by $email_address.
|
|
*
|
|
* @param string $email_address User e-mail address.
|
|
* @param int $page Pagination page (unused; all data erased in one pass).
|
|
*
|
|
* @return array{items_removed: int, items_retained: int, messages: array<mixed>, done: bool}
|
|
*/
|
|
public static function erase( string $email_address, int $page = 1 ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- required by WP privacy API signature
|
|
$user = get_user_by( 'email', $email_address );
|
|
if ( ! $user instanceof \WP_User ) {
|
|
return self::done_response( 0 );
|
|
}
|
|
|
|
$attachment_ids = self::get_attachment_ids( $user->ID );
|
|
if ( empty( $attachment_ids ) ) {
|
|
return self::done_response( 0 );
|
|
}
|
|
|
|
$removed = self::delete_plugin_data( $attachment_ids );
|
|
|
|
return self::done_response( $removed );
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Private helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Builds a standard "done" response array.
|
|
*
|
|
* @param int $removed Number of records removed.
|
|
*
|
|
* @return array{items_removed: int, items_retained: int, messages: array<mixed>, done: bool}
|
|
*/
|
|
private static function done_response( int $removed ): array {
|
|
return array(
|
|
'items_removed' => $removed,
|
|
'items_retained' => 0,
|
|
'messages' => array(),
|
|
'done' => true,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns all indexed attachment IDs for a given user (no pagination).
|
|
*
|
|
* @param int $user_id WordPress user ID.
|
|
*
|
|
* @return array<int, int>
|
|
*/
|
|
private static function get_attachment_ids( int $user_id ): array {
|
|
global $wpdb;
|
|
|
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
$raw = $wpdb->get_col(
|
|
$wpdb->prepare(
|
|
"SELECT p.ID
|
|
FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->prefix}mra_media_index mi ON p.ID = mi.attachment_id
|
|
WHERE p.post_type = 'attachment'
|
|
AND p.post_author = %d
|
|
ORDER BY p.ID ASC",
|
|
$user_id
|
|
)
|
|
);
|
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
|
|
if ( ! is_array( $raw ) ) {
|
|
return array();
|
|
}
|
|
|
|
return array_map( static fn ( mixed $v ): int => is_numeric( $v ) ? (int) $v : 0, $raw );
|
|
}
|
|
|
|
/**
|
|
* Deletes all plugin data rows for the given attachment IDs.
|
|
*
|
|
* Removes records from mra_external_results, mra_media_usage, and
|
|
* mra_media_index. Returns the total number of affected rows.
|
|
*
|
|
* @param array<int, int> $ids Attachment IDs to delete.
|
|
*
|
|
* @return int Total rows deleted across all three tables.
|
|
*/
|
|
private static function delete_plugin_data( array $ids ): int {
|
|
global $wpdb;
|
|
|
|
$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
|
|
$removed = 0;
|
|
|
|
// phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
|
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
"DELETE FROM {$wpdb->prefix}mra_external_results WHERE attachment_id IN ({$placeholders})",
|
|
...$ids
|
|
)
|
|
);
|
|
$removed += (int) $wpdb->rows_affected;
|
|
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
"DELETE FROM {$wpdb->prefix}mra_media_usage WHERE attachment_id IN ({$placeholders})",
|
|
...$ids
|
|
)
|
|
);
|
|
$removed += (int) $wpdb->rows_affected;
|
|
|
|
$wpdb->query(
|
|
$wpdb->prepare(
|
|
"DELETE FROM {$wpdb->prefix}mra_media_index WHERE attachment_id IN ({$placeholders})",
|
|
...$ids
|
|
)
|
|
);
|
|
$removed += (int) $wpdb->rows_affected;
|
|
|
|
// phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
|
|
|
return $removed;
|
|
}
|
|
}
|