'dashicons-update',
'plugin_activate' => 'dashicons-plus-alt',
'plugin_deactivate' => 'dashicons-dismiss',
'theme_update' => 'dashicons-admin-appearance',
'theme_switch' => 'dashicons-randomize',
'core_update' => 'dashicons-wordpress',
'settings_change' => 'dashicons-admin-settings',
'content_publish' => 'dashicons-edit',
'user_created' => 'dashicons-admin-users',
'php_error' => 'dashicons-warning',
);
public static function type_label( string $type ): string {
$labels = array(
'plugin_update' => __( 'Plugin update', 'aftercare' ),
'plugin_activate' => __( 'Plugin activated', 'aftercare' ),
'plugin_deactivate' => __( 'Plugin deactivated', 'aftercare' ),
'theme_update' => __( 'Theme update', 'aftercare' ),
'theme_switch' => __( 'Theme switch', 'aftercare' ),
'core_update' => __( 'Core update', 'aftercare' ),
'settings_change' => __( 'Settings change', 'aftercare' ),
'content_publish' => __( 'Content published', 'aftercare' ),
'user_created' => __( 'User created', 'aftercare' ),
'php_error' => __( 'PHP fatal error', 'aftercare' ),
);
return $labels[ $type ] ?? $type;
}
public function render(): void {
$ledger = new LedgerRepository();
$filters = $this->current_filters();
$page = isset( $_GET['paged'] ) ? max( 1, absint( wp_unslash( $_GET['paged'] ) ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only pagination.
$events = $ledger->query( $filters, self::PER_PAGE, $page );
$total = $ledger->count( $filters );
$pages = (int) ceil( $total / self::PER_PAGE );
Menu::header( __( 'Change Ledger', 'aftercare' ) );
echo '
';
if ( empty( $events ) ) {
echo '' . esc_html__( 'No events match. Site changes are recorded automatically as they happen.', 'aftercare' ) . '
';
} else {
echo '';
foreach ( $events as $event ) {
self::render_event_row( $event );
}
echo '
';
}
if ( $pages > 1 ) {
echo '';
$base = add_query_arg( array_filter( array_merge( array( 'page' => 'aftercare-ledger' ), $filters ) ), admin_url( 'admin.php' ) );
echo wp_kses_post(
paginate_links(
array(
'base' => $base . '%_%',
'format' => '&paged=%#%',
'current' => $page,
'total' => $pages,
)
) ?? ''
);
echo '
';
}
Menu::footer();
}
/**
* @param array $event
*/
public static function render_event_row( array $event ): void {
$icon = self::TYPE_ICONS[ $event['event_type'] ] ?? 'dashicons-marker';
echo '';
echo '';
echo '' . esc_html( (string) $event['summary'] ) . '';
echo '';
printf(
/* translators: 1: actor name, 2: human time diff */
esc_html__( 'by %1$s ยท %2$s ago', 'aftercare' ),
esc_html( (string) ( $event['actor_label'] ?: __( 'system', 'aftercare' ) ) ),
esc_html( human_time_diff( strtotime( $event['occurred_at'] . ' UTC' ) ) )
);
echo '';
}
/**
* @return array{type?: string, from?: string, to?: string}
*/
private function current_filters(): array {
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only filters.
$filters = array();
if ( ! empty( $_GET['type'] ) ) {
$filters['type'] = sanitize_key( wp_unslash( $_GET['type'] ) );
}
foreach ( array( 'from', 'to' ) as $key ) {
if ( ! empty( $_GET[ $key ] ) ) {
$date = sanitize_text_field( wp_unslash( $_GET[ $key ] ) );
if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) {
$filters[ $key ] = $date;
}
}
}
return $filters;
// phpcs:enable
}
/**
* Stream the full ledger as CSV.
*/
public static function handle_export(): void {
if ( ! current_user_can( Menu::CAP ) ) {
wp_die( esc_html__( 'You do not have permission to do that.', 'aftercare' ) );
}
check_admin_referer( 'aftercare_ledger_export' );
$ledger = new LedgerRepository();
$events = $ledger->query( array(), 100000 );
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=aftercare-ledger-' . gmdate( 'Y-m-d' ) . '.csv' );
$out = fopen( 'php://output', 'w' );
// An empty escape character gives RFC 4180 output (quotes are only
// ever doubled), which JSON payloads containing \" need. PHP 8.4
// also deprecates leaving the argument out.
fputcsv( $out, array( 'occurred_at', 'event_type', 'summary', 'actor', 'payload' ), ',', '"', '' );
foreach ( $events as $event ) {
fputcsv(
$out,
array(
$event['occurred_at'],
$event['event_type'],
$event['summary'],
$event['actor_label'],
$event['payload'],
),
',',
'"',
''
);
}
fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
exit;
}
}