where(fn ($query) => $this->scopeToCustomer($query, $subject->customerId)) ->get(); return new ProviderExportResult('activity_log', $activities->map(fn (Activity $activity) => [ 'id' => $activity->id, 'log_name' => $activity->log_name, 'description' => $activity->description, 'subject_type' => $activity->subject_type, 'subject_id' => $activity->subject_id, 'event' => $activity->event, 'properties' => $activity->properties?->toArray(), 'created_at' => $activity->created_at?->toIso8601String(), ])->all()); } public function exportForUser(UserSubject $subject): ProviderExportResult { return new ProviderExportResult('activity_log', []); } public function eraseForCustomer(CustomerSubject $subject): ProviderErasureResult { $affected = Activity::query() ->where(fn ($query) => $this->scopeToCustomer($query, $subject->customerId)) ->get(); if ($affected->isEmpty()) { return new ProviderErasureResult('activity_log', ErasureOutcome::Skipped, 'No activity log entries for this customer.'); } foreach ($affected as $activity) { $activity->update(['properties' => $this->redact($activity->properties?->toArray() ?? [])]); } return new ProviderErasureResult( 'activity_log', ErasureOutcome::Pseudonymized, 'PII-bearing properties redacted on matching audit log entries; who/what/when metadata (log_name, subject, event, timestamp, causer) retained for audit integrity.' ); } public function eraseForUser(UserSubject $subject): ProviderErasureResult { return new ProviderErasureResult( 'activity_log', ErasureOutcome::Skipped, 'A User only ever appears here as causer_id (who performed an action), not as the PII content of a log entry — redacting that would erode the audit trail\'s own record of who acted.' ); } private function scopeToCustomer($query, int $customerId): void { $customerMorph = (new Customer)->getMorphClass(); $addressMorph = (new Address)->getMorphClass(); $cartAddressMorph = (new CartAddress)->getMorphClass(); $orderAddressMorph = (new OrderAddress)->getMorphClass(); $transactionMorph = (new Transaction)->getMorphClass(); $addressIds = Address::where('customer_id', $customerId)->pluck('id'); $cartIds = Cart::where('customer_id', $customerId)->pluck('id'); $cartAddressIds = CartAddress::whereIn('cart_id', $cartIds)->pluck('id'); $orderIds = Order::where('customer_id', $customerId)->pluck('id'); $orderAddressIds = OrderAddress::whereIn('order_id', $orderIds)->pluck('id'); $transactionIds = Transaction::whereIn('order_id', $orderIds)->pluck('id'); $query ->where(fn ($q) => $q->where('subject_type', $customerMorph)->where('subject_id', $customerId)) ->orWhere(fn ($q) => $q->where('subject_type', $addressMorph)->whereIn('subject_id', $addressIds)) ->orWhere(fn ($q) => $q->where('subject_type', $cartAddressMorph)->whereIn('subject_id', $cartAddressIds)) ->orWhere(fn ($q) => $q->where('subject_type', $orderAddressMorph)->whereIn('subject_id', $orderAddressIds)) ->orWhere(fn ($q) => $q->where('subject_type', $transactionMorph)->whereIn('subject_id', $transactionIds)); } /** * @param array $properties * @return array */ private function redact(array $properties): array { return array_map(function ($value) { if (is_array($value)) { return array_map(fn () => self::REDACTED, $value); } return self::REDACTED; }, $properties); } }