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

489 lines
18 KiB
PHP

<?php
/**
* Admin settings and analytics UI.
*
* @package ModernAudioPlayer
*/
namespace ModernAudioPlayer;
defined( 'ABSPATH' ) || exit;
class Admin {
/**
* Register admin hooks.
*
* @return void
*/
public function register() {
add_action( 'admin_menu', array( $this, 'register_menus' ) );
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'admin_post_map_reset_design_settings', array( $this, 'handle_reset_design_settings' ) );
}
/**
* Register admin menu pages.
*
* @return void
*/
public function register_menus() {
add_menu_page(
__( 'Velora Player', 'modern-audio-player' ),
__( 'Velora Player', 'modern-audio-player' ),
'manage_options',
'map-settings',
array( $this, 'render_settings_page' ),
'dashicons-format-audio',
56
);
add_submenu_page(
'map-settings',
__( 'Design Settings', 'modern-audio-player' ),
__( 'Design Settings', 'modern-audio-player' ),
'manage_options',
'map-settings',
array( $this, 'render_settings_page' )
);
add_submenu_page(
'map-settings',
__( 'Analytics', 'modern-audio-player' ),
__( 'Analytics', 'modern-audio-player' ),
'manage_options',
'map-analytics',
array( $this, 'render_analytics_page' )
);
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
register_setting(
'map_settings_group',
MAP_OPTION_KEY,
array(
'type' => 'array',
'sanitize_callback' => array( '\ModernAudioPlayer\Settings', 'sanitize' ),
'default' => Settings::defaults(),
)
);
}
/**
* Enqueue admin design assets.
*
* @param string $hook_suffix Current screen hook.
* @return void
*/
public function enqueue_assets( $hook_suffix ) {
if ( 'toplevel_page_map-settings' !== $hook_suffix ) {
return;
}
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_style(
'map-admin-settings',
MAP_PLUGIN_URL . 'assets/css/admin-settings.css',
array( 'wp-color-picker' ),
MAP_VERSION
);
wp_enqueue_style(
'map-player-preview',
MAP_PLUGIN_URL . 'assets/css/player.css',
array(),
MAP_VERSION
);
wp_enqueue_script( 'wp-color-picker' );
wp_enqueue_script(
'map-admin-settings',
MAP_PLUGIN_URL . 'assets/js/admin-settings.js',
array( 'jquery', 'wp-color-picker' ),
MAP_VERSION,
true
);
$settings = Settings::get();
$resolved_design = Settings::get_resolved_design_settings( $settings['default_theme'], $settings );
$presets = Settings::get_design_presets();
$preset_tokens = array();
foreach ( $presets as $slug => $preset ) {
$preset_tokens[ $slug ] = $preset['tokens'];
}
wp_add_inline_script(
'map-admin-settings',
'window.mapAdminSettings = ' . wp_json_encode(
array(
'optionKey' => MAP_OPTION_KEY,
'presets' => $preset_tokens,
'resolvedDesign' => $resolved_design,
'i18n' => array(
'rangeValueLabel' => __( '%1$s %2$s', 'modern-audio-player' ),
'resetConfirm' => __( 'Reset all design settings back to the default preset values?', 'modern-audio-player' ),
),
)
) . ';',
'before'
);
}
/**
* Handle reset action.
*
* @return void
*/
public function handle_reset_design_settings() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to perform this action.', 'modern-audio-player' ) );
}
check_admin_referer( 'map_reset_design_settings' );
update_option( MAP_OPTION_KEY, Settings::defaults() );
$redirect = add_query_arg(
array(
'page' => 'map-settings',
'map-reset' => 1,
),
admin_url( 'admin.php' )
);
wp_safe_redirect( $redirect );
exit;
}
/**
* Render settings page.
*
* @return void
*/
public function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$settings = Settings::get();
$schema = Settings::get_design_schema();
$resolved_design = Settings::get_resolved_design_settings( $settings['default_theme'], $settings );
$preview_style = Settings::build_design_css_variables( $resolved_design );
$presets = Settings::get_design_presets();
$preview_preset = isset( $presets[ $settings['default_theme'] ] ) ? $presets[ $settings['default_theme'] ]['label'] : $presets[ Settings::get_default_preset_slug() ]['label'];
?>
<div class="wrap map-admin-page">
<h1><?php esc_html_e( 'Velora Player Design System', 'modern-audio-player' ); ?></h1>
<p class="map-admin-page__intro">
<?php esc_html_e( 'Manage the global player design tokens used by both the block renderer and shortcode output.', 'modern-audio-player' ); ?>
</p>
<?php if ( isset( $_GET['settings-updated'] ) ) : ?>
<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Design settings saved.', 'modern-audio-player' ); ?></p></div>
<?php endif; ?>
<?php if ( isset( $_GET['map-reset'] ) ) : ?>
<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Design settings reset to the plugin defaults.', 'modern-audio-player' ); ?></p></div>
<?php endif; ?>
<div class="map-admin-layout">
<form class="map-design-form" action="options.php" method="post">
<?php settings_fields( 'map_settings_group' ); ?>
<div class="map-settings-shell">
<div class="map-settings-shell__header">
<div class="map-settings-shell__meta">
<span class="map-settings-shell__eyebrow"><?php esc_html_e( 'Global Design Controls', 'modern-audio-player' ); ?></span>
<h2><?php esc_html_e( 'Design Backend', 'modern-audio-player' ); ?></h2>
<p><?php esc_html_e( 'Switch between sections, edit tokens quickly, and keep the preview visible while you work.', 'modern-audio-player' ); ?></p>
</div>
<div class="map-settings-actions map-settings-actions--header">
<?php submit_button( __( 'Save Design Settings', 'modern-audio-player' ), 'primary', 'submit', false ); ?>
<a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=map_reset_design_settings' ), 'map_reset_design_settings' ) ); ?>" class="button button-secondary map-reset-button">
<?php esc_html_e( 'Reset to Defaults', 'modern-audio-player' ); ?>
</a>
</div>
</div>
<h2 class="nav-tab-wrapper wp-clearfix">
<a href="#map-section-presets" class="nav-tab nav-tab-active" data-map-tab="presets"><?php esc_html_e( 'Presets', 'modern-audio-player' ); ?></a>
<a href="#map-section-colors" class="nav-tab" data-map-tab="colors"><?php esc_html_e( 'Colors', 'modern-audio-player' ); ?></a>
<a href="#map-section-text" class="nav-tab" data-map-tab="text"><?php esc_html_e( 'Text', 'modern-audio-player' ); ?></a>
<a href="#map-section-layout" class="nav-tab" data-map-tab="layout"><?php esc_html_e( 'Layout', 'modern-audio-player' ); ?></a>
<a href="#map-section-controls" class="nav-tab" data-map-tab="controls"><?php esc_html_e( 'Controls', 'modern-audio-player' ); ?></a>
</h2>
<div class="map-settings-panels">
<?php foreach ( $schema as $section_key => $section ) : ?>
<section id="map-section-<?php echo esc_attr( $section_key ); ?>" class="map-settings-card map-settings-panel<?php echo 'presets' === $section_key ? ' is-active' : ''; ?>" data-map-panel="<?php echo esc_attr( $section_key ); ?>">
<div class="map-settings-card__header">
<h2><?php echo esc_html( $section['title'] ); ?></h2>
<p><?php echo esc_html( $section['description'] ); ?></p>
</div>
<div class="map-field-grid">
<?php foreach ( $section['fields'] as $field_key => $field ) : ?>
<div class="map-field-card map-field-card--<?php echo esc_attr( $field['type'] ); ?>">
<div class="map-field-card__top">
<label class="map-field-card__label" for="<?php echo esc_attr( $field_key ); ?>"><?php echo esc_html( $field['label'] ); ?></label>
<?php $this->render_field( $field_key, $field, $settings, $resolved_design ); ?>
</div>
<?php if ( ! empty( $field['description'] ) ) : ?>
<p class="description"><?php echo esc_html( $field['description'] ); ?></p>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endforeach; ?>
</div>
<div class="map-settings-actions map-settings-actions--footer">
<?php submit_button( __( 'Save Design Settings', 'modern-audio-player' ), 'primary', 'submit', false ); ?>
</div>
</div>
</form>
<aside class="map-preview-panel">
<div class="map-settings-card map-settings-card--sticky">
<div class="map-settings-card__header">
<h2><?php esc_html_e( 'Live Preview', 'modern-audio-player' ); ?></h2>
<p><?php esc_html_e( 'This static preview reflects the current saved values and updates as you edit the form.', 'modern-audio-player' ); ?></p>
</div>
<div class="map-admin-preview-shell">
<div class="map-player map-theme-<?php echo esc_attr( $settings['default_theme'] ); ?><?php echo empty( $resolved_design['player_show_cover_image'] ) ? ' map-player--no-cover' : ''; ?>" data-map-admin-preview data-map-has-cover="<?php echo empty( $resolved_design['player_show_cover_image'] ) ? 'false' : 'true'; ?>" style="<?php echo esc_attr( $preview_style ); ?>">
<div class="map-player__cover-wrap"<?php echo empty( $resolved_design['player_show_cover_image'] ) ? ' style="display:none;"' : ''; ?>>
<div class="map-admin-preview-cover"></div>
</div>
<div class="map-player__content">
<div class="map-player__header">
<div class="map-player__eyebrow" data-map-preview-theme<?php echo empty( $resolved_design['player_show_theme_label'] ) ? ' style="display:none;"' : ''; ?>><?php echo esc_html( $preview_preset ); ?></div>
<div class="map-player__title"><?php esc_html_e( 'Velora Weekly Mix', 'modern-audio-player' ); ?></div>
</div>
<div class="map-player__controls">
<div class="map-player__actions">
<button type="button" class="map-player__toggle" aria-pressed="false">
<span class="map-player__toggle-icon map-player__toggle-icon--play" aria-hidden="true"></span>
<span class="map-player__toggle-text"><?php esc_html_e( 'Play', 'modern-audio-player' ); ?></span>
</button>
<div class="map-player__transport">
<button type="button" class="map-player__icon-button" disabled>
<span class="map-player__icon map-player__icon--previous" aria-hidden="true"></span>
</button>
<button type="button" class="map-player__icon-button">
<span class="map-player__icon map-player__icon--next" aria-hidden="true"></span>
</button>
<button type="button" class="map-player__icon-button">
<span class="map-player__icon map-player__icon--repeat" aria-hidden="true"></span>
</button>
</div>
</div>
<div class="map-player__timeline">
<input type="range" class="map-player__progress" min="0" max="100" value="38" step="0.1" />
<div class="map-player__time">
<span><?php esc_html_e( '1:12', 'modern-audio-player' ); ?></span>
<span><?php esc_html_e( '3:08', 'modern-audio-player' ); ?></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</aside>
</div>
</div>
<?php
}
/**
* Render a field based on schema metadata.
*
* @param string $field_key Field key.
* @param array<string, mixed> $field Field definition.
* @param array<string, mixed> $settings Saved settings.
* @param array<string, mixed> $resolved_design Resolved design values.
* @return void
*/
private function render_field( $field_key, $field, $settings, $resolved_design ) {
$option_name = MAP_OPTION_KEY . '[' . $field_key . ']';
$value = isset( $settings[ $field_key ] ) && '' !== $settings[ $field_key ] ? $settings[ $field_key ] : $resolved_design[ $field_key ];
$override = isset( $settings[ $field_key ] ) && '' !== $settings[ $field_key ];
switch ( $field['type'] ) {
case 'color':
?>
<input
type="text"
id="<?php echo esc_attr( $field_key ); ?>"
class="map-color-field"
name="<?php echo esc_attr( $option_name ); ?>"
value="<?php echo esc_attr( (string) $value ); ?>"
data-map-field="<?php echo esc_attr( $field_key ); ?>"
data-default-color="<?php echo esc_attr( (string) $resolved_design[ $field_key ] ); ?>"
/>
<?php
break;
case 'range':
?>
<div class="map-range-control">
<input
type="range"
id="<?php echo esc_attr( $field_key ); ?>"
name="<?php echo esc_attr( $option_name ); ?>"
value="<?php echo esc_attr( (string) $value ); ?>"
min="<?php echo esc_attr( (string) $field['min'] ); ?>"
max="<?php echo esc_attr( (string) $field['max'] ); ?>"
step="<?php echo esc_attr( (string) $field['step'] ); ?>"
data-map-field="<?php echo esc_attr( $field_key ); ?>"
data-unit="<?php echo esc_attr( (string) $field['unit'] ); ?>"
/>
<span class="map-range-control__value" data-map-range-value="<?php echo esc_attr( $field_key ); ?>">
<?php echo esc_html( (string) $value . (string) $field['unit'] ); ?>
</span>
</div>
<?php
break;
case 'checkbox':
?>
<label class="map-switch" for="<?php echo esc_attr( $field_key ); ?>">
<input
type="checkbox"
id="<?php echo esc_attr( $field_key ); ?>"
name="<?php echo esc_attr( $option_name ); ?>"
value="1"
data-map-field="<?php echo esc_attr( $field_key ); ?>"
<?php checked( ! empty( $value ) ); ?>
/>
<span class="map-switch__track" aria-hidden="true"></span>
<span class="map-switch__label"><?php esc_html_e( 'Enabled', 'modern-audio-player' ); ?></span>
</label>
<?php
break;
case 'checkbox_group':
?>
<fieldset class="map-checkbox-group">
<?php foreach ( $field['options'] as $option_value => $option_label ) : ?>
<label>
<input
type="checkbox"
name="<?php echo esc_attr( $option_name ); ?>[]"
value="<?php echo esc_attr( $option_value ); ?>"
<?php checked( in_array( $option_value, (array) $settings[ $field_key ], true ) ); ?>
/>
<span><?php echo esc_html( $option_label ); ?></span>
</label>
<?php endforeach; ?>
</fieldset>
<?php
break;
case 'select':
?>
<select
id="<?php echo esc_attr( $field_key ); ?>"
name="<?php echo esc_attr( $option_name ); ?>"
class="map-select-control"
data-map-preset-select="true"
>
<?php foreach ( $field['options'] as $option_value => $option_label ) : ?>
<option value="<?php echo esc_attr( $option_value ); ?>" <?php selected( $settings[ $field_key ], $option_value ); ?>>
<?php echo esc_html( $option_label ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Selecting a preset updates the visible field values immediately. Saved customizations remain layered over the chosen preset.', 'modern-audio-player' ); ?></p>
<?php
break;
}
if ( 0 === strpos( $field_key, 'player_' ) ) {
?>
<p class="map-field-state">
<?php
echo $override
? esc_html__( 'Custom override saved for this token.', 'modern-audio-player' )
: esc_html__( 'Currently inherited from the selected preset.', 'modern-audio-player' );
?>
</p>
<?php
}
}
/**
* Render analytics page.
*
* @return void
*/
public function render_analytics_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$summary = Analytics::get_summary();
$tracks = Analytics::get_top_tracks( 50 );
?>
<div class="wrap">
<h1><?php esc_html_e( 'Velora Player Analytics', 'modern-audio-player' ); ?></h1>
<div style="display:flex;gap:16px;flex-wrap:wrap;margin:20px 0;">
<div class="postbox" style="padding:16px;min-width:180px;">
<strong><?php esc_html_e( 'Tracked Sources', 'modern-audio-player' ); ?></strong>
<div style="font-size:24px;"><?php echo esc_html( number_format_i18n( $summary['track_count'] ) ); ?></div>
</div>
<div class="postbox" style="padding:16px;min-width:180px;">
<strong><?php esc_html_e( 'Total Plays', 'modern-audio-player' ); ?></strong>
<div style="font-size:24px;"><?php echo esc_html( number_format_i18n( $summary['total_plays'] ) ); ?></div>
</div>
<div class="postbox" style="padding:16px;min-width:220px;">
<strong><?php esc_html_e( 'Last Played', 'modern-audio-player' ); ?></strong>
<div style="font-size:16px;">
<?php echo $summary['last_played'] ? esc_html( get_date_from_gmt( $summary['last_played'], get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ) : esc_html__( 'No data yet', 'modern-audio-player' ); ?>
</div>
</div>
</div>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'Track', 'modern-audio-player' ); ?></th>
<th><?php esc_html_e( 'Audio Source', 'modern-audio-player' ); ?></th>
<th><?php esc_html_e( 'Plays', 'modern-audio-player' ); ?></th>
<th><?php esc_html_e( 'Last Played', 'modern-audio-player' ); ?></th>
</tr>
</thead>
<tbody>
<?php if ( empty( $tracks ) ) : ?>
<tr>
<td colspan="4"><?php esc_html_e( 'No analytics recorded yet.', 'modern-audio-player' ); ?></td>
</tr>
<?php else : ?>
<?php foreach ( $tracks as $track ) : ?>
<tr>
<td><?php echo esc_html( $track->track_title ? $track->track_title : __( 'Untitled Track', 'modern-audio-player' ) ); ?></td>
<td>
<a href="<?php echo esc_url( $track->audio_src ); ?>" target="_blank" rel="noopener noreferrer">
<?php echo esc_html( $track->audio_src ); ?>
</a>
</td>
<td><?php echo esc_html( number_format_i18n( (int) $track->play_count ) ); ?></td>
<td><?php echo $track->last_played ? esc_html( get_date_from_gmt( $track->last_played, get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ) : esc_html__( 'Never', 'modern-audio-player' ); ?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
}