EVLFL_Admin::PAGE_SLUG, 'evlfl_event' => absint( $id ), ), admin_url( 'admin.php' ) ); } /** * User cell. Deleted users get a placeholder. * * @param int $user_id User ID. * @param bool $full Include email (details view). * @return string Escaped HTML. */ public static function user_html( $user_id, $full = false ) { $user_id = absint( $user_id ); $user = get_userdata( $user_id ); if ( ! $user ) { /* translators: %d: user ID. */ return '' . esc_html( sprintf( __( '[Deleted User #%d]', 'accesstrace-for-learndash' ), $user_id ) ) . ''; } $name = esc_html( $user->display_name ); if ( current_user_can( 'edit_user', $user_id ) ) { $name = '' . $name . ''; } $meta = $user->user_login; if ( $full && '' !== (string) $user->user_email && current_user_can( 'edit_user', $user_id ) ) { $meta .= ' · ' . $user->user_email; } /* translators: %d: user ID. */ $meta .= ' · ' . sprintf( __( 'ID %d', 'accesstrace-for-learndash' ), $user_id ); return $name . '
' . esc_html( $meta ) . ''; } /** * Course cell. Deleted courses get a placeholder. * * @param int $course_id Course ID. * @return string Escaped HTML. */ public static function course_html( $course_id ) { $course_id = absint( $course_id ); $post = get_post( $course_id ); if ( ! $post ) { /* translators: %d: course ID. */ return '' . esc_html( sprintf( __( '[Deleted Course #%d]', 'accesstrace-for-learndash' ), $course_id ) ) . ''; } $title = '' !== $post->post_title ? $post->post_title : __( '(no title)', 'accesstrace-for-learndash' ); $out = esc_html( $title ); $link = current_user_can( 'edit_post', $course_id ) ? get_edit_post_link( $course_id ) : ''; if ( $link ) { $out = '' . $out . ''; } /* translators: %d: course ID. */ return $out . '
' . esc_html( sprintf( __( 'ID %d', 'accesstrace-for-learndash' ), $course_id ) ) . ''; } /** * Status indicator with icon and text (never color only). * * @param string $status Status. * @return string Escaped HTML. */ public static function status_html( $status ) { // Inline SVG instead of dashicons: every glyph shares one 16x16 box drawn around the // same circle, so all three statuses line up identically with their label (the // dashicons "help" glyph sits lower and smaller in its font box than "yes-alt"). $marks = array( 'valid' => '', 'invalid' => '', 'unknown' => '', ); $status = isset( $marks[ $status ] ) ? $status : 'unknown'; $icon = ''; return '' . $icon . '' . esc_html( EVLFL_Event::label( 'status', $status ) ) . ''; } /** * Tags allowed when printing the pre-escaped HTML this class builds: the normal post * allowlist plus the few SVG elements/attributes used by status_html(). * * @return array */ public static function allowed_html() { $allowed = wp_kses_allowed_html( 'post' ); $allowed['svg'] = array( 'class' => true, 'viewbox' => true, 'width' => true, 'height' => true, 'aria-hidden' => true, 'focusable' => true, ); $allowed['circle'] = array( 'cx' => true, 'cy' => true, 'r' => true, 'fill' => true, ); $allowed['path'] = array( 'd' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, ); return $allowed; } /** * Actor text. * * @param array $row Row. * @return string Escaped HTML. */ public static function actor_html( array $row ) { $label = EVLFL_Event::label( 'actor_type', $row['actor_type'] ); if ( ! empty( $row['actor_id'] ) ) { return esc_html( $label ) . ': ' . self::user_html( $row['actor_id'] ); } return esc_html( $label ); } /** * Local date/time from a GMT datetime string. * * @param string $gmt GMT datetime. * @return string Plain text. */ public static function format_time( $gmt ) { $ts = strtotime( (string) $gmt . ' UTC' ); if ( false === $ts ) { return ''; } return wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $ts ); } /** * Flatten metadata into readable label/value pairs. * * @param array $data Metadata. * @param string $prefix Key prefix. * @return array Plain text. */ public static function flatten_metadata( array $data, $prefix = '' ) { $out = array(); foreach ( $data as $key => $value ) { $path = '' === $prefix ? (string) $key : $prefix . ' › ' . $key; if ( is_array( $value ) ) { $out += self::flatten_metadata( $value, $path ); } elseif ( is_bool( $value ) ) { $out[ $path ] = $value ? __( 'Yes', 'accesstrace-for-learndash' ) : __( 'No', 'accesstrace-for-learndash' ); } elseif ( null === $value ) { $out[ $path ] = '—'; } else { $out[ $path ] = (string) $value; } } return $out; } /** * Build the details view model. * * @param array $row Log row. * @return array{rows:array,metadata:array} */ public static function build( array $row ) { $rows = array( array( __( 'Event', 'accesstrace-for-learndash' ), '#' . (int) $row['id'] ), array( __( 'User', 'accesstrace-for-learndash' ), self::user_html( $row['user_id'], true ) ), array( __( 'Course', 'accesstrace-for-learndash' ), self::course_html( $row['course_id'] ) ), array( __( 'Action', 'accesstrace-for-learndash' ), esc_html( EVLFL_Event::label( 'action', $row['action'] ) ) ), array( __( 'Source', 'accesstrace-for-learndash' ), esc_html( EVLFL_Event::label( 'source', $row['source'] ) ) ), array( __( 'Trigger', 'accesstrace-for-learndash' ), esc_html( EVLFL_Event::label( 'trigger', $row['event_trigger'] ) ) ), array( __( 'Actor', 'accesstrace-for-learndash' ), self::actor_html( $row ) ), array( __( 'Verification Status', 'accesstrace-for-learndash' ), self::status_html( $row['status'] ) ), array( __( 'Verification Reason', 'accesstrace-for-learndash' ), esc_html( (string) $row['verification_reason'] ) ), array( __( 'Confidence', 'accesstrace-for-learndash' ), esc_html( EVLFL_Event::label( 'confidence', $row['confidence'] ) ) ), array( __( 'Event Date', 'accesstrace-for-learndash' ), esc_html( self::format_time( $row['event_time'] ) ) ), array( __( 'Created Date', 'accesstrace-for-learndash' ), esc_html( self::format_time( $row['created_at'] ) ) ), ); return array( 'rows' => $rows, 'metadata' => self::flatten_metadata( $row['metadata'] ), ); } }