PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / core / Privacy.php

Privacy.php in 404 Solution trunk, at includes/core/Privacy.php

228 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * WordPress privacy integration (export/erase + privacy policy content).
9 *
10 * WordPress's privacy tools are keyed on email address, so we map email -> WP user -> user_login,
11 * then export/anonymize plugin log rows associated with that username (via lookup table).
12 */
13 class ABJ_404_Solution_Privacy {
14
15 const EXPORTER_ID = 'abj404-solution';
16 const ERASER_ID = 'abj404-solution';
17
18 /** @return void */
19 public static function init(): void {
20 // Add privacy policy content in wp-admin.
21 add_action('admin_init', array(__CLASS__, 'addPrivacyPolicyContent'));
22
23 // Register exporter/eraser with WP privacy tools.
24 add_filter('wp_privacy_personal_data_exporters', array(__CLASS__, 'registerExporter'));
25 add_filter('wp_privacy_personal_data_erasers', array(__CLASS__, 'registerEraser'));
26 }
27
28 /** @return void */
29 public static function addPrivacyPolicyContent(): void {
30 if (!function_exists('wp_add_privacy_policy_content')) {
31 return;
32 }
33
34 $tpl = ABJ_404_Solution_FileSystemService::readFileContents(
35 dirname(__DIR__) . '/html/privacyPolicyContent.html'
36 );
37 $content = strtr($tpl, [
38 '{intro}' => esc_html__(
39 '404 Solution logs 404 hits and redirects to help you diagnose broken links and configure redirects.',
40 '404-solution'
41 ),
42 '{logging_details}' => esc_html__(
43 'Log entries may include requested URLs, referrers, timestamps, and an IP address. By default, IP addresses are anonymized; you can enable raw IP logging in the plugin settings.',
44 '404-solution'
45 ),
46 '{tools_pointer}' => esc_html__(
47 'You can export or erase personal data via WordPress Tools > Export/Erase Personal Data.',
48 '404-solution'
49 ),
50 ]);
51
52 wp_add_privacy_policy_content('404 Solution', wp_kses_post($content));
53 }
54
55 /**
56 * @param array<string, mixed> $exporters
57 * @return array<string, mixed>
58 */
59 public static function registerExporter(array $exporters): array {
60 $exporters[self::EXPORTER_ID] = array(
61 'exporter_friendly_name' => __('404 Solution Logs', '404-solution'),
62 'callback' => array(__CLASS__, 'exporter'),
63 );
64 return $exporters;
65 }
66
67 /**
68 * @param array<string, mixed> $erasers
69 * @return array<string, mixed>
70 */
71 public static function registerEraser(array $erasers): array {
72 $erasers[self::ERASER_ID] = array(
73 'eraser_friendly_name' => __('404 Solution Logs', '404-solution'),
74 'callback' => array(__CLASS__, 'eraser'),
75 );
76 return $erasers;
77 }
78
79 /** @return ABJ_404_Solution_LogsRepository */
80 private static function resolveLogsRepo() {
81 if (class_exists('ABJ_404_Solution_ServiceContainer')) {
82 $svc = ABJ_404_Solution_ServiceContainer::safeGet('logs_repository');
83 if ($svc instanceof ABJ_404_Solution_LogsRepository) {
84 return $svc;
85 }
86 }
87 return abj_service('logs_repository');
88 }
89
90 /**
91 * @param mixed $email_address
92 * @return string|null
93 */
94 private static function getUsernameFromEmail($email_address) {
95 if (!function_exists('get_user_by')) {
96 return null;
97 }
98 $email_address = is_string($email_address) ? trim($email_address) : '';
99 if ($email_address === '') {
100 return null;
101 }
102
103 $user = ABJ_404_Solution_UserRef::fromWpUser(get_user_by('email', $email_address));
104 if ($user === null) {
105 return null;
106 }
107 $username = $user->getLogin();
108 return $username !== '' ? $username : null;
109 }
110
111 /**
112 * @param string $email_address
113 * @param int $page
114 * @return array{data: array<int, mixed>, done: bool}
115 */
116 public static function exporter(string $email_address, int $page = 1): array {
117 $username = self::getUsernameFromEmail($email_address);
118 if ($username === null) {
119 return array(
120 'data' => array(),
121 'done' => true,
122 );
123 }
124
125 $dao = self::resolveLogsRepo();
126 $perPage = 50;
127 $page = max(1, absint($page));
128
129 $rows = method_exists($dao, 'getLogsv2RowsForLookupValue')
130 ? $dao->getLogsv2RowsForLookupValue($username, $page, $perPage)
131 : array();
132
133 $data = array();
134 foreach ($rows as $row) {
135 $rowIdRaw = isset($row['id']) ? $row['id'] : null;
136 $rowId = (is_scalar($rowIdRaw) ? (string)$rowIdRaw : uniqid('', true));
137 $tsRaw = isset($row['timestamp']) ? $row['timestamp'] : '';
138 $urlRaw = isset($row['requested_url']) ? $row['requested_url'] : '';
139 $destRaw = isset($row['dest_url']) ? $row['dest_url'] : '';
140 $refRaw = isset($row['referrer']) ? $row['referrer'] : '';
141 $ipRaw = isset($row['user_ip']) ? $row['user_ip'] : '';
142 $data[] = array(
143 'group_id' => 'abj404_solution_logs',
144 'group_label' => __('404 Solution Logs', '404-solution'),
145 'item_id' => 'abj404_log_' . $rowId,
146 'data' => array(
147 array(
148 'name' => __('Timestamp', '404-solution'),
149 'value' => is_scalar($tsRaw) ? (string)$tsRaw : '',
150 ),
151 array(
152 'name' => __('Requested URL', '404-solution'),
153 'value' => is_scalar($urlRaw) ? (string)$urlRaw : '',
154 ),
155 array(
156 'name' => __('Destination URL', '404-solution'),
157 'value' => is_scalar($destRaw) ? (string)$destRaw : '',
158 ),
159 array(
160 'name' => __('Referrer', '404-solution'),
161 'value' => is_scalar($refRaw) ? (string)$refRaw : '',
162 ),
163 array(
164 'name' => __('IP Address', '404-solution'),
165 'value' => is_scalar($ipRaw) ? (string)$ipRaw : '',
166 ),
167 ),
168 );
169 }
170
171 $done = count((array)$rows) < $perPage;
172
173 return array(
174 'data' => $data,
175 'done' => $done,
176 );
177 }
178
179 /**
180 * @param string $email_address
181 * @param int $page
182 * @return array{items_removed: bool, items_retained: bool, messages: array<int, string>, done: bool}
183 */
184 public static function eraser(string $email_address, int $page = 1): array {
185 $username = self::getUsernameFromEmail($email_address);
186 if ($username === null) {
187 return array(
188 'items_removed' => false,
189 'items_retained' => false,
190 'messages' => array(),
191 'done' => true,
192 );
193 }
194
195 $dao = self::resolveLogsRepo();
196 $perPage = 100;
197 $page = max(1, absint($page));
198
199 $ids = method_exists($dao, 'getLogsv2IdsForLookupValue')
200 ? $dao->getLogsv2IdsForLookupValue($username, $page, $perPage)
201 : array();
202
203 if (empty($ids)) {
204 return array(
205 'items_removed' => false,
206 'items_retained' => false,
207 'messages' => array(),
208 'done' => true,
209 );
210 }
211
212 $ok = method_exists($dao, 'anonymizeLogsv2RowsByIds')
213 ? $dao->anonymizeLogsv2RowsByIds($ids)
214 : false;
215
216 $removed = $ok ? true : false;
217 $retained = $ok ? false : true;
218
219 return array(
220 'items_removed' => $removed,
221 'items_retained' => $retained,
222 'messages' => $ok ? array() : array(__('Some items could not be anonymized.', '404-solution')),
223 'done' => count($ids) < $perPage,
224 );
225 }
226 }
227
228