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 / logs / LogsRepository.php

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

308 lines 12.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 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 * @param array<string, mixed> $entry
218 * @return array<string, mixed>|null
219 */
220 public function sanitizeLogEntry(array $entry): ?array {
221 return $this->writer->sanitizeLogEntry($entry);
222 }
223
224 // =========================================================================
225 // Lookup table (delegated to LogsLookupRepository)
226 // =========================================================================
227
228 /** @inheritDoc */
229 function insertLookupValueAndGetID($valueToInsert) {
230 return $this->lookups->insertLookupValueAndGetID($valueToInsert);
231 }
232
233 /** @inheritDoc */
234 function getLookupIDForUser($userName) {
235 return $this->lookups->getLookupIDForUser($userName);
236 }
237
238 /** @inheritDoc */
239 public function correctDuplicateLookupValues(): void {
240 $this->lookups->correctDuplicateLookupValues();
241 }
242
243 // =========================================================================
244 // Pipeline trace decode (static; the codec stays on the facade so existing
245 // ABJ_404_Solution_LogsRepository::decompressPipelineTrace() call sites
246 // in View_Logs and DataAccess keep working without rewiring).
247 // =========================================================================
248
249 /** @inheritDoc */
250 public static function decompressPipelineTrace(?string $raw): ?array {
251 if ($raw === null || $raw === '') { return null; }
252 $decoded = base64_decode($raw, true);
253 if ($decoded === false) { return null; }
254 $json = @gzuncompress($decoded);
255 if ($json === false) { return null; }
256 $result = json_decode($json, true);
257 return is_array($result) ? $result : null;
258 }
259
260 // =========================================================================
261 // Hits-table rollup: thin forwarders to ABJ_404_Solution_LogsHitsRollupService
262 // =========================================================================
263
264 /** @inheritDoc */
265 function recordLogsHitsRollupStalenessSignal(): void { $this->rollup->recordLogsHitsRollupStalenessSignal(); }
266
267 /** @inheritDoc */
268 function hitsTableNeedsRebuild() { return $this->rollup->hitsTableNeedsRebuild(); }
269
270 /** @inheritDoc */
271 function getLogsHitsTableLastUpdated() { return $this->rollup->getLogsHitsTableLastUpdated(); }
272
273 /** @inheritDoc */
274 function createRedirectsForViewHitsTable(): bool { return $this->rollup->createRedirectsForViewHitsTable(); }
275
276 /** @inheritDoc */
277 function logsHitsTableExists() { return $this->rollup->logsHitsTableExists(); }
278
279 /** @inheritDoc */
280 function scheduleHitsTableRebuild(): void { $this->rollup->scheduleHitsTableRebuild(); }
281
282 /** @inheritDoc */
283 function getMaxLogId() { return $this->rollup->getMaxLogId(); }
284
285 /** @inheritDoc */
286 function getMinLogId() { return $this->rollup->getMinLogId(); }
287
288 /** @inheritDoc */
289 function getStoredMaxLogId() { return $this->rollup->getStoredMaxLogId(); }
290
291 /** @inheritDoc */
292 function getLogsHitsTableLastScheduledAt() { return $this->rollup->getLogsHitsTableLastScheduledAt(); }
293
294 /**
295 * Backwards-compatible accessor used by some integration tests that need
296 * the rollup-internal collation resolution. Delegates to the rollup
297 * service. Not part of LogsRepositoryInterface.
298 *
299 * @return string
300 */
301 public function resolveHitsJoinCollation(): string {
302 if (method_exists($this->rollup, 'resolveHitsJoinCollation')) {
303 return $this->rollup->resolveHitsJoinCollation();
304 }
305 return '';
306 }
307 }
308