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 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 LicenseTaxonomy(); // Initialize admin components. if ( is_admin() ) { new ListColumns(); } // Initialize frontend components (only if licensed). if ( ! is_admin() && LicenseManager::is_license_valid() ) { new TemplateLoader(); new Shortcodes(); } elseif ( ! is_admin() ) { // Register shortcodes that show license message. new Shortcodes( true ); // Unlicensed mode. } // Initialize widgets (always needed for admin widget management). new Widgets(); // Initialize AJAX handlers. new Ajax(); // Initialize ActivityPub integration (only if licensed and enabled). if ( get_option( 'wp_fedistream_enable_activitypub', 1 ) && LicenseManager::is_license_valid() ) { 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 Prometheus integration. if ( get_option( 'wp_fedistream_enable_prometheus', 0 ) && $this->is_prometheus_active() ) { new PrometheusIntegration(); } // Initialize user library and notifications. new UserLibrary(); new LibraryPage(); new Notifications(); // Initialize license manager. LicenseManager::get_instance(); } /** * 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' ) ); // 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( '%s', esc_url( admin_url( 'admin.php?page=fedistream-settings' ) ), esc_html__( 'Settings', 'wp-fedistream' ) ); $dashboard_link = sprintf( '%s', 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; } /** * 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; ?>

handle_settings_save( $current_tab ); // Get current settings. $enable_activitypub = get_option( 'wp_fedistream_enable_activitypub', 1 ); $enable_woocommerce = get_option( 'wp_fedistream_enable_woocommerce', 0 ); $enable_prometheus = get_option( 'wp_fedistream_enable_prometheus', 0 ); $max_upload_size = get_option( 'wp_fedistream_max_upload_size', 50 ); $default_license = get_option( 'wp_fedistream_default_license', 'all-rights-reserved' ); // 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' ), ); ?>

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, $enable_prometheus ); break; } ?>
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 '

' . esc_html__( 'License settings saved.', 'wp-fedistream' ) . '

'; 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 '

' . esc_html__( 'Settings saved.', 'wp-fedistream' ) . '

'; 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 ); update_option( 'wp_fedistream_enable_prometheus', isset( $_POST['enable_prometheus'] ) ? 1 : 0 ); echo '

' . esc_html__( 'Integration settings saved.', 'wp-fedistream' ) . '

'; 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'; ?>


0 ) : ?>

MB

is_activitypub_active() ) : ?>

is_woocommerce_active() ) : ?>

is_prometheus_active() ) : ?>

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 the path to a template file. * * Checks theme for override first, then falls back to plugin template. * * @param string $template Template name (without extension). * @return string Full path to template file. */ public function get_template_path( string $template ): string { // Check theme for override. $theme_template = locate_template( "fedistream/{$template}.php" ); if ( $theme_template ) { return $theme_template; } // Use plugin template. return WP_FEDISTREAM_PATH . "templates/{$template}.php"; } /** * Render a PHP template. * * @param string $template Template name (without extension). * @param array $context Template context variables. * @param bool $is_main_template Whether this is the main page template. * @return string Rendered template. */ public function render( string $template, array $context = array(), bool $is_main_template = false ): string { // If we're already rendering the main template, block any other renders. if ( self::$rendering_main_template && ! $is_main_template ) { return ''; } // Prevent infinite recursion in rendering. if ( self::$render_depth >= self::MAX_RENDER_DEPTH ) { return ''; } // Set main template lock if this is the main template. $was_main = self::$rendering_main_template; if ( $is_main_template ) { self::$rendering_main_template = true; } ++self::$render_depth; try { $template_path = $this->get_template_path( $template ); if ( ! file_exists( $template_path ) ) { throw new \Exception( "Template not found: {$template}" ); } // Extract context variables for use in template. // phpcs:ignore WordPress.PHP.DontExtract.extract_extract extract( $context, EXTR_SKIP ); // Start output buffering. ob_start(); // Include the template. include $template_path; // Get the rendered content. $result = ob_get_clean(); } finally { --self::$render_depth; if ( $is_main_template ) { self::$rendering_main_template = $was_main; } } return $result; } /** * Render a partial template (helper for use within templates). * * @param string $partial Partial template name (without extension). * @param array $context Template context variables. * @return string Rendered partial. */ public function render_partial( string $partial, array $context = array() ): string { return $this->render( $partial, $context, false ); } /** * 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' ); } /** * Check if WP Prometheus plugin is active. * * @return bool */ public function is_prometheus_active(): bool { return defined( 'WP_PROMETHEUS_VERSION' ) || class_exists( 'WP_Prometheus\Plugin' ); } }