PluginProbe ʕ •ᴥ•ʔ
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More / 4.0.0
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More v4.0.0
4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 trunk 1.0.0 2.0.0 2.0.1 2.0.2 2.0.3 3.0 3.0.1 3.0.2 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.2.0 3.2.1 3.2.2 3.2.4 3.2.5 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1
superb-blocks / src / data / controllers / class-log-controller.php
superb-blocks / src / data / controllers Last commit date
class-cache-controller.php 1 month ago class-css-controller.php 1 month ago class-domainshift-controller.php 1 month ago class-key-controller.php 1 month ago class-log-controller.php 1 month ago class-option-controller.php 1 month ago class-rest-controller.php 1 month ago
class-log-controller.php
223 lines
1 <?php
2
3 namespace SuperbAddons\Data\Controllers;
4
5 defined('ABSPATH') || exit();
6
7 use Exception;
8
9 class LogController
10 {
11 const LOG_OPTION_KEY = 'superbaddons_errorlogs';
12 const LOG_MAX_ENTRIES = 50;
13
14 const CRON_SHARE_ERROR_LOGS = 'superbaddons_share_error_logs';
15 const SHARE_ENDPOINT = 'addons-status/logs/share';
16 const FEEDBACK_ENDPOINT = 'addons-status/feedback/share';
17
18 public static function GetLogs()
19 {
20 return get_option(self::LOG_OPTION_KEY, false);
21 }
22
23 public static function HandleException($exception)
24 {
25 try {
26 $settings = OptionController::GetSettings();
27 if (!$settings[SettingsOptionKey::LOGS_ENABLED]) {
28 return;
29 }
30
31 $exception_stack = array();
32 $trace = $exception->getTrace();
33 $code = $exception->getCode();
34 $message = $exception->getMessage();
35 $minimum_amount = 2;
36 $current_trace = 1;
37 $should_break_next = false;
38 foreach ($trace as $trace_item) {
39 $trace_class = isset($trace_item['class']) ? $trace_item['class'] : 'Unknown Class';
40 // Remove ABSPATH from trace to anonymize and prevent including server path
41 $trace_item_string = sprintf("%s (%s): %s -> %s", str_replace(rtrim(ABSPATH, '/\\'), '', isset($trace_item['file']) ? $trace_item['file'] : 'Unknown File'), isset($trace_item['line']) ? $trace_item['line'] : '??', $trace_class, isset($trace_item['function']) ? $trace_item['function'] : 'Unknown Function');
42
43 array_push($exception_stack, $trace_item_string);
44 if ($should_break_next) {
45 // Limit the number of traces to add to the stack
46 break;
47 }
48 if ($current_trace >= $minimum_amount && false === strpos($trace_class, "superb-blocks")) {
49 // Break after adding the next trace
50 $should_break_next = true;
51 }
52 $current_trace++;
53 }
54 $exception_stack = implode("\n\n", $exception_stack);
55
56 if ($code > 0) {
57 $message .= sprintf(" (Code: %d)", $code);
58 }
59
60 LogController::AddLog($message, $exception_stack);
61 } catch (Exception $ex) {
62 // do nothing if error log somehow fails
63 }
64 }
65
66 public static function LogInfo($title, $details = '')
67 {
68 try {
69 $settings = OptionController::GetSettings();
70 if (!$settings[SettingsOptionKey::LOGS_ENABLED]) {
71 return;
72 }
73 self::AddLog($title, $details);
74 } catch (Exception $ex) {
75 // do nothing if log fails
76 }
77 }
78
79 private static function AddLog($title, $stack)
80 {
81 try {
82 $logs = self::GetLogs();
83 if (!$logs) {
84 $logs = array();
85 }
86
87 $log_count = count($logs);
88 // add new log entry
89 if ($log_count >= self::LOG_MAX_ENTRIES) {
90 // slice to only allow max number of log entries
91 $logs = array_slice($logs, 1, self::LOG_MAX_ENTRIES);
92 }
93
94 if ($log_count >= 1 && isset($logs[$log_count - 1]['stack']) && $logs[$log_count - 1]['stack'] === $stack) {
95 // Don't add duplicate log entries
96 return;
97 }
98
99 array_push($logs, array(
100 "time" => time(),
101 "shared" => false,
102 "version" => sanitize_text_field(SUPERBADDONS_VERSION),
103 "title" => sanitize_text_field($title),
104 "stack" => sanitize_textarea_field($stack)
105 ));
106
107 update_option(self::LOG_OPTION_KEY, $logs, false);
108 } catch (Exception $e) {
109 // do nothing if error log somehow fails
110 }
111 }
112
113 public static function ClearLogs()
114 {
115 if (!self::GetLogs()) {
116 return true;
117 }
118
119 return delete_option(self::LOG_OPTION_KEY);
120 }
121
122 public static function AddCronAction()
123 {
124 add_action(self::CRON_SHARE_ERROR_LOGS, array(self::class, 'ShareErrorLogsCronEvent'));
125 }
126
127 public static function MaybeSubscribeCron()
128 {
129 if (!wp_next_scheduled(self::CRON_SHARE_ERROR_LOGS)) {
130 wp_schedule_event(time(), 'daily', self::CRON_SHARE_ERROR_LOGS);
131 }
132 }
133
134 public static function ShareErrorLogsCronEvent()
135 {
136 try {
137 $settings = OptionController::GetSettings();
138 if (!$settings[SettingsOptionKey::LOG_SHARE_ENABLED]) {
139 return;
140 }
141
142 $logs = self::GetLogs();
143 if (!$logs) {
144 return;
145 }
146
147 $logs_to_share = array();
148 foreach ($logs as $log) {
149 if (!$log['shared']) {
150 array_push($logs_to_share, $log);
151 }
152 }
153
154 if (count($logs_to_share) === 0) {
155 return;
156 }
157
158 $response = DomainShiftController::RemotePost(
159 self::SHARE_ENDPOINT,
160 array(
161 'headers' => array('Content-Type' => 'application/json'),
162 'method' => 'POST',
163 'body' => wp_json_encode(
164 array(
165 'action' => 'share_error_logs',
166 'logs' => $logs_to_share
167 )
168 )
169 )
170 );
171 $status = wp_remote_retrieve_response_code($response);
172 if (!is_array($response) || is_wp_error($response) || $status !== 200) {
173 throw new Exception(esc_html(sprintf(/* translators: %d: http status code */__("Error logs could not be shared. Status %d.", "superb-blocks"), $status)));
174 }
175
176 foreach ($logs as &$log) {
177 $log['shared'] = true;
178 }
179
180 update_option(self::LOG_OPTION_KEY, $logs, false);
181 } catch (Exception $ex) {
182 self::HandleException($ex);
183 }
184 }
185
186 public static function SendFeedback($sanitized_message)
187 {
188 if (strlen($sanitized_message) > 1000) {
189 $sanitized_message = substr($sanitized_message, 0, 1000) . "...";
190 }
191 $response = DomainShiftController::RemotePost(
192 self::FEEDBACK_ENDPOINT,
193 array(
194 'headers' => array('Content-Type' => 'application/json'),
195 'method' => 'POST',
196 'body' => wp_json_encode(
197 array(
198 'action' => 'share_feedback',
199 'feedback' => array(
200 'message' => $sanitized_message,
201 "time" => time(),
202 "premium" => KeyController::HasValidPremiumKey(),
203 "version" => sanitize_text_field(SUPERBADDONS_VERSION),
204 )
205 )
206 )
207 )
208 );
209 $status = wp_remote_retrieve_response_code($response);
210 if (!is_array($response) || is_wp_error($response) || $status !== 200) {
211 throw new Exception(esc_html(sprintf(/* translators: %d: http status code */__("Feedback could not be shared. Status %d.", "superb-blocks"), $status)));
212 }
213 }
214
215 public static function MaybeUnsubscribeCron()
216 {
217 $timestamp = wp_next_scheduled(self::CRON_SHARE_ERROR_LOGS);
218 if ($timestamp) {
219 wp_unschedule_event($timestamp, self::CRON_SHARE_ERROR_LOGS);
220 }
221 }
222 }
223