PluginProbe
404 Solution / 4.1.13
404 Solution v4.1.13
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 / Privacy.php

Privacy.php in 404 Solution 4.1.13, at includes/Privacy.php

225 lines 7.7 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 $content = '<p>' . esc_html__(
35 '404 Solution logs 404 hits and redirects to help you diagnose broken links and configure redirects.',
36 '404-solution'
37 ) . '</p>';
38
39 $content .= '<p>' . esc_html__(
40 '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.',
41 '404-solution'
42 ) . '</p>';
43
44 $content .= '<p>' . esc_html__(
45 'You can export or erase personal data via WordPress Tools > Export/Erase Personal Data.',
46 '404-solution'
47 ) . '</p>';
48
49 wp_add_privacy_policy_content('404 Solution', wp_kses_post($content));
50 }
51
52 /**
53 * @param array<string, mixed> $exporters
54 * @return array<string, mixed>
55 */
56 public static function registerExporter(array $exporters): array {
57 $exporters[self::EXPORTER_ID] = array(
58 'exporter_friendly_name' => __('404 Solution Logs', '404-solution'),
59 'callback' => array(__CLASS__, 'exporter'),
60 );
61 return $exporters;
62 }
63
64 /**
65 * @param array<string, mixed> $erasers
66 * @return array<string, mixed>
67 */
68 public static function registerEraser(array $erasers): array {
69 $erasers[self::ERASER_ID] = array(
70 'eraser_friendly_name' => __('404 Solution Logs', '404-solution'),
71 'callback' => array(__CLASS__, 'eraser'),
72 );
73 return $erasers;
74 }
75
76 /** @return ABJ_404_Solution_DataAccess */
77 private static function resolveDao() {
78 if (class_exists('ABJ_404_Solution_ServiceContainer')) {
79 $svc = ABJ_404_Solution_ServiceContainer::safeGet('data_access');
80 if ($svc instanceof ABJ_404_Solution_DataAccess) {
81 return $svc;
82 }
83 }
84 return abj_service('data_access');
85 }
86
87 /**
88 * @param mixed $email_address
89 * @return string|null
90 */
91 private static function getUsernameFromEmail($email_address) {
92 if (!function_exists('get_user_by')) {
93 return null;
94 }
95 $email_address = is_string($email_address) ? trim($email_address) : '';
96 if ($email_address === '') {
97 return null;
98 }
99
100 $user = get_user_by('email', $email_address);
101 if (!is_object($user) || !isset($user->user_login)) {
102 return null;
103 }
104 $username = (string)$user->user_login;
105 return $username !== '' ? $username : null;
106 }
107
108 /**
109 * @param string $email_address
110 * @param int $page
111 * @return array{data: array<int, mixed>, done: bool}
112 */
113 public static function exporter(string $email_address, int $page = 1): array {
114 $username = self::getUsernameFromEmail($email_address);
115 if ($username === null) {
116 return array(
117 'data' => array(),
118 'done' => true,
119 );
120 }
121
122 $dao = self::resolveDao();
123 $perPage = 50;
124 $page = max(1, absint($page));
125
126 $rows = method_exists($dao, 'getLogsv2RowsForLookupValue')
127 ? $dao->getLogsv2RowsForLookupValue($username, $page, $perPage)
128 : array();
129
130 $data = array();
131 foreach ($rows as $row) {
132 $rowIdRaw = isset($row['id']) ? $row['id'] : null;
133 $rowId = (is_scalar($rowIdRaw) ? (string)$rowIdRaw : uniqid('', true));
134 $tsRaw = isset($row['timestamp']) ? $row['timestamp'] : '';
135 $urlRaw = isset($row['requested_url']) ? $row['requested_url'] : '';
136 $destRaw = isset($row['dest_url']) ? $row['dest_url'] : '';
137 $refRaw = isset($row['referrer']) ? $row['referrer'] : '';
138 $ipRaw = isset($row['user_ip']) ? $row['user_ip'] : '';
139 $data[] = array(
140 'group_id' => 'abj404_solution_logs',
141 'group_label' => __('404 Solution Logs', '404-solution'),
142 'item_id' => 'abj404_log_' . $rowId,
143 'data' => array(
144 array(
145 'name' => __('Timestamp', '404-solution'),
146 'value' => is_scalar($tsRaw) ? (string)$tsRaw : '',
147 ),
148 array(
149 'name' => __('Requested URL', '404-solution'),
150 'value' => is_scalar($urlRaw) ? (string)$urlRaw : '',
151 ),
152 array(
153 'name' => __('Destination URL', '404-solution'),
154 'value' => is_scalar($destRaw) ? (string)$destRaw : '',
155 ),
156 array(
157 'name' => __('Referrer', '404-solution'),
158 'value' => is_scalar($refRaw) ? (string)$refRaw : '',
159 ),
160 array(
161 'name' => __('IP Address', '404-solution'),
162 'value' => is_scalar($ipRaw) ? (string)$ipRaw : '',
163 ),
164 ),
165 );
166 }
167
168 $done = count((array)$rows) < $perPage;
169
170 return array(
171 'data' => $data,
172 'done' => $done,
173 );
174 }
175
176 /**
177 * @param string $email_address
178 * @param int $page
179 * @return array{items_removed: bool, items_retained: bool, messages: array<int, string>, done: bool}
180 */
181 public static function eraser(string $email_address, int $page = 1): array {
182 $username = self::getUsernameFromEmail($email_address);
183 if ($username === null) {
184 return array(
185 'items_removed' => false,
186 'items_retained' => false,
187 'messages' => array(),
188 'done' => true,
189 );
190 }
191
192 $dao = self::resolveDao();
193 $perPage = 100;
194 $page = max(1, absint($page));
195
196 $ids = method_exists($dao, 'getLogsv2IdsForLookupValue')
197 ? $dao->getLogsv2IdsForLookupValue($username, $page, $perPage)
198 : array();
199
200 if (empty($ids)) {
201 return array(
202 'items_removed' => false,
203 'items_retained' => false,
204 'messages' => array(),
205 'done' => true,
206 );
207 }
208
209 $ok = method_exists($dao, 'anonymizeLogsv2RowsByIds')
210 ? $dao->anonymizeLogsv2RowsByIds($ids)
211 : false;
212
213 $removed = $ok ? true : false;
214 $retained = $ok ? false : true;
215
216 return array(
217 'items_removed' => $removed,
218 'items_retained' => $retained,
219 'messages' => $ok ? array() : array(__('Some items could not be anonymized.', '404-solution')),
220 'done' => count($ids) < $perPage,
221 );
222 }
223 }
224
225