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'] ),
);
}
}