*/ class Wpstream_Admin { /** * Most parts a multipart VOD upload may be split into. * * The storage backend assembles at most this many parts, so a larger * split uploads every byte and still can never complete. * * @var int */ const MULTIPART_MAX_PARTS = 1000; /** * Store plugin main class to allow public access. * * @since 20180622 * @var object The main class. */ public $main; /** * The ID of this plugin. * * @since 3.0.1 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 3.0.1 * @access private * @var string $version The current version of this plugin. */ private $version; /** * @var array Definitions of the per-event "global" streaming options (record, * view count, autoplay, encryption, etc.). Each entry holds a * translated label, a help "details" string, and a default value. * Populated lazily on `init` by load_global_event_options(). */ public $global_event_options ; /** * Initialize the class and set its properties. * * @since 3.0.1 * @param string $plugin_name The name (ID) of this plugin. * @param string $version The version of this plugin. * @param object $plugin_main The main plugin class instance, kept for public access. * @return void */ public function __construct( $plugin_name, $version,$plugin_main ) { // Remember the plugin identity and the main class for later use. $this->plugin_name = $plugin_name; $this->version = $version; $this->main = $plugin_main; // Build the translatable global event options once WP (and its i18n) is ready. add_action('init', array($this, 'load_global_event_options')); } /** * Populate $global_event_options with the translatable per-event settings. * * Deferred to the `init` hook so the esc_html__() translations resolve * against a loaded text domain. * * @return void */ public function load_global_event_options() { // Each key maps to a label/details/default triple used when rendering the event option toggles. $this->global_event_options = array( 'record' => array( 'name' => esc_html__('Record Live Stream','wpstream'), 'details' => esc_html__('If enabled, live streams will be recorded and saved to your library.','wpstream'), 'defaults' => 'no', ), 'view_count' => array( 'name' => esc_html__('Display Viewer Count','wpstream'), 'details' => esc_html__('If enabled, the live viewer count will show up in the player.','wpstream'), 'defaults' => 'yes', ), 'domain_lock' => array( 'name' => esc_html__('Lock To Website','wpstream'), 'details' => sprintf ( esc_html__('If enabled, live video will only display on %1$s, otherwise it can show up on any website.','wpstream'),get_bloginfo('wpurl') ), 'defaults' => 'no', ), 'autoplay' => array( 'name' => esc_html__('Autoplay','wpstream'), 'details' => esc_html__('If enabled, live video will attempt to start playing automatically. This is only achievable in some browsers.','wpstream'), 'defaults' => 'yes', ), 'mute' => array( 'name' => esc_html__('Start Muted','wpstream'), 'details' => esc_html__('If enabled, live video will start muted. This may increase the rate of autoplay in some browsers. ','wpstream'), 'defaults' => 'no', ), 'low_latency' => array( 'name' => esc_html__('Low Latency (beta)','wpstream'), 'details' => esc_html__('Shortens the live delay between streamer and viewers. Useful for interactive applications like gaming, auctions, trading etc. Low latency may worsen the viewer experience on some devices.','wpstream'), 'defaults' => 'no', ), 'adaptive_bitrate' => array( 'name' => esc_html__('Adaptive Bitrate (beta)','wpstream'), 'details' => esc_html__('Ensures a smooth and uninterrupted viewing experience by adjusting video quality for viewers with reduced network speed or device capabilities.','wpstream'), 'defaults' => 'no', ), 'encrypt' =>array( 'name' => esc_html__('Encrypt Live Stream','wpstream'), 'details' => esc_html__('If enabled, video data will be encrypted. Enabling encryption may lead to reduced website performance under certain configurations. Encrypted video may not display in all browsers.','wpstream'), 'defaults' => 'no', ), 'ses_encrypt'=>array( 'name' => esc_html__('Use Sessions with Encryption','wpstream'), 'details' => esc_html__('If enabled, encryption key distribution will be checked against valid user sessions. Setting may malfunction or lead to reduced website performance under certain configurations. ','wpstream'), 'defaults' => 'no', ), ); /** * Filter the catalog of per-channel options shown as toggles. * * Each entry is `key => array( name, details, defaults )`. An added key * renders a toggle on the settings and channel screens; it is stored * only if the Channel Settings Module knows the key. * * @since 4.14.0 * * @param array $options Option catalog keyed by setting. */ $this->global_event_options = (array) apply_filters( 'wpstream_default_event_options', $this->global_event_options ); } /** * Register and enqueue the plugin's admin-area stylesheets. * * Hooked (via Wpstream_Loader) to admin_enqueue_scripts. * * @since 3.0.1 * @return void */ public function enqueue_styles() { /** * This function is provided for demonstration purposes only. * * An instance of this class should be passed to the run() function * defined in Wpstream_Loader as all of the hooks are defined * in that particular class. * * The Wpstream_Loader will then create the relationship * between the defined hooks and the functions defined in this * class. */ // No external webfont: the admin UI falls back to the system font stack // declared in the stylesheets, so no request leaves the site for fonts. // Main admin stylesheet; filemtime() is used as the cache-busting version. wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/wpstream-admin.css', array(), filemtime(plugin_dir_path(__FILE__) . 'css/wpstream-admin.css' ), 'all' ); // Ensure the WP media library scripts/styles are available (for logo/image pickers) if not already loaded. if (!did_action('wp_enqueue_media')) { wp_enqueue_media(); } } /** * Register, enqueue and localize the plugin's admin-area JavaScript. * * Enqueues jQuery UI helpers, the fileupload lib, the admin control/upload * scripts, the start-streaming and settings scripts, and the onboarding * bundles. Each script is fed a localized vars array (translated strings, * URLs, nonces, feature flags). Some bundles are only loaded on specific * admin screens. Hooked (via Wpstream_Loader) to admin_enqueue_scripts. * * @since 3.0.1 * @return void */ public function enqueue_scripts() { // jQuery UI widgets used by the settings/date pickers and sliders. wp_enqueue_script("jquery-ui-slider"); wp_enqueue_script("jquery-ui-datepicker"); // Blueimp-style file upload library backing the recordings/VOD uploader. wp_enqueue_script('jquery.fileupload', plugin_dir_url( __FILE__ ) .'js/jquery.fileupload.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); // Shared admin utility helpers, then the main admin control script that drives uploads and channel actions. wp_enqueue_script( 'wpstream-admin-utils', plugin_dir_url( __FILE__ ) .'js/utils/admin_utils.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); wp_enqueue_script('wpstream-admin-control', plugin_dir_url( __FILE__ ) .'js/admin_control.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); // Hand the control script all of its translated status strings, the admin URL, and the multipart upload nonce. wp_localize_script('wpstream-admin-control', 'wpstream_admin_control_vars', array( 'admin_url' => get_admin_url(), 'multipart_upload_nonce' => wp_create_nonce( 'wpstream_multipart_upload_nonce' ), 'recordings_nonce' => wp_create_nonce( 'wpstream_recordings_nonce' ), 'loading_url' => WPSTREAM_PLUGIN_DIR_URL.'/img/loading.gif', 'download_mess' => esc_html__('Click to download!','wpstream'), 'uploading' => esc_html__('We are uploading your file. Do not close this window!','wpstream'), 'upload_complete2' => esc_html__('Upload Complete! You can upload another file!','wpstream'), 'not_accepted' => esc_html__('The file is not an accepted video format','wpstream'), 'upload_complete' => esc_html__('Upload Complete!','wpstream'), 'no_band' => esc_html__('Not enough streaming data.','wpstream'), 'no_band_no_store' => esc_html__('Not enough streaming data or storage.','wpstream'), 'no_streaming_hours' => esc_html__('Not enough streaming hours.','wpstream'), 'exceeding_limit' => esc_html__('File size exceeds 5GB. Initiating multipart upload...','wpstream'), 'upload_failed' => esc_html__('Upload Failed!','wpstream'), 'upload_failed2' => esc_html__('Upload Failed! Please Try again!','wpstream'), 'choose_a_file' => esc_html__('Choose a file…','wpstream'), 'preparing_multipart' => esc_html__('Preparing multipart upload...','wpstream'), 'uploading_part' => esc_html__('Uploading part {part} of {total}...','wpstream'), 'upload_failed_part' => esc_html__('Failed to upload part {part}. Please try again.','wpstream'), 'completing_upload' => esc_html__('Completing upload. Please wait...','wpstream'), 'upload_failed_part_retry' => esc_html__('Failed to upload part {part}. Retrying...','wpstream'), 'choose_recording' => esc_html__( 'Choose Recording', 'wpstream' ), 'select_recording' => esc_html__( 'Please select a recording from the list', 'wpstream' ), 'invalid_response' => esc_html__('Invalid response from server. Missing required upload data.', 'wpstream'), 'video_processing' => esc_html__( 'The video is still processing', 'wpstream' ), 'file_name_text' => esc_html__('File Name:','wpstream'), 'channel_create_error' => esc_html__('Something did not work. Please try again.', 'wpstream'), 'select_caption_file' => esc_html__('Select .vtt Captions File', 'wpstream'), 'select_button' => esc_html__('Select', 'wpstream'), 'remove_button' => esc_html__('Remove', 'wpstream'), 'use_streaming_hours' => $this->main->quota_manager->get_streaming_ui_flags_from_cache()['use_streaming_hours'], )); // Recordings list script + its localized labels and the "create VOD from recording" links (free vs paid depending on WooCommerce). wp_enqueue_script('wpstream-recordings-videos-list', plugin_dir_url( __FILE__ ) .'js/recordings_videos_list.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); wp_localize_script( 'wpstream-recordings-videos-list', 'wpstream_recordings_videos_list_vars', array( 'delete_file' => esc_html__('Delete file', 'wpstream'), 'download' => esc_html__( 'Download', 'wpstream'), 'download_available' => esc_html__( 'Click to download! The url will work for the next 20 minutes!', 'wpstream'), 'add_free_video_url' => esc_url( admin_url( 'post-new.php?post_type=wpstream_product_vod') . '&new_video_name=' ), 'create_ftv_vod' => esc_html__( 'Create new Free-To-View VOD from this recording' , 'wpstream' ), 'woocommerce_exists' => class_exists( 'WooCommerce' ), 'add_paid_video_url' => esc_url( admin_url( 'post-new.php?post_type=product').'&new_video_name=' ), 'create_ptv_vod' => esc_html__( 'Create new Pay-Per-View VOD from this recording' , 'wpstream' ), ) ); // Settings-page script with its save/logo-picker strings and the broadcaster page URL. wp_enqueue_script('wpstream-settings', plugin_dir_url( __DIR__ ) .'/admin/js/wpstream_settings.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); wp_localize_script('wpstream-settings', 'wpstream_settings_vars', array( 'error_message' => esc_html__( 'Failed to save settings. Please try again.', 'wpstream'), 'choose_image_text' => esc_html__( 'Choose Logo Image', 'wpstream'), 'select_image_text' => esc_html__( 'Select Image', 'wpstream'), 'update_successful' => esc_html__( 'Update Successful.', 'wpstream'), 'update_failed' => esc_html__( 'Something went wrong. Try again.', 'wpstream'), 'broadcaster_url' => esc_url( esc_url(home_url('/broadcaster-page/') ) ), )); // Identify the current admin screen so screen-specific bundles load only where needed. $current_screen=get_current_screen(); // On the credentials/channels/recordings/onboard screens, load the quota widget updater. if ( in_array( $current_screen->base, ['toplevel_page_wpstream_credentials', 'wpstream_page_wpstream_live_channels', 'wpstream_page_wpstream_recordings', 'wpstream_page_wpstream_onboard'] ) ) { wp_enqueue_script( 'wpstream-user-quota-update', plugin_dir_url( __DIR__ ) . 'admin/js/wpstream-user-quota.js', array(), WPSTREAM_PLUGIN_VERSION, true ); wp_localize_script( 'wpstream-user-quota-update', 'wpstream_user_quota_vars', array( 'admin_url' => get_admin_url() )); } // Add localized variables for broadcaster // Provide the broadcaster script (registered elsewhere) with its AJAX URL and nonce. wp_localize_script('wpstream-broadcaster', 'wpstream_broadcaster_vars', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('wpstream_broadcaster_nonce'), 'plugin_url' => plugin_dir_url(__FILE__), // Gates the broadcaster's diagnostic console logging (see broadcaster.js). 'debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, )); } /** * Add Plugin Administation menu * * Registers the top-level "WpStream" menu and its sub-pages (Credentials, * All Channels, Recordings, Settings, Quick Start), each mapped to a render * callback on this class and gated to the `administrator` capability. * * @since 3.0.1 * @return void */ public function wpstream_manage_admin_menu() { // Top-level menu; its page defaults to the Credentials screen and uses the WpStream icon at position 20. add_menu_page( __('WpStream','wpstream'), __('WpStream ','wpstream'), 'administrator', 'wpstream_credentials', array($this,'wpstream_set_wpstream_credentials'), WPSTREAM_PLUGIN_DIR_URL.'img/wpstream-icon-menu_2.png',20 ); // Credentials sub-page (same slug as the parent so it is the default view). add_submenu_page( 'wpstream_credentials', __('WpStream Credentials','wpstream'), __('Credentials','wpstream'), 'administrator', 'wpstream_credentials', array($this,'wpstream_set_wpstream_credentials') ); // All Channels listing. add_submenu_page( 'wpstream_credentials', __('WpStream Live Channels','wpstream'), __('All Channels','wpstream'), 'administrator', 'wpstream_live_channels', array( $this,'wpstream_new_general_set')); // Recordings / media management. add_submenu_page( 'wpstream_credentials', __('WpStream Recordings','wpstream'), __('Recordings','wpstream'), 'administrator', 'wpstream_recordings', array($this,'wpstream_media_management')); // Global plugin settings. add_submenu_page( 'wpstream_credentials', __('WpStream Settings','wpstream'), __('Settings','wpstream'), 'administrator', 'wpstream_settings', array($this,'wpstream_settings')); // Quick Start / onboarding entry point. add_submenu_page( 'wpstream_credentials', __('WpStream Quick Start','wpstream'), __('WpStream Quick Start','wpstream'), 'administrator', 'wpstream_onboard', array($this,'wpstream_pre_onboard_display')); } /** * Shows events wpstream * * Renders the "All Channels" admin screen: the Pay-Per-View (WooCommerce * `product`) channel list, the Free-To-View (`wpstream_product`) list, the * quota/pack summary, and the "no channels" call-to-action. Each channel is * drawn via wpstream_live_stream_unit(). Echoes HTML directly. * * @since 3.0.1 * @return void */ public function wpstream_new_general_set() { return $this->main->get_live_channel_presentation()->render_channels_page(); } public function wpstream_social_share($the_id){ return $this->main->get_live_channel_presentation()->render_part( 'social_share', array( $the_id ) ); } public function wpstream_live_stream_unit($the_id,$is_front=''){ $context = '' === $is_front ? 'admin' : $is_front; return $this->main->get_live_channel_presentation()->render_channel( $the_id, $context ); } public function wpstream_live_stream_unit_for_theme($the_id,$is_front=''){ return $this->main->get_live_channel_presentation()->render_part( 'theme_channel', array( $the_id, $is_front ) ); } public function wpstream_close_modal_button(){ return $this->main->get_live_channel_presentation()->render_part( 'close_modal_button' ); } public function wpstream_local_event_options_toggle( $is_basic_stream_mode = false ) { return $this->main->get_live_channel_presentation()->render_part( 'local_event_options_toggle', array( $is_basic_stream_mode ) ); } public function wpstream_basic_stream_mode_message() { return $this->main->get_live_channel_presentation()->render_part( 'basic_stream_mode_message' ); } public function wpstream_display_modal_seetings($the_id){ return $this->main->get_live_channel_presentation()->render_part( 'settings_modal', array( $the_id ) ); } public function wpstream_display_modal_share($the_id){ return $this->main->get_live_channel_presentation()->render_part( 'share_modal', array( $the_id ) ); } public function wpstream_display_modal_broadcast($the_id,$external_software_streaming_class,$obs_uri,$obs_stream){ return $this->main->get_live_channel_presentation()->render_part( 'broadcast_modal', array( $the_id, $external_software_streaming_class, $obs_uri, $obs_stream ) ); } public function wpstream_encoder_table(){ return $this->main->get_live_channel_presentation()->render_part( 'encoder_table' ); } public function wpstream_encoder_panel( $encoder, $obs_uri, $obs_stream, $visible = false ){ return $this->main->get_live_channel_presentation()->render_part( 'encoder_panel', array( $encoder, $obs_uri, $obs_stream, $visible ) ); } private function wpstream_settings_fields(){ // Definition of every settings field, keyed and grouped by 'tab' (general, subscription, messages, default options, VOD defaults, support). return array( 1 => array( 'tab' => 'general_options', 'label' => esc_html__('Slug for free video/channel pages ','wpstream'), 'name' => 'free_media_slug', 'type' => 'text', 'details' => esc_html__('This will replace the default "wpstream" of all your free video/channel urls. Special characters like "&" are not permitted. To have your new slug show up you need to re-save the "Permalinks Settings" under Settings -> Permalinks, even if not making any changes.','wpstream'), ), 'free_vod_slug' => array( 'tab' => 'general_options', 'label' => esc_html__('Slug for free VOD pages ','wpstream'), 'name' => 'free_media_slug_vod', 'type' => 'text', 'details' => esc_html__('This will replace the default "wpstream_vod" of all your free VOD urls. Special characters like "&" are not permitted. To have your new slug show up you need to re-save the "Permalinks Settings" under Settings -> Permalinks, even if not making any changes.','wpstream'), ), 2 => array( 'tab' => 'general_options', 'label' => esc_html__('Non-Admin User Roles Allowed to Broadcast','wpstream'), 'name' => 'stream_role', 'type' => 'user_roles', 'details' => esc_html__('These types of users can stream via frontend shortcodes / blocks. Single individual channels are automaticlally created for streaming by non-admins.','wpstream'), ), 3 => array( 'tab' => 'general_options', 'label' => esc_html__('Non Admin Streamers Channel Type.','wpstream'), 'name' => 'user_streaming_channel_type', 'type' => 'select', 'select_values'=>array( 'free' => esc_html__('Free Live Channel','wpstream'), 'paid' => esc_html__('Pay-Per-View','wpstream') ), 'details' => esc_html__('Choose whether the channels assigned to non-admins are free-for-all or pay-per-view (WooCommerce product).','wpstream'), ), 4 => array( 'tab' => 'general_options', 'label' => esc_html__('Default Pay-Per-View Price','wpstream'), 'name' => 'user_streaming_default_price', 'type' => 'text', 'details' => esc_html__('Default price of pay-per-view channels assigned to non-admins.','wpstream'), ), 6 => array( 'tab' => 'subscription_options', 'label' => esc_html__('Use Global Subscription Mode','wpstream'), 'name' => 'global_sub', 'type' => 'slidertoogle', 'details' => esc_html__('If enabled, a client can access all the media products (live and VOD) by purchasing a single subscription. The "WooCommerce Subscriptions" plugin is required.','wpstream'), ), 7 => array( 'tab' => 'subscription_options', 'label' => esc_html__('Subscription ID for Global Subscription Mode','wpstream'), 'name' => 'global_sub_id', 'type' => 'text', 'details' => esc_html__('ID of the subscription product to be purchased for global access to media. All non-subscription video products that are not already attached to a subscription will be accessible to users that have purchased it.','wpstream'), ), 8 => array( 'tab' => 'messages_options', 'label' => esc_html__('PPV not logged in message','wpstream'), 'name' => 'product_not_login', 'type' => 'text', 'details' => esc_html__('This message will be displayed on top of the media player for pay-per-view items when user is not logged in.','wpstream'), 'default' => esc_html__('You must be logged in to watch this video.','wpstream'), ), 9 => array( 'tab' => 'messages_options', 'label' => esc_html__('PPV not purchased message','wpstream'), 'name' => 'product_not_bought', 'type' => 'text', 'details' => esc_html__('This message will be displayed on top of the media player for common pay-per-view items that have not been purchased.','wpstream'), 'default' => esc_html__('You did not yet purchase this item.','wpstream'), ), 10 => array( 'tab' => 'messages_options', 'label' => esc_html__('Subscription not purchased message','wpstream'), 'name' => 'product_not_subscribe', 'type' => 'text', 'details' => esc_html__('This message will be displayed on top of the media player for subscription-type pay-per-view items that have not been purchased.','wpstream'), 'default' => esc_html__(' You did not yet subscribe to this item.','wpstream'), ), 11 => array( 'tab' => 'messages_options', 'label' => esc_html__('Thank you message','wpstream'), 'name' => 'product_thankyou', 'type' => 'text', 'details' => esc_html__('This message will be displayed on the thank you page (after purchase) and the confirmation email.','wpstream'), 'default' => esc_html__('Thanks for your purchase. You can access your item at any time by visiting the following page: {item_link}','wpstream'), ), 12 => array( 'tab' => 'messages_options', 'label' => esc_html__('Subscription Active message','wpstream'), 'name' => 'subscription_active', 'type' => 'text', 'details' => esc_html__('This message will be displayed on subscription product page.','wpstream'), 'default' => esc_html__('Your Subscription is Active','wpstream'), ), 13 => array( 'tab' => 'messages_options', 'label' => esc_html__('You are not live message','wpstream'), 'name' => 'you_are_not_live', 'type' => 'text', 'details' => esc_html__('This message will be displayed in player.','wpstream'), 'default' => esc_html__('We are not live at this moment','wpstream'), ), 14 => array( 'tab' => 'general_options', 'label' => esc_html__('Video player theme','wpstream'), 'name' => 'video_player_theme', 'type' => 'select', 'select_values'=>array( 'default' => esc_html__('Default','wpstream'), 'city' => esc_html__('City','wpstream'), 'forest' => esc_html__('Forest','wpstream'), 'fantasy' => esc_html__('Fantasy','wpstream'), 'sea' => esc_html__('Sea','wpstream'), ), 'details' => esc_html__('Choose the video player theme to have a different look for the player.','wpstream'), ), 'wpstream_player_logo' => array( 'tab' => 'general_options', 'name' => 'player_logo', 'label' => esc_html__('Logo for the video player','wpstream'), 'type' => 'image', 'details' => esc_html__('This logo will be displayed on the the video player.','wpstream'), 'default' => '', 'image_size' => 'thumbnail', ), // hide the video player logo opacity for now // 'wpstream_player_logo_opacity' => array( // 'tab' => 'general_options', // 'name' => 'player_logo_opacity', // 'label' => esc_html__('Opacity of the video player logo','wpstream'), // 'type' => 'range', // 'details' => esc_html__('Set the opacity of the logo','wpstream'), // 'default' => '', // 'image_size' => 'thumbnail', // ), 'wpsteram_player_logo_position' => array( 'tab' => 'general_options', 'name' => 'player_logo_position', 'label' => esc_html__('Position of the video player logo','wpstream'), 'type' => 'select', 'select_values'=>array( 'top-left' => esc_html__('Top Left','wpstream'), 'top-right' => esc_html__('Top Right','wpstream'), 'bottom-left' => esc_html__('Bottom Left','wpstream'), 'bottom-right' => esc_html__('Bottom Right','wpstream'), ), 'details' => esc_html__('Choose the position of the logo on the video player.','wpstream'), 'default' => '', ), 99 => array( 'tab' => 'default_options', 'label' => esc_html__('Events Options ','wpstream'), 'name' => 'user_streaming_global_channel_options', 'type' => 'user_streaming_global_channel_options', 'details' => esc_html__('Global Options for live events.','wpstream'), ), 100 => array( 'tab' => 'default_options_vod', 'label' => esc_html__('Autoplay','wpstream'), 'name' => 'vod_autoplay', 'type' => 'slidertoogle', 'details' => esc_html__('If enabled, video will attempt to start playing automatically. This is only achievable in some browsers.','wpstream'), ), 101 => array( 'tab' => 'default_options_vod', 'label' => esc_html__('Start Muted','wpstream'), 'name' => 'vod_start_muted', 'type' => 'slidertoogle', 'details' => esc_html__('If enabled, video will start muted. This may increase the rate of autoplay in some browsers.','wpstream'), ), 102 => array( 'tab' => 'default_options_vod', 'label' => esc_html__('Lock To Website','wpstream'), 'name' => 'vod_domain_lock', 'type' => 'slidertoogle', 'details' =>sprintf ( esc_html__('If enabled, video will only display on %1$s, otherwise it can show up on any website.','wpstream'),get_bloginfo('wpurl') ), ), 103 => array( 'tab' => 'default_options_vod', 'label' => esc_html__('Encrypt Video','wpstream'), 'name' => 'vod_encrypt', 'type' => 'slidertoogle', 'details' => esc_html__('If enabled, video data will be encrypted. Enabling encryption may lead to reduced website performance under certain configurations. Encrypted video may not display in all browsers.','wpstream'), ), /* 'vod_domain_lock' =>array( 'name' => esc_html__('Video On Demand - Lock To Website','wpstream'), 'details' => sprintf ( esc_html__('If enabled, VODS will only display on %1$s, otherwise they can show up on any website.','wpstream'),get_bloginfo('wpurl') ), 'defaults' => 'no', ), 'vod_encrypt' =>array( 'name' => esc_html__('Encrypt Video on Demand','wpstream'), 'details' => esc_html__('If enabled, video data will be encrypted. Enabling encryption may lead to reduced website performance under certain configurations. Encrypted video may not display in all browsers.','wpstream'), 'defaults' => 'no', ),*/ 104 => array( 'tab' => 'support_tab', 'label' => esc_html__('Logs','wpstream'), 'name' => 'logs', 'type' => 'logs_table', 'details' => esc_html__('This is the error log of the plugin.','wpstream'), ) ); } /** * Stop the request unless the current user may manage plugin settings. * * Shared by the settings and credentials screens so both enforce the * same capability (`manage_options`). * * @return void */ private function wpstream_require_settings_capability(){ if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not enough permissions to make this change', 'wpstream' ) ); } } /** * Render the WpStream Settings admin screen and persist submitted values. * * On POST it verifies the nonce, then stores only the fields declared for * the active tab as `wpstream_*` options (the streamer role is checked * against the site's roles, the "default options" tab folds its checkboxes * into one map), and flushes rewrite rules. It then renders the tabbed * form from the same field definitions. Echoes HTML. * * @return void */ public function wpstream_settings(){ // Admin-only screen. $this->wpstream_require_settings_capability(); $active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general_options'; /** * Filter the settings field definitions. * * Field definitions drive both the save allow-list and the rendered * form, so an added field (`tab`, `label`, `name`, `type`, `details`) * is rendered on its tab and saved as the `wpstream_{name}` option * through sanitize_text_field(). * * @since 4.14.0 * * @param array $fields Field definitions. * @param string $active_tab Tab being rendered or saved. */ $wpstream_settings_array = (array) apply_filters( 'wpstream_settings_fields', $this->wpstream_settings_fields(), $active_tab ); /** * Filter the settings tabs. * * Added tabs render their fields from `wpstream_settings_fields` * and fire `wpstream_settings_tab_{slug}` for a custom body. * * @since 4.14.0 * * @param array $tabs Tab slug => label. * @param string $active_tab Tab being rendered or saved. */ $tabs = (array) apply_filters( 'wpstream_settings_tabs', array( 'general_options' => esc_html__( 'General Options', 'wpstream' ), 'default_options' => esc_html__( 'Default Channel Settings', 'wpstream' ), 'default_options_vod' => esc_html__( 'VOD Settings', 'wpstream' ), 'subscription_options' => esc_html__( 'Subscription Options', 'wpstream' ), 'messages_options' => esc_html__( 'Customize Messages', 'wpstream' ), 'support_tab' => esc_html__( 'Support', 'wpstream' ), ), $active_tab ); // Handle a settings submission. if($_SERVER['REQUEST_METHOD'] === 'POST'){ // CSRF protection. check_admin_referer( 'wpstream-settings-nonce', 'wpstream-settings-nonce' ); // Only the fields declared for the active tab may be persisted. $allowed = array(); foreach ( $wpstream_settings_array as $field ) { if ( isset( $field['tab'], $field['name'] ) && $field['tab'] === $active_tab ) { $allowed[] = $field['name']; } } // Option values as they were before this submission, and as written by it // (both keyed by option name) — announced together once the save is complete. $previous_options = array(); $saved_options = array(); // If the "channel type" section was shown but no streamer role was chosen, clear the stored role. if( isset($_POST['user_streaming_channel_type_hidden']) && intval($_POST['user_streaming_channel_type_hidden'])==1 && !isset($_POST['stream_role']) ){ $previous_options['wpstream_stream_role'] = get_option( 'wpstream_stream_role' ); $saved_options['wpstream_stream_role'] = ''; update_option( sanitize_key('wpstream_stream_role'), '' ); } // Persist each declared field as a sanitized wpstream_* option; anything else is ignored. foreach ( $allowed as $variable ) { if ( ! isset( $_POST[ $variable ] ) ) { continue; } $value = $_POST[ $variable ]; // Streamer role is a list of role slugs; keep only the roles the form offers // (editable roles minus administrator, who can always broadcast). if ( 'stream_role' === $variable ) { $known = array_diff( array_keys( get_editable_roles() ), array( 'administrator' ) ); $roles = array_values( array_intersect( (array) $value, $known ) ); $previous_options['wpstream_stream_role'] = get_option( 'wpstream_stream_role' ); $saved_options['wpstream_stream_role'] = $roles; update_option( 'wpstream_stream_role', $roles ); continue; } $option_name = sanitize_key( 'wpstream_' . $variable ); $previous_options[ $option_name ] = get_option( $option_name ); $saved_options[ $option_name ] = sanitize_text_field( $value ); update_option( $option_name, $saved_options[ $option_name ] ); } // On the "default options" tab, fold the per-option checkboxes into a single 1/0 map. if ( 'default_options' === $active_tab ) { $event_settings=array(); foreach($this->global_event_options as $key=>$option){ $event_settings[$key]=''; if(isset($_POST['wpstream_event_set_'.$key]) && $_POST['wpstream_event_set_'.$key]=='on'){ $event_settings[$key]=1; }else{ $event_settings[$key]=0; } } $this->main->channel_settings->apply( array( 'type' => 'save_defaults', 'options' => $event_settings, ) ); } // reset permalinkgs // Slug settings can change permalinks, so drop and rebuild the rewrite rules. global $wp_rewrite; update_option( "rewrite_rules", FALSE ); $wp_rewrite->flush_rules( true ); /** * Fires after a WpStream Settings tab was saved and the rewrite * rules rebuilt. Default Channel Settings saved from the * "default options" tab are announced separately by * wpstream_default_channel_settings_saved. * * @since 4.14.0 * * @param string $active_tab Settings tab that was submitted. * @param array $saved Option values written, keyed by option name. * @param array $previous The values those options held before. */ do_action( 'wpstream_settings_saved', $active_tab, $saved_options, $previous_options ); } // Open the settings panel and the form. print '

'.__('WpStream Settings','wpstream').'

'; // Tab navigation bar; the current tab gets the nav-tab-active class. print ''; $help_link=''; // Basic-stream mode disables/annotates some default-channel controls. $is_basic_stream_mode = $this->wpstream_is_basic_streaming_mode(); print '
'; // Pick the contextual "Video Help" docs link for the active tab. switch ($active_tab) { case 'general_options': $help_link='https://docs.wpstream.net/docs/general-settings/'; break; case 'default_options': $help_link='https://docs.wpstream.net/docs/default-channel-settings/'; break; case 'default_options_vod': $help_link='https://docs.wpstream.net/docs/vod-settings/'; break; case 'subscription_options': $help_link='https://docs.wpstream.net/docs/subscription-options/'; break; case 'messages_options': $help_link='https://docs.wpstream.net/docs/customize-messages/'; break; } print '
'; // Render each field that belongs to the active tab. foreach ($wpstream_settings_array as $key=>$option){ // Skip fields from other tabs. if($option['tab']!=$active_tab){ continue; } // Intro blurb (and basic-mode notice) shown above the global channel options control. if ( $option['type']=='user_streaming_global_channel_options' ) { print '
'; print esc_html__( 'These settings will apply to newly created channels; existing channels will not change settings if you change them here', 'wpstream'); if ( $is_basic_stream_mode ) { $this->wpstream_basic_stream_mode_message(); } print '
'; } print '
'; // Load this option's stored value. $options_value = get_option('wpstream_'.$option['name']) ; // Render the appropriate control for this field's type. switch( $option['type'] ) { // Multi-select of WordPress user roles allowed to broadcast. case 'user_roles': print ''; print $this->wpstream_select_user_roles(esc_attr( $option['name'] ),$options_value); print '
'.wp_kses_post( $option['details'] ).'
'; break; // The grid of default per-event streaming options. case 'user_streaming_global_channel_options': $exclude_array=array(); $this->user_streaming_global_channel_options( $option['name'], $options_value, $exclude_array, $is_basic_stream_mode ); break; // Plain text input; falls back to the field's default when unset. case 'text': if($options_value==''){ $options_value=''; if(isset($option['default'])){ $options_value=$option['default']; } } print ''; print ''; print '
'.wp_kses_post( $option['details'] ).'
'; break; // Dropdown built from the field's select_values map; a hidden mirror marks the field as present. case 'select': print ''; print ''; print ''; print '
'.wp_kses_post( $option['details'] ).'
'; break; // On/off switch; a hidden 0-valued twin ensures "off" posts a value. case 'slidertoogle': print ''; print '
'; print '
'.wp_kses_post( $option['details'] ).'
'; print ''; print '
'; break; // Media-library image picker with preview + upload/remove buttons. case 'image': $image_url = $options_value ? esc_url($options_value) : ''; $has_image = !empty($image_url); print ''; print '
'; print ''; // Preview area print '
'; print 'Preview'; print '
'; // Upload/remove buttons print '
'; print ''; print ''; print '
'; print '
'; print '
' . wp_kses_post( $option['details'] ) . '
'; break; // 0-100 range slider. case 'range': print ''; print ''; print '
'.wp_kses_post( $option['details'] ).'
'; break; // Delegates to the Support tab renderer (plugin error log table). case 'logs_table': $this->wpstream_support_tab(); break; } print '
'; } /** * Fires inside the options wrapper of a settings tab, after its fields. * * The dynamic part is the tab slug. Callbacks echo their own markup * and own its escaping; fields they add to the form are saved only * when declared through `wpstream_settings_fields`. * * @since 4.14.0 * * @param string $active_tab Tab being rendered. */ do_action( 'wpstream_settings_tab_' . $active_tab, $active_tab ); print '
'; // options wrapper // Contextual help link for tabs that have one. if($help_link!==''){ print ''; } print '
'; // Save button (hidden on the read-only Support tab). if ( $active_tab != 'support_tab') { print '
'; print ''; print '
'; print '
'; } // CSRF nonce for the submission handled at the top of this method. print ' '; print '
'; print '
'; } /** * Get system information for support tab * * @return array System information */ private function get_system_info() { global $wp_version; // Environment facts: PHP/WP version, debug flag, memory limit and the plugin version. $php_version = phpversion(); $wp_version_info = $wp_version; $site_debug_mode = (defined('WP_DEBUG') && WP_DEBUG); $wp_memory_limit = WP_MEMORY_LIMIT; $wpstream_version = WPSTREAM_PLUGIN_VERSION; $wpstream_plugin_outdated = false; // Check if plugin is outdated // Mark the plugin outdated if WP's update transient lists an available update for it. $update_plugins = get_site_transient('update_plugins'); if (isset($update_plugins->response['plugin/wpstream.php'])) { $wpstream_plugin_outdated = true; } // Check API status // API is "connected" when a WpStream cloud token can be obtained. $api_status = false; if (method_exists($this->main->wpstream_live_connection, 'is_connected')) { $api_status = $this->main->wpstream_live_connection->is_connected(); } // Return the collected diagnostics for the Support tab to render. return array( 'php_version' => $php_version, 'wp_version' => $wp_version_info, 'site_debug_mode' => $site_debug_mode, 'wp_memory_limit' => $wp_memory_limit, 'wpstream_version' => $wpstream_version, 'wpstream_plugin_outdated' => $wpstream_plugin_outdated, 'api_status' => $api_status ); } /** * Render system information HTML * * Prints the Support-tab diagnostics table (PHP/WP versions, debug mode, * memory limit, plugin version + update button, API connection) with a * warning/OK dashicon per row. Echoes HTML. * * @return void */ private function render_system_info() { // Pull the current environment diagnostics. $system_info = $this->get_system_info(); // Below: diagnostics table; each row shows a value and a warning/OK indicator based on recommended thresholds. ?>

render_system_info(); ?>

wpstream_get_plugins_data(); if (empty($plugins_data)) { echo ''; } else { // One row per plugin; append an "update available" tooltip when a newer version exists. foreach ($plugins_data as $plugin) { echo ''; echo ''; echo ''; echo ''; } } ?>
' . esc_html__('No WPStream plugins found.', 'wpstream') . '
' . esc_html($plugin['name']) . ''; echo esc_html($plugin['version']); if ( isset($plugin['new_version']) ) { echo '
'; echo ''; echo ''; echo '
' . sprintf( esc_html__('A new version is available: %s', 'wpstream'), esc_html($plugin['new_version']) ) . '
'; echo '
'; } echo '

'; } else { // One row per log entry (time, type, description). foreach ($logs as $log) { echo ''; echo ''; echo ''; echo ''; echo ''; } } ?>
' . esc_html__('No logs found.', 'wpstream') . '
' . esc_html(date('Y-m-d H:i:s', $log['timestamp'] ) ) . '' . esc_html($log['type']) . '' . esc_html($log['description']) . '
array( 'path' => 'wpstream/wpstream.php', ), 'WooCommerce' => array( 'path' => 'woocommerce/woocommerce.php', ), 'Meta Box' => array( 'path' => 'meta-box/meta-box.php', ), 'One Click Demo Import' => array( 'path' => 'one-click-demo-import/one-click-demo-import.php', ), 'Better Messages' => array( 'path' => 'bp-better-messages/bp-better-messages.php', ), ); // Build an info array for every installed plugin (filtered down afterwards). foreach ($all_plugins as $plugin_path => $plugin_data) { $is_active = is_plugin_active( $plugin_path ); $has_update = isset( $update_data->response[$plugin_path] ); $plugin_info = [ 'name' => $plugin_data['Name'], 'version' => $plugin_data['Version'], 'path' => $plugin_path, 'active' => $is_active ? 'Yes' : 'No', 'needs_update' => $has_update ? 'Yes' : 'No' ]; // Record the offered version when an update is pending. if ($has_update) { $plugin_info['new_version'] = $update_data->response[$plugin_path]->new_version; } $all_plugins_info[] = $plugin_info; } // Filter out the elements from $all_plugins_info that are not in $wpstream_plugins foreach ( $all_plugins_info as $key => $plugin_info ) { // compare $plugin_info['path'] against the path property on each $wpstream_plugins element item // if the path is not in $wpstream_plugins, unset the element if ( !in_array( $plugin_info['path'], array_column( $wpstream_plugins, 'path' ) ) ) { unset( $all_plugins_info[$key] ); } } return $all_plugins_info; } /** * Print a dismissible admin notice when a WpStream plugin update is pending. * * @return void */ public function wpstream_render_outdated_plugin_notice() { // Only render when WP's update transient lists an update for the plugin. $has_update = get_site_transient('update_plugins'); if (isset($has_update->response['plugin/wpstream.php'])) { // Below: warning notice linking to the WP updates page. ?>

' . esc_html__('updates page', 'wpstream') . '' ); ?>

global_event_options as $key=>$option){ // Skip options listed in the exclude array (used by the per-channel modal). if( is_array($local_array) && !in_array($key,$local_array)){ print '
'; print ''; print '
'; print '
'.$option['details'].'
'; print ' '; print '
'; print '
'; } } } /* * Set user roles * * @since 3.0.1 */ /** * Build a multi-select of editable user roles (administrator excluded) * for the "roles allowed to broadcast" setting. * * @param string $name Field name (rendered as name="{name}[]"). * @param array|string $value Currently selected role keys ('' becomes an empty array). * @return string HTML '; unset( $roles['administrator'] ); // One option per role, pre-selecting those already chosen. foreach ($roles as $key=>$role){ $return .= '