PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
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 / logs / LogsRepository.php

LogsRepository.php in 404 Solution 4.3.0, at includes/logs/LogsRepository.php

318 lines 13.2 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 require_once __DIR__ . '/LogsRepositoryInterface.php';
8 require_once __DIR__ . '/LogsHitsRollupServiceInterface.php';
9 require_once __DIR__ . '/LogsHitsRollupService.php';
10 require_once __DIR__ . '/LogsLookupRepository.php';
11 require_once __DIR__ . '/LogsPrivacyService.php';
12 require_once __DIR__ . '/LogsReadQueries.php';
13 require_once __DIR__ . '/LogsHitsDataPopulator.php';
14 require_once __DIR__ . '/LogsEntrySanitizer.php';
15 require_once __DIR__ . '/LogsRequestedUrlColumnMetadata.php';
16 require_once __DIR__ . '/LogsWriteRecoveryPolicy.php';
17 require_once __DIR__ . '/LogsQueueFlusher.php';
18 require_once __DIR__ . '/LogsWriter.php';
19
20 /**
21 * Public-surface facade for the logs subsystem. Implements the
22 * LogsRepositoryInterface contract that callers (PluginLogic, ViewReadService,
23 * RedirectsRetentionService, Privacy, etc.) depend on, and composes the
24 * five single-responsibility collaborators that hold the actual logic:
25 *
26 * - LogsWriter - log-write pipeline (logRedirectHit, queue, flush, recovery)
27 * - LogsHitsDataPopulator - join logshits/logsid/last_used onto result rows
28 * - LogsReadQueries - admin-list-table reads + daily-activity trend
29 * - LogsLookupRepository - lkup table CRUD
30 * - LogsPrivacyService - GDPR export and erasure
31 * - LogsHitsRollupService - hits-rollup lifecycle (rebuild / scheduling / locking)
32 *
33 * The facade also owns the static pipeline-trace decode utility used by
34 * View_Logs + DataAccess, and re-exports the hits-rollup constants for
35 * backwards-compatible callers reaching at LogsRepository::CONST_NAME.
36 */
37 class ABJ_404_Solution_LogsRepository implements ABJ_404_Solution_LogsRepositoryInterface {
38
39 // Backwards-compatible constants for callers reaching at
40 // ABJ_404_Solution_LogsRepository::CONST_NAME. Authoritative copies
41 // live on ABJ_404_Solution_LogsHitsRollupService.
42 const UPDATE_LOGS_HITS_TABLE_HOOK = ABJ_404_Solution_LogsHitsRollupService::UPDATE_LOGS_HITS_TABLE_HOOK;
43 const HITS_TABLE_MAX_AGE_SECONDS = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_MAX_AGE_SECONDS;
44 const HITS_TABLE_SCHEDULE_COOLDOWN_SECONDS = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_SCHEDULE_COOLDOWN_SECONDS;
45 const HITS_TABLE_REBUILD_LOCK_TTL_SECONDS = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_REBUILD_LOCK_TTL_SECONDS;
46 const HITS_TABLE_PREAGG_CHUNK_SIZE = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_PREAGG_CHUNK_SIZE;
47 const HITS_TABLE_DIRECT_PATH_THRESHOLD = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_DIRECT_PATH_THRESHOLD;
48 const HITS_TABLE_LAST_SCHEDULED_FLAG = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_LAST_SCHEDULED_FLAG;
49 const HITS_TABLE_LAST_REFRESHED_FLAG = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_LAST_REFRESHED_FLAG;
50 const HITS_TABLE_FIRST_STALE_DETECTED_FLAG = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_FIRST_STALE_DETECTED_FLAG;
51 const HITS_TABLE_STALE_NOTICE_TRANSIENT = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_STALE_NOTICE_TRANSIENT;
52 const HITS_TABLE_STALE_NOTICE_THRESHOLD_SECONDS = ABJ_404_Solution_LogsHitsRollupService::HITS_TABLE_STALE_NOTICE_THRESHOLD_SECONDS;
53
54 /** @var int Max age for cached daily-activity trend data. */
55 const TREND_DATA_CACHE_TTL_SECONDS = ABJ_404_Solution_LogsReadQueries::TREND_DATA_CACHE_TTL_SECONDS;
56
57 /** @var ABJ_404_Solution_LogsHitsRollupServiceInterface */
58 private $rollup;
59
60 /** @var ABJ_404_Solution_LogsLookupRepository */
61 private $lookups;
62
63 /** @var ABJ_404_Solution_LogsPrivacyService */
64 private $privacy;
65
66 /** @var ABJ_404_Solution_LogsReadQueries */
67 private $reads;
68
69 /** @var ABJ_404_Solution_LogsHitsDataPopulator */
70 private $hitsPopulator;
71
72 /** @var ABJ_404_Solution_LogsWriter */
73 private $writer;
74
75 /**
76 * @param ABJ_404_Solution_DatabaseCore $dbCore
77 * @param ABJ_404_Solution_Functions|null $functions
78 * @param ABJ_404_Solution_Logging|null $logging
79 * @param ABJ_404_Solution_RebuildHealthState|null $rebuildHealth Forwarded to the rollup service when one is constructed internally.
80 * @param ABJ_404_Solution_LogsHitsRollupServiceInterface|null $rollup Pre-built rollup service (preferred wiring). When null, an internal one is constructed.
81 * @param ABJ_404_Solution_DatabaseErrorClassifier|null $errorClassifier
82 * @param ABJ_404_Solution_DatabaseCollationHelper|null $collationHelper
83 * @param ABJ_404_Solution_DatabaseNoticeStateHolder|null $noticeState
84 */
85 public function __construct(
86 ABJ_404_Solution_DatabaseCore $dbCore,
87 $functions = null,
88 $logging = null,
89 $rebuildHealth = null,
90 $rollup = null,
91 $errorClassifier = null,
92 $collationHelper = null,
93 $noticeState = null
94 ) {
95 $f = $functions !== null ? $functions : abj_service('functions');
96 $logger = $logging !== null ? $logging : abj_service('logging');
97 $ec = $errorClassifier !== null ? $errorClassifier : $dbCore->errorClassifier();
98 $ch = $collationHelper !== null ? $collationHelper : $dbCore->collationHelper();
99 $ns = $noticeState !== null ? $noticeState : $dbCore->noticeState();
100 if ($rollup instanceof ABJ_404_Solution_LogsHitsRollupServiceInterface) {
101 $this->rollup = $rollup;
102 } else {
103 $this->rollup = new ABJ_404_Solution_LogsHitsRollupService($dbCore, $logger, $rebuildHealth);
104 }
105 $this->lookups = new ABJ_404_Solution_LogsLookupRepository($dbCore);
106 $this->privacy = new ABJ_404_Solution_LogsPrivacyService($dbCore);
107 $this->reads = new ABJ_404_Solution_LogsReadQueries($dbCore, $f, $logger);
108 $this->hitsPopulator = new ABJ_404_Solution_LogsHitsDataPopulator($dbCore);
109 $this->writer = new ABJ_404_Solution_LogsWriter($dbCore, $f, $logger, $ec, $ch, $ns, $this->lookups);
110 }
111
112 /**
113 * Accessor for callers that need to drive the rollup directly (e.g. tests
114 * inspecting scheduling state). Not part of the LogsRepository interface.
115 *
116 * @return ABJ_404_Solution_LogsHitsRollupServiceInterface
117 */
118 public function getRollupService(): ABJ_404_Solution_LogsHitsRollupServiceInterface {
119 return $this->rollup;
120 }
121
122 // =========================================================================
123 // Log read queries (delegated to LogsReadQueries + LogsHitsDataPopulator)
124 // =========================================================================
125
126 /** @inheritDoc */
127 function populateLogsData($rows) {
128 return $this->hitsPopulator->populate($rows, $this);
129 }
130
131 /** @inheritDoc */
132 function getDistinctLoggedUrls(
133 int $recentLogWindow = self::DEFAULT_DISTINCT_RECENT_LOG_WINDOW,
134 int $distinctUrlCap = self::DEFAULT_DISTINCT_URL_CAP
135 ): array {
136 return $this->reads->getDistinctLoggedUrls($recentLogWindow, $distinctUrlCap);
137 }
138
139 /** @inheritDoc */
140 function getLogsIDandURL($specificURL = '') {
141 return $this->reads->getLogsIDandURL($specificURL);
142 }
143
144 /** @inheritDoc */
145 function getLogsIDandURLLike($specificURL, $limitResults) {
146 return $this->reads->getLogsIDandURLLike($specificURL, $limitResults);
147 }
148
149 /** @inheritDoc */
150 function getLogRecords($tableOptions) {
151 return $this->reads->getLogRecords($tableOptions);
152 }
153
154 /** @inheritDoc */
155 public function getDailyActivityTrend(int $days = 30): array {
156 return $this->reads->getDailyActivityTrend($days, $this);
157 }
158
159 // =========================================================================
160 // GDPR (delegated to LogsPrivacyService)
161 // =========================================================================
162
163 /** @inheritDoc */
164 public function getLogsv2IdsForLookupValue($lkupValue, $page = 1, $perPage = 100) {
165 return $this->privacy->getLogsv2IdsForLookupValue($lkupValue, $page, $perPage);
166 }
167
168 /** @inheritDoc */
169 public function getLogsv2RowsForLookupValue($lkupValue, $page = 1, $perPage = 50) {
170 return $this->privacy->getLogsv2RowsForLookupValue($lkupValue, $page, $perPage);
171 }
172
173 /** @inheritDoc */
174 public function anonymizeLogsv2RowsByIds($ids) {
175 return $this->privacy->anonymizeLogsv2RowsByIds($ids);
176 }
177
178 // =========================================================================
179 // Log writes (delegated to LogsWriter)
180 // =========================================================================
181
182 /** @inheritDoc */
183 function logRedirectHit(ABJ_404_Solution_RedirectHitLogEntry $entry): void {
184 $this->writer->logRedirectHit($entry);
185 }
186
187 /** @inheritDoc */
188 function queueLogEntry(array $entry): void {
189 $this->writer->queueLogEntry($entry);
190 }
191
192 /** @inheritDoc */
193 function flushLogQueue(): void {
194 $this->writer->flushLogQueue();
195 }
196
197 /**
198 * Backwards-compatible accessor (not on the interface). Forwarded to the
199 * writer; DataAccess re-exports it as a pass-through for legacy callers.
200 */
201 public function isTableFullError(string $error): bool {
202 return $this->writer->isTableFullError($error);
203 }
204
205 /**
206 * Backwards-compatible accessor (not on the interface). Forwarded to the
207 * writer.
208 */
209 public function autoTrimLogsv2IfNeeded(string $tableName, string $errorMessage): bool {
210 return $this->writer->autoTrimLogsv2IfNeeded($tableName, $errorMessage);
211 }
212
213 /**
214 * Backwards-compatible accessor (not on the interface). Forwarded to the
215 * writer.
216 *
217 * @return wpdb|null
218 */
219 public function getIsolatedWpdb(): ?wpdb {
220 return $this->writer->getIsolatedWpdb();
221 }
222
223 /**
224 * Backwards-compatible accessor (not on the interface). Forwarded to the
225 * writer.
226 *
227 * @param array<string, mixed> $entry
228 * @return array<string, mixed>|null
229 */
230 public function sanitizeLogEntry(array $entry): ?array {
231 return $this->writer->sanitizeLogEntry($entry);
232 }
233
234 // =========================================================================
235 // Lookup table (delegated to LogsLookupRepository)
236 // =========================================================================
237
238 /** @inheritDoc */
239 function insertLookupValueAndGetID($valueToInsert) {
240 return $this->lookups->insertLookupValueAndGetID($valueToInsert);
241 }
242
243 /** @inheritDoc */
244 function getLookupIDForUser($userName) {
245 return $this->lookups->getLookupIDForUser($userName);
246 }
247
248 /** @inheritDoc */
249 public function correctDuplicateLookupValues(): void {
250 $this->lookups->correctDuplicateLookupValues();
251 }
252
253 // =========================================================================
254 // Pipeline trace decode (static; the codec stays on the facade so existing
255 // ABJ_404_Solution_LogsRepository::decompressPipelineTrace() call sites
256 // in View_Logs and DataAccess keep working without rewiring).
257 // =========================================================================
258
259 /** @inheritDoc */
260 public static function decompressPipelineTrace(?string $raw): ?array {
261 if ($raw === null || $raw === '') { return null; }
262 $decoded = base64_decode($raw, true);
263 if ($decoded === false) { return null; }
264 $json = @gzuncompress($decoded);
265 if ($json === false) { return null; }
266 $result = json_decode($json, true);
267 return is_array($result) ? $result : null;
268 }
269
270 // =========================================================================
271 // Hits-table rollup: thin forwarders to ABJ_404_Solution_LogsHitsRollupService
272 // =========================================================================
273
274 /** @inheritDoc */
275 function recordLogsHitsRollupStalenessSignal(): void { $this->rollup->recordLogsHitsRollupStalenessSignal(); }
276
277 /** @inheritDoc */
278 function hitsTableNeedsRebuild() { return $this->rollup->hitsTableNeedsRebuild(); }
279
280 /** @inheritDoc */
281 function getLogsHitsTableLastUpdated() { return $this->rollup->getLogsHitsTableLastUpdated(); }
282
283 /** @inheritDoc */
284 function createRedirectsForViewHitsTable(): bool { return $this->rollup->createRedirectsForViewHitsTable(); }
285
286 /** @inheritDoc */
287 function logsHitsTableExists() { return $this->rollup->logsHitsTableExists(); }
288
289 /** @inheritDoc */
290 function scheduleHitsTableRebuild(): void { $this->rollup->scheduleHitsTableRebuild(); }
291
292 /** @inheritDoc */
293 function getMaxLogId() { return $this->rollup->getMaxLogId(); }
294
295 /** @inheritDoc */
296 function getMinLogId() { return $this->rollup->getMinLogId(); }
297
298 /** @inheritDoc */
299 function getStoredMaxLogId() { return $this->rollup->getStoredMaxLogId(); }
300
301 /** @inheritDoc */
302 function getLogsHitsTableLastScheduledAt() { return $this->rollup->getLogsHitsTableLastScheduledAt(); }
303
304 /**
305 * Backwards-compatible accessor used by some integration tests that need
306 * the rollup-internal collation resolution. Delegates to the rollup
307 * service. Not part of LogsRepositoryInterface.
308 *
309 * @return string
310 */
311 public function resolveHitsJoinCollation(): string {
312 if (method_exists($this->rollup, 'resolveHitsJoinCollation')) {
313 return $this->rollup->resolveHitsJoinCollation();
314 }
315 return '';
316 }
317 }
318