logger = $logger;
$this->cache = $cache;
}
/**
* Register hooks.
*/
public function init(): void {
add_action( 'wp_dashboard_setup', [ $this, 'register_widget' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] );
}
/**
* Register the dashboard widget.
*/
public function register_widget(): void {
wp_add_dashboard_widget(
$this->widget_id,
__( 'Piperless — Audio Storage', 'piperless' ),
[ $this, 'render' ]
);
}
/**
* Render the dashboard widget.
*/
public function render(): void {
$file_stats = $this->get_file_stats();
$without = $this->get_posts_without_audio();
$disk = $this->get_disk_usage();
$total_files = ( $file_stats['mp3']['count'] ?? 0 )
+ ( $file_stats['wav']['count'] ?? 0 )
+ ( $file_stats['opus']['count'] ?? 0 );
?>
render_file_card(
__( 'Opus', 'piperless' ),
$file_stats['opus'] ?? [ 'count' => 0, 'bytes' => 0 ],
'#1565c0'
); ?>
render_file_card(
__( 'MP3', 'piperless' ),
$file_stats['mp3'] ?? [ 'count' => 0, 'bytes' => 0 ],
'#008a20'
); ?>
render_file_card(
__( 'WAV', 'piperless' ),
$file_stats['wav'] ?? [ 'count' => 0, 'bytes' => 0 ],
'#f56e28'
); ?>
human_size( $disk['free'] ) ),
esc_html( $this->human_size( $disk['total'] ) )
);
?>
int, 'bytes' => int].
* @param string $color Accent color hex.
*/
private function render_file_card( string $label, array $stats, string $color ): void {
$count = $stats['count'] ?? 0;
$size = $this->human_size( $stats['bytes'] ?? 0 );
?>
*/
private function get_file_stats(): array {
$cache_dir = $this->cache->dir();
$stats = [
'mp3' => [ 'count' => 0, 'bytes' => 0 ],
'wav' => [ 'count' => 0, 'bytes' => 0 ],
'opus' => [ 'count' => 0, 'bytes' => 0 ],
];
if ( ! is_dir( $cache_dir ) ) {
return $stats;
}
foreach ( [ 'mp3', 'wav', 'opus' ] as $ext ) {
$files = (array) glob( $cache_dir . '/*.' . $ext );
foreach ( $files as $file ) {
$size = @filesize( $file );
if ( false !== $size ) {
$stats[ $ext ]['count']++;
$stats[ $ext ]['bytes'] += $size;
}
}
}
return $stats;
}
/**
* Count published posts / pages without an audio transcript.
*
* @return int
*/
private function get_posts_without_audio(): int {
global $wpdb;
$types = $this->supported_post_types();
$placeholders = implode( ',', array_fill( 0, count( $types ), '%s' ) );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$count = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*)
FROM {$wpdb->posts} p
WHERE p.post_type IN ( {$placeholders} )
AND p.post_status = 'publish'
AND NOT EXISTS (
SELECT 1 FROM {$wpdb->postmeta} pm
WHERE pm.post_id = p.ID
AND pm.meta_key = '_piperless_audio_url'
AND pm.meta_value != ''
)",
...$types
)
);
return (int) $count;
}
/**
* Get disk usage stats for the uploads directory volume.
*
* @return array{total:float,free:float,used:float,pct:float}
*/
private function get_disk_usage(): array {
$dir = $this->cache->dir();
// Walk up until we find a directory that exists.
while ( ! is_dir( $dir ) && '/' !== $dir ) {
$dir = dirname( $dir );
}
$total = @disk_total_space( $dir );
$free = @disk_free_space( $dir );
$fallback = [ 'total' => 0.0, 'free' => 0.0, 'used' => 0.0, 'pct' => 0.0 ];
if ( false === $total || null === $total || false === $free || null === $free || $total <= 0.0 ) {
return $fallback;
}
$used = $total - $free;
$pct = ( $used / $total ) * 100.0;
return [
'total' => (float) $total,
'free' => (float) $free,
'used' => (float) $used,
'pct' => (float) $pct,
];
}
/**
* Format bytes as a human-readable string.
*
* @param float $bytes Raw byte count.
* @return string e.g. "24.5 MB"
*/
private function human_size( float $bytes ): string {
if ( $bytes <= 0.0 ) {
return '0 B';
}
$units = [ 'B', 'KB', 'MB', 'GB', 'TB' ];
$exp = (int) floor( log( $bytes, 1024 ) );
$exp = min( $exp, count( $units ) - 1 );
$val = $bytes / ( 1024 ** $exp );
return round( $val, 1 ) . ' ' . $units[ $exp ];
}
/**
* Enqueue dashboard-specific styles on the dashboard page only.
*
* @param string $hook_suffix Current admin page hook.
*/
public function enqueue_styles( string $hook_suffix ): void {
if ( 'index.php' !== $hook_suffix ) {
return;
}
wp_enqueue_style(
'piperless-dashboard',
PIPERLESS_PLUGIN_URL . 'assets/css/dashboard.css',
[],
PIPERLESS_VERSION
);
}
/**
* Supported post types for audio transcripts.
*
* @return string[]
*/
private function supported_post_types(): array {
$types = apply_filters( 'piperless_post_types', [ 'post', 'page' ] );
return is_array( $types ) ? $types : [ 'post' ];
}
}