This document provides detailed technical information about the plugin's architecture, API integrations, and implementation patterns.
setlist-player/
├── includes/
│ ├── class-setlist-player-youtube-api.php # YouTube Data API v3 wrapper
│ ├── class-setlist-player-settings.php # Admin settings page
│ └── class-setlist-player-rest-controller.php # REST API controller
├── src/ # Block editor assets (Phase 3+)
├── tests/ # PHPUnit tests
├── bin/ # Development scripts
└── setlist-player.php # Main plugin file
Location: includes/class-setlist-player-youtube-api.php
Responsibility: Handle all YouTube Data API v3 interactions using WordPress HTTP API.
Key Methods:
class Setlist_Player_YouTube_API {
/**
* Validate API key by making a test call.
*
* @param string $api_key The YouTube API key to validate.
* @return bool|WP_Error True if valid, WP_Error on failure.
*/
public function validate_api_key( $api_key ) {
// Test endpoint: videos.list with a known video ID
// Quota cost: 1 unit
}
/**
* Search for videos by keyword.
*
* @param string $query Search query.
* @param int $max_results Number of results (default 5).
* @return array|WP_Error Array of video results or WP_Error.
*/
public function search_videos( $query, $max_results = 5 ) {
// Endpoint: /youtube/v3/search
// Quota cost: 100 units per search
}
/**
* Get video details by ID.
*
* @param string $video_id YouTube video ID.
* @return array|WP_Error Video details or WP_Error.
*/
public function get_video_details( $video_id ) {
// Endpoint: /youtube/v3/videos
// Quota cost: 1 unit per video
}
}Endpoint: GET https://www.googleapis.com/youtube/v3/videos
Parameters:
part=id(required)id=dQw4w9WgXcQ(test video ID)key=YOUR_API_KEY(required)
Quota Cost: 1 unit
Example Request:
$url = add_query_arg(
array(
'part' => 'id',
'id' => 'dQw4w9WgXcQ',
'key' => $api_key,
),
'https://www.googleapis.com/youtube/v3/videos'
);
$response = wp_remote_get( $url, array( 'timeout' => 15 ) );Success Response:
{
"items": [
{
"kind": "youtube#video",
"id": "dQw4w9WgXcQ"
}
]
}Error Response:
{
"error": {
"code": 400,
"message": "API key not valid. Please pass a valid API key.",
"errors": [...]
}
}Endpoint: GET https://www.googleapis.com/youtube/v3/search
Parameters:
part=snippet(required)q=search query(required)type=video(filter to videos only)maxResults=5(1-50)key=YOUR_API_KEY(required)
Quota Cost: 100 units
Example Request:
$url = add_query_arg(
array(
'part' => 'snippet',
'q' => 'kyuss gardenia',
'type' => 'video',
'maxResults' => 5,
'key' => $api_key,
),
'https://www.googleapis.com/youtube/v3/search'
);
$response = wp_remote_get( $url, array( 'timeout' => 15 ) );Success Response:
{
"items": [
{
"id": {
"videoId": "VIDEO_ID"
},
"snippet": {
"title": "Kyuss - Gardenia",
"description": "...",
"thumbnails": {
"default": { "url": "...", "width": 120, "height": 90 },
"medium": { "url": "...", "width": 320, "height": 180 },
"high": { "url": "...", "width": 480, "height": 360 }
},
"channelTitle": "Channel Name"
}
}
]
}Endpoint: GET https://www.googleapis.com/youtube/v3/videos
Parameters:
part=snippet,contentDetails(required)id=VIDEO_ID(required, comma-separated for multiple)key=YOUR_API_KEY(required)
Quota Cost: 1 unit per video
Example Request:
$url = add_query_arg(
array(
'part' => 'snippet,contentDetails',
'id' => 'c_gCpMwqM34',
'key' => $api_key,
),
'https://www.googleapis.com/youtube/v3/videos'
);
$response = wp_remote_get( $url, array( 'timeout' => 15 ) );Success Response:
{
"items": [
{
"id": "c_gCpMwqM34",
"snippet": {
"title": "Kyuss - Green Machine",
"description": "...",
"thumbnails": { ... },
"channelTitle": "..."
},
"contentDetails": {
"duration": "PT3M33S"
}
}
]
}$response = wp_remote_get( $url, array( 'timeout' => 15 ) );
if ( is_wp_error( $response ) ) {
return new WP_Error(
'youtube_api_error',
__( 'Failed to connect to YouTube API.', 'setlist-player' ),
array( 'status' => 500 )
);
}
$code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $code ) {
$body = json_decode( wp_remote_retrieve_body( $response ), true );
$message = $body['error']['message'] ?? __( 'Unknown YouTube API error.', 'setlist-player' );
return new WP_Error( 'youtube_api_error', $message, array( 'status' => $code ) );
}
return json_decode( wp_remote_retrieve_body( $response ), true );Location: includes/class-setlist-player-rest-controller.php
Extends: WP_REST_Controller
Namespace: setlist-player/v1
Endpoints:
Route: GET /wp-json/setlist-player/v1/youtube/search
Parameters:
q(string, required): Search query
Permission: edit_posts capability
Example Request:
fetch('/wp-json/setlist-player/v1/youtube/search?q=kyuss')
.then(response => response.json())
.then(data => console.log(data));Success Response (200):
{
"videos": [
{
"id": "VIDEO_ID",
"title": "Kyuss - Gardenia",
"thumbnail": "https://...",
"channelTitle": "Channel Name"
}
]
}Error Response (4xx/5xx):
{
"code": "youtube_api_error",
"message": "API key not configured",
"data": {
"status": 500
}
}Route: GET /wp-json/setlist-player/v1/youtube/video/{id}
Parameters:
id(string, in path): YouTube video ID
Permission: edit_posts capability
Example Request:
fetch('/wp-json/setlist-player/v1/youtube/video/c_gCpMwqM34')
.then(response => response.json())
.then(data => console.log(data));Success Response (200):
{
"id": "c_gCpMwqM34",
"title": "Kyuss - Green Machine",
"description": "...",
"thumbnail": "https://...",
"duration": "3:33",
"channelTitle": "Channel Name"
}class Setlist_Player_REST_Controller extends WP_REST_Controller {
protected $namespace = 'setlist-player/v1';
protected $rest_base = 'youtube';
private $youtube_api;
public function __construct() {
$this->youtube_api = new Setlist_Player_YouTube_API();
}
public function register_routes() {
// Search videos endpoint
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/search',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
)
);
// Get single video endpoint
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/video/(?P<id>[\w-]+)',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
)
);
}
public function get_items_permissions_check( $request ) {
return current_user_can( 'edit_posts' );
}
public function get_item_permissions_check( $request ) {
return current_user_can( 'edit_posts' );
}
public function get_items( $request ) {
$query = $request->get_param( 'q' );
$results = $this->youtube_api->search_videos( $query );
if ( is_wp_error( $results ) ) {
return $results;
}
return rest_ensure_response( $results );
}
public function get_item( $request ) {
$video_id = $request->get_param( 'id' );
$result = $this->youtube_api->get_video_details( $video_id );
if ( is_wp_error( $result ) ) {
return $result;
}
return rest_ensure_response( $result );
}
public function get_collection_params() {
return array(
'q' => array(
'required' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
}Location: includes/class-setlist-player-settings.php
Responsibility: Admin settings page for YouTube API key configuration.
Key Features:
- Settings page under "Settings" → "Setlist Player"
- Single text field for API key
- Server-side validation on save using YouTube API wrapper
- Admin notices for success/error states
- API key stored in WordPress options (encrypted recommended for production)
Settings API Pattern:
class Setlist_Player_Settings {
private $option_name = 'setlist_player_youtube_api_key';
public function register_settings() {
register_setting(
'setlist_player_settings',
$this->option_name,
array(
'type' => 'string',
'sanitize_callback' => array( $this, 'validate_api_key' ),
)
);
add_settings_section(
'setlist_player_api_settings',
__( 'YouTube API Configuration', 'setlist-player' ),
array( $this, 'render_section_description' ),
'setlist-player-settings'
);
add_settings_field(
'youtube_api_key',
__( 'YouTube API Key', 'setlist-player' ),
array( $this, 'render_api_key_field' ),
'setlist-player-settings',
'setlist_player_api_settings'
);
}
public function validate_api_key( $api_key ) {
$api_key = sanitize_text_field( $api_key );
$old_value = get_option( $this->option_name );
if ( empty( $api_key ) ) {
add_settings_error(
$this->option_name,
'api_key_empty',
__( 'API key cannot be empty.', 'setlist-player' )
);
return $old_value;
}
$youtube_api = new Setlist_Player_YouTube_API();
$is_valid = $youtube_api->validate_api_key( $api_key );
if ( is_wp_error( $is_valid ) ) {
add_settings_error(
$this->option_name,
'api_key_invalid',
sprintf(
/* translators: %s: error message from YouTube API */
__( 'API key validation failed: %s', 'setlist-player' ),
$is_valid->get_error_message()
)
);
return $old_value;
}
add_settings_error(
$this->option_name,
'api_key_saved',
__( 'API key validated and saved successfully.', 'setlist-player' ),
'success'
);
return $api_key;
}
}- API Key Storage: Stored in WordPress options table. Consider encrypting for production.
- REST API Permissions: Only users with
edit_postscapability can access endpoints. - Nonce Validation: Handled automatically by WordPress REST API.
- Input Sanitization: All user inputs sanitized via
sanitize_text_field()and validated. - Output Escaping: All output properly escaped using
esc_html(),esc_url(), etc. - Rate Limiting: YouTube API has daily quota limits (10,000 units/day default).
Test Coverage:
- YouTube API wrapper methods (mock HTTP responses)
- Settings validation callbacks
- REST controller permission checks
- REST controller response formatting
Example Test:
class Test_YouTube_API extends WP_UnitTestCase {
public function test_validate_api_key_with_valid_key() {
// Mock wp_remote_get to return success
add_filter( 'pre_http_request', function() {
return array(
'response' => array( 'code' => 200 ),
'body' => json_encode( array( 'items' => array() ) ),
);
});
$youtube_api = new Setlist_Player_YouTube_API();
$result = $youtube_api->validate_api_key( 'test-key' );
$this->assertTrue( $result );
}
}Test Coverage:
- Settings page save functionality
- REST API endpoint responses
- Error handling flows
Main Plugin File (setlist-player.php):
// Load dependencies.
require_once SETLIST_PLAYER_PLUGIN_DIR . 'includes/class-setlist-player-youtube-api.php';
require_once SETLIST_PLAYER_PLUGIN_DIR . 'includes/class-setlist-player-settings.php';
require_once SETLIST_PLAYER_PLUGIN_DIR . 'includes/class-setlist-player-rest-controller.php';
// Initialize settings page.
$setlist_player_settings = new Setlist_Player_Settings();
add_action( 'admin_menu', array( $setlist_player_settings, 'add_settings_page' ) );
add_action( 'admin_init', array( $setlist_player_settings, 'register_settings' ) );
// Initialize REST API.
add_action( 'rest_api_init', function() {
$controller = new Setlist_Player_REST_Controller();
$controller->register_routes();
});- Cache YouTube API responses for 24 hours to reduce quota usage
- Use WordPress Transients API for temporary storage
- Cache key pattern:
setlist_player_video_{video_id}
- Log YouTube API errors to debug.log in WP_DEBUG mode
- Track quota usage for admin monitoring
- Use WordPress salts/keys for encryption
- Consider using
openssl_encrypt()for API key storage