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 / stats / RedirectHitCountHistogramRepository.php

RedirectHitCountHistogramRepository.php in 404 Solution trunk, at includes/stats/RedirectHitCountHistogramRepository.php

91 lines 3.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 /** Query failure for the privacy-bucketed redirect hit-count histogram. */
8 class ABJ_404_Solution_RedirectHitCountHistogramQueryException extends RuntimeException {
9 }
10
11 /**
12 * Reads the privacy-preserving redirect hit-count telemetry histogram.
13 *
14 * This repository deliberately returns only fixed aggregate buckets. It must
15 * never expose redirect URLs or per-row traffic patterns. Query failures throw
16 * so the diagnostics collector omits the field instead of publishing a false
17 * zero histogram.
18 */
19 class ABJ_404_Solution_RedirectHitCountHistogramRepository {
20
21 /** @var ABJ_404_Solution_DatabaseQueryInterface */
22 private $dbCore;
23
24 /** @var ABJ_404_Solution_TableReadinessGate */
25 private $readiness;
26
27 public function __construct(
28 ABJ_404_Solution_DatabaseQueryInterface $dbCore,
29 ABJ_404_Solution_TableReadinessGate $readiness
30 ) {
31 $this->dbCore = $dbCore;
32 $this->readiness = $readiness;
33 }
34
35 /**
36 * Aggregate active redirects into fixed denormalized hit-count buckets.
37 *
38 * @return array<string, int>
39 * @throws ABJ_404_Solution_RedirectHitCountHistogramQueryException
40 */
41 public function getRedirectHitCountHistogram(): array {
42 $emptyHistogram = array(
43 'zero_hits' => 0,
44 'one_to_ten_hits' => 0,
45 'eleven_to_hundred_hits' => 0,
46 'over_hundred_hits' => 0,
47 );
48 if ($this->readiness->isKnownAbsent('{wp_abj404_redirects}')) {
49 return $emptyHistogram;
50 }
51
52 $redirectStatuses = ABJ404_STATUS_MANUAL . ", " . ABJ404_STATUS_AUTO . ", " . ABJ404_STATUS_REGEX;
53 $query = "SELECT
54 SUM(CASE WHEN disabled = 0 AND status IN (" . $redirectStatuses . ") AND logshits <= 0 THEN 1 ELSE 0 END) as zero_hits,
55 SUM(CASE WHEN disabled = 0 AND status IN (" . $redirectStatuses . ") AND logshits BETWEEN 1 AND 10 THEN 1 ELSE 0 END) as one_to_ten_hits,
56 SUM(CASE WHEN disabled = 0 AND status IN (" . $redirectStatuses . ") AND logshits BETWEEN 11 AND 100 THEN 1 ELSE 0 END) as eleven_to_hundred_hits,
57 SUM(CASE WHEN disabled = 0 AND status IN (" . $redirectStatuses . ") AND logshits > 100 THEN 1 ELSE 0 END) as over_hundred_hits
58 FROM {wp_abj404_redirects}
59 WHERE status IN (" . $redirectStatuses . ")";
60 $query = $this->dbCore->doTableNameReplacements($query);
61
62 $result = $this->dbCore->queryAndGetResults($query);
63 if (!empty($result['last_error']) || !empty($result['timed_out'])) {
64 $lastError = $result['last_error'] ?? 'unknown';
65 $lastErrorText = is_scalar($lastError)
66 ? (string)$lastError
67 : (is_object($lastError) ? get_class($lastError) : gettype($lastError));
68 $context = !empty($result['timed_out'])
69 ? 'timed_out=true'
70 : 'last_error=' . $lastErrorText;
71 throw new ABJ_404_Solution_RedirectHitCountHistogramQueryException(
72 'Redirect hit histogram query failed (' . $context . ')'
73 );
74 }
75 $rows = is_array($result['rows'] ?? null) ? $result['rows'] : array();
76 $row = !empty($rows) && is_array($rows[0] ?? null) ? $rows[0] : array();
77
78 return array(
79 'zero_hits' => self::scalarToInt($row['zero_hits'] ?? 0),
80 'one_to_ten_hits' => self::scalarToInt($row['one_to_ten_hits'] ?? 0),
81 'eleven_to_hundred_hits' => self::scalarToInt($row['eleven_to_hundred_hits'] ?? 0),
82 'over_hundred_hits' => self::scalarToInt($row['over_hundred_hits'] ?? 0),
83 );
84 }
85
86 /** @param mixed $value */
87 private static function scalarToInt($value): int {
88 return is_scalar($value) ? intval($value) : 0;
89 }
90 }
91