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 / feedback / FeedbackPluginSettingsSnapshot.php

FeedbackPluginSettingsSnapshot.php in 404 Solution trunk, at includes/feedback/FeedbackPluginSettingsSnapshot.php

150 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // allow-no-test-found: covered by tests/FeedbackPayloadSchemaContractTest.php through the public feedback payload builder
4
5 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 require_once __DIR__ . '/FeedbackTransportLog.php';
10
11 /**
12 * Builds the deny-by-default plugin-settings diagnostic snapshot.
13 *
14 * The allowlist is intentionally a map of setting name to wire type. Adding a
15 * new option to abj404_settings does not disclose it; a maintainer must add it
16 * here deliberately after checking that the value is neither a credential nor
17 * user-authored free text. The keys below come from PluginLogicDefaults and the
18 * settings policies that persist per-page, sorting, and scheduling behavior.
19 */
20 class ABJ_404_Solution_FeedbackPluginSettingsSnapshot {
21
22 private const TYPE_BOOL = 'bool';
23 private const TYPE_INT = 'int';
24 private const TYPE_STRING = 'string';
25
26 /**
27 * Behavioral configuration safe to transmit in support diagnostics.
28 *
29 * Deliberately absent: GSC/OAuth/API credentials, email addresses,
30 * user-authored templates/patterns/ignore lists, and filesystem paths.
31 *
32 * @var array<string, string>
33 */
34 private const ALLOWLIST = array(
35 'default_redirect' => self::TYPE_INT,
36 'capture_404' => self::TYPE_BOOL,
37 'capture_deletion' => self::TYPE_INT,
38 'manual_deletion' => self::TYPE_INT,
39 'log_deletion' => self::TYPE_INT,
40 'send_error_logs' => self::TYPE_BOOL,
41 'debug_mode' => self::TYPE_BOOL,
42 'log_raw_ips' => self::TYPE_BOOL,
43 'maximum_log_disk_usage' => self::TYPE_INT,
44 'remove_matches' => self::TYPE_BOOL,
45 'suggest_max' => self::TYPE_INT,
46 'suggest_cats' => self::TYPE_BOOL,
47 'suggest_tags' => self::TYPE_BOOL,
48 'suggest_minscore' => self::TYPE_INT,
49 'suggest_minscore_enabled' => self::TYPE_BOOL,
50 'update_suggest_url' => self::TYPE_BOOL,
51 'auto_redirects' => self::TYPE_BOOL,
52 'old_permalink_structure_redirects' => self::TYPE_BOOL,
53 'auto_slugs' => self::TYPE_BOOL,
54 'auto_trash_redirect' => self::TYPE_BOOL,
55 'auto_score' => self::TYPE_INT,
56 'auto_score_title' => self::TYPE_INT,
57 'auto_score_category_tag' => self::TYPE_INT,
58 'auto_score_content' => self::TYPE_INT,
59 'auto_deletion' => self::TYPE_INT,
60 'auto_302_expiration_days' => self::TYPE_INT,
61 'auto_cats' => self::TYPE_BOOL,
62 'auto_tags' => self::TYPE_BOOL,
63 'template_redirect_priority' => self::TYPE_INT,
64 'dest404_behavior' => self::TYPE_STRING,
65 'redirect_all_requests' => self::TYPE_BOOL,
66 'auto_trash_junk_urls' => self::TYPE_BOOL,
67 'perpage' => self::TYPE_INT,
68 'page_redirects_order_by' => self::TYPE_STRING,
69 'page_redirects_order' => self::TYPE_STRING,
70 'captured_order_by' => self::TYPE_STRING,
71 'captured_order' => self::TYPE_STRING,
72 'admin_notification' => self::TYPE_INT,
73 'admin_notification_frequency' => self::TYPE_STRING,
74 'admin_notification_digest_limit' => self::TYPE_INT,
75 'days_wait_before_major_update' => self::TYPE_INT,
76 );
77
78 /**
79 * Read normalized settings and return only approved scalar values.
80 *
81 * @return array<string, bool|int|string>
82 */
83 public function collect(): array {
84 $repository = function_exists('abj_service_optional')
85 ? abj_service_optional('options_repository')
86 : null;
87 if (!is_object($repository) || !method_exists($repository, 'getOptions')) {
88 return array();
89 }
90
91 try {
92 $options = $repository->getOptions(true);
93 } catch (\Throwable $e) {
94 ABJ_404_Solution_FeedbackTransportLog::log(
95 'warn',
96 'FeedbackPluginSettingsSnapshot options_repository probe failed: ' . $e->getMessage()
97 );
98 return array();
99 }
100 if (!is_array($options)) {
101 ABJ_404_Solution_FeedbackTransportLog::log(
102 'warn',
103 'FeedbackPluginSettingsSnapshot expected options array, got ' . gettype($options)
104 );
105 return array();
106 }
107
108 $snapshot = array();
109 foreach (self::ALLOWLIST as $key => $type) {
110 if (!array_key_exists($key, $options) || !is_scalar($options[$key])) {
111 continue;
112 }
113 $normalized = $this->normalizeValue($options[$key], $type);
114 if ($normalized !== null) {
115 $snapshot[$key] = $normalized;
116 }
117 }
118 return $snapshot;
119 }
120
121 /**
122 * @param mixed $value
123 * @return bool|int|string|null
124 */
125 private function normalizeValue($value, string $type) {
126 if ($type === self::TYPE_INT) {
127 return is_numeric($value) ? (int)$value : null;
128 }
129 if ($type === self::TYPE_BOOL) {
130 if (in_array($value, array(true, 1, '1', 'on', 'yes'), true)) {
131 return true;
132 }
133 if (in_array($value, array(false, 0, '0', 'off', 'no', ''), true)) {
134 return false;
135 }
136 return null;
137 }
138 if ($type === self::TYPE_STRING) {
139 if (is_string($value)) {
140 return $value;
141 }
142 if (is_int($value) || is_float($value) || is_bool($value)) {
143 return (string)$value;
144 }
145 return null;
146 }
147 return null;
148 }
149 }
150