Permissions System
Centralized permission management providing consistent access control across all platform components. All permission checks are defined in a single source of truth for security, maintainability, and consistency.
Core Permission Functions
ec_can_manage_artist()
Primary permission check for artist management capabilities.
/**
* Check if user can manage specific artist
*
* @param int $user_id User ID (defaults to current user)
* @param int $artist_id Artist profile ID
* @return bool True if user can manage artist
*/
$can_manage = ec_can_manage_artist( $user_id, $artist_id );
Permission Logic (ec_can_manage_artist() / ec_can_create_artist_profiles() live in the network-activated extrachill-users plugin; inc/core/filters/permissions.php adds request helpers and WordPress capability filtering):
See extrachill-users for the canonical permission logic (administrator override, author/roster membership, etc.). This plugin treats those helpers as the source of truth and uses them via function_exists() checks where relevant.
REST API Permission Helpers
The permissions system includes context-aware helper functions for REST API endpoints:
ec_get_permission_artist_id()
Extracts and validates artist ID from request data:
/**
* Extract artist ID from request data and validate permissions
*
* @param array $data Request data (POST, GET, or other)
* @return int Artist ID if user can manage, 0 otherwise
*/
function ec_get_permission_artist_id( $data ) {
$artist_id = isset( $data['artist_id'] ) ? (int) $data['artist_id'] : 0;
if ( ! $artist_id ) {
return 0;
}
return ec_can_manage_artist( get_current_user_id(), $artist_id ) ? $artist_id : 0;
}
ec_get_permission_link_page_id()
Extracts link page ID and validates that user can manage the associated artist:
/**
* Extract link page ID from request data and validate permissions
*
* @param array $data Request data (POST, GET, or other)
* @return int|false Artist ID if user can manage link page, false otherwise
*/
function ec_get_permission_link_page_id( $data ) {
$link_page_id = isset( $data['link_page_id'] ) ? (int) $data['link_page_id'] : 0;
if ( ! $link_page_id ) {
return false;
}
$artist_id = apply_filters('ec_get_artist_id', $link_page_id);
if ( ! $artist_id ) {
return false;
}
return ec_can_manage_artist( get_current_user_id(), $artist_id ) ? $artist_id : false;
}
ec_get_permission_is_admin()
Checks if current user is administrator:
function ec_get_permission_is_admin( $data ) {
return current_user_can( 'manage_options' );
}
ec_get_permission_can_create_artists()
Checks if user can create new artist profiles:
function ec_get_permission_can_create_artists( $data ) {
return ec_can_create_artist_profiles( get_current_user_id() );
}
Artist Profile Creation
User Artist Profile Limits
/**
* Check if user can create new artist profiles
*
* @param int $user_id User ID (defaults to current user)
* @return bool True if user can create profiles
*/
$can_create = ec_can_create_artist_profiles($user_id);
Logic:
- Administrators: Always allowed
- Regular users: Limited by configuration (typically 5 profiles per user)
Permission Usage Patterns
Template Permission Checks
// In management templates
if (ec_can_manage_artist(get_current_user_id(), $artist_id)) {
// Show management interface
include 'manage-interface.php';
} else {
// Show access denied message
echo '<p>Access denied.</p>';
}
REST API Security
All management operations use the WordPress REST API with proper nonce verification and permission checks:
// REST API permission validation in Gutenberg blocks
function rest_api_permission_check($request) {
// Check user authentication
if (!is_user_logged_in()) {
return false;
}
// Extract and validate artist ID from request
$artist_id = ec_get_permission_artist_id( $request->get_json_params() );
if ( ! $artist_id ) {
return false;
}
return true;
}
// Register REST endpoint with permission check
register_rest_route('extrachill/v1', '/artists/(?P<artist_id>d+)', [
'methods' => 'POST',
'callback' => 'rest_api_handler',
'permission_callback' => 'rest_api_permission_check'
]);
Gutenberg Block Management
Artist profile and link page editing is handled via Gutenberg block editor:
// Block-based management (primary interface)
// Location: src/blocks/artist-manager/
// Location: src/blocks/link-page-editor/
// Block provides tab-based interface for:
// - Profile information editing
// - Roster/member management
// - Subscriber management
// Permissions automatically validated in block REST endpoints
// Uses ec_can_manage_artist() for access control
Form Submission Security
All form submissions include nonce verification and permission checks:
// Admin post handlers
function handle_form_submission() {
// Verify nonce
check_admin_referer('save_action_nonce');
// Extract and validate artist ID
$artist_id = ec_get_permission_artist_id( $_POST );
if ( ! $artist_id ) {
wp_die('Access denied');
}
// Process form data
// ...
}
WordPress Capability Integration
Custom Capabilities
The system registers custom capabilities that are dynamically granted based on permission checks:
/**
* WordPress capability filtering for artist permissions
*/
function ec_filter_user_capabilities( $allcaps, $caps, $args, $user ) {
$user_id = $user->ID;
$cap = $args[0];
$object_id = isset( $args[2] ) ? $args[2] : null;
// Allow create_artist_profiles capability
if ( $cap === 'create_artist_profiles' ) {
if ( ec_can_create_artist_profiles( $user_id ) ) {
$allcaps[$cap] = true;
}
return $allcaps;
}
// Allow manage_artist_members capability
if ( $cap === 'manage_artist_members' && $object_id ) {
if ( ec_can_manage_artist( $user_id, $object_id ) ) {
$allcaps[$cap] = true;
}
return $allcaps;
}
// Allow view_artist_link_page_analytics capability
if ( $cap === 'view_artist_link_page_analytics' && $object_id ) {
if ( get_post_type( $object_id ) === 'artist_link_page' ) {
$artist_id = apply_filters('ec_get_artist_id', $object_id);
if ( $artist_id && ec_can_manage_artist( $user_id, $artist_id ) ) {
$allcaps[$cap] = true;
}
}
return $allcaps;
}
// Allow post editing capabilities for artist profiles
if ( $object_id && get_post_type( $object_id ) === 'artist_profile' ) {
if ( ec_can_manage_artist( $user_id, $object_id ) ) {
$post_caps = array( 'edit_post', 'delete_post', 'read_post', 'publish_post', 'manage_artist_members' );
if ( in_array( $cap, $post_caps ) ) {
$allcaps[$cap] = true;
}
}
}
return $allcaps;
}
add_filter( 'user_has_cap', 'ec_filter_user_capabilities', 10, 4 );
User-Artist Relationships
Membership Data Structure
Artist membership stored in user meta:
// User meta structure
$artist_profile_ids = get_user_meta($user_id, '_artist_profile_ids', true);
// Returns: [123, 456, 789] - array of artist IDs user can manage
Membership Management
// Add user to artist (called by join-flow and roster invitations)
function add_user_to_artist($user_id, $artist_id) {
$current_ids = get_user_meta($user_id, '_artist_profile_ids', true);
if (!is_array($current_ids)) {
$current_ids = [];
}
if (!in_array($artist_id, $current_ids)) {
$current_ids[] = $artist_id;
update_user_meta($user_id, '_artist_profile_ids', $current_ids);
}
}
// Remove user from artist
function remove_user_from_artist($user_id, $artist_id) {
$current_ids = get_user_meta($user_id, '_artist_profile_ids', true);
if (is_array($current_ids)) {
$current_ids = array_diff($current_ids, [$artist_id]);
update_user_meta($user_id, '_artist_profile_ids', $current_ids);
}
}
Security Best Practices
Nonce Verification
All REST API requests and forms use WordPress nonce system:
// Form nonce generation
wp_nonce_field('save_action', 'save_action_nonce');
// REST API nonce generation
wp_localize_script('block-script', 'wpApiSettings', [
'rest_url' => rest_url('/'),
'nonce' => wp_create_nonce('wp_rest')
]);
Input Sanitization
All user input processed through WordPress sanitization:
// Sanitize form data
$title = sanitize_text_field($_POST['title']);
$url = esc_url_raw($_POST['url']);
$content = wp_kses_post($_POST['content']);
Output Escaping
All output properly escaped for security:
// Escape for different contexts
echo esc_html($title);
echo esc_url($link_url);
echo esc_attr($css_class);