velora-player/includes/class-analytics.php
2026-03-23 16:55:03 +01:00

175 lines
4.0 KiB
PHP

<?php
/**
* Analytics service.
*
* @package ModernAudioPlayer
*/
namespace ModernAudioPlayer;
defined( 'ABSPATH' ) || exit;
class Analytics {
/**
* Create database table on activation.
*
* @return void
*/
public static function create_table() {
global $wpdb;
$table_name = self::table_name();
$charset_collate = $wpdb->get_charset_collate();
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$sql = "CREATE TABLE {$table_name} (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
source_hash varchar(64) NOT NULL,
audio_src text NOT NULL,
track_title varchar(255) NOT NULL DEFAULT '',
play_count bigint(20) unsigned NOT NULL DEFAULT 0,
last_played datetime NULL DEFAULT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY source_hash (source_hash),
KEY play_count (play_count),
KEY last_played (last_played)
) {$charset_collate};";
dbDelta( $sql );
}
/**
* Get the analytics table name.
*
* @return string
*/
public static function table_name() {
global $wpdb;
return $wpdb->prefix . 'map_analytics';
}
/**
* Build a stable source hash.
*
* @param string $audio_src Audio URL.
* @return string
*/
public static function build_source_hash( $audio_src ) {
return hash( 'sha256', esc_url_raw( trim( (string) $audio_src ) ) );
}
/**
* Record a play.
*
* Count rule:
* One request counts once for the rendered player instance session. The frontend
* only sends this call on the first meaningful play event per player rendered on a page.
*
* @param string $audio_src Audio source URL.
* @param string $track_title Track title.
* @return bool
*/
public static function record_play( $audio_src, $track_title = '' ) {
global $wpdb;
$audio_src = esc_url_raw( $audio_src );
$track_title = sanitize_text_field( $track_title );
$source_hash = self::build_source_hash( $audio_src );
$table_name = self::table_name();
$current_time = current_time( 'mysql', true );
if ( empty( $audio_src ) ) {
return false;
}
$existing_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT id FROM {$table_name} WHERE source_hash = %s LIMIT 1",
$source_hash
)
);
if ( $existing_id ) {
$result = $wpdb->query(
$wpdb->prepare(
"UPDATE {$table_name}
SET play_count = play_count + 1,
track_title = %s,
last_played = %s,
updated_at = %s
WHERE source_hash = %s",
$track_title,
$current_time,
$current_time,
$source_hash
)
);
return false !== $result;
}
$result = $wpdb->insert(
$table_name,
array(
'source_hash' => $source_hash,
'audio_src' => $audio_src,
'track_title' => $track_title,
'play_count' => 1,
'last_played' => $current_time,
'created_at' => $current_time,
'updated_at' => $current_time,
),
array( '%s', '%s', '%s', '%d', '%s', '%s', '%s' )
);
return false !== $result;
}
/**
* Fetch top tracks.
*
* @param int $limit Row limit.
* @return array<int, object>
*/
public static function get_top_tracks( $limit = 20 ) {
global $wpdb;
$table_name = self::table_name();
$limit = max( 1, absint( $limit ) );
return $wpdb->get_results(
$wpdb->prepare(
"SELECT source_hash, audio_src, track_title, play_count, last_played
FROM {$table_name}
ORDER BY play_count DESC, last_played DESC
LIMIT %d",
$limit
)
);
}
/**
* Get aggregate stats.
*
* @return array<string, int|string|null>
*/
public static function get_summary() {
global $wpdb;
$table_name = self::table_name();
$row = $wpdb->get_row(
"SELECT COUNT(*) AS track_count, COALESCE(SUM(play_count), 0) AS total_plays, MAX(last_played) AS last_played FROM {$table_name}",
ARRAY_A
);
return array(
'track_count' => isset( $row['track_count'] ) ? (int) $row['track_count'] : 0,
'total_plays' => isset( $row['total_plays'] ) ? (int) $row['total_plays'] : 0,
'last_played' => $row['last_played'] ?? null,
);
}
}