diff --git a/album-scores.php b/album-scores.php
new file mode 100644
index 0000000..a4e19e4
--- /dev/null
+++ b/album-scores.php
@@ -0,0 +1,286 @@
+scrobbles_table = 'song_scrobbles'; // Added $wpdb->prefix for table names
+ $this->scores_table = 'album_scores';
+
+ // Add admin menu
+ add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
+
+ // Enqueue admin scripts
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
+
+ // Add AJAX handler
+ add_action( 'wp_ajax_asm_update_score', array( $this, 'ajax_update_score' ) );
+ }
+
+ /**
+ * Enqueue custom JavaScript for handling AJAX requests
+ */
+ public function enqueue_admin_scripts( $hook ) {
+ // Only enqueue on our plugin's admin page
+ if ( $hook !== 'toplevel_page_album-score-manager' ) {
+ return;
+ }
+
+ // Enqueue the script
+ wp_enqueue_script(
+ 'asm-admin-script',
+ plugin_dir_url( __FILE__ ) . 'js/asm-admin.js', // You'll create this file next
+ array( 'jquery' ),
+ '1.1',
+ true
+ );
+
+ // Localize script to pass AJAX URL and nonce
+ wp_localize_script(
+ 'asm-admin-script',
+ 'asm_ajax_obj',
+ array(
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
+ 'nonce' => wp_create_nonce( 'asm_ajax_nonce' ),
+ )
+ );
+ }
+
+ /**
+ * Add a new menu item under the WordPress admin sidebar
+ */
+ public function add_admin_menu() {
+ add_menu_page(
+ 'Album Score Manager', // Page title
+ 'Album Scores', // Menu title
+ 'manage_options', // Capability
+ 'album-score-manager', // Menu slug
+ array( $this, 'admin_page' ), // Callback function
+ 'dashicons-awards', // Icon
+ 6 // Position
+ );
+ }
+
+ /**
+ * Admin page callback
+ */
+ public function admin_page() {
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( __( 'You do not have sufficient permissions to access this page.' ) );
+ }
+
+ global $wpdb;
+
+ // Fetch albums sorted by total plays
+ $albums = $wpdb->get_results(
+ "SELECT album_name, COUNT(*) as play_count
+ FROM `{$this->scrobbles_table}`
+ WHERE album_name IS NOT NULL
+ GROUP BY album_name
+ ORDER BY play_count DESC",
+ OBJECT
+ );
+
+ // Fetch existing scores
+ $existing_scores = $wpdb->get_results(
+ "SELECT album_name, score, author, cover_url FROM `{$this->scores_table}`",
+ ARRAY_A
+ );
+
+ $scores_assoc = array();
+ foreach ( $existing_scores as $score ) {
+ $scores_assoc[ $score['album_name'] ] = array(
+ 'score' => $score['score'],
+ 'author' => $score['author'],
+ 'cover_url' => $score['cover_url'],
+ );
+ }
+
+ // Prepare latest author and cover_url from song_scrobbles
+ $latest_entries = $this->get_latest_song_scrobbles( $albums );
+
+ ?>
+
+
Album Score Manager
+
+
+
+ Album Name |
+ Author |
+ Cover |
+ Total Plays |
+ Score |
+ Status |
+
+
+
+
+ album_name );
+ $play_count = intval( $album->play_count );
+
+ // Fetch latest song_scrobbles entry for this album
+ $latest_entry = isset( $latest_entries[ $album->album_name ] ) ? $latest_entries[ $album->album_name ] : null;
+
+ $latest_author = $latest_entry ? esc_html( $latest_entry->author ) : '';
+ $latest_cover_url = $latest_entry ? esc_url( $latest_entry->cover_url ) : '';
+
+ // Existing scores
+ $current_score = isset( $scores_assoc[ $album->album_name ]['score'] ) ? esc_html( $scores_assoc[ $album->album_name ]['score'] ) : '';
+ $current_author = isset( $scores_assoc[ $album->album_name ]['author'] ) ? esc_html( $scores_assoc[ $album->album_name ]['author'] ) : $latest_author;
+ $current_cover_url = isset( $scores_assoc[ $album->album_name ]['cover_url'] ) ? esc_url( $scores_assoc[ $album->album_name ]['cover_url'] ) : $latest_cover_url;
+ ?>
+
+ |
+ |
+
+
+
+ |
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ No albums found. |
+
+
+
+
+
+ get_row( $wpdb->prepare(
+ "SELECT author, cover_url
+ FROM `{$this->scrobbles_table}`
+ WHERE album_name = %s
+ ORDER BY id DESC
+ LIMIT 1",
+ $album->album_name
+ ) );
+
+ if ( $latest_entry ) {
+ $latest_entries[ $album->album_name ] = $latest_entry;
+ }
+ }
+
+ return $latest_entries;
+ }
+
+ /**
+ * AJAX handler to update a single album's score
+ */
+ public function ajax_update_score() {
+ // Check nonce
+ check_ajax_referer( 'asm_ajax_nonce', 'nonce' );
+
+ // Check user capabilities
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_send_json_error( 'Unauthorized user.' );
+ wp_die();
+ }
+
+ // Get and sanitize input
+ $album = isset( $_POST['album'] ) ? sanitize_text_field( $_POST['album'] ) : '';
+ $score = isset( $_POST['score'] ) ? sanitize_text_field( $_POST['score'] ) : '';
+ $cover_url = isset( $_POST['cover_url'] ) ? esc_url_raw( $_POST['cover_url'] ) : '';
+ $author = isset( $_POST['author'] ) ? sanitize_text_field( $_POST['author'] ) : '';
+
+ // Validate input
+ $valid_scores = array( 'F', 'C', 'B', 'A', 'S', 'S+' );
+ if ( empty( $album ) || empty( $score ) || ! in_array( $score, $valid_scores, true ) ) {
+ wp_send_json_error( 'Invalid input.' );
+ wp_die();
+ }
+
+ global $wpdb;
+
+ // Check if the album already has a score
+ $existing = $wpdb->get_var( $wpdb->prepare(
+ "SELECT id FROM `{$this->scores_table}` WHERE `album_name` = %s",
+ $album
+ ) );
+
+ if ( $existing ) {
+ // Update existing record
+ $updated_rows = $wpdb->update(
+ $this->scores_table,
+ array(
+ 'score' => $score,
+ 'author' => $author,
+ 'cover_url' => $cover_url,
+ ),
+ array( 'id' => $existing ),
+ array( '%s', '%s', '%s' ),
+ array( '%d' )
+ );
+
+ if ( false !== $updated_rows ) {
+ wp_send_json_success( 'Score updated successfully.' );
+ } else {
+ wp_send_json_error( 'Failed to update score.' );
+ }
+ } else {
+ // Insert new record
+ $inserted_rows = $wpdb->insert(
+ $this->scores_table,
+ array(
+ 'album_name' => $album,
+ 'score' => $score,
+ 'author' => $author,
+ 'cover_url' => $cover_url,
+ ),
+ array( '%s', '%s', '%s', '%s' )
+ );
+
+ if ( false !== $inserted_rows ) {
+ wp_send_json_success( 'Score added successfully.' );
+ } else {
+ wp_send_json_error( 'Failed to add score.' );
+ }
+ }
+
+ wp_die();
+ }
+
+}
\ No newline at end of file
diff --git a/js/asm-admin.js b/js/asm-admin.js
new file mode 100644
index 0000000..0679ffa
--- /dev/null
+++ b/js/asm-admin.js
@@ -0,0 +1,49 @@
+jQuery(document).ready(function($) {
+ // Listen for changes on any score dropdown
+ $('.asm-score-dropdown').on('change', function() {
+ var dropdown = $(this);
+ var album = dropdown.data('album');
+ var score = dropdown.val();
+
+ // Get the author and cover_url from the respective inputs in the same row
+ var row = $('#asm-row-' + album);
+ var author = dropdown.data('author');
+ var cover_url = dropdown.data('cover');
+
+ // Status cell
+ var statusCell = row.find('.asm-status');
+
+ if (score === '') {
+ // If no score is selected, do not proceed
+ statusCell.html('No score selected.');
+ return;
+ }
+
+ // Show loading message
+ statusCell.html('Updating...');
+
+ // AJAX request
+ $.ajax({
+ url: asm_ajax_obj.ajax_url,
+ type: 'POST',
+ data: {
+ action: 'asm_update_score',
+ nonce: asm_ajax_obj.nonce,
+ album: album,
+ score: score,
+ author: author,
+ cover_url: cover_url
+ },
+ success: function(response) {
+ if (response.success) {
+ statusCell.html('' + response.data + '');
+ } else {
+ statusCell.html('' + response.data + '');
+ }
+ },
+ error: function() {
+ statusCell.html('AJAX error.');
+ }
+ });
+ });
+});
diff --git a/plugin.php b/plugin.php
index f2dad10..53f83cf 100644
--- a/plugin.php
+++ b/plugin.php
@@ -13,8 +13,10 @@ if (!defined('ABSPATH')) {
// Include the separate plugins
require_once(plugin_dir_path(__FILE__) . 'album-collage-generator.php');
+require_once(plugin_dir_path(__FILE__) . 'album-scores.php');
require_once(plugin_dir_path(__FILE__) . 'scrobble-handler.php');
// Initialize both classes
new AlbumCollageGenerator();
new ScrobbleHandler();
+new AlbumScoreManager();