PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / features / class-eventviewer.php

class-eventviewer.php in DecaLog 4.4.0, at includes/features/class-eventviewer.php

735 lines 29.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Event viewer
4 *
5 * Handles a view for a specific event.
6 *
7 * @package Features
8 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
9 * @since 1.0.0
10 */
11
12 namespace Decalog\Plugin\Feature;
13
14 use Decalog\Storage\AbstractStorage;
15 use Decalog\Storage\APCuStorage;
16 use Decalog\Storage\DBStorage;
17 use Decalog\System\Date;
18 use Decalog\System\Option;
19 use Decalog\System\PHP;
20 use Decalog\System\Timezone;
21 use Feather;
22 use Decalog\System\Database;
23 use Decalog\System\User;
24 use Decalog\System\UserAgent;
25 use Decalog\System\L10n;
26 use Decalog\System\GeoIP;
27 use Decalog\Plugin\Feature\SDK;
28
29 /**
30 * Define the event viewer functionality.
31 *
32 * Handles a view for a specific event.
33 *
34 * @package Features
35 * @author Pierre Lannoy <https://pierre.lannoy.fr/>.
36 * @since 1.0.0
37 */
38 class EventViewer {
39
40 /**
41 * The internal logger.
42 *
43 * @since 1.0.0
44 * @access protected
45 * @var DLogger $logger The plugin admin logger.
46 */
47 protected $logger;
48
49 /**
50 * The screen id.
51 *
52 * @since 1.0.0
53 * @var string $screen_id The screen id.
54 */
55 private static $screen_id = 'decalog_event_viewer';
56
57 /**
58 * The full event detail.
59 *
60 * @since 1.0.0
61 * @var array $event The full event detail.
62 */
63 private $event = null;
64
65 /**
66 * The events log id.
67 *
68 * @since 1.0.0
69 * @var array $logid The events log id.
70 */
71 private $logid = null;
72
73 /**
74 * The event id.
75 *
76 * @since 1.0.0
77 * @var array $eventid The event id.
78 */
79 private $eventid = null;
80
81 /**
82 * The device.
83 *
84 * @since 1.0.0
85 * @var object $device The device.
86 */
87 private $device = null;
88
89 /**
90 * Initialize the class and set its properties.
91 *
92 * @param string $logid The events log id.
93 * @param string $eventid The specific event id.
94 * @param DLogger $logger The internal logger.
95 * @since 1.0.0
96 */
97 public function __construct( $logid, $eventid, $logger ) {
98 $this->logid = $logid;
99 $this->logger = $logger;
100 $this->eventid = $eventid;
101 $this->event = null;
102 $this->device = UserAgent::get( '-' );
103 $log = null;
104 $loggers = Option::network_get( 'loggers' );
105 if ( array_key_exists( $this->logid, $loggers ) ) {
106 $bucket_name = 'decalog_' . str_replace( '-', '', $this->logid );
107 switch ( $loggers[ $this->logid ]['configuration']['constant-storage'] ) {
108 case 'apcu':
109 $storage = new APCuStorage( $bucket_name );
110 break;
111 default:
112 global $wpdb;
113 $storage = new DBStorage( $bucket_name );
114 }
115 $log = $storage->get_by_id( $this->eventid );
116 }
117 if ( isset( $log ) ) {
118 foreach ( Events::get() as $logged ) {
119 if ( $logged['id'] === $this->logid ) {
120 if ( ! array_key_exists( 'limit', $logged ) || in_array( $log['site_id'], $logged ['limit'] ) ) {
121 $this->event = $log;
122 $this->device = UserAgent::get( $this->event['user_agent'] );
123 break;
124 }
125 }
126 }
127 }
128 wp_enqueue_script( 'postbox' );
129 }
130
131 /**
132 * Append custom panel HTML to the "Screen Options" box of the current page.
133 * Callback for the 'screen_settings' filter.
134 *
135 * @param string $current Current content.
136 * @param object $screen The current screen.
137 * @return string The HTML code to append to "Screen Options".
138 * @since 1.0.0
139 */
140 public function display_screen_settings( $current, $screen ) {
141 if ( is_object( $screen ) && false !== strpos( $screen->id, 'page_decalog-viewer' ) && false === strpos( $current, 'id="option-decalog-viewer"' ) ) {
142 $current .= '<div id="option-decalog-viewer" class="metabox-prefs custom-options-panel requires-autosave"><input type="hidden" name="_wpnonce-decalog_viewer" value="' . wp_create_nonce( 'save_settings_decalog_viewer' ) . '" />';
143 $current .= $this->get_options();
144 $current .= '</div>';
145 }
146 return $current;
147 }
148
149 /**
150 * Add options.
151 *
152 * @since 1.0.0
153 */
154 public function add_metaboxes_options() {
155 $this->add_metaboxes();
156 }
157
158 /**
159 * Get the box options.
160 *
161 * @return string The HTML code to append.
162 * @since 1.0.0
163 */
164 public function get_options() {
165 $result = '<fieldset class="metabox-prefs">';
166 $result .= '<legend>' . decalog_esc_html__( 'Boxes', 'decalog' ) . '</legend>';
167 $result .= $this->meta_box_prefs();
168 $result .= '</fieldset>';
169 return $result;
170 }
171
172 /**
173 * Prints the meta box preferences.
174 *
175 * @return string The HTML code to append.
176 * @since 1.0.0
177 */
178 public function meta_box_prefs() {
179 global $wp_meta_boxes;
180 $result = '';
181 if ( empty( $wp_meta_boxes[ self::$screen_id ] ) ) {
182 return '';
183 }
184 $hidden = get_hidden_meta_boxes( self::$screen_id );
185 foreach ( array_keys( $wp_meta_boxes[ self::$screen_id ] ) as $context ) {
186 foreach ( array( 'high', 'core', 'default', 'low' ) as $priority ) {
187 if ( ! isset( $wp_meta_boxes[ self::$screen_id ][ $context ][ $priority ] ) ) {
188 continue;
189 }
190 foreach ( $wp_meta_boxes[ self::$screen_id ][ $context ][ $priority ] as $box ) {
191 if ( false === $box || ! $box['title'] ) {
192 continue;
193 }
194 if ( 'submitdiv' === $box['id'] || 'linksubmitdiv' === $box['id'] ) {
195 continue;
196 }
197 $box_id = $box['id'];
198 $result .= '<label for="' . $box_id . '-hide">';
199 $result .= '<input class="hide-postbox-tog" name="' . $box_id . '-hide" type="checkbox" id="' . $box_id . '-hide" value="' . $box_id . '"' . ( ! in_array( $box_id, $hidden, false ) ? ' checked="checked"' : '' ) . ' />';
200 $result .= $box['title'] . '</label>';
201 }
202 }
203 }
204 return $result;
205 }
206
207 /**
208 * Get the event viewer.
209 *
210 * @since 1.0.0
211 **/
212 public function get() {
213 echo '<div class="wrap">';
214 if ( isset( $this->event ) ) {
215 $icon = '<img style="width:30px;float:left;padding-right:8px;" src="' . EventTypes::$icons[ $this->event['level'] ] . '" />';
216 $name = ChannelTypes::$channel_names[ strtoupper( $this->event['channel'] ) ] . '&nbsp;#' . $this->event['id'];
217 // phpcs:ignore
218 echo '<h2>' . $icon . $name . '</h2>';
219 settings_errors();
220 echo '<form name="decalog_event" method="post">';
221 echo '<div id="dashboard-widgets-wrap">';
222 wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
223 wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
224 echo ' <div id="dashboard-widgets" class="metabox-holder">';
225 echo ' <div id="postbox-container-1" class="postbox-container">';
226 do_meta_boxes( self::$screen_id, 'advanced', null );
227 echo ' </div>';
228 echo ' <div id="postbox-container-2" class="postbox-container">';
229 do_meta_boxes( self::$screen_id, 'side', null );
230 echo ' </div>';
231 echo ' <div id="postbox-container-3" class="postbox-container">';
232 do_meta_boxes( self::$screen_id, 'column3', null );
233 echo ' </div>';
234 echo ' <div id="postbox-container-4" class="postbox-container">';
235 do_meta_boxes( self::$screen_id, 'column4', null );
236 echo ' </div>';
237 echo ' </div>';
238 echo '</div>';
239 echo '</form>';
240 } else {
241 echo '<h2>' . decalog_esc_html__( 'Forbidden', 'decalog' ) . '</h2>';
242 settings_errors();
243 echo '<p>' . decalog_esc_html__( 'The event or events log you tried to access is out of your scope.', 'decalog' ) . '</p>';
244 echo '<p>' . decalog_esc_html__( 'If you think this is an error, please contact the network administrator with these details:', 'decalog' );
245 echo '<ul>';
246 // phpcs:ignore
247 echo '<li>' . sprintf( decalog_esc_html__( 'Events log: %s', 'decalog' ), '<code>' . $this->logid . '</code>' ) . '</li>';
248 // phpcs:ignore
249 echo '<li>' . sprintf( decalog_esc_html__( 'Event: %s', 'decalog' ), '<code>' . $this->eventid . '</code>' ) . '</li>';
250 echo '</ul>';
251 echo '</p>';
252 $this->logger->warning( sprintf( 'Trying to access out of scope event #%s from events log {%s}.', $this->eventid, $this->logid ), 403 );
253 }
254 echo '</div>';
255 }
256
257 /**
258 * Add footer scripts.
259 *
260 * @since 1.0.0
261 */
262 public function add_footer() {
263 $result = '<script>';
264 $result .= ' jQuery(document).ready( function($) {';
265 $result .= " $('.if-js-closed').removeClass('if-js-closed').addClass('closed');";
266 $result .= " if(typeof postboxes !== 'undefined')";
267 $result .= " postboxes.add_postbox_toggles('" . self::$screen_id . "');";
268 $result .= ' });';
269 $result .= '</script>';
270 // phpcs:ignore
271 echo $result;
272 }
273
274 /**
275 * Add all the needed meta boxes.
276 *
277 * @since 1.0.0
278 */
279 public function add_metaboxes() {
280 // Left column.
281 add_meta_box( 'decalog-main', decalog_esc_html__( 'Event', 'decalog' ), [ $this, 'event_widget' ], self::$screen_id, 'advanced' );
282 add_meta_box( 'decalog-message', decalog_esc_html__( 'Content', 'decalog' ), [ $this, 'message_widget' ], self::$screen_id, 'advanced' );
283 add_meta_box( 'decalog-wordpress', 'WordPress', [ $this, 'wordpress_widget' ], self::$screen_id, 'advanced' );
284 if ( $this->device->class_is_bot ) {
285 add_meta_box( 'decalog-bot', decalog_esc_html__( 'Bot details', 'decalog' ), [ $this, 'bot_widget' ], self::$screen_id, 'advanced' );
286 } elseif ( $this->device->class_is_desktop || $this->device->class_is_mobile ) {
287 add_meta_box( 'decalog-device', decalog_esc_html__( 'Device details', 'decalog' ), [ $this, 'device_widget' ], self::$screen_id, 'advanced' );
288 } elseif ( class_exists( 'PODeviceDetector\API\Device' ) ) {
289 add_meta_box( 'decalog-other', decalog_esc_html__( 'Client details', 'decalog' ), [ $this, 'call_widget' ], self::$screen_id, 'advanced' );
290 }
291 if ( 'unknown' !== ( $this->event['verb'] ?? 'unknown' ) ) {
292 add_meta_box( 'decalog-http', decalog_esc_html__( 'HTTP request', 'decalog' ), [ $this, 'http_widget' ], self::$screen_id, 'advanced' );
293 }
294 add_meta_box( 'decalog-php', decalog_esc_html__( 'PHP introspection', 'decalog' ), [ $this, 'php_widget' ], self::$screen_id, 'advanced' );
295 // Right column.
296 /* translators: like in the sentence "PHP backtrace" or "WordPress backtrace" */
297 add_meta_box( 'decalog-wpbacktrace', sprintf( decalog_esc_html__( '%s backtrace', 'decalog' ), 'WordPress' ), [ $this, 'wpbacktrace_widget' ], self::$screen_id, 'side' );
298 /* translators: like in the sentence "PHP backtrace" or "WordPress backtrace" */
299 add_meta_box( 'decalog-phpbacktrace', sprintf( decalog_esc_html__( '%s backtrace', 'decalog' ), 'PHP' ), [ $this, 'phpbacktrace_widget' ], self::$screen_id, 'side' );
300 }
301
302 /**
303 * Get the action image.
304 *
305 * @param string $url The url to call.
306 * @param string $text The anchor to display.
307 * @return string The action image, ready to print.
308 * @since 3.3.0
309 */
310 protected function get_action( $url, $text ) {
311 return '<a href="' . $url . '" target="_blank">' . esc_html( $text ) . '</a>';
312 }
313
314 /**
315 * Get the action image.
316 *
317 * @param string $box The box where to display actions.
318 * @return string The actions texts, ready to print.
319 * @since 3.3.0
320 */
321 protected function get_actions( $box ) {
322 $item = $this->event;
323 $item['logger_id'] = $this->logid;
324 $item['country_code'] = '';
325 if ( false === strpos( $item['remote_ip'], '{' ) ) {
326 $geoip = new GeoIP();
327 $item['country_code'] = $geoip->get_iso3166_alpha2( $item['remote_ip'] );
328 }
329
330 /**
331 * Filters the available actions for the current item.
332 *
333 * @See https://github.com/Pierre-Lannoy/wp-decalog/blob/master/HOOKS.md
334 * @since 3.3.0
335 * @param array $item The full event with metadata.
336 */
337 $actions = apply_filters( 'decalog_event_view_actions_for_' . $box, [], $item );
338
339 $result = '';
340 $list = [];
341 foreach ( $actions as $action ) {
342 if ( isset( $action['url'] ) && isset( $action['text'] ) ) {
343 $list[] = $this->get_action( $action['url'], $action['text'] );
344 }
345 }
346 if ( 0 < count( $list ) ) {
347 $result = implode( ' | ', $list );
348 }
349 return $result;
350 }
351
352 /**
353 * Print an activity block.
354 *
355 * @param string $content The content of the block.
356 * @param string $box The corresponding box id.
357 * @since 1.0.0
358 */
359 private function output_activity_block( $content, $box ) {
360 $actions = $this->get_actions( $box );
361 echo '<div class="activity-block" style="padding-bottom: 0;padding-top: 0;">';
362 // phpcs:ignore
363 echo $content;
364 if ( '' !== $actions ) {
365 echo '<div style="font-size:x-small; margin-bottom:-4px;text-align:right">' . $actions . '</div>';
366 }
367 echo '</div>';
368 }
369
370 /**
371 * Get a section to include in a block.
372 *
373 * @param string $content The content of the section.
374 * @return string The section, ready to print.
375 * @since 1.0.0
376 */
377 private function get_section( $content ) {
378 return '<div style="margin-bottom: 10px;">' . $content . '</div>';
379 }
380
381 /**
382 * Get an icon.
383 *
384 * @param string $icon_name The name of the icon.
385 * @param string $background The background color.
386 * @return string The icon, as image, ready to print.
387 * @since 1.0.0
388 */
389 private function get_icon( $icon_name, $background = '#F9F9F9' ) {
390 return '<img style="width:18px;float:left;padding-right:6px;" src="' . Feather\Icons::get_base64( $icon_name, $background, '#9999BB' ) . '" />';
391 }
392
393 /**
394 * Get a component icon.
395 *
396 * @param string $component The name of the component.
397 * @return string The icon, as image, ready to print.
398 * @since 3.1.0
399 */
400 private function get_component_icon( $component ) {
401 $icon = SDK::get_icon( $component );
402 if ( '' === $icon ) {
403 return $this->get_icon( 'box' );
404 }
405 return '<img style="width:18px;float:left;padding-right:6px;" src="' . $icon . '" />';
406 }
407
408 /**
409 * Get an external link markup.
410 *
411 * @param string $url The url.
412 * @return string The link markup, ready to print.
413 * @since 1.0.0
414 */
415 private function get_external_link( $url ) {
416 if ( '' === $url ) {
417 return '';
418 }
419 return '<a href="' . $url . '" target="_blank"><img style="width:10px;padding-left:4px;padding-right:6px;vertical-align:text-top;" src="' . Feather\Icons::get_base64( 'external-link', 'none', '#9999BB' ) . '" /></a>';
420 }
421
422 /**
423 * Get an intenel link markup.
424 *
425 * @param string $url The url.
426 * @param string $anchor The anchor.
427 * @return string The link markup, ready to print.
428 * @since 1.0.0
429 */
430 private function get_internal_link( $url, $anchor ) {
431 if ( '' === $url ) {
432 return $anchor;
433 }
434 return '<a href="' . $url . '" style="text-decoration:none;color:inherit;">' . $anchor . '</a>';
435 }
436
437 /**
438 * Get content of the event widget box.
439 *
440 * @since 1.0.0
441 */
442 public function event_widget() {
443 // Event type.
444 $icon = '<img style="width:18px;float:left;padding-right:6px;" src="' . EventTypes::$icons[ $this->event['level'] ] . '" />';
445 $level = ucwords( strtolower( EventTypes::$level_names[ EventTypes::$levels[ $this->event['level'] ] ] ) );
446 $channel = ChannelTypes::$channel_names[ strtoupper( $this->event['channel'] ) ];
447 $content = '<span style="width:40%;cursor: default;float:left">' . $icon . $level . '</span>';
448 $content .= '<span style="width:60%;cursor: default;">' . $this->get_icon( 'activity', 'none' ) . $channel . '</span>';
449 $event = $this->get_section( $content );
450 // Event time.
451 $time = Date::get_date_from_mysql_utc( $this->event['timestamp'], Timezone::network_get()->getName(), 'Y-m-d H:i:s' );
452 $dif = Date::get_positive_time_diff_from_mysql_utc( $this->event['timestamp'] );
453 $content = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'clock' ) . $time . '</span> <span style="color:silver">(' . $dif . ')</span>';
454 $hour = $this->get_section( $content );
455 // Event source.
456 $class = ClassTypes::$classe_names[ strtolower( $this->event['class'] ) ];
457 $component = $this->event['component'] . ' ' . $this->event['version'];
458 $content = '<span style="width:40%;cursor: default;float:left">' . $this->get_icon( 'folder' ) . $class . '</span>';
459 $content .= '<span style="width:60%;cursor: default;">' . $this->get_component_icon( $this->event['component'] ) . $component . '</span>';
460 $source = $this->get_section( $content );
461 // Instance.
462 $content = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'cpu' ) . $this->event['instance'] . '</span>';
463 $instance = $this->get_section( $content );
464
465 $this->output_activity_block( $event . $hour . $source . $instance, 'event' );
466 }
467
468 /**
469 * Get content of the message widget box.
470 *
471 * @since 1.0.0
472 */
473 public function message_widget() {
474 // Event code.
475 $content = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'tag' ) . $this->event['code'] . '</span>';
476 $error = $this->get_section( $content );
477 // Event message.
478 $content = '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'message-square' ) . $this->event['message'] . '</span>';
479 $message = $this->get_section( $content );
480
481 $this->output_activity_block( $error . $message, 'content' );
482 }
483
484 /**
485 * Get content of the WordPress widget box.
486 *
487 * @since 1.0.0
488 */
489 public function wordpress_widget() {
490 // User detail.
491 if ( 'anonymous' === $this->event['user_name'] ) {
492 $user = $this->get_section( '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'user' ) . decalog_esc_html__( 'Anonymous user', 'decalog' ) . '</span>' );
493 } elseif ( 0 === strpos( $this->event['user_name'], '{' ) ) {
494 $user = $this->get_section( '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'user' ) . decalog_esc_html__( 'Pseudonymized user', 'decalog' ) . '</span>' );
495 } elseif ( 0 !== (int) $this->event['user_id'] ) {
496 $user = $this->get_section( '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'user-check' ) . User::get_user_string( (int) $this->event['user_id'] ) . '</span>' );
497 } else {
498 $user = $this->get_section( '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'user-x' ) . decalog_esc_html__( 'Deleted user', 'decalog' ) . '</span>' );
499 }
500 // Site detail.
501 $content = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'layout' ) . $this->event['site_name'] . '</span>';
502 $site = $this->get_section( $content );
503
504 $this->output_activity_block( $user . $site, 'wp' );
505 }
506
507 /**
508 * Get content of the http widget box.
509 *
510 * @since 1.0.0
511 */
512 public function http_widget() {
513 // Server detail.
514 $ip = $this->event['remote_ip'];
515 $icon = '';
516 if ( 0 === strpos( $ip, '{' ) ) {
517 $ip = decalog_esc_html__( 'obfuscated IP', 'decalog' );
518 } else {
519 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE ) ) {
520 $geoip = new GeoIP();
521 $icon = $geoip->get_flag( $ip, '', 'width:14px;padding-left:4px;padding-right:4px;vertical-align:baseline;', '', '', false, true );
522 }
523 }
524 // phpcs:ignore
525 $ip = sprintf( decalog_esc_html__( 'from %s', 'decalog' ), $icon . $ip );
526 $content = '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'layout' ) . $this->event['server'] . ' ' . $ip . '</span>';
527 $server = $this->get_section( $content );
528 // Request detail.
529 $verb = $this->event['verb'];
530 if ( '-' !== $verb ) {
531 $verb = '<span style="vertical-align: middle;font-size:8px;padding:2px 6px;text-transform:uppercase;font-weight: bold;background-color:#9999BB;color:#F9F9F9;border-radius:2px;cursor: default;word-break: break-word;">' . $verb . '</span>';
532 } else {
533 $verb = '';
534 }
535 $content = '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'server' ) . $this->event['url'] . '&nbsp;' . $verb . '</span>';
536 $request = $this->get_section( $content );
537 // referrer detail.
538 $referrer = '';
539 if ( isset( $this->event['referrer'] ) ) {
540 $content = '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'arrow-left-circle' ) . $this->event['referrer'] . '</span>';
541 $referrer = $this->get_section( $content );
542 }
543 $this->output_activity_block( $server . $request . $referrer, 'http' );
544 }
545
546 /**
547 * Get content of the device widget box.
548 *
549 * @since 1.0.0
550 */
551 private function get_client() {
552 if ( $this->device->client_is_browser ) {
553 $url = [
554 'site' => $this->event['site_id'],
555 'type' => 'browser',
556 'id' => $this->device->client_short_name,
557 ];
558 $iclient = '<img style="width:16px;float:left;padding-right:6px;" src="' . $this->device->browser_icon_base64() . '" />';
559 $client = $this->get_internal_link( UserAgent::get_analytics_url( $url ), ( '-' !== $this->device->client_name ? $this->device->client_name : decalog_esc_html__( 'Generic', 'decalog' ) ) ) . ( '-' !== $this->device->client_version ? ' ' . $this->device->client_version : '' );
560 $content = '<span style="width:100%;cursor: default;">' . $iclient . $client . '</span> <span style="color:silver">(' . $this->device->client_engine . ')</span>';
561 return $this->get_section( $content );
562 }
563 $url = [
564 'site' => $this->event['site_id'],
565 'type' => 'browser',
566 'id' => $this->device->client_short_name,
567 ];
568 if ( '' !== $this->device->client_name ) {
569 $client = $this->get_internal_link( UserAgent::get_analytics_url( $url ), ( '-' !== $this->device->client_name ? $this->device->client_name : decalog_esc_html__( 'Generic', 'decalog' ) ) ) . ( '-' !== $this->device->client_version ? ' ' . $this->device->client_version : '' );
570 $content = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'play-circle' ) . $client . '</span> <span style="color:silver">' . $this->device->client_full_type . '</span>';
571 } else {
572 $content = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'play-circle' ) . decalog_esc_html__( 'Local shell', 'decalog' ) . '</span>';
573 }
574
575 return $this->get_section( $content );
576 }
577
578 /**
579 * Get content of the device widget box.
580 *
581 * @since 1.0.0
582 */
583 public function device_widget() {
584 if ( '' === $this->device->brand_short_name ) {
585 $id = '-';
586 } else {
587 $id = $this->device->brand_short_name;
588 }
589 $url = [
590 'site' => $this->event['site_id'],
591 'type' => 'device',
592 'id' => $id,
593 'extended' => '' !== $this->device->model_name ? $this->device->model_name : '-',
594 ];
595 $idevice = '<img style="width:16px;float:left;padding-right:6px;" src="' . $this->device->brand_icon_base64() . '" />';
596 $device = ( '-' !== $this->device->brand_name && '' !== $this->device->brand_name ? $this->device->brand_name : decalog_esc_html__( 'Generic', 'decalog' ) ) . ( '-' !== $this->device->model_name ? ' ' . $this->device->model_name : '' );
597 $device = $this->get_internal_link( UserAgent::get_analytics_url( $url ), $device );
598 $url = [
599 'site' => $this->event['site_id'],
600 'type' => 'os',
601 'id' => $this->device->os_short_name,
602 ];
603 $ios = '<img style="width:16px;float:left;padding-right:6px;" src="' . $this->device->os_icon_base64() . '" />';
604 $os = ( '-' !== $this->device->os_name ? $this->get_internal_link( UserAgent::get_analytics_url( $url ), $this->device->os_name ) : decalog_esc_html__( 'Unknown', 'decalog' ) ) . ( '-' !== $this->device->os_version ? ' ' . $this->device->os_version : '' );
605 $content = '<span style="width:40%;cursor: default;float:left">' . $idevice . $device . '</span>';
606 $content .= '<span style="width:60%;cursor: default;">' . $ios . $os . '</span>';
607 $model = $this->get_section( $content );
608
609 $this->output_activity_block( $model . $this->get_client(), 'device' );
610 }
611
612 /**
613 * Get content of the bot widget box.
614 *
615 * @since 1.0.0
616 */
617 public function bot_widget() {
618 $ibot = '<img style="width:16px;float:left;padding-right:7px;padding-left:1px;" src="' . $this->device->bot_icon_base64() . '" />';
619 $bot = ( 1 < strlen( $this->device->bot_name ) ? $this->device->bot_name : decalog_esc_html__( 'Unknown', 'decalog' ) );
620 $content = '<span style="width:100%;cursor: default;">' . $ibot . $bot . $this->get_external_link( $this->device->bot_url ) . '</span>';
621 $model = $this->get_section( $content );
622 $imanuf = $this->get_icon( 'home' );
623 $manuf = ( 1 < strlen( $this->device->bot_producer_name ) ? $this->device->bot_producer_name : decalog_esc_html__( 'Unknown', 'decalog' ) );
624 $itype = $this->get_icon( 'info' );
625 $type = $this->device->bot_full_category;
626 $content = '<span style="width:40%;cursor: default;float:left">' . $itype . $type . '</span>';
627 $content .= '<span style="width:60%;cursor: default;">' . $imanuf . $manuf . $this->get_external_link( $this->device->bot_producer_url ) . '</span>';
628 $prod = $this->get_section( $content );
629
630 $this->output_activity_block( $model . $prod, 'device' );
631 }
632
633 /**
634 * Get content of the call widget box.
635 *
636 * @since 1.0.0
637 */
638 public function call_widget() {
639 $this->output_activity_block( $this->get_client(), 'device' );
640 }
641
642 /**
643 * Get content of the php widget box.
644 *
645 * @since 1.0.0
646 */
647 public function php_widget() {
648 // File detail.
649 if ( array_key_exists( 'file', $this->event ) ) {
650 $element = PHP::normalized_file( $this->event['file'] ) . ':' . ( $this->event['line'] ?? '' );
651 } else {
652 $element = decalog_esc_html__( 'Unknown', 'decalog' );
653 }
654 $element = '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'file-text' ) . $element . '</span>';
655 $file = $this->get_section( $element );
656 // Function detail.
657 $element = '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'code', 'none' ) . ( $this->event['function'] ?? '' ) . '</span>';
658 $function = $this->get_section( $element );
659 // Function detail.
660 $element = '<span style="width:100%;cursor: default;word-break: break-all;">' . $this->get_icon( 'layers' ) . ( $this->event['classname'] ?? '' ) . '</span>';
661 $class = $this->get_section( $element );
662 $this->output_activity_block( $class . $function . $file, 'php' );
663 }
664
665 /**
666 * Get content of the php backtrace widget box.
667 *
668 * @since 1.0.0
669 */
670 public function phpbacktrace_widget() {
671 // phpcs:ignore
672 $trace = unserialize( $this->event['trace'] ?? '' );
673 $content = '';
674 if ( is_array( $trace ) ) {
675 if ( array_key_exists( 'error', $trace ) ) {
676 $error = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'alert-triangle' ) . $trace['error'] . '</span>';
677 $content = $this->get_section( $error );
678 } else {
679 foreach ( array_reverse( $trace['callstack'] ) as $idx => $item ) {
680 if ( array_key_exists( 'call', $item ) ) {
681 if ( $idx < 10 ) {
682 $element = '<span style="font-family:monospace;font-size:8px;font-weight: bold;vertical-align: middle;padding:3px 5px;background-color:#F9F9F9;color:#9999BB;border:2px solid #9999BB;border-radius:50%;cursor: default;">' . $idx . '</span> &nbsp;' . $item['call'];
683 } else {
684 $element = '<span style="font-family:monospace;font-size:8px;font-weight: bold;vertical-align: middle;padding:3px;background-color:#F9F9F9;color:#9999BB;border:2px solid #9999BB;border-radius:50%;cursor: default;">' . $idx . '</span> &nbsp;' . $item['call'];
685 }
686 if ( array_key_exists( 'file', $item ) ) {
687 $element .= '<br/><span style="float:left;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="width:100%;cursor: default;">' . $this->get_icon( 'file-text' ) . $item['file'] . '</span>';
688 }
689 } else {
690 $element = '<span style="float:left;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="width:100%;cursor: default;">' . $this->get_icon( 'eye-off' ) . decalog_esc_html__( 'No backtrace available', 'decalog' ) . '</span>';
691 }
692 $content .= $this->get_section( $element );
693 }
694 }
695 } else {
696 $content = '<span style="float:left;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="width:100%;cursor: default;">' . $this->get_icon( 'eye-off' ) . decalog_esc_html__( 'No backtrace available', 'decalog' ) . '</span>';
697 }
698 $this->output_activity_block( $content, 'phpbacktrace' );
699 }
700
701 /**
702 * Get content of the WordPress backtrace widget box.
703 *
704 * @since 1.0.0
705 */
706 public function wpbacktrace_widget() {
707 // phpcs:ignore
708 $trace = unserialize( $this->event['trace'] ?? '' );
709 $content = '';
710 if ( is_array( $trace ) ) {
711 if ( array_key_exists( 'error', $trace ) ) {
712 $error = '<span style="width:100%;cursor: default;">' . $this->get_icon( 'alert-triangle' ) . $trace['error'] . '</span>';
713 $content = $this->get_section( $error );
714 } else {
715 foreach ( array_reverse( $trace['wordpress'] ) as $idx => $item ) {
716 if ( '' !== $item ) {
717 if ( $idx < 10 ) {
718 $element = '<span style="font-family:monospace;font-size:8px;font-weight: bold;vertical-align: middle;padding:3px 5px;background-color:#F9F9F9;color:#9999BB;border:2px solid #9999BB;border-radius:50%;cursor: default;">' . $idx . '</span> &nbsp;' . $item;
719 } else {
720 $element = '<span style="font-family:monospace;font-size:8px;font-weight: bold;vertical-align: middle;padding:3px;background-color:#F9F9F9;color:#9999BB;border:2px solid #9999BB;border-radius:50%;cursor: default;">' . $idx . '</span> &nbsp;' . $item;
721 }
722 } else {
723 $element = '<span style="float:left;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="width:100%;cursor: default;">' . $this->get_icon( 'eye-off' ) . decalog_esc_html__( 'No backtrace available', 'decalog' ) . '</span>';
724 }
725 $content .= $this->get_section( $element );
726 }
727 }
728 } else {
729 $content = '<span style="float:left;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="width:100%;cursor: default;">' . $this->get_icon( 'eye-off' ) . decalog_esc_html__( 'No backtrace available', 'decalog' ) . '</span>';
730 }
731 $this->output_activity_block( $content, 'wpbacktrace' );
732 }
733
734 }
735