2026-01-28 23:23:05 +01:00
< ? php
/**
* Main plugin class .
*
* @ package WP_FediStream
*/
namespace WP_FediStream ;
use WP_FediStream\ActivityPub\Integration as ActivityPubIntegration ;
use WP_FediStream\ActivityPub\RestApi as ActivityPubRestApi ;
use WP_FediStream\Admin\ListColumns ;
use WP_FediStream\Frontend\Ajax ;
use WP_FediStream\Frontend\Shortcodes ;
use WP_FediStream\Frontend\TemplateLoader ;
use WP_FediStream\Frontend\Widgets ;
use WP_FediStream\PostTypes\Artist ;
use WP_FediStream\WooCommerce\Integration as WooCommerceIntegration ;
use WP_FediStream\WooCommerce\DigitalDelivery ;
use WP_FediStream\WooCommerce\StreamingAccess ;
use WP_FediStream\PostTypes\Album ;
use WP_FediStream\PostTypes\Track ;
use WP_FediStream\PostTypes\Playlist ;
use WP_FediStream\Taxonomies\Genre ;
use WP_FediStream\Taxonomies\Mood ;
use WP_FediStream\Taxonomies\License ;
use WP_FediStream\User\Library as UserLibrary ;
use WP_FediStream\User\LibraryPage ;
use WP_FediStream\User\Notifications ;
2026-01-29 12:03:05 +01:00
use WP_FediStream\License\Manager as LicenseManager ;
2026-01-28 23:23:05 +01:00
// Prevent direct file access.
if ( ! defined ( 'ABSPATH' ) ) {
exit ;
}
/**
* Plugin singleton class .
*
* Initializes and manages all plugin components .
*/
final class Plugin {
/**
* Singleton instance .
*
* @ var Plugin | null
*/
private static ? Plugin $instance = null ;
/**
* Twig environment instance .
*
* @ var \Twig\Environment | null
*/
private ? \Twig\Environment $twig = null ;
2026-02-02 17:04:38 +01:00
/**
* Current Twig render depth to prevent infinite recursion .
*
* @ var int
*/
private static int $render_depth = 0 ;
/**
* Maximum allowed Twig render depth .
*
* @ var int
*/
private const MAX_RENDER_DEPTH = 5 ;
2026-01-28 23:23:05 +01:00
/**
* Post type instances .
*
* @ var array
*/
private array $post_types = array ();
/**
* Taxonomy instances .
*
* @ var array
*/
private array $taxonomies = array ();
/**
* Get singleton instance .
*
* @ return Plugin
*/
public static function get_instance () : Plugin {
if ( null === self :: $instance ) {
self :: $instance = new self ();
}
return self :: $instance ;
}
/**
* Private constructor to enforce singleton pattern .
*/
private function __construct () {
$this -> init_twig ();
$this -> init_components ();
$this -> init_hooks ();
$this -> load_textdomain ();
}
/**
* Prevent cloning .
*
* @ return void
*/
private function __clone () {}
/**
* Prevent unserialization .
*
* @ throws \Exception Always throws to prevent unserialization .
* @ return void
*/
public function __wakeup () : void {
throw new \Exception ( 'Cannot unserialize singleton' );
}
/**
* Initialize Twig template engine .
*
* @ return void
*/
private function init_twig () : void {
$loader = new \Twig\Loader\FilesystemLoader ( WP_FEDISTREAM_PATH . 'templates' );
$this -> twig = new \Twig\Environment (
$loader ,
array (
'cache' => WP_FEDISTREAM_PATH . 'cache/twig' ,
'auto_reload' => WP_DEBUG ,
)
);
// Add WordPress escaping functions.
$this -> twig -> addFunction ( new \Twig\TwigFunction ( 'esc_html' , 'esc_html' ) );
$this -> twig -> addFunction ( new \Twig\TwigFunction ( 'esc_attr' , 'esc_attr' ) );
$this -> twig -> addFunction ( new \Twig\TwigFunction ( 'esc_url' , 'esc_url' ) );
$this -> twig -> addFunction ( new \Twig\TwigFunction ( 'esc_js' , 'esc_js' ) );
$this -> twig -> addFunction ( new \Twig\TwigFunction ( 'wp_nonce_field' , 'wp_nonce_field' , array ( 'is_safe' => array ( 'html' ) ) ) );
$this -> twig -> addFunction ( new \Twig\TwigFunction ( '__' , '__' ) );
$this -> twig -> addFunction ( new \Twig\TwigFunction ( '_e' , '_e' ) );
}
/**
* Initialize plugin components .
*
* @ return void
*/
private function init_components () : void {
// Initialize post types.
$this -> post_types [ 'artist' ] = new Artist ();
$this -> post_types [ 'album' ] = new Album ();
$this -> post_types [ 'track' ] = new Track ();
$this -> post_types [ 'playlist' ] = new Playlist ();
// Initialize taxonomies.
$this -> taxonomies [ 'genre' ] = new Genre ();
$this -> taxonomies [ 'mood' ] = new Mood ();
$this -> taxonomies [ 'license' ] = new License ();
// Initialize admin components.
if ( is_admin () ) {
new ListColumns ();
}
2026-01-29 12:03:05 +01:00
// Initialize frontend components (only if licensed).
if ( ! is_admin () && LicenseManager :: is_license_valid () ) {
2026-01-28 23:23:05 +01:00
new TemplateLoader ();
new Shortcodes ();
2026-01-29 12:03:05 +01:00
} elseif ( ! is_admin () ) {
// Register shortcodes that show license message.
new Shortcodes ( true ); // Unlicensed mode.
2026-01-28 23:23:05 +01:00
}
// Initialize widgets (always needed for admin widget management).
new Widgets ();
// Initialize AJAX handlers.
new Ajax ();
2026-01-29 12:03:05 +01:00
// Initialize ActivityPub integration (only if licensed and enabled).
if ( get_option ( 'wp_fedistream_enable_activitypub' , 1 ) && LicenseManager :: is_license_valid () ) {
2026-01-28 23:23:05 +01:00
new ActivityPubIntegration ();
new ActivityPubRestApi ();
}
// Initialize WooCommerce integration.
if ( get_option ( 'wp_fedistream_enable_woocommerce' , 0 ) && $this -> is_woocommerce_active () ) {
new WooCommerceIntegration ();
new DigitalDelivery ();
new StreamingAccess ();
}
// Initialize user library and notifications.
new UserLibrary ();
new LibraryPage ();
new Notifications ();
2026-01-29 12:03:05 +01:00
// Initialize license manager.
LicenseManager :: get_instance ();
2026-01-28 23:23:05 +01:00
}
/**
* Initialize WordPress hooks .
*
* @ return void
*/
private function init_hooks () : void {
add_action ( 'init' , array ( $this , 'maybe_install_defaults' ), 20 );
add_action ( 'admin_menu' , array ( $this , 'add_admin_menu' ) );
add_action ( 'admin_enqueue_scripts' , array ( $this , 'enqueue_admin_assets' ) );
add_action ( 'wp_enqueue_scripts' , array ( $this , 'enqueue_frontend_assets' ) );
2026-01-28 23:52:12 +01:00
// Add settings link to plugins page.
add_filter ( 'plugin_action_links_' . WP_FEDISTREAM_BASENAME , array ( $this , 'add_plugin_action_links' ) );
}
/**
* Add action links to the plugins page .
*
* @ param array $links Existing action links .
* @ return array Modified action links .
*/
public function add_plugin_action_links ( array $links ) : array {
$settings_link = sprintf (
'<a href="%s">%s</a>' ,
esc_url ( admin_url ( 'admin.php?page=fedistream-settings' ) ),
esc_html__ ( 'Settings' , 'wp-fedistream' )
);
$dashboard_link = sprintf (
'<a href="%s">%s</a>' ,
esc_url ( admin_url ( 'admin.php?page=fedistream' ) ),
esc_html__ ( 'Dashboard' , 'wp-fedistream' )
);
// Add our links at the beginning.
array_unshift ( $links , $dashboard_link , $settings_link );
return $links ;
2026-01-28 23:23:05 +01:00
}
/**
* Maybe install default taxonomy terms .
*
* @ return void
*/
public function maybe_install_defaults () : void {
Installer :: install_defaults ();
}
/**
* Load plugin textdomain .
*
* @ return void
*/
private function load_textdomain () : void {
load_plugin_textdomain (
'wp-fedistream' ,
false ,
dirname ( WP_FEDISTREAM_BASENAME ) . '/languages'
);
}
/**
* Add admin menu .
*
* @ return void
*/
public function add_admin_menu () : void {
// Main menu.
add_menu_page (
__ ( 'FediStream' , 'wp-fedistream' ),
__ ( 'FediStream' , 'wp-fedistream' ),
'edit_fedistream_tracks' ,
'fedistream' ,
array ( $this , 'render_dashboard_page' ),
'dashicons-format-audio' ,
30
);
// Dashboard submenu.
add_submenu_page (
'fedistream' ,
__ ( 'Dashboard' , 'wp-fedistream' ),
__ ( 'Dashboard' , 'wp-fedistream' ),
'edit_fedistream_tracks' ,
'fedistream' ,
array ( $this , 'render_dashboard_page' )
);
// Artists submenu.
add_submenu_page (
'fedistream' ,
__ ( 'Artists' , 'wp-fedistream' ),
__ ( 'Artists' , 'wp-fedistream' ),
'edit_fedistream_artists' ,
'edit.php?post_type=fedistream_artist'
);
// Albums submenu.
add_submenu_page (
'fedistream' ,
__ ( 'Albums' , 'wp-fedistream' ),
__ ( 'Albums' , 'wp-fedistream' ),
'edit_fedistream_albums' ,
'edit.php?post_type=fedistream_album'
);
// Tracks submenu.
add_submenu_page (
'fedistream' ,
__ ( 'Tracks' , 'wp-fedistream' ),
__ ( 'Tracks' , 'wp-fedistream' ),
'edit_fedistream_tracks' ,
'edit.php?post_type=fedistream_track'
);
// Playlists submenu.
add_submenu_page (
'fedistream' ,
__ ( 'Playlists' , 'wp-fedistream' ),
__ ( 'Playlists' , 'wp-fedistream' ),
'edit_fedistream_playlists' ,
'edit.php?post_type=fedistream_playlist'
);
// Genres submenu.
add_submenu_page (
'fedistream' ,
__ ( 'Genres' , 'wp-fedistream' ),
__ ( 'Genres' , 'wp-fedistream' ),
'manage_fedistream_genres' ,
'edit-tags.php?taxonomy=fedistream_genre'
);
// Settings submenu.
add_submenu_page (
'fedistream' ,
__ ( 'Settings' , 'wp-fedistream' ),
__ ( 'Settings' , 'wp-fedistream' ),
'manage_fedistream_settings' ,
'fedistream-settings' ,
array ( $this , 'render_settings_page' )
);
}
/**
* Render dashboard page .
*
* @ return void
*/
public function render_dashboard_page () : void {
// Get stats.
$artist_count = wp_count_posts ( 'fedistream_artist' ) -> publish ? ? 0 ;
$album_count = wp_count_posts ( 'fedistream_album' ) -> publish ? ? 0 ;
$track_count = wp_count_posts ( 'fedistream_track' ) -> publish ? ? 0 ;
$playlist_count = wp_count_posts ( 'fedistream_playlist' ) -> publish ? ? 0 ;
?>
< div class = " wrap " >
< h1 >< ? php esc_html_e ( 'FediStream Dashboard' , 'wp-fedistream' ); ?> </h1>
< div class = " fedistream-dashboard " >
< div class = " fedistream-stats " style = " display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin: 20px 0; " >
< div class = " fedistream-stat-box " style = " background: #fff; padding: 20px; border: 1px solid #ccd0d4; border-radius: 4px; " >
< h3 style = " margin: 0 0 10px; " >< ? php esc_html_e ( 'Artists' , 'wp-fedistream' ); ?> </h3>
< p style = " font-size: 2em; margin: 0; color: #2271b1; " >< ? php echo esc_html ( $artist_count ); ?> </p>
< a href = " <?php echo esc_url( admin_url( 'edit.php?post_type=fedistream_artist' ) ); ?> " >< ? php esc_html_e ( 'Manage Artists' , 'wp-fedistream' ); ?> </a>
</ div >
< div class = " fedistream-stat-box " style = " background: #fff; padding: 20px; border: 1px solid #ccd0d4; border-radius: 4px; " >
< h3 style = " margin: 0 0 10px; " >< ? php esc_html_e ( 'Albums' , 'wp-fedistream' ); ?> </h3>
< p style = " font-size: 2em; margin: 0; color: #2271b1; " >< ? php echo esc_html ( $album_count ); ?> </p>
< a href = " <?php echo esc_url( admin_url( 'edit.php?post_type=fedistream_album' ) ); ?> " >< ? php esc_html_e ( 'Manage Albums' , 'wp-fedistream' ); ?> </a>
</ div >
< div class = " fedistream-stat-box " style = " background: #fff; padding: 20px; border: 1px solid #ccd0d4; border-radius: 4px; " >
< h3 style = " margin: 0 0 10px; " >< ? php esc_html_e ( 'Tracks' , 'wp-fedistream' ); ?> </h3>
< p style = " font-size: 2em; margin: 0; color: #2271b1; " >< ? php echo esc_html ( $track_count ); ?> </p>
< a href = " <?php echo esc_url( admin_url( 'edit.php?post_type=fedistream_track' ) ); ?> " >< ? php esc_html_e ( 'Manage Tracks' , 'wp-fedistream' ); ?> </a>
</ div >
< div class = " fedistream-stat-box " style = " background: #fff; padding: 20px; border: 1px solid #ccd0d4; border-radius: 4px; " >
< h3 style = " margin: 0 0 10px; " >< ? php esc_html_e ( 'Playlists' , 'wp-fedistream' ); ?> </h3>
< p style = " font-size: 2em; margin: 0; color: #2271b1; " >< ? php echo esc_html ( $playlist_count ); ?> </p>
< a href = " <?php echo esc_url( admin_url( 'edit.php?post_type=fedistream_playlist' ) ); ?> " >< ? php esc_html_e ( 'Manage Playlists' , 'wp-fedistream' ); ?> </a>
</ div >
</ div >
< div class = " fedistream-quick-actions " style = " background: #fff; padding: 20px; border: 1px solid #ccd0d4; border-radius: 4px; margin: 20px 0; " >
< h2 >< ? php esc_html_e ( 'Quick Actions' , 'wp-fedistream' ); ?> </h2>
< p >
< a href = " <?php echo esc_url( admin_url( 'post-new.php?post_type=fedistream_artist' ) ); ?> " class = " button button-primary " >< ? php esc_html_e ( 'Add Artist' , 'wp-fedistream' ); ?> </a>
< a href = " <?php echo esc_url( admin_url( 'post-new.php?post_type=fedistream_album' ) ); ?> " class = " button " >< ? php esc_html_e ( 'Add Album' , 'wp-fedistream' ); ?> </a>
< a href = " <?php echo esc_url( admin_url( 'post-new.php?post_type=fedistream_track' ) ); ?> " class = " button " >< ? php esc_html_e ( 'Add Track' , 'wp-fedistream' ); ?> </a>
< a href = " <?php echo esc_url( admin_url( 'post-new.php?post_type=fedistream_playlist' ) ); ?> " class = " button " >< ? php esc_html_e ( 'Add Playlist' , 'wp-fedistream' ); ?> </a>
</ p >
</ div >
< div class = " fedistream-info " style = " background: #fff; padding: 20px; border: 1px solid #ccd0d4; border-radius: 4px; " >
< h2 >< ? php esc_html_e ( 'Getting Started' , 'wp-fedistream' ); ?> </h2>
< ol >
< li >< ? php esc_html_e ( 'Add your artists or bands.' , 'wp-fedistream' ); ?> </li>
< li >< ? php esc_html_e ( 'Create albums and assign them to artists.' , 'wp-fedistream' ); ?> </li>
< li >< ? php esc_html_e ( 'Upload tracks and add them to albums.' , 'wp-fedistream' ); ?> </li>
< li >< ? php esc_html_e ( 'Create playlists to curate your music.' , 'wp-fedistream' ); ?> </li>
< li >< ? php esc_html_e ( 'Share your music via ActivityPub to the Fediverse!' , 'wp-fedistream' ); ?> </li>
</ ol >
</ div >
</ div >
</ div >
< ? php
}
/**
* Render settings page .
*
* @ return void
*/
public function render_settings_page () : void {
// Check user capabilities.
if ( ! current_user_can ( 'manage_fedistream_settings' ) ) {
return ;
}
2026-01-29 12:03:05 +01:00
// Get current tab.
$current_tab = isset ( $_GET [ 'tab' ] ) ? sanitize_key ( $_GET [ 'tab' ] ) : 'license' ;
2026-01-28 23:23:05 +01:00
2026-01-29 12:03:05 +01:00
// Handle form submissions.
$this -> handle_settings_save ( $current_tab );
2026-01-28 23:23:05 +01:00
// Get current settings.
$enable_activitypub = get_option ( 'wp_fedistream_enable_activitypub' , 1 );
$enable_woocommerce = get_option ( 'wp_fedistream_enable_woocommerce' , 0 );
$max_upload_size = get_option ( 'wp_fedistream_max_upload_size' , 50 );
$default_license = get_option ( 'wp_fedistream_default_license' , 'all-rights-reserved' );
2026-01-29 12:03:05 +01:00
// License settings.
$license_key = LicenseManager :: get_license_key ();
$license_server_url = LicenseManager :: get_server_url ();
$license_status = LicenseManager :: get_cached_status ();
$license_data = LicenseManager :: get_cached_data ();
$last_check = LicenseManager :: get_last_check ();
$tabs = array (
'license' => __ ( 'License' , 'wp-fedistream' ),
'settings' => __ ( 'Default Settings' , 'wp-fedistream' ),
'integrations' => __ ( 'Integrations' , 'wp-fedistream' ),
);
2026-01-28 23:23:05 +01:00
?>
< div class = " wrap " >
< h1 >< ? php esc_html_e ( 'FediStream Settings' , 'wp-fedistream' ); ?> </h1>
2026-01-29 12:03:05 +01:00
< nav class = " nav-tab-wrapper wp-clearfix " >
< ? php foreach ( $tabs as $tab_key => $tab_label ) : ?>
< a href = " <?php echo esc_url( admin_url( 'admin.php?page=fedistream-settings&tab=' . $tab_key ) ); ?> "
class = " nav-tab <?php echo $current_tab === $tab_key ? 'nav-tab-active' : ''; ?> " >
< ? php echo esc_html ( $tab_label ); ?>
</ a >
< ? php endforeach ; ?>
</ nav >
< div class = " fedistream-settings-content " >
< ? php
switch ( $current_tab ) {
case 'license' :
$this -> render_license_tab ( $license_key , $license_server_url , $license_status , $license_data , $last_check );
break ;
case 'settings' :
$this -> render_settings_tab ( $max_upload_size , $default_license );
break ;
case 'integrations' :
$this -> render_integrations_tab ( $enable_activitypub , $enable_woocommerce );
break ;
}
?>
</ div >
</ div >
< ? php
}
/**
* Handle settings form submission .
*
* @ param string $tab Current tab .
* @ return void
*/
private function handle_settings_save ( string $tab ) : void {
if ( ! isset ( $_POST [ 'fedistream_settings_nonce' ] ) || ! wp_verify_nonce ( sanitize_key ( $_POST [ 'fedistream_settings_nonce' ] ), 'fedistream_save_settings' ) ) {
return ;
}
switch ( $tab ) {
case 'license' :
LicenseManager :: save_settings ( array (
'license_key' => isset ( $_POST [ 'license_key' ] ) ? sanitize_text_field ( wp_unslash ( $_POST [ 'license_key' ] ) ) : '' ,
'server_url' => isset ( $_POST [ 'license_server_url' ] ) ? esc_url_raw ( wp_unslash ( $_POST [ 'license_server_url' ] ) ) : '' ,
'server_secret' => isset ( $_POST [ 'license_server_secret' ] ) ? sanitize_text_field ( wp_unslash ( $_POST [ 'license_server_secret' ] ) ) : '' ,
) );
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__ ( 'License settings saved.' , 'wp-fedistream' ) . '</p></div>' ;
break ;
case 'settings' :
update_option ( 'wp_fedistream_max_upload_size' , absint ( $_POST [ 'max_upload_size' ] ? ? 50 ) );
update_option ( 'wp_fedistream_default_license' , sanitize_text_field ( wp_unslash ( $_POST [ 'default_license' ] ? ? 'all-rights-reserved' ) ) );
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__ ( 'Settings saved.' , 'wp-fedistream' ) . '</p></div>' ;
break ;
case 'integrations' :
update_option ( 'wp_fedistream_enable_activitypub' , isset ( $_POST [ 'enable_activitypub' ] ) ? 1 : 0 );
update_option ( 'wp_fedistream_enable_woocommerce' , isset ( $_POST [ 'enable_woocommerce' ] ) ? 1 : 0 );
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__ ( 'Integration settings saved.' , 'wp-fedistream' ) . '</p></div>' ;
break ;
}
}
/**
* Render the License tab .
*
* @ param string $license_key License key .
* @ param string $server_url Server URL .
* @ param string $status License status .
* @ param array $license_data License data .
* @ param int $last_check Last check timestamp .
* @ return void
*/
private function render_license_tab ( string $license_key , string $server_url , string $status , array $license_data , int $last_check ) : void {
$status_classes = array (
'valid' => 'notice-success' ,
'invalid' => 'notice-error' ,
'expired' => 'notice-warning' ,
'revoked' => 'notice-error' ,
'inactive' => 'notice-warning' ,
'unchecked' => 'notice-info' ,
'unconfigured' => 'notice-info' ,
);
$status_messages = array (
'valid' => __ ( 'License is active and valid.' , 'wp-fedistream' ),
'invalid' => __ ( 'License is invalid.' , 'wp-fedistream' ),
'expired' => __ ( 'License has expired.' , 'wp-fedistream' ),
'revoked' => __ ( 'License has been revoked.' , 'wp-fedistream' ),
'inactive' => __ ( 'License is inactive. Please activate it.' , 'wp-fedistream' ),
'unchecked' => __ ( 'License has not been validated yet.' , 'wp-fedistream' ),
'unconfigured' => __ ( 'License server is not configured.' , 'wp-fedistream' ),
);
$status_icons = array (
'valid' => 'dashicons-yes-alt' ,
'invalid' => 'dashicons-dismiss' ,
'expired' => 'dashicons-warning' ,
'revoked' => 'dashicons-dismiss' ,
'inactive' => 'dashicons-marker' ,
'unchecked' => 'dashicons-info-outline' ,
'unconfigured' => 'dashicons-admin-generic' ,
);
$status_class = $status_classes [ $status ] ? ? 'notice-info' ;
$status_message = $status_messages [ $status ] ? ? __ ( 'Unknown status.' , 'wp-fedistream' );
$status_icon = $status_icons [ $status ] ? ? 'dashicons-info-outline' ;
?>
< div class = " fedistream-license-status notice <?php echo esc_attr( $status_class ); ?> " style = " padding: 12px; display: flex; align-items: center; gap: 10px; " >
< span class = " dashicons <?php echo esc_attr( $status_icon ); ?> " style = " font-size: 24px; width: 24px; height: 24px; " ></ span >
< div >
< strong >< ? php echo esc_html ( $status_message ); ?> </strong>
< ? php if ( 'valid' === $status && ! empty ( $license_data [ 'expires_at' ] ) ) : ?>
< br >
< span class = " description " >
< ? php
printf (
/* translators: %s: Expiration date */
esc_html__ ( 'Expires: %s' , 'wp-fedistream' ),
esc_html ( $license_data [ 'expires_at' ] )
);
?>
</ span >
< ? php elseif ( 'valid' === $status && empty ( $license_data [ 'expires_at' ] ) ) : ?>
< br >
< span class = " description " >< ? php esc_html_e ( 'Lifetime license' , 'wp-fedistream' ); ?> </span>
< ? php endif ; ?>
< ? php if ( $last_check > 0 ) : ?>
< br >
< span class = " description " >
< ? php
printf (
/* translators: %s: Time ago */
esc_html__ ( 'Last checked: %s' , 'wp-fedistream' ),
esc_html ( human_time_diff ( $last_check , time () ) . ' ' . __ ( 'ago' , 'wp-fedistream' ) )
);
?>
</ span >
< ? php endif ; ?>
</ div >
2026-01-28 23:23:05 +01:00
</ div >
2026-01-29 12:03:05 +01:00
< form method = " post " action = " " id = " fedistream-license-form " >
< ? php wp_nonce_field ( 'fedistream_save_settings' , 'fedistream_settings_nonce' ); ?>
< table class = " form-table " >
< tr >
< th scope = " row " >
< label for = " license_server_url " >< ? php esc_html_e ( 'License Server URL' , 'wp-fedistream' ); ?> </label>
</ th >
< td >
< input type = " url " name = " license_server_url " id = " license_server_url "
value = " <?php echo esc_attr( $server_url ); ?> "
class = " regular-text " placeholder = " https://example.com " >
< p class = " description " >< ? php esc_html_e ( 'The URL of your license server.' , 'wp-fedistream' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >
< label for = " license_key " >< ? php esc_html_e ( 'License Key' , 'wp-fedistream' ); ?> </label>
</ th >
< td >
< input type = " text " name = " license_key " id = " license_key "
value = " <?php echo esc_attr( $license_key ); ?> "
class = " regular-text " placeholder = " XXXX-XXXX-XXXX-XXXX " >
< p class = " description " >< ? php esc_html_e ( 'Your license key from your purchase.' , 'wp-fedistream' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >
< label for = " license_server_secret " >< ? php esc_html_e ( 'Server Secret' , 'wp-fedistream' ); ?> </label>
</ th >
< td >
< input type = " password " name = " license_server_secret " id = " license_server_secret "
value = " " class = " regular-text " placeholder = " <?php echo esc_attr( ! empty( LicenseManager::get_server_secret() ) ? '••••••••••••••••' : '' ); ?> " >
< p class = " description " >< ? php esc_html_e ( '64-character verification secret from your license account. Leave empty to keep existing.' , 'wp-fedistream' ); ?> </p>
</ td >
</ tr >
</ table >
< p class = " submit " >
< ? php submit_button ( __ ( 'Save License Settings' , 'wp-fedistream' ), 'primary' , 'submit' , false ); ?>
< button type = " button " id = " fedistream-validate-license " class = " button button-secondary " style = " margin-left: 10px; " >
< span class = " dashicons dashicons-yes " style = " vertical-align: middle; margin-top: -2px; " ></ span >
< ? php esc_html_e ( 'Validate License' , 'wp-fedistream' ); ?>
</ button >
< button type = " button " id = " fedistream-activate-license " class = " button button-secondary " style = " margin-left: 10px; " >
< span class = " dashicons dashicons-admin-network " style = " vertical-align: middle; margin-top: -2px; " ></ span >
< ? php esc_html_e ( 'Activate License' , 'wp-fedistream' ); ?>
</ button >
< span id = " fedistream-license-spinner " class = " spinner " style = " float: none; margin-top: 4px; " ></ span >
</ p >
</ form >
< div id = " fedistream-license-message " style = " display: none; margin-top: 10px; " ></ div >
< script type = " text/javascript " >
var fedistreamLicenseNonce = '<?php echo esc_js( wp_create_nonce( ' fedistream_license_action ' ) ); ?>' ;
</ script >
< ? php
}
/**
* Render the Default Settings tab .
*
* @ param int $max_upload_size Max upload size in MB .
* @ param string $default_license Default license .
* @ return void
*/
private function render_settings_tab ( int $max_upload_size , string $default_license ) : void {
?>
< form method = " post " action = " " >
< ? php wp_nonce_field ( 'fedistream_save_settings' , 'fedistream_settings_nonce' ); ?>
< table class = " form-table " >
< tr >
< th scope = " row " >
< label for = " max_upload_size " >< ? php esc_html_e ( 'Max Upload Size' , 'wp-fedistream' ); ?> </label>
</ th >
< td >
< input type = " number " name = " max_upload_size " id = " max_upload_size " value = " <?php echo esc_attr( $max_upload_size ); ?> " min = " 1 " max = " 500 " class = " small-text " > MB
< p class = " description " >< ? php esc_html_e ( 'Maximum file size for audio uploads.' , 'wp-fedistream' ); ?> </p>
</ td >
</ tr >
< tr >
< th scope = " row " >
< label for = " default_license " >< ? php esc_html_e ( 'Default License' , 'wp-fedistream' ); ?> </label>
</ th >
< td >
< select name = " default_license " id = " default_license " >
< option value = " all-rights-reserved " < ? php selected ( $default_license , 'all-rights-reserved' ); ?> ><?php esc_html_e( 'All Rights Reserved', 'wp-fedistream' ); ?></option>
< option value = " cc-by " < ? php selected ( $default_license , 'cc-by' ); ?> >CC BY</option>
< option value = " cc-by-sa " < ? php selected ( $default_license , 'cc-by-sa' ); ?> >CC BY-SA</option>
< option value = " cc-by-nc " < ? php selected ( $default_license , 'cc-by-nc' ); ?> >CC BY-NC</option>
< option value = " cc-by-nc-sa " < ? php selected ( $default_license , 'cc-by-nc-sa' ); ?> >CC BY-NC-SA</option>
< option value = " cc0 " < ? php selected ( $default_license , 'cc0' ); ?> >CC0 (Public Domain)</option>
</ select >
< p class = " description " >< ? php esc_html_e ( 'Default license for new uploads.' , 'wp-fedistream' ); ?> </p>
</ td >
</ tr >
</ table >
< ? php submit_button (); ?>
</ form >
< ? php
}
/**
* Render the Integrations tab .
*
* @ param int $enable_activitypub Whether ActivityPub is enabled .
* @ param int $enable_woocommerce Whether WooCommerce integration is enabled .
* @ return void
*/
private function render_integrations_tab ( int $enable_activitypub , int $enable_woocommerce ) : void {
?>
< form method = " post " action = " " >
< ? php wp_nonce_field ( 'fedistream_save_settings' , 'fedistream_settings_nonce' ); ?>
< table class = " form-table " >
< tr >
< th scope = " row " >< ? php esc_html_e ( 'ActivityPub Integration' , 'wp-fedistream' ); ?> </th>
< td >
< label >
< input type = " checkbox " name = " enable_activitypub " value = " 1 " < ? php checked ( $enable_activitypub , 1 ); ?> >
< ? php esc_html_e ( 'Enable ActivityPub features' , 'wp-fedistream' ); ?>
</ label >
< p class = " description " >< ? php esc_html_e ( 'Publish releases to the Fediverse and allow followers.' , 'wp-fedistream' ); ?> </p>
< ? php if ( ! $this -> is_activitypub_active () ) : ?>
< p class = " description " style = " color: #dba617; " >
< span class = " dashicons dashicons-warning " style = " font-size: 16px; width: 16px; height: 16px; vertical-align: text-bottom; " ></ span >
< ? php esc_html_e ( 'The ActivityPub plugin is recommended for full Fediverse integration.' , 'wp-fedistream' ); ?>
</ p >
< ? php endif ; ?>
</ td >
</ tr >
< tr >
< th scope = " row " >< ? php esc_html_e ( 'WooCommerce Integration' , 'wp-fedistream' ); ?> </th>
< td >
< label >
< input type = " checkbox " name = " enable_woocommerce " value = " 1 " < ? php checked ( $enable_woocommerce , 1 ); ?> <?php disabled( ! $this->is_woocommerce_active() ); ?>>
< ? php esc_html_e ( 'Enable WooCommerce features' , 'wp-fedistream' ); ?>
</ label >
< ? php if ( ! $this -> is_woocommerce_active () ) : ?>
< p class = " description " style = " color: #d63638; " >
< span class = " dashicons dashicons-dismiss " style = " font-size: 16px; width: 16px; height: 16px; vertical-align: text-bottom; " ></ span >
< ? php esc_html_e ( 'WooCommerce is not installed or active.' , 'wp-fedistream' ); ?>
</ p >
< ? php else : ?>
< p class = " description " >< ? php esc_html_e ( 'Sell albums and tracks through WooCommerce.' , 'wp-fedistream' ); ?> </p>
< ? php endif ; ?>
</ td >
</ tr >
</ table >
< ? php submit_button (); ?>
</ form >
2026-01-28 23:23:05 +01:00
< ? php
}
/**
* Enqueue admin assets .
*
* @ param string $hook_suffix The current admin page .
* @ return void
*/
public function enqueue_admin_assets ( string $hook_suffix ) : void {
// Only enqueue on FediStream pages.
$screen = get_current_screen ();
if ( ! $screen ) {
return ;
}
$fedistream_screens = array (
'toplevel_page_fedistream' ,
'fedistream_page_fedistream-settings' ,
'fedistream_artist' ,
'fedistream_album' ,
'fedistream_track' ,
'fedistream_playlist' ,
'edit-fedistream_artist' ,
'edit-fedistream_album' ,
'edit-fedistream_track' ,
'edit-fedistream_playlist' ,
'edit-fedistream_genre' ,
'edit-fedistream_mood' ,
'edit-fedistream_license' ,
);
if ( ! in_array ( $screen -> id , $fedistream_screens , true ) ) {
return ;
}
wp_enqueue_style (
'wp-fedistream-admin' ,
WP_FEDISTREAM_URL . 'assets/css/admin.css' ,
array (),
WP_FEDISTREAM_VERSION
);
wp_enqueue_script (
'wp-fedistream-admin' ,
WP_FEDISTREAM_URL . 'assets/js/admin.js' ,
array ( 'jquery' , 'jquery-ui-sortable' ),
WP_FEDISTREAM_VERSION ,
true
);
}
/**
* Enqueue frontend assets .
*
* @ return void
*/
public function enqueue_frontend_assets () : void {
// Always enqueue as shortcodes/widgets can be used anywhere.
// Assets are lightweight and properly cached.
wp_enqueue_style (
'wp-fedistream' ,
WP_FEDISTREAM_URL . 'assets/css/frontend.css' ,
array (),
WP_FEDISTREAM_VERSION
);
wp_enqueue_script (
'wp-fedistream' ,
WP_FEDISTREAM_URL . 'assets/js/frontend.js' ,
array (),
WP_FEDISTREAM_VERSION ,
true
);
wp_localize_script (
'wp-fedistream' ,
'wpFediStream' ,
array (
'ajaxUrl' => admin_url ( 'admin-ajax.php' ),
'nonce' => wp_create_nonce ( 'wp-fedistream-nonce' ),
)
);
}
/**
* Get Twig environment .
*
* @ return \Twig\Environment
*/
public function get_twig () : \Twig\Environment {
return $this -> twig ;
}
/**
* Render a Twig template .
*
* @ param string $template Template name ( without . twig extension ) .
* @ param array $context Template context variables .
* @ return string Rendered template .
*/
public function render ( string $template , array $context = array () ) : string {
2026-02-02 17:04:38 +01:00
// Prevent infinite recursion in Twig rendering.
if ( self :: $render_depth >= self :: MAX_RENDER_DEPTH ) {
return '<!-- FediStream: render depth exceeded -->' ;
}
++ self :: $render_depth ;
try {
$result = $this -> twig -> render ( $template . '.twig' , $context );
} finally {
-- self :: $render_depth ;
}
return $result ;
2026-01-28 23:23:05 +01:00
}
/**
* Get a post type instance .
*
* @ param string $name Post type name .
* @ return object | null Post type instance or null .
*/
public function get_post_type ( string $name ) : ? object {
return $this -> post_types [ $name ] ? ? null ;
}
/**
* Get a taxonomy instance .
*
* @ param string $name Taxonomy name .
* @ return object | null Taxonomy instance or null .
*/
public function get_taxonomy ( string $name ) : ? object {
return $this -> taxonomies [ $name ] ? ? null ;
}
/**
* Check if WooCommerce is active .
*
* @ return bool
*/
public function is_woocommerce_active () : bool {
return class_exists ( 'WooCommerce' );
}
/**
* Check if ActivityPub plugin is active .
*
* @ return bool
*/
public function is_activitypub_active () : bool {
return class_exists ( 'Activitypub\Activitypub' );
}
}