| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* The admin-specific functionality of the plugin. |
| 5 |
* |
| 6 |
* @link http://wpstream.net |
| 7 |
* @since 3.0.1 |
| 8 |
* |
| 9 |
* @package Wpstream |
| 10 |
* @subpackage Wpstream/admin |
| 11 |
*/ |
| 12 |
|
| 13 |
|
| 14 |
// Exit if accessed directly. |
| 15 |
if ( ! defined( 'ABSPATH' ) ) { |
| 16 |
exit; |
| 17 |
} |
| 18 |
|
| 19 |
/** |
| 20 |
* The admin-specific functionality of the plugin. |
| 21 |
* |
| 22 |
* This is the admin "god class": it wires up the whole wp-admin surface of the |
| 23 |
* plugin. Responsibilities include registering admin CSS/JS, building the |
| 24 |
* top-level WpStream menu and its sub-pages (Credentials, Channels, Recordings, |
| 25 |
* Settings, Quick Start), rendering channel/settings/onboarding screens and |
| 26 |
* their modal dialogs, defining the per-event global streaming options, hooking |
| 27 |
* into WooCommerce to register the custom stream/VOD product types and their |
| 28 |
* pricing/metabox behaviour, rendering post metaboxes, driving the multipart S3 |
| 29 |
* upload AJAX endpoints, and emitting the various admin notices (plugin update, |
| 30 |
* cache flush, theme, etc.). Many methods echo HTML directly. |
| 31 |
* |
| 32 |
* @package Wpstream |
| 33 |
* @subpackage Wpstream/admin |
| 34 |
* @author wpstream <office@wpstream.net> |
| 35 |
*/ |
| 36 |
class Wpstream_Admin { |
| 37 |
|
| 38 |
/** |
| 39 |
* Most parts a multipart VOD upload may be split into. |
| 40 |
* |
| 41 |
* The storage backend assembles at most this many parts, so a larger |
| 42 |
* split uploads every byte and still can never complete. |
| 43 |
* |
| 44 |
* @var int |
| 45 |
*/ |
| 46 |
const MULTIPART_MAX_PARTS = 1000; |
| 47 |
|
| 48 |
/** |
| 49 |
* Store plugin main class to allow public access. |
| 50 |
* |
| 51 |
* @since 20180622 |
| 52 |
* @var object The main class. |
| 53 |
*/ |
| 54 |
public $main; |
| 55 |
|
| 56 |
|
| 57 |
/** |
| 58 |
* The ID of this plugin. |
| 59 |
* |
| 60 |
* @since 3.0.1 |
| 61 |
* @access private |
| 62 |
* @var string $plugin_name The ID of this plugin. |
| 63 |
*/ |
| 64 |
private $plugin_name; |
| 65 |
|
| 66 |
/** |
| 67 |
* The version of this plugin. |
| 68 |
* |
| 69 |
* @since 3.0.1 |
| 70 |
* @access private |
| 71 |
* @var string $version The current version of this plugin. |
| 72 |
*/ |
| 73 |
private $version; |
| 74 |
|
| 75 |
/** |
| 76 |
* @var array Definitions of the per-event "global" streaming options (record, |
| 77 |
* view count, autoplay, encryption, etc.). Each entry holds a |
| 78 |
* translated label, a help "details" string, and a default value. |
| 79 |
* Populated lazily on `init` by load_global_event_options(). |
| 80 |
*/ |
| 81 |
public $global_event_options ; |
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
|
| 86 |
|
| 87 |
/** |
| 88 |
* Initialize the class and set its properties. |
| 89 |
* |
| 90 |
* @since 3.0.1 |
| 91 |
* @param string $plugin_name The name (ID) of this plugin. |
| 92 |
* @param string $version The version of this plugin. |
| 93 |
* @param object $plugin_main The main plugin class instance, kept for public access. |
| 94 |
* @return void |
| 95 |
*/ |
| 96 |
public function __construct( $plugin_name, $version,$plugin_main ) { |
| 97 |
|
| 98 |
// Remember the plugin identity and the main class for later use. |
| 99 |
$this->plugin_name = $plugin_name; |
| 100 |
$this->version = $version; |
| 101 |
$this->main = $plugin_main; |
| 102 |
|
| 103 |
// Build the translatable global event options once WP (and its i18n) is ready. |
| 104 |
add_action('init', array($this, 'load_global_event_options')); |
| 105 |
} |
| 106 |
|
| 107 |
/** |
| 108 |
* Populate $global_event_options with the translatable per-event settings. |
| 109 |
* |
| 110 |
* Deferred to the `init` hook so the esc_html__() translations resolve |
| 111 |
* against a loaded text domain. |
| 112 |
* |
| 113 |
* @return void |
| 114 |
*/ |
| 115 |
public function load_global_event_options() { |
| 116 |
// Each key maps to a label/details/default triple used when rendering the event option toggles. |
| 117 |
$this->global_event_options = array( |
| 118 |
'record' => array( |
| 119 |
'name' => esc_html__('Record Live Stream','wpstream'), |
| 120 |
'details' => esc_html__('If enabled, live streams will be recorded and saved to your library.','wpstream'), |
| 121 |
'defaults' => 'no', |
| 122 |
), |
| 123 |
'view_count' => array( |
| 124 |
'name' => esc_html__('Display Viewer Count','wpstream'), |
| 125 |
'details' => esc_html__('If enabled, the live viewer count will show up in the player.','wpstream'), |
| 126 |
'defaults' => 'yes', |
| 127 |
), |
| 128 |
'domain_lock' => array( |
| 129 |
'name' => esc_html__('Lock To Website','wpstream'), |
| 130 |
'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') ), |
| 131 |
'defaults' => 'no', |
| 132 |
), |
| 133 |
'autoplay' => array( |
| 134 |
'name' => esc_html__('Autoplay','wpstream'), |
| 135 |
'details' => esc_html__('If enabled, live video will attempt to start playing automatically. This is only achievable in some browsers.','wpstream'), |
| 136 |
'defaults' => 'yes', |
| 137 |
), |
| 138 |
'mute' => array( |
| 139 |
'name' => esc_html__('Start Muted','wpstream'), |
| 140 |
'details' => esc_html__('If enabled, live video will start muted. This may increase the rate of autoplay in some browsers. ','wpstream'), |
| 141 |
'defaults' => 'no', |
| 142 |
), |
| 143 |
'low_latency' => array( |
| 144 |
'name' => esc_html__('Low Latency (beta)','wpstream'), |
| 145 |
'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'), |
| 146 |
'defaults' => 'no', |
| 147 |
), |
| 148 |
'adaptive_bitrate' => array( |
| 149 |
'name' => esc_html__('Adaptive Bitrate (beta)','wpstream'), |
| 150 |
'details' => esc_html__('Ensures a smooth and uninterrupted viewing experience by adjusting video quality for viewers with reduced network speed or device capabilities.','wpstream'), |
| 151 |
'defaults' => 'no', |
| 152 |
), |
| 153 |
'encrypt' =>array( |
| 154 |
'name' => esc_html__('Encrypt Live Stream','wpstream'), |
| 155 |
'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'), |
| 156 |
'defaults' => 'no', |
| 157 |
), |
| 158 |
'ses_encrypt'=>array( |
| 159 |
'name' => esc_html__('Use Sessions with Encryption','wpstream'), |
| 160 |
'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'), |
| 161 |
'defaults' => 'no', |
| 162 |
), |
| 163 |
); |
| 164 |
|
| 165 |
/** |
| 166 |
* Filter the catalog of per-channel options shown as toggles. |
| 167 |
* |
| 168 |
* Each entry is `key => array( name, details, defaults )`. An added key |
| 169 |
* renders a toggle on the settings and channel screens; it is stored |
| 170 |
* only if the Channel Settings Module knows the key. |
| 171 |
* |
| 172 |
* @since 4.14.0 |
| 173 |
* |
| 174 |
* @param array $options Option catalog keyed by setting. |
| 175 |
*/ |
| 176 |
$this->global_event_options = (array) apply_filters( 'wpstream_default_event_options', $this->global_event_options ); |
| 177 |
} |
| 178 |
|
| 179 |
/** |
| 180 |
* Register and enqueue the plugin's admin-area stylesheets. |
| 181 |
* |
| 182 |
* Hooked (via Wpstream_Loader) to admin_enqueue_scripts. |
| 183 |
* |
| 184 |
* @since 3.0.1 |
| 185 |
* @return void |
| 186 |
*/ |
| 187 |
public function enqueue_styles() { |
| 188 |
|
| 189 |
/** |
| 190 |
* This function is provided for demonstration purposes only. |
| 191 |
* |
| 192 |
* An instance of this class should be passed to the run() function |
| 193 |
* defined in Wpstream_Loader as all of the hooks are defined |
| 194 |
* in that particular class. |
| 195 |
* |
| 196 |
* The Wpstream_Loader will then create the relationship |
| 197 |
* between the defined hooks and the functions defined in this |
| 198 |
* class. |
| 199 |
*/ |
| 200 |
// No external webfont: the admin UI falls back to the system font stack |
| 201 |
// declared in the stylesheets, so no request leaves the site for fonts. |
| 202 |
// Main admin stylesheet; filemtime() is used as the cache-busting version. |
| 203 |
wp_enqueue_style( |
| 204 |
$this->plugin_name, |
| 205 |
plugin_dir_url( __FILE__ ) . 'css/wpstream-admin.css', |
| 206 |
array(), |
| 207 |
filemtime(plugin_dir_path(__FILE__) . 'css/wpstream-admin.css' ), |
| 208 |
'all' |
| 209 |
); |
| 210 |
|
| 211 |
// Ensure the WP media library scripts/styles are available (for logo/image pickers) if not already loaded. |
| 212 |
if (!did_action('wp_enqueue_media')) { |
| 213 |
wp_enqueue_media(); |
| 214 |
} |
| 215 |
} |
| 216 |
|
| 217 |
/** |
| 218 |
* Register, enqueue and localize the plugin's admin-area JavaScript. |
| 219 |
* |
| 220 |
* Enqueues jQuery UI helpers, the fileupload lib, the admin control/upload |
| 221 |
* scripts, the start-streaming and settings scripts, and the onboarding |
| 222 |
* bundles. Each script is fed a localized vars array (translated strings, |
| 223 |
* URLs, nonces, feature flags). Some bundles are only loaded on specific |
| 224 |
* admin screens. Hooked (via Wpstream_Loader) to admin_enqueue_scripts. |
| 225 |
* |
| 226 |
* @since 3.0.1 |
| 227 |
* @return void |
| 228 |
*/ |
| 229 |
public function enqueue_scripts() { |
| 230 |
|
| 231 |
// jQuery UI widgets used by the settings/date pickers and sliders. |
| 232 |
wp_enqueue_script("jquery-ui-slider"); |
| 233 |
wp_enqueue_script("jquery-ui-datepicker"); |
| 234 |
// Blueimp-style file upload library backing the recordings/VOD uploader. |
| 235 |
wp_enqueue_script('jquery.fileupload', plugin_dir_url( __FILE__ ) .'js/jquery.fileupload.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); |
| 236 |
|
| 237 |
// Shared admin utility helpers, then the main admin control script that drives uploads and channel actions. |
| 238 |
wp_enqueue_script( 'wpstream-admin-utils', plugin_dir_url( __FILE__ ) .'js/utils/admin_utils.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); |
| 239 |
wp_enqueue_script('wpstream-admin-control', plugin_dir_url( __FILE__ ) .'js/admin_control.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); |
| 240 |
// Hand the control script all of its translated status strings, the admin URL, and the multipart upload nonce. |
| 241 |
wp_localize_script('wpstream-admin-control', 'wpstream_admin_control_vars', |
| 242 |
array( |
| 243 |
'admin_url' => get_admin_url(), |
| 244 |
'multipart_upload_nonce' => wp_create_nonce( 'wpstream_multipart_upload_nonce' ), |
| 245 |
'recordings_nonce' => wp_create_nonce( 'wpstream_recordings_nonce' ), |
| 246 |
'loading_url' => WPSTREAM_PLUGIN_DIR_URL.'/img/loading.gif', |
| 247 |
'download_mess' => esc_html__('Click to download!','wpstream'), |
| 248 |
'uploading' => esc_html__('We are uploading your file. Do not close this window!','wpstream'), |
| 249 |
'upload_complete2' => esc_html__('Upload Complete! You can upload another file!','wpstream'), |
| 250 |
'not_accepted' => esc_html__('The file is not an accepted video format','wpstream'), |
| 251 |
'upload_complete' => esc_html__('Upload Complete!','wpstream'), |
| 252 |
'no_band' => esc_html__('Not enough streaming data.','wpstream'), |
| 253 |
'no_band_no_store' => esc_html__('Not enough streaming data or storage.','wpstream'), |
| 254 |
'no_streaming_hours' => esc_html__('Not enough streaming hours.','wpstream'), |
| 255 |
'exceeding_limit' => esc_html__('File size exceeds 5GB. Initiating multipart upload...','wpstream'), |
| 256 |
'upload_failed' => esc_html__('Upload Failed!','wpstream'), |
| 257 |
'upload_failed2' => esc_html__('Upload Failed! Please Try again!','wpstream'), |
| 258 |
'choose_a_file' => esc_html__('Choose a file…','wpstream'), |
| 259 |
'preparing_multipart' => esc_html__('Preparing multipart upload...','wpstream'), |
| 260 |
'uploading_part' => esc_html__('Uploading part {part} of {total}...','wpstream'), |
| 261 |
'upload_failed_part' => esc_html__('Failed to upload part {part}. Please try again.','wpstream'), |
| 262 |
'completing_upload' => esc_html__('Completing upload. Please wait...','wpstream'), |
| 263 |
'upload_failed_part_retry' => esc_html__('Failed to upload part {part}. Retrying...','wpstream'), |
| 264 |
'choose_recording' => esc_html__( 'Choose Recording', 'wpstream' ), |
| 265 |
'select_recording' => esc_html__( 'Please select a recording from the list', 'wpstream' ), |
| 266 |
'invalid_response' => esc_html__('Invalid response from server. Missing required upload data.', 'wpstream'), |
| 267 |
'video_processing' => esc_html__( 'The video is still processing', 'wpstream' ), |
| 268 |
'file_name_text' => esc_html__('File Name:','wpstream'), |
| 269 |
'channel_create_error' => esc_html__('Something did not work. Please try again.', 'wpstream'), |
| 270 |
'select_caption_file' => esc_html__('Select .vtt Captions File', 'wpstream'), |
| 271 |
'select_button' => esc_html__('Select', 'wpstream'), |
| 272 |
'remove_button' => esc_html__('Remove', 'wpstream'), |
| 273 |
'use_streaming_hours' => $this->main->quota_manager->get_streaming_ui_flags_from_cache()['use_streaming_hours'], |
| 274 |
)); |
| 275 |
|
| 276 |
// Recordings list script + its localized labels and the "create VOD from recording" links (free vs paid depending on WooCommerce). |
| 277 |
wp_enqueue_script('wpstream-recordings-videos-list', plugin_dir_url( __FILE__ ) .'js/recordings_videos_list.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); |
| 278 |
wp_localize_script( 'wpstream-recordings-videos-list', 'wpstream_recordings_videos_list_vars', |
| 279 |
array( |
| 280 |
'delete_file' => esc_html__('Delete file', 'wpstream'), |
| 281 |
'download' => esc_html__( 'Download', 'wpstream'), |
| 282 |
'download_available' => esc_html__( 'Click to download! The url will work for the next 20 minutes!', 'wpstream'), |
| 283 |
'add_free_video_url' => esc_url( admin_url( 'post-new.php?post_type=wpstream_product_vod') . '&new_video_name=' ), |
| 284 |
'create_ftv_vod' => esc_html__( 'Create new Free-To-View VOD from this recording' , 'wpstream' ), |
| 285 |
'woocommerce_exists' => class_exists( 'WooCommerce' ), |
| 286 |
'add_paid_video_url' => esc_url( admin_url( 'post-new.php?post_type=product').'&new_video_name=' ), |
| 287 |
'create_ptv_vod' => esc_html__( 'Create new Pay-Per-View VOD from this recording' , 'wpstream' ), |
| 288 |
) |
| 289 |
); |
| 290 |
|
| 291 |
|
| 292 |
// Settings-page script with its save/logo-picker strings and the broadcaster page URL. |
| 293 |
wp_enqueue_script('wpstream-settings', plugin_dir_url( __DIR__ ) .'/admin/js/wpstream_settings.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true); |
| 294 |
wp_localize_script('wpstream-settings', 'wpstream_settings_vars', array( |
| 295 |
'error_message' => esc_html__( 'Failed to save settings. Please try again.', 'wpstream'), |
| 296 |
'choose_image_text' => esc_html__( 'Choose Logo Image', 'wpstream'), |
| 297 |
'select_image_text' => esc_html__( 'Select Image', 'wpstream'), |
| 298 |
'update_successful' => esc_html__( 'Update Successful.', 'wpstream'), |
| 299 |
'update_failed' => esc_html__( 'Something went wrong. Try again.', 'wpstream'), |
| 300 |
'broadcaster_url' => esc_url( esc_url(home_url('/broadcaster-page/') ) ), |
| 301 |
)); |
| 302 |
|
| 303 |
|
| 304 |
// Identify the current admin screen so screen-specific bundles load only where needed. |
| 305 |
$current_screen=get_current_screen(); |
| 306 |
|
| 307 |
// On the credentials/channels/recordings/onboard screens, load the quota widget updater. |
| 308 |
if ( in_array( $current_screen->base, ['toplevel_page_wpstream_credentials', 'wpstream_page_wpstream_live_channels', 'wpstream_page_wpstream_recordings', 'wpstream_page_wpstream_onboard'] ) ) { |
| 309 |
wp_enqueue_script( 'wpstream-user-quota-update', plugin_dir_url( __DIR__ ) . 'admin/js/wpstream-user-quota.js', array(), WPSTREAM_PLUGIN_VERSION, true ); |
| 310 |
wp_localize_script( 'wpstream-user-quota-update', 'wpstream_user_quota_vars', array( |
| 311 |
'admin_url' => get_admin_url() |
| 312 |
)); |
| 313 |
} |
| 314 |
|
| 315 |
// Add localized variables for broadcaster |
| 316 |
// Provide the broadcaster script (registered elsewhere) with its AJAX URL and nonce. |
| 317 |
wp_localize_script('wpstream-broadcaster', 'wpstream_broadcaster_vars', array( |
| 318 |
'ajax_url' => admin_url('admin-ajax.php'), |
| 319 |
'nonce' => wp_create_nonce('wpstream_broadcaster_nonce'), |
| 320 |
'plugin_url' => plugin_dir_url(__FILE__), |
| 321 |
// Gates the broadcaster's diagnostic console logging (see broadcaster.js). |
| 322 |
'debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG, |
| 323 |
)); |
| 324 |
|
| 325 |
} |
| 326 |
|
| 327 |
|
| 328 |
/** |
| 329 |
* Add Plugin Administation menu |
| 330 |
* |
| 331 |
* Registers the top-level "WpStream" menu and its sub-pages (Credentials, |
| 332 |
* All Channels, Recordings, Settings, Quick Start), each mapped to a render |
| 333 |
* callback on this class and gated to the `administrator` capability. |
| 334 |
* |
| 335 |
* @since 3.0.1 |
| 336 |
* @return void |
| 337 |
*/ |
| 338 |
public function wpstream_manage_admin_menu() { |
| 339 |
|
| 340 |
// Top-level menu; its page defaults to the Credentials screen and uses the WpStream icon at position 20. |
| 341 |
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 ); |
| 342 |
// Credentials sub-page (same slug as the parent so it is the default view). |
| 343 |
add_submenu_page( 'wpstream_credentials', __('WpStream Credentials','wpstream'), __('Credentials','wpstream'), 'administrator', 'wpstream_credentials', array($this,'wpstream_set_wpstream_credentials') ); |
| 344 |
// All Channels listing. |
| 345 |
add_submenu_page( 'wpstream_credentials', __('WpStream Live Channels','wpstream'), __('All Channels','wpstream'), 'administrator', 'wpstream_live_channels', array( $this,'wpstream_new_general_set')); |
| 346 |
// Recordings / media management. |
| 347 |
add_submenu_page( 'wpstream_credentials', __('WpStream Recordings','wpstream'), __('Recordings','wpstream'), 'administrator', 'wpstream_recordings', array($this,'wpstream_media_management')); |
| 348 |
// Global plugin settings. |
| 349 |
add_submenu_page( 'wpstream_credentials', __('WpStream Settings','wpstream'), __('Settings','wpstream'), 'administrator', 'wpstream_settings', array($this,'wpstream_settings')); |
| 350 |
|
| 351 |
// Quick Start / onboarding entry point. |
| 352 |
add_submenu_page( 'wpstream_credentials', __('WpStream Quick Start','wpstream'), __('WpStream Quick Start','wpstream'), 'administrator', 'wpstream_onboard', array($this,'wpstream_pre_onboard_display')); |
| 353 |
|
| 354 |
|
| 355 |
|
| 356 |
|
| 357 |
} |
| 358 |
|
| 359 |
|
| 360 |
|
| 361 |
|
| 362 |
/** |
| 363 |
* Shows events wpstream |
| 364 |
* |
| 365 |
* Renders the "All Channels" admin screen: the Pay-Per-View (WooCommerce |
| 366 |
* `product`) channel list, the Free-To-View (`wpstream_product`) list, the |
| 367 |
* quota/pack summary, and the "no channels" call-to-action. Each channel is |
| 368 |
* drawn via wpstream_live_stream_unit(). Echoes HTML directly. |
| 369 |
* |
| 370 |
* @since 3.0.1 |
| 371 |
* @return void |
| 372 |
*/ |
| 373 |
public function wpstream_new_general_set() { |
| 374 |
return $this->main->get_live_channel_presentation()->render_channels_page(); |
| 375 |
} |
| 376 |
public function wpstream_social_share($the_id){ |
| 377 |
return $this->main->get_live_channel_presentation()->render_part( 'social_share', array( $the_id ) ); |
| 378 |
} |
| 379 |
public function wpstream_live_stream_unit($the_id,$is_front=''){ |
| 380 |
$context = '' === $is_front ? 'admin' : $is_front; |
| 381 |
return $this->main->get_live_channel_presentation()->render_channel( $the_id, $context ); |
| 382 |
} |
| 383 |
public function wpstream_live_stream_unit_for_theme($the_id,$is_front=''){ |
| 384 |
return $this->main->get_live_channel_presentation()->render_part( 'theme_channel', array( $the_id, $is_front ) ); |
| 385 |
} |
| 386 |
public function wpstream_close_modal_button(){ |
| 387 |
return $this->main->get_live_channel_presentation()->render_part( 'close_modal_button' ); |
| 388 |
} |
| 389 |
public function wpstream_local_event_options_toggle( $is_basic_stream_mode = false ) { |
| 390 |
return $this->main->get_live_channel_presentation()->render_part( 'local_event_options_toggle', array( $is_basic_stream_mode ) ); |
| 391 |
} |
| 392 |
public function wpstream_basic_stream_mode_message() { |
| 393 |
return $this->main->get_live_channel_presentation()->render_part( 'basic_stream_mode_message' ); |
| 394 |
} |
| 395 |
public function wpstream_display_modal_seetings($the_id){ |
| 396 |
return $this->main->get_live_channel_presentation()->render_part( 'settings_modal', array( $the_id ) ); |
| 397 |
} |
| 398 |
public function wpstream_display_modal_share($the_id){ |
| 399 |
return $this->main->get_live_channel_presentation()->render_part( 'share_modal', array( $the_id ) ); |
| 400 |
} |
| 401 |
public function wpstream_display_modal_broadcast($the_id,$external_software_streaming_class,$obs_uri,$obs_stream){ |
| 402 |
return $this->main->get_live_channel_presentation()->render_part( |
| 403 |
'broadcast_modal', |
| 404 |
array( $the_id, $external_software_streaming_class, $obs_uri, $obs_stream ) |
| 405 |
); |
| 406 |
} |
| 407 |
public function wpstream_encoder_table(){ |
| 408 |
return $this->main->get_live_channel_presentation()->render_part( 'encoder_table' ); |
| 409 |
} |
| 410 |
public function wpstream_encoder_panel( $encoder, $obs_uri, $obs_stream, $visible = false ){ |
| 411 |
return $this->main->get_live_channel_presentation()->render_part( |
| 412 |
'encoder_panel', |
| 413 |
array( $encoder, $obs_uri, $obs_stream, $visible ) |
| 414 |
); |
| 415 |
} |
| 416 |
private function wpstream_settings_fields(){ |
| 417 |
// Definition of every settings field, keyed and grouped by 'tab' (general, subscription, messages, default options, VOD defaults, support). |
| 418 |
return array( |
| 419 |
1 => array( |
| 420 |
'tab' => 'general_options', |
| 421 |
'label' => esc_html__('Slug for free video/channel pages ','wpstream'), |
| 422 |
'name' => 'free_media_slug', |
| 423 |
'type' => 'text', |
| 424 |
'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'), |
| 425 |
), |
| 426 |
|
| 427 |
'free_vod_slug' => array( |
| 428 |
'tab' => 'general_options', |
| 429 |
'label' => esc_html__('Slug for free VOD pages ','wpstream'), |
| 430 |
'name' => 'free_media_slug_vod', |
| 431 |
'type' => 'text', |
| 432 |
'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'), |
| 433 |
), |
| 434 |
|
| 435 |
2 => array( |
| 436 |
'tab' => 'general_options', |
| 437 |
'label' => esc_html__('Non-Admin User Roles Allowed to Broadcast','wpstream'), |
| 438 |
'name' => 'stream_role', |
| 439 |
'type' => 'user_roles', |
| 440 |
'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'), |
| 441 |
|
| 442 |
), |
| 443 |
3 => array( |
| 444 |
'tab' => 'general_options', |
| 445 |
'label' => esc_html__('Non Admin Streamers Channel Type.','wpstream'), |
| 446 |
'name' => 'user_streaming_channel_type', |
| 447 |
'type' => 'select', |
| 448 |
'select_values'=>array( |
| 449 |
'free' => esc_html__('Free Live Channel','wpstream'), |
| 450 |
'paid' => esc_html__('Pay-Per-View','wpstream') |
| 451 |
), |
| 452 |
'details' => esc_html__('Choose whether the channels assigned to non-admins are free-for-all or pay-per-view (WooCommerce product).','wpstream'), |
| 453 |
), |
| 454 |
|
| 455 |
4 => array( |
| 456 |
'tab' => 'general_options', |
| 457 |
'label' => esc_html__('Default Pay-Per-View Price','wpstream'), |
| 458 |
'name' => 'user_streaming_default_price', |
| 459 |
'type' => 'text', |
| 460 |
'details' => esc_html__('Default price of pay-per-view channels assigned to non-admins.','wpstream'), |
| 461 |
), |
| 462 |
|
| 463 |
|
| 464 |
|
| 465 |
6 => array( |
| 466 |
'tab' => 'subscription_options', |
| 467 |
'label' => esc_html__('Use Global Subscription Mode','wpstream'), |
| 468 |
'name' => 'global_sub', |
| 469 |
'type' => 'slidertoogle', |
| 470 |
'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'), |
| 471 |
), |
| 472 |
|
| 473 |
7 => array( |
| 474 |
'tab' => 'subscription_options', |
| 475 |
'label' => esc_html__('Subscription ID for Global Subscription Mode','wpstream'), |
| 476 |
'name' => 'global_sub_id', |
| 477 |
'type' => 'text', |
| 478 |
'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'), |
| 479 |
), |
| 480 |
8 => array( |
| 481 |
'tab' => 'messages_options', |
| 482 |
'label' => esc_html__('PPV not logged in message','wpstream'), |
| 483 |
'name' => 'product_not_login', |
| 484 |
'type' => 'text', |
| 485 |
'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'), |
| 486 |
'default' => esc_html__('You must be logged in to watch this video.','wpstream'), |
| 487 |
), |
| 488 |
9 => array( |
| 489 |
'tab' => 'messages_options', |
| 490 |
'label' => esc_html__('PPV not purchased message','wpstream'), |
| 491 |
'name' => 'product_not_bought', |
| 492 |
'type' => 'text', |
| 493 |
'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'), |
| 494 |
'default' => esc_html__('You did not yet purchase this item.','wpstream'), |
| 495 |
), |
| 496 |
10 => array( |
| 497 |
'tab' => 'messages_options', |
| 498 |
'label' => esc_html__('Subscription not purchased message','wpstream'), |
| 499 |
'name' => 'product_not_subscribe', |
| 500 |
'type' => 'text', |
| 501 |
'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'), |
| 502 |
'default' => esc_html__(' You did not yet subscribe to this item.','wpstream'), |
| 503 |
), |
| 504 |
11 => array( |
| 505 |
'tab' => 'messages_options', |
| 506 |
'label' => esc_html__('Thank you message','wpstream'), |
| 507 |
'name' => 'product_thankyou', |
| 508 |
'type' => 'text', |
| 509 |
'details' => esc_html__('This message will be displayed on the thank you page (after purchase) and the confirmation email.','wpstream'), |
| 510 |
'default' => esc_html__('Thanks for your purchase. You can access your item at any time by visiting the following page: {item_link}','wpstream'), |
| 511 |
), |
| 512 |
|
| 513 |
12 => array( |
| 514 |
'tab' => 'messages_options', |
| 515 |
'label' => esc_html__('Subscription Active message','wpstream'), |
| 516 |
'name' => 'subscription_active', |
| 517 |
'type' => 'text', |
| 518 |
'details' => esc_html__('This message will be displayed on subscription product page.','wpstream'), |
| 519 |
'default' => esc_html__('Your Subscription is Active','wpstream'), |
| 520 |
), |
| 521 |
13 => array( |
| 522 |
'tab' => 'messages_options', |
| 523 |
'label' => esc_html__('You are not live message','wpstream'), |
| 524 |
'name' => 'you_are_not_live', |
| 525 |
'type' => 'text', |
| 526 |
'details' => esc_html__('This message will be displayed in player.','wpstream'), |
| 527 |
'default' => esc_html__('We are not live at this moment','wpstream'), |
| 528 |
), |
| 529 |
|
| 530 |
14 => array( |
| 531 |
'tab' => 'general_options', |
| 532 |
'label' => esc_html__('Video player theme','wpstream'), |
| 533 |
'name' => 'video_player_theme', |
| 534 |
'type' => 'select', |
| 535 |
'select_values'=>array( |
| 536 |
'default' => esc_html__('Default','wpstream'), |
| 537 |
'city' => esc_html__('City','wpstream'), |
| 538 |
'forest' => esc_html__('Forest','wpstream'), |
| 539 |
'fantasy' => esc_html__('Fantasy','wpstream'), |
| 540 |
'sea' => esc_html__('Sea','wpstream'), |
| 541 |
), |
| 542 |
'details' => esc_html__('Choose the video player theme to have a different look for the player.','wpstream'), |
| 543 |
), |
| 544 |
'wpstream_player_logo' => array( |
| 545 |
'tab' => 'general_options', |
| 546 |
'name' => 'player_logo', |
| 547 |
'label' => esc_html__('Logo for the video player','wpstream'), |
| 548 |
'type' => 'image', |
| 549 |
'details' => esc_html__('This logo will be displayed on the the video player.','wpstream'), |
| 550 |
'default' => '', |
| 551 |
'image_size' => 'thumbnail', |
| 552 |
), |
| 553 |
// hide the video player logo opacity for now |
| 554 |
// 'wpstream_player_logo_opacity' => array( |
| 555 |
// 'tab' => 'general_options', |
| 556 |
// 'name' => 'player_logo_opacity', |
| 557 |
// 'label' => esc_html__('Opacity of the video player logo','wpstream'), |
| 558 |
// 'type' => 'range', |
| 559 |
// 'details' => esc_html__('Set the opacity of the logo','wpstream'), |
| 560 |
// 'default' => '', |
| 561 |
// 'image_size' => 'thumbnail', |
| 562 |
// ), |
| 563 |
'wpsteram_player_logo_position' => array( |
| 564 |
'tab' => 'general_options', |
| 565 |
'name' => 'player_logo_position', |
| 566 |
'label' => esc_html__('Position of the video player logo','wpstream'), |
| 567 |
'type' => 'select', |
| 568 |
'select_values'=>array( |
| 569 |
'top-left' => esc_html__('Top Left','wpstream'), |
| 570 |
'top-right' => esc_html__('Top Right','wpstream'), |
| 571 |
'bottom-left' => esc_html__('Bottom Left','wpstream'), |
| 572 |
'bottom-right' => esc_html__('Bottom Right','wpstream'), |
| 573 |
), |
| 574 |
'details' => esc_html__('Choose the position of the logo on the video player.','wpstream'), |
| 575 |
'default' => '', |
| 576 |
), |
| 577 |
|
| 578 |
|
| 579 |
|
| 580 |
99 => array( |
| 581 |
'tab' => 'default_options', |
| 582 |
'label' => esc_html__('Events Options ','wpstream'), |
| 583 |
'name' => 'user_streaming_global_channel_options', |
| 584 |
'type' => 'user_streaming_global_channel_options', |
| 585 |
'details' => esc_html__('Global Options for live events.','wpstream'), |
| 586 |
), |
| 587 |
|
| 588 |
100 => array( |
| 589 |
'tab' => 'default_options_vod', |
| 590 |
'label' => esc_html__('Autoplay','wpstream'), |
| 591 |
'name' => 'vod_autoplay', |
| 592 |
'type' => 'slidertoogle', |
| 593 |
'details' => esc_html__('If enabled, video will attempt to start playing automatically. This is only achievable in some browsers.','wpstream'), |
| 594 |
), |
| 595 |
|
| 596 |
|
| 597 |
101 => array( |
| 598 |
'tab' => 'default_options_vod', |
| 599 |
'label' => esc_html__('Start Muted','wpstream'), |
| 600 |
'name' => 'vod_start_muted', |
| 601 |
'type' => 'slidertoogle', |
| 602 |
'details' => esc_html__('If enabled, video will start muted. This may increase the rate of autoplay in some browsers.','wpstream'), |
| 603 |
), |
| 604 |
|
| 605 |
102 => array( |
| 606 |
'tab' => 'default_options_vod', |
| 607 |
'label' => esc_html__('Lock To Website','wpstream'), |
| 608 |
'name' => 'vod_domain_lock', |
| 609 |
'type' => 'slidertoogle', |
| 610 |
'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') ), |
| 611 |
), |
| 612 |
|
| 613 |
103 => array( |
| 614 |
'tab' => 'default_options_vod', |
| 615 |
'label' => esc_html__('Encrypt Video','wpstream'), |
| 616 |
'name' => 'vod_encrypt', |
| 617 |
'type' => 'slidertoogle', |
| 618 |
'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'), |
| 619 |
), |
| 620 |
|
| 621 |
/* 'vod_domain_lock' =>array( |
| 622 |
'name' => esc_html__('Video On Demand - Lock To Website','wpstream'), |
| 623 |
'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') ), |
| 624 |
'defaults' => 'no', |
| 625 |
), |
| 626 |
'vod_encrypt' =>array( |
| 627 |
'name' => esc_html__('Encrypt Video on Demand','wpstream'), |
| 628 |
'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'), |
| 629 |
'defaults' => 'no', |
| 630 |
),*/ |
| 631 |
|
| 632 |
104 => array( |
| 633 |
'tab' => 'support_tab', |
| 634 |
'label' => esc_html__('Logs','wpstream'), |
| 635 |
'name' => 'logs', |
| 636 |
'type' => 'logs_table', |
| 637 |
'details' => esc_html__('This is the error log of the plugin.','wpstream'), |
| 638 |
) |
| 639 |
|
| 640 |
); |
| 641 |
} |
| 642 |
|
| 643 |
/** |
| 644 |
* Stop the request unless the current user may manage plugin settings. |
| 645 |
* |
| 646 |
* Shared by the settings and credentials screens so both enforce the |
| 647 |
* same capability (`manage_options`). |
| 648 |
* |
| 649 |
* @return void |
| 650 |
*/ |
| 651 |
private function wpstream_require_settings_capability(){ |
| 652 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 653 |
wp_die( esc_html__( 'Not enough permissions to make this change', 'wpstream' ) ); |
| 654 |
} |
| 655 |
} |
| 656 |
|
| 657 |
/** |
| 658 |
* Render the WpStream Settings admin screen and persist submitted values. |
| 659 |
* |
| 660 |
* On POST it verifies the nonce, then stores only the fields declared for |
| 661 |
* the active tab as `wpstream_*` options (the streamer role is checked |
| 662 |
* against the site's roles, the "default options" tab folds its checkboxes |
| 663 |
* into one map), and flushes rewrite rules. It then renders the tabbed |
| 664 |
* form from the same field definitions. Echoes HTML. |
| 665 |
* |
| 666 |
* @return void |
| 667 |
*/ |
| 668 |
public function wpstream_settings(){ |
| 669 |
|
| 670 |
// Admin-only screen. |
| 671 |
$this->wpstream_require_settings_capability(); |
| 672 |
|
| 673 |
|
| 674 |
$active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general_options'; |
| 675 |
|
| 676 |
/** |
| 677 |
* Filter the settings field definitions. |
| 678 |
* |
| 679 |
* Field definitions drive both the save allow-list and the rendered |
| 680 |
* form, so an added field (`tab`, `label`, `name`, `type`, `details`) |
| 681 |
* is rendered on its tab and saved as the `wpstream_{name}` option |
| 682 |
* through sanitize_text_field(). |
| 683 |
* |
| 684 |
* @since 4.14.0 |
| 685 |
* |
| 686 |
* @param array $fields Field definitions. |
| 687 |
* @param string $active_tab Tab being rendered or saved. |
| 688 |
*/ |
| 689 |
$wpstream_settings_array = (array) apply_filters( 'wpstream_settings_fields', $this->wpstream_settings_fields(), $active_tab ); |
| 690 |
|
| 691 |
/** |
| 692 |
* Filter the settings tabs. |
| 693 |
* |
| 694 |
* Added tabs render their fields from `wpstream_settings_fields` |
| 695 |
* and fire `wpstream_settings_tab_{slug}` for a custom body. |
| 696 |
* |
| 697 |
* @since 4.14.0 |
| 698 |
* |
| 699 |
* @param array $tabs Tab slug => label. |
| 700 |
* @param string $active_tab Tab being rendered or saved. |
| 701 |
*/ |
| 702 |
$tabs = (array) apply_filters( |
| 703 |
'wpstream_settings_tabs', |
| 704 |
array( |
| 705 |
'general_options' => esc_html__( 'General Options', 'wpstream' ), |
| 706 |
'default_options' => esc_html__( 'Default Channel Settings', 'wpstream' ), |
| 707 |
'default_options_vod' => esc_html__( 'VOD Settings', 'wpstream' ), |
| 708 |
'subscription_options' => esc_html__( 'Subscription Options', 'wpstream' ), |
| 709 |
'messages_options' => esc_html__( 'Customize Messages', 'wpstream' ), |
| 710 |
'support_tab' => esc_html__( 'Support', 'wpstream' ), |
| 711 |
), |
| 712 |
$active_tab |
| 713 |
); |
| 714 |
|
| 715 |
// Handle a settings submission. |
| 716 |
if($_SERVER['REQUEST_METHOD'] === 'POST'){ |
| 717 |
// CSRF protection. |
| 718 |
check_admin_referer( 'wpstream-settings-nonce', 'wpstream-settings-nonce' ); |
| 719 |
|
| 720 |
// Only the fields declared for the active tab may be persisted. |
| 721 |
$allowed = array(); |
| 722 |
foreach ( $wpstream_settings_array as $field ) { |
| 723 |
if ( isset( $field['tab'], $field['name'] ) && $field['tab'] === $active_tab ) { |
| 724 |
$allowed[] = $field['name']; |
| 725 |
} |
| 726 |
} |
| 727 |
|
| 728 |
// Option values as they were before this submission, and as written by it |
| 729 |
// (both keyed by option name) — announced together once the save is complete. |
| 730 |
$previous_options = array(); |
| 731 |
$saved_options = array(); |
| 732 |
|
| 733 |
// If the "channel type" section was shown but no streamer role was chosen, clear the stored role. |
| 734 |
if( isset($_POST['user_streaming_channel_type_hidden']) && intval($_POST['user_streaming_channel_type_hidden'])==1 && !isset($_POST['stream_role']) ){ |
| 735 |
$previous_options['wpstream_stream_role'] = get_option( 'wpstream_stream_role' ); |
| 736 |
$saved_options['wpstream_stream_role'] = ''; |
| 737 |
update_option( sanitize_key('wpstream_stream_role'), '' ); |
| 738 |
} |
| 739 |
|
| 740 |
// Persist each declared field as a sanitized wpstream_* option; anything else is ignored. |
| 741 |
foreach ( $allowed as $variable ) { |
| 742 |
if ( ! isset( $_POST[ $variable ] ) ) { |
| 743 |
continue; |
| 744 |
} |
| 745 |
$value = $_POST[ $variable ]; |
| 746 |
|
| 747 |
// Streamer role is a list of role slugs; keep only the roles the form offers |
| 748 |
// (editable roles minus administrator, who can always broadcast). |
| 749 |
if ( 'stream_role' === $variable ) { |
| 750 |
$known = array_diff( array_keys( get_editable_roles() ), array( 'administrator' ) ); |
| 751 |
$roles = array_values( array_intersect( (array) $value, $known ) ); |
| 752 |
$previous_options['wpstream_stream_role'] = get_option( 'wpstream_stream_role' ); |
| 753 |
$saved_options['wpstream_stream_role'] = $roles; |
| 754 |
update_option( 'wpstream_stream_role', $roles ); |
| 755 |
continue; |
| 756 |
} |
| 757 |
|
| 758 |
$option_name = sanitize_key( 'wpstream_' . $variable ); |
| 759 |
$previous_options[ $option_name ] = get_option( $option_name ); |
| 760 |
$saved_options[ $option_name ] = sanitize_text_field( $value ); |
| 761 |
update_option( $option_name, $saved_options[ $option_name ] ); |
| 762 |
} |
| 763 |
|
| 764 |
|
| 765 |
// On the "default options" tab, fold the per-option checkboxes into a single 1/0 map. |
| 766 |
if ( 'default_options' === $active_tab ) { |
| 767 |
$event_settings=array(); |
| 768 |
foreach($this->global_event_options as $key=>$option){ |
| 769 |
$event_settings[$key]=''; |
| 770 |
if(isset($_POST['wpstream_event_set_'.$key]) && $_POST['wpstream_event_set_'.$key]=='on'){ |
| 771 |
$event_settings[$key]=1; |
| 772 |
}else{ |
| 773 |
$event_settings[$key]=0; |
| 774 |
} |
| 775 |
} |
| 776 |
$this->main->channel_settings->apply( |
| 777 |
array( |
| 778 |
'type' => 'save_defaults', |
| 779 |
'options' => $event_settings, |
| 780 |
) |
| 781 |
); |
| 782 |
} |
| 783 |
|
| 784 |
|
| 785 |
|
| 786 |
|
| 787 |
// reset permalinkgs |
| 788 |
// Slug settings can change permalinks, so drop and rebuild the rewrite rules. |
| 789 |
global $wp_rewrite; |
| 790 |
|
| 791 |
update_option( "rewrite_rules", FALSE ); |
| 792 |
$wp_rewrite->flush_rules( true ); |
| 793 |
|
| 794 |
/** |
| 795 |
* Fires after a WpStream Settings tab was saved and the rewrite |
| 796 |
* rules rebuilt. Default Channel Settings saved from the |
| 797 |
* "default options" tab are announced separately by |
| 798 |
* wpstream_default_channel_settings_saved. |
| 799 |
* |
| 800 |
* @since 4.14.0 |
| 801 |
* |
| 802 |
* @param string $active_tab Settings tab that was submitted. |
| 803 |
* @param array $saved Option values written, keyed by option name. |
| 804 |
* @param array $previous The values those options held before. |
| 805 |
*/ |
| 806 |
do_action( 'wpstream_settings_saved', $active_tab, $saved_options, $previous_options ); |
| 807 |
|
| 808 |
} |
| 809 |
|
| 810 |
|
| 811 |
|
| 812 |
|
| 813 |
// Open the settings panel and the form. |
| 814 |
print '<div class="theme_options_tab_wpstream" style="display:block;" > |
| 815 |
<h1>'.__('WpStream Settings','wpstream').'</h1> |
| 816 |
<form method="post" action="" >'; |
| 817 |
|
| 818 |
// Tab navigation bar; the current tab gets the nav-tab-active class. |
| 819 |
print '<h2 class="nav-tab-wrapper">'; |
| 820 |
foreach ( $tabs as $tab_slug => $tab_label ) { |
| 821 |
$tab_slug = sanitize_key( $tab_slug ); |
| 822 |
print '<a href="' . esc_url( add_query_arg( array( 'page' => 'wpstream_settings', 'tab' => $tab_slug ), admin_url( 'admin.php' ) ) ) . '" class="nav-tab ' . ( $active_tab === $tab_slug ? 'nav-tab-active' : '' ) . '">' . esc_html( $tab_label ) . '</a>'; |
| 823 |
} |
| 824 |
print '</h2>'; |
| 825 |
$help_link=''; |
| 826 |
|
| 827 |
// Basic-stream mode disables/annotates some default-channel controls. |
| 828 |
$is_basic_stream_mode = $this->wpstream_is_basic_streaming_mode(); |
| 829 |
print '<div class="wpstream_option_wrapper">'; |
| 830 |
|
| 831 |
// Pick the contextual "Video Help" docs link for the active tab. |
| 832 |
switch ($active_tab) { |
| 833 |
case 'general_options': |
| 834 |
$help_link='https://docs.wpstream.net/docs/general-settings/'; |
| 835 |
break; |
| 836 |
case 'default_options': |
| 837 |
$help_link='https://docs.wpstream.net/docs/default-channel-settings/'; |
| 838 |
break; |
| 839 |
case 'default_options_vod': |
| 840 |
$help_link='https://docs.wpstream.net/docs/vod-settings/'; |
| 841 |
break; |
| 842 |
case 'subscription_options': |
| 843 |
$help_link='https://docs.wpstream.net/docs/subscription-options/'; |
| 844 |
break; |
| 845 |
case 'messages_options': |
| 846 |
$help_link='https://docs.wpstream.net/docs/customize-messages/'; |
| 847 |
break; |
| 848 |
} |
| 849 |
|
| 850 |
print '<div class="options_wrapper">'; |
| 851 |
// Render each field that belongs to the active tab. |
| 852 |
foreach ($wpstream_settings_array as $key=>$option){ |
| 853 |
// Skip fields from other tabs. |
| 854 |
if($option['tab']!=$active_tab){ |
| 855 |
continue; |
| 856 |
} |
| 857 |
|
| 858 |
// Intro blurb (and basic-mode notice) shown above the global channel options control. |
| 859 |
if ( $option['type']=='user_streaming_global_channel_options' ) { |
| 860 |
print '<div class="default-channel-settings-info">'; |
| 861 |
print esc_html__( 'These settings will apply to newly created channels; existing channels will not change settings if you change them here', 'wpstream'); |
| 862 |
if ( $is_basic_stream_mode ) { |
| 863 |
$this->wpstream_basic_stream_mode_message(); |
| 864 |
} |
| 865 |
print '</div>'; |
| 866 |
|
| 867 |
} |
| 868 |
print '<div class="wpstream_option">'; |
| 869 |
// Load this option's stored value. |
| 870 |
$options_value = get_option('wpstream_'.$option['name']) ; |
| 871 |
|
| 872 |
|
| 873 |
// Render the appropriate control for this field's type. |
| 874 |
switch( $option['type'] ) { |
| 875 |
// Multi-select of WordPress user roles allowed to broadcast. |
| 876 |
case 'user_roles': |
| 877 |
print '<label for="'.esc_attr( $option['name'] ).'">'.wp_kses_post( $option['label'] ).'</label>'; |
| 878 |
print $this->wpstream_select_user_roles(esc_attr( $option['name'] ),$options_value); |
| 879 |
print '<div class="settings_details">'.wp_kses_post( $option['details'] ).'</div>'; |
| 880 |
break; |
| 881 |
// The grid of default per-event streaming options. |
| 882 |
case 'user_streaming_global_channel_options': |
| 883 |
$exclude_array=array(); |
| 884 |
$this->user_streaming_global_channel_options( |
| 885 |
$option['name'], |
| 886 |
$options_value, |
| 887 |
$exclude_array, |
| 888 |
$is_basic_stream_mode |
| 889 |
); |
| 890 |
break; |
| 891 |
// Plain text input; falls back to the field's default when unset. |
| 892 |
case 'text': |
| 893 |
if($options_value==''){ |
| 894 |
$options_value=''; |
| 895 |
if(isset($option['default'])){ |
| 896 |
$options_value=$option['default']; |
| 897 |
} |
| 898 |
} |
| 899 |
|
| 900 |
print '<label for="'.esc_attr( $option['name'] ).'">'.wp_kses_post( $option['label'] ).'</label>'; |
| 901 |
print '<input class="wpstream-text-input-setting" id="'.esc_attr( $option['name'] ).'" type="'.$option['type'].'" size="36" name="'.esc_attr( $option['name'] ).'" value="'.esc_attr($options_value).'" />'; |
| 902 |
print '<div class="settings_details">'.wp_kses_post( $option['details'] ).'</div>'; |
| 903 |
break; |
| 904 |
// Dropdown built from the field's select_values map; a hidden mirror marks the field as present. |
| 905 |
case 'select': |
| 906 |
print '<label for="'.esc_attr( $option['name'] ).'">'.wp_kses_post( $option['label'] ).'</label>'; |
| 907 |
print '<select id="'.esc_attr( $option['name'] ).'" name="'.esc_attr( $option['name'] ).'" >'; |
| 908 |
foreach($option['select_values'] as $key=>$value){ |
| 909 |
print '<option value="'.$key.'" '; |
| 910 |
// Pre-select the currently stored value. |
| 911 |
if( $key == esc_html($options_value) ){ |
| 912 |
print ' selected '; |
| 913 |
} |
| 914 |
print '>'.$value.'</option>'; |
| 915 |
} |
| 916 |
print '</select>'; |
| 917 |
print '<input type="hidden" name="'.esc_attr( $option['name'] ).'_hidden" value="1" >'; |
| 918 |
print '<div class="settings_details">'.wp_kses_post( $option['details'] ).'</div>'; |
| 919 |
break; |
| 920 |
// On/off switch; a hidden 0-valued twin ensures "off" posts a value. |
| 921 |
case 'slidertoogle': |
| 922 |
print '<label for="'.esc_attr( $option['name'] ).'">'.wp_kses_post( $option['label'] ).'</label>'; |
| 923 |
print '<div style="display: flex; gap: 25px; justify-content: space-between;">'; |
| 924 |
print '<div class="settings_details">'.wp_kses_post( $option['details'] ).'</div>'; |
| 925 |
print '<label class="wpstream_switch"> |
| 926 |
<input type="hidden" class="wpstream_event_option_itemc" value="0" name="'.esc_attr( $option['name'] ).'" > |
| 927 |
<input type="checkbox" class="wpstream_event_option_itemc" value="1" name="'.esc_attr( $option['name'] ).'" '; |
| 928 |
if( intval($options_value) !==0 ){ |
| 929 |
print ' checked '; |
| 930 |
} |
| 931 |
print '> <span class="wpstream_slider round"></span>'; |
| 932 |
print '</label>'; |
| 933 |
print '</div>'; |
| 934 |
break; |
| 935 |
// Media-library image picker with preview + upload/remove buttons. |
| 936 |
case 'image': |
| 937 |
$image_url = $options_value ? esc_url($options_value) : ''; |
| 938 |
$has_image = !empty($image_url); |
| 939 |
|
| 940 |
print '<label for="' . esc_attr( $option['name'] ) . '">' . wp_kses_post( $option['label'] ) . '</label>'; |
| 941 |
print '<div class="wpstream-image-upload-wrapper">'; |
| 942 |
print '<input type="hidden" id="' . esc_attr( $option['name'] ) . '" name="' . esc_attr( $option['name'] ) . '" value="' . $image_url . '" />'; |
| 943 |
|
| 944 |
// Preview area |
| 945 |
print '<div class="wpstream-image-preview" style="' . (!$has_image ? 'display:none;' : '') . '">'; |
| 946 |
print '<img src="' . $image_url . '" alt="Preview" />'; |
| 947 |
print '</div>'; |
| 948 |
|
| 949 |
// Upload/remove buttons |
| 950 |
print '<div class="wpstream-image-upload-buttons">'; |
| 951 |
print '<button type="button" class="wpstream-upload-image button">' . esc_html__('Upload Image', 'wpstream') . '</button>'; |
| 952 |
print '<button type="button" class="wpstream-remove-image button" style="' . (!$has_image ? 'display:none;' : '') . '">' . esc_html__('Remove Image', 'wpstream') . '</button>'; |
| 953 |
print '</div>'; |
| 954 |
|
| 955 |
print '</div>'; |
| 956 |
print '<div class="settings_details">' . wp_kses_post( $option['details'] ) . '</div>'; |
| 957 |
break; |
| 958 |
// 0-100 range slider. |
| 959 |
case 'range': |
| 960 |
print '<label for="'.esc_attr( $option['name'] ).'">'.wp_kses_post( $option['label'] ).'</label>'; |
| 961 |
print '<input class="wpstream-range-input" type="range" id="'.esc_attr( $option['name'] ).'" name="'.esc_attr( $option['name'] ).'" min="0" max="100" step="10" value="'.esc_attr($options_value).'" />'; |
| 962 |
print '<div class="settings_details">'.wp_kses_post( $option['details'] ).'</div>'; |
| 963 |
break; |
| 964 |
// Delegates to the Support tab renderer (plugin error log table). |
| 965 |
case 'logs_table': |
| 966 |
$this->wpstream_support_tab(); |
| 967 |
break; |
| 968 |
} |
| 969 |
print '</div>'; |
| 970 |
} |
| 971 |
|
| 972 |
/** |
| 973 |
* Fires inside the options wrapper of a settings tab, after its fields. |
| 974 |
* |
| 975 |
* The dynamic part is the tab slug. Callbacks echo their own markup |
| 976 |
* and own its escaping; fields they add to the form are saved only |
| 977 |
* when declared through `wpstream_settings_fields`. |
| 978 |
* |
| 979 |
* @since 4.14.0 |
| 980 |
* |
| 981 |
* @param string $active_tab Tab being rendered. |
| 982 |
*/ |
| 983 |
do_action( 'wpstream_settings_tab_' . $active_tab, $active_tab ); |
| 984 |
print '</div>'; // options wrapper |
| 985 |
// Contextual help link for tabs that have one. |
| 986 |
if($help_link!==''){ |
| 987 |
print '<div class="wpstream_options_help"><a href="'.esc_url($help_link).'" target="_blank" >'.esc_html__('Video Help','wpstream').'</a></div>'; |
| 988 |
} |
| 989 |
print '</div>'; |
| 990 |
|
| 991 |
|
| 992 |
// Save button (hidden on the read-only Support tab). |
| 993 |
if ( $active_tab != 'support_tab') { |
| 994 |
print '<div class="wpstream-save-settings">'; |
| 995 |
print '<input type="submit" name="submit" class="wpstream_button wpstream_button_action" value="'.__('Save Changes','wpstream').'" />'; |
| 996 |
print '<div class="spinner"></div>'; |
| 997 |
print '</div>'; |
| 998 |
} |
| 999 |
|
| 1000 |
// CSRF nonce for the submission handled at the top of this method. |
| 1001 |
print '<input id="wpstream-settings-nonce" name="wpstream-settings-nonce" type="hidden" value="'.wp_create_nonce('wpstream-settings-nonce').'" /> '; |
| 1002 |
print '</form>'; |
| 1003 |
print '</div>'; |
| 1004 |
|
| 1005 |
} |
| 1006 |
|
| 1007 |
/** |
| 1008 |
* Get system information for support tab |
| 1009 |
* |
| 1010 |
* @return array System information |
| 1011 |
*/ |
| 1012 |
private function get_system_info() { |
| 1013 |
global $wp_version; |
| 1014 |
|
| 1015 |
// Environment facts: PHP/WP version, debug flag, memory limit and the plugin version. |
| 1016 |
$php_version = phpversion(); |
| 1017 |
$wp_version_info = $wp_version; |
| 1018 |
$site_debug_mode = (defined('WP_DEBUG') && WP_DEBUG); |
| 1019 |
$wp_memory_limit = WP_MEMORY_LIMIT; |
| 1020 |
|
| 1021 |
$wpstream_version = WPSTREAM_PLUGIN_VERSION; |
| 1022 |
$wpstream_plugin_outdated = false; |
| 1023 |
|
| 1024 |
// Check if plugin is outdated |
| 1025 |
// Mark the plugin outdated if WP's update transient lists an available update for it. |
| 1026 |
$update_plugins = get_site_transient('update_plugins'); |
| 1027 |
if (isset($update_plugins->response['plugin/wpstream.php'])) { |
| 1028 |
$wpstream_plugin_outdated = true; |
| 1029 |
} |
| 1030 |
|
| 1031 |
// Check API status |
| 1032 |
// API is "connected" when a WpStream cloud token can be obtained. |
| 1033 |
$api_status = false; |
| 1034 |
if (method_exists($this->main->wpstream_live_connection, 'is_connected')) { |
| 1035 |
$api_status = $this->main->wpstream_live_connection->is_connected(); |
| 1036 |
} |
| 1037 |
|
| 1038 |
// Return the collected diagnostics for the Support tab to render. |
| 1039 |
return array( |
| 1040 |
'php_version' => $php_version, |
| 1041 |
'wp_version' => $wp_version_info, |
| 1042 |
'site_debug_mode' => $site_debug_mode, |
| 1043 |
'wp_memory_limit' => $wp_memory_limit, |
| 1044 |
'wpstream_version' => $wpstream_version, |
| 1045 |
'wpstream_plugin_outdated' => $wpstream_plugin_outdated, |
| 1046 |
'api_status' => $api_status |
| 1047 |
); |
| 1048 |
} |
| 1049 |
|
| 1050 |
/** |
| 1051 |
* Render system information HTML |
| 1052 |
* |
| 1053 |
* Prints the Support-tab diagnostics table (PHP/WP versions, debug mode, |
| 1054 |
* memory limit, plugin version + update button, API connection) with a |
| 1055 |
* warning/OK dashicon per row. Echoes HTML. |
| 1056 |
* |
| 1057 |
* @return void |
| 1058 |
*/ |
| 1059 |
private function render_system_info() { |
| 1060 |
// Pull the current environment diagnostics. |
| 1061 |
$system_info = $this->get_system_info(); |
| 1062 |
// Below: diagnostics table; each row shows a value and a warning/OK indicator based on recommended thresholds. |
| 1063 |
?> |
| 1064 |
|
| 1065 |
<div class="wpstream-system-info"> |
| 1066 |
<h3><?php esc_html_e('System Information', 'wpstream'); ?></h3> |
| 1067 |
<table class="widefat"> |
| 1068 |
<tbody> |
| 1069 |
<tr> |
| 1070 |
<td><strong><?php esc_html_e('PHP Version', 'wpstream'); ?></strong></td> |
| 1071 |
<td><?php echo esc_html($system_info['php_version']); ?></td> |
| 1072 |
<td> |
| 1073 |
<?php if (version_compare($system_info['php_version'], '7.4', '<')): ?> |
| 1074 |
<span class="dashicons dashicons-warning" style="color: #ffb900;"></span> |
| 1075 |
<?php esc_html_e('We recommend PHP 7.4 or higher', 'wpstream'); ?> |
| 1076 |
<?php else: ?> |
| 1077 |
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> |
| 1078 |
<?php endif; ?> |
| 1079 |
</td> |
| 1080 |
</tr> |
| 1081 |
<tr> |
| 1082 |
<td><strong><?php esc_html_e('WordPress Version', 'wpstream'); ?></strong></td> |
| 1083 |
<td><?php echo esc_html($system_info['wp_version']); ?></td> |
| 1084 |
<td> |
| 1085 |
<?php if (version_compare($system_info['wp_version'], '5.6', '<')): ?> |
| 1086 |
<span class="dashicons dashicons-warning" style="color: #ffb900;"></span> |
| 1087 |
<?php esc_html_e('We recommend WordPress 5.6 or higher', 'wpstream'); ?> |
| 1088 |
<?php else: ?> |
| 1089 |
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> |
| 1090 |
<?php endif; ?> |
| 1091 |
</td> |
| 1092 |
</tr> |
| 1093 |
<tr> |
| 1094 |
<td><strong><?php esc_html_e('WP Debug Mode', 'wpstream'); ?></strong></td> |
| 1095 |
<td><?php echo $system_info['site_debug_mode'] ? esc_html__('Enabled', 'wpstream') : esc_html__('Disabled', 'wpstream'); ?></td> |
| 1096 |
<td> |
| 1097 |
<?php if ($system_info['site_debug_mode']): ?> |
| 1098 |
<span class="dashicons dashicons-info" style="color: #00a0d2;"></span> |
| 1099 |
<?php esc_html_e('Debug mode should be disabled on production sites', 'wpstream'); ?> |
| 1100 |
<?php else: ?> |
| 1101 |
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> |
| 1102 |
<?php endif; ?> |
| 1103 |
</td> |
| 1104 |
</tr> |
| 1105 |
<tr> |
| 1106 |
<td><strong><?php esc_html_e('WP Memory Limit', 'wpstream'); ?></strong></td> |
| 1107 |
<td><?php echo esc_html($system_info['wp_memory_limit']); ?></td> |
| 1108 |
<td> |
| 1109 |
<?php |
| 1110 |
$memory_limit = wp_convert_hr_to_bytes($system_info['wp_memory_limit']); |
| 1111 |
if ($memory_limit < 64 * 1024 * 1024): // 64MB |
| 1112 |
?> |
| 1113 |
<span class="dashicons dashicons-warning" style="color: #ffb900;"></span> |
| 1114 |
<?php esc_html_e('We recommend at least 64MB', 'wpstream'); ?> |
| 1115 |
<?php else: ?> |
| 1116 |
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> |
| 1117 |
<?php endif; ?> |
| 1118 |
</td> |
| 1119 |
</tr> |
| 1120 |
<tr> |
| 1121 |
<td><strong><?php esc_html_e('WpStream Version', 'wpstream'); ?></strong></td> |
| 1122 |
<td><?php echo esc_html($system_info['wpstream_version']); ?></td> |
| 1123 |
<td style="display: flex; align-items: center; gap: 5px;"> |
| 1124 |
<?php if ($system_info['wpstream_plugin_outdated']): ?> |
| 1125 |
<span class="dashicons dashicons-warning" style="color: #ffb900;"></span> |
| 1126 |
<?php esc_html_e('Update available', 'wpstream'); ?> |
| 1127 |
<div class="update-button-wrapper"> |
| 1128 |
<button class="wpstream-update-plugin-button button button-primary" data-plugin="wpstream/wpstream.php"> |
| 1129 |
<?php esc_html_e('Update Now', 'wpstream'); ?> |
| 1130 |
</button> |
| 1131 |
</div> |
| 1132 |
<?php else: ?> |
| 1133 |
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> |
| 1134 |
<?php endif; ?> |
| 1135 |
</td> |
| 1136 |
</tr> |
| 1137 |
<tr> |
| 1138 |
<td><strong><?php esc_html_e('API Connection', 'wpstream'); ?></strong></td> |
| 1139 |
<td><?php echo $system_info['api_status'] ? esc_html__('Connected', 'wpstream') : esc_html__('Disconnected', 'wpstream'); ?></td> |
| 1140 |
<td> |
| 1141 |
<?php if (!$system_info['api_status']): ?> |
| 1142 |
<span class="dashicons dashicons-warning" style="color: #ffb900;"></span> |
| 1143 |
<?php esc_html_e('API connection issue', 'wpstream'); ?> |
| 1144 |
<?php else: ?> |
| 1145 |
<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span> |
| 1146 |
<?php endif; ?> |
| 1147 |
</td> |
| 1148 |
</tr> |
| 1149 |
</tbody> |
| 1150 |
</table> |
| 1151 |
</div> |
| 1152 |
<?php |
| 1153 |
} |
| 1154 |
|
| 1155 |
/** |
| 1156 |
* Render support tab content |
| 1157 |
* |
| 1158 |
* Prints the Support tab: the system-info table, a table of relevant active |
| 1159 |
* plugins with update tooltips, and a table of the plugin's recent logs. |
| 1160 |
* Echoes HTML. |
| 1161 |
* |
| 1162 |
* @return void |
| 1163 |
*/ |
| 1164 |
public function wpstream_support_tab() { |
| 1165 |
// Below: Support tab markup (system info, active plugins table, recent logs table). |
| 1166 |
?> |
| 1167 |
<div class="wrap"> |
| 1168 |
<div class="wpstream-support-tab-root"> |
| 1169 |
<?php $this->render_system_info(); ?> |
| 1170 |
|
| 1171 |
<div class="wpstream-plugins-table-container"> |
| 1172 |
<h3><?php esc_html_e('Active Plugins', 'wpstream'); ?></h3> |
| 1173 |
<table class="widefat wpstream-plugins-table"> |
| 1174 |
<thead> |
| 1175 |
<tr> |
| 1176 |
<th><?php esc_html_e('Plugin', 'wpstream'); ?></th> |
| 1177 |
<th><?php esc_html_e('Version', 'wpstream'); ?></th> |
| 1178 |
</tr> |
| 1179 |
</thead> |
| 1180 |
<tbody> |
| 1181 |
<?php |
| 1182 |
// Build the relevant-plugins list; show a placeholder row when none are found. |
| 1183 |
$plugins_data = $this->wpstream_get_plugins_data(); |
| 1184 |
if (empty($plugins_data)) { |
| 1185 |
echo '<tr><td colspan="3">' . esc_html__('No WPStream plugins found.', 'wpstream') . '</td></tr>'; |
| 1186 |
} else { |
| 1187 |
// One row per plugin; append an "update available" tooltip when a newer version exists. |
| 1188 |
foreach ($plugins_data as $plugin) { |
| 1189 |
echo '<tr>'; |
| 1190 |
echo '<td>' . esc_html($plugin['name']) . '</td>'; |
| 1191 |
echo '<td>'; |
| 1192 |
echo esc_html($plugin['version']); |
| 1193 |
if ( isset($plugin['new_version']) ) { |
| 1194 |
echo '<div class="wpstream-tooltip-container">'; |
| 1195 |
echo '<span class="dashicons dashicons-info wpstream-tooltip" title="' . esc_attr($plugin['new_version']) . '">'; |
| 1196 |
echo '</span>'; |
| 1197 |
echo '<div class="wpstream-custom-tooltip">' . sprintf( |
| 1198 |
esc_html__('A new version is available: %s', 'wpstream'), |
| 1199 |
esc_html($plugin['new_version']) |
| 1200 |
) . '</div>'; |
| 1201 |
echo '</div>'; |
| 1202 |
} |
| 1203 |
echo '</td>'; |
| 1204 |
echo '</tr>'; |
| 1205 |
} |
| 1206 |
} |
| 1207 |
?> |
| 1208 |
</tbody> |
| 1209 |
</table> |
| 1210 |
</div> |
| 1211 |
|
| 1212 |
<div class="wpstream-logs-table-container"> |
| 1213 |
<h3><?php esc_html_e('Recent Logs', 'wpstream'); ?></h3> |
| 1214 |
<table class="widefat wpstream-logs-table"> |
| 1215 |
<thead> |
| 1216 |
<tr> |
| 1217 |
<th><?php esc_html_e('Time', 'wpstream'); ?></th> |
| 1218 |
<th><?php esc_html_e('Type', 'wpstream'); ?></th> |
| 1219 |
<th><?php esc_html_e('Description', 'wpstream'); ?></th> |
| 1220 |
</tr> |
| 1221 |
</thead> |
| 1222 |
<tbody> |
| 1223 |
<?php |
| 1224 |
// Load the stored plugin logs; show a placeholder row when empty. |
| 1225 |
$logs = get_option('wpstream_logs'); |
| 1226 |
if ( !is_array($logs) || empty($logs) ) { |
| 1227 |
echo '<tr><td colspan="3">' . esc_html__('No logs found.', 'wpstream') . '</td></tr>'; |
| 1228 |
} else { |
| 1229 |
// One row per log entry (time, type, description). |
| 1230 |
foreach ($logs as $log) { |
| 1231 |
echo '<tr>'; |
| 1232 |
echo '<td>' . esc_html(date('Y-m-d H:i:s', $log['timestamp'] ) ) . '</td>'; |
| 1233 |
echo '<td>' . esc_html($log['type']) . '</td>'; |
| 1234 |
echo '<td>' . esc_html($log['description']) . '</td>'; |
| 1235 |
echo '</tr>'; |
| 1236 |
} |
| 1237 |
} |
| 1238 |
?> |
| 1239 |
</tbody> |
| 1240 |
</table> |
| 1241 |
</div> |
| 1242 |
</div> |
| 1243 |
</div> |
| 1244 |
<?php |
| 1245 |
} |
| 1246 |
|
| 1247 |
/** |
| 1248 |
* Get plugins data. |
| 1249 |
* |
| 1250 |
* Collects name/version/active/update info for a fixed allow-list of plugins |
| 1251 |
* relevant to WpStream (WpStream, WooCommerce, Meta Box, One Click Demo |
| 1252 |
* Import, Better Messages), used by the Support tab. |
| 1253 |
* |
| 1254 |
* @return array List of plugin info arrays (name, version, path, active, needs_update, [new_version]). |
| 1255 |
*/ |
| 1256 |
public function wpstream_get_plugins_data() { |
| 1257 |
// Ensure get_plugins() is available in non-admin contexts. |
| 1258 |
if (!function_exists('get_plugins')) { |
| 1259 |
require_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 1260 |
} |
| 1261 |
|
| 1262 |
// Get all installed plugins |
| 1263 |
$all_plugins = get_plugins(); |
| 1264 |
$update_data = get_site_transient('update_plugins'); |
| 1265 |
$all_plugins_info = []; |
| 1266 |
|
| 1267 |
// We want to get data only for the WpStream plugins |
| 1268 |
$wpstream_plugins = array( |
| 1269 |
'WpStream' => array( |
| 1270 |
'path' => 'wpstream/wpstream.php', |
| 1271 |
), |
| 1272 |
'WooCommerce' => array( |
| 1273 |
'path' => 'woocommerce/woocommerce.php', |
| 1274 |
), |
| 1275 |
'Meta Box' => array( |
| 1276 |
'path' => 'meta-box/meta-box.php', |
| 1277 |
), |
| 1278 |
'One Click Demo Import' => array( |
| 1279 |
'path' => 'one-click-demo-import/one-click-demo-import.php', |
| 1280 |
), |
| 1281 |
'Better Messages' => array( |
| 1282 |
'path' => 'bp-better-messages/bp-better-messages.php', |
| 1283 |
), |
| 1284 |
|
| 1285 |
); |
| 1286 |
|
| 1287 |
// Build an info array for every installed plugin (filtered down afterwards). |
| 1288 |
foreach ($all_plugins as $plugin_path => $plugin_data) { |
| 1289 |
$is_active = is_plugin_active( $plugin_path ); |
| 1290 |
$has_update = isset( $update_data->response[$plugin_path] ); |
| 1291 |
|
| 1292 |
$plugin_info = [ |
| 1293 |
'name' => $plugin_data['Name'], |
| 1294 |
'version' => $plugin_data['Version'], |
| 1295 |
'path' => $plugin_path, |
| 1296 |
'active' => $is_active ? 'Yes' : 'No', |
| 1297 |
'needs_update' => $has_update ? 'Yes' : 'No' |
| 1298 |
]; |
| 1299 |
|
| 1300 |
// Record the offered version when an update is pending. |
| 1301 |
if ($has_update) { |
| 1302 |
$plugin_info['new_version'] = $update_data->response[$plugin_path]->new_version; |
| 1303 |
} |
| 1304 |
|
| 1305 |
$all_plugins_info[] = $plugin_info; |
| 1306 |
} |
| 1307 |
|
| 1308 |
// Filter out the elements from $all_plugins_info that are not in $wpstream_plugins |
| 1309 |
foreach ( $all_plugins_info as $key => $plugin_info ) { |
| 1310 |
// compare $plugin_info['path'] against the path property on each $wpstream_plugins element item |
| 1311 |
// if the path is not in $wpstream_plugins, unset the element |
| 1312 |
if ( !in_array( $plugin_info['path'], array_column( $wpstream_plugins, 'path' ) ) ) { |
| 1313 |
unset( $all_plugins_info[$key] ); |
| 1314 |
} |
| 1315 |
} |
| 1316 |
|
| 1317 |
return $all_plugins_info; |
| 1318 |
} |
| 1319 |
|
| 1320 |
/** |
| 1321 |
* Print a dismissible admin notice when a WpStream plugin update is pending. |
| 1322 |
* |
| 1323 |
* @return void |
| 1324 |
*/ |
| 1325 |
public function wpstream_render_outdated_plugin_notice() { |
| 1326 |
// Only render when WP's update transient lists an update for the plugin. |
| 1327 |
$has_update = get_site_transient('update_plugins'); |
| 1328 |
if (isset($has_update->response['plugin/wpstream.php'])) { |
| 1329 |
// Below: warning notice linking to the WP updates page. |
| 1330 |
?> |
| 1331 |
<div class="notice notice-warning is-dismissible"> |
| 1332 |
<p> |
| 1333 |
<?php |
| 1334 |
printf( |
| 1335 |
/* translators: 1: Link to update page */ |
| 1336 |
esc_html__('A new version of WpStream is available. Please update to the latest version for the best experience. Go to the %1$s to update now.', 'wpstream'), |
| 1337 |
'<a href="' . esc_url(admin_url('update-core.php')) . '">' . esc_html__('updates page', 'wpstream') . '</a>' |
| 1338 |
); |
| 1339 |
?> |
| 1340 |
</p> |
| 1341 |
</div> |
| 1342 |
<?php |
| 1343 |
} |
| 1344 |
} |
| 1345 |
|
| 1346 |
|
| 1347 |
/** |
| 1348 |
* Set user roles |
| 1349 |
* |
| 1350 |
* Render the grid of per-event streaming option switches (from |
| 1351 |
* $global_event_options). Reused for both the site-wide default settings |
| 1352 |
* and the per-channel settings modal. Echoes HTML. |
| 1353 |
* |
| 1354 |
* @param string $name Field name prefix/context (unused in body; kept for callers). |
| 1355 |
* @param array|string $value Stored 1/0 map of option states (empty falls back to defaults). |
| 1356 |
* @param array|string $local_array Option keys to EXCLUDE from this render (per-channel context). |
| 1357 |
* @param bool $disabled True to render the switches disabled. |
| 1358 |
* @param bool $is_basic_stream_mode True to disable switches for basic-streaming accounts. |
| 1359 |
* @return void |
| 1360 |
*/ |
| 1361 |
public function user_streaming_global_channel_options( |
| 1362 |
$name, |
| 1363 |
$value, |
| 1364 |
$local_array='', |
| 1365 |
$disabled = false, |
| 1366 |
$is_basic_stream_mode = false |
| 1367 |
) { |
| 1368 |
|
| 1369 |
// Render one switch per defined global event option. |
| 1370 |
foreach($this->global_event_options as $key=>$option){ |
| 1371 |
|
| 1372 |
// Skip options listed in the exclude array (used by the per-channel modal). |
| 1373 |
if( is_array($local_array) && !in_array($key,$local_array)){ |
| 1374 |
print '<div class="wpstream_setting_event_unit_wrapper wpstream-setting-'.esc_attr($key).' ">'; |
| 1375 |
|
| 1376 |
print '<label for="'.$option['name'].'">'.$option['name'].'</label>'; |
| 1377 |
|
| 1378 |
print '<div style="display: flex; gap: 25px; justify-content: space-between;">'; |
| 1379 |
print '<div class="settings_details">'.$option['details'].'</div>'; |
| 1380 |
print ' |
| 1381 |
<label class="wpstream_switch"> |
| 1382 |
<input type="checkbox" class="wpstream_event_option_item" data-attr-ajaxname="'.esc_attr($key).'" name="wpstream_event_set_'.esc_attr($key).'" '; |
| 1383 |
// Check state: only a stored value of 1 renders the switch on. |
| 1384 |
// An absent key resolves to off everywhere the settings are |
| 1385 |
// consumed, so the screen must not promise otherwise. |
| 1386 |
if( isset($value[$key]) && intval($value[$key]) !== 0 ){ |
| 1387 |
print ' checked '; |
| 1388 |
} |
| 1389 |
// Disable the switch when requested or in basic-streaming mode. |
| 1390 |
if ( $disabled || $is_basic_stream_mode ) { |
| 1391 |
print ' disabled '; |
| 1392 |
} |
| 1393 |
|
| 1394 |
|
| 1395 |
print '> <span class="wpstream_slider round"></span>'; |
| 1396 |
print '</label>'; |
| 1397 |
print '</div>'; |
| 1398 |
|
| 1399 |
|
| 1400 |
print '</div>'; |
| 1401 |
} |
| 1402 |
} |
| 1403 |
|
| 1404 |
|
| 1405 |
} |
| 1406 |
|
| 1407 |
|
| 1408 |
|
| 1409 |
|
| 1410 |
|
| 1411 |
|
| 1412 |
|
| 1413 |
|
| 1414 |
|
| 1415 |
/* |
| 1416 |
* Set user roles |
| 1417 |
* |
| 1418 |
* @since 3.0.1 |
| 1419 |
*/ |
| 1420 |
/** |
| 1421 |
* Build a multi-select of editable user roles (administrator excluded) |
| 1422 |
* for the "roles allowed to broadcast" setting. |
| 1423 |
* |
| 1424 |
* @param string $name Field name (rendered as name="{name}[]"). |
| 1425 |
* @param array|string $value Currently selected role keys ('' becomes an empty array). |
| 1426 |
* @return string HTML <select> markup. |
| 1427 |
*/ |
| 1428 |
public function wpstream_select_user_roles($name,$value){ |
| 1429 |
// Normalize an empty stored value to an array for in_array() below. |
| 1430 |
if($value==''){ |
| 1431 |
$value=array(); |
| 1432 |
} |
| 1433 |
|
| 1434 |
// All editable roles minus administrator (admins always can broadcast). |
| 1435 |
$roles = get_editable_roles(); |
| 1436 |
$return = '<select id="wpstream_user_roles" name="'.esc_html($name).'[]" multiple>'; |
| 1437 |
unset( $roles['administrator'] ); |
| 1438 |
|
| 1439 |
// One option per role, pre-selecting those already chosen. |
| 1440 |
foreach ($roles as $key=>$role){ |
| 1441 |
$return .= '<option value="'.$key.'" '; |
| 1442 |
if( in_array($key, $value) ){ |
| 1443 |
$return .= ' selected '; |
| 1444 |
} |
| 1445 |
$return .= '>'.$role['name'].'</option>'; |
| 1446 |
} |
| 1447 |
$return .= '</select>'; |
| 1448 |
|
| 1449 |
|
| 1450 |
return $return; |
| 1451 |
} |
| 1452 |
|
| 1453 |
|
| 1454 |
/* |
| 1455 |
* Set credential admin function |
| 1456 |
* |
| 1457 |
* @since 3.0.1 |
| 1458 |
*/ |
| 1459 |
/** |
| 1460 |
* Render the Credentials admin screen and save submitted API credentials. |
| 1461 |
* |
| 1462 |
* On POST it stores the WpStream username/password options and clears the |
| 1463 |
* cached token/quota transients so the next request re-authenticates. It |
| 1464 |
* then shows the connection status and the credentials form. Echoes HTML. |
| 1465 |
* |
| 1466 |
* @return void |
| 1467 |
*/ |
| 1468 |
public function wpstream_set_wpstream_credentials(){ |
| 1469 |
$credentials_from_constants = wpstream_api_credentials_from_constants(); |
| 1470 |
|
| 1471 |
// Handle a credentials submission. |
| 1472 |
if($_SERVER['REQUEST_METHOD'] === 'POST'){ |
| 1473 |
// CSRF/permission gate: only an admin submitting the credentials |
| 1474 |
// form (with its nonce) may overwrite the stored API credentials. |
| 1475 |
$this->wpstream_require_settings_capability(); |
| 1476 |
check_admin_referer( 'wpstream_save_credentials', 'wpstream_credentials_nonce' ); |
| 1477 |
|
| 1478 |
// wp-config owns the complete pair. A forged/manual POST must be the |
| 1479 |
// same no-op as the disabled form presented to the administrator. |
| 1480 |
if ( ! $credentials_from_constants ) { |
| 1481 |
|
| 1482 |
$allowed_html = array(); |
| 1483 |
$exclude_array = array(); |
| 1484 |
$allowed_html = array(); |
| 1485 |
|
| 1486 |
// Username before this submission: tells a cleared form apart from a new account. |
| 1487 |
$previous_username = (string) get_option( 'wpstream_api_username', '' ); |
| 1488 |
|
| 1489 |
// Persist only the recognised credential fields. |
| 1490 |
foreach($_POST as $variable=>$value){ |
| 1491 |
if ($variable!='submit'){ |
| 1492 |
if (!in_array($variable, $exclude_array) ){ |
| 1493 |
switch ( $variable ) { |
| 1494 |
case 'api_username': |
| 1495 |
// Unslashed first: WordPress slashes every $_POST value, |
| 1496 |
// so a username containing a quote would otherwise be |
| 1497 |
// stored with a stray backslash and never authenticate. |
| 1498 |
// Then sanitized as text. |
| 1499 |
update_option( sanitize_key('wpstream_api_username'), sanitize_text_field( wp_unslash( $value ) ) ); |
| 1500 |
break; |
| 1501 |
case 'api_password': |
| 1502 |
// Only unslashed, never sanitized: the password is a secret |
| 1503 |
// sent verbatim to the API, and sanitizing would silently |
| 1504 |
// change it. Unslashing undoes the escaping WordPress adds |
| 1505 |
// to every $_POST value, without which a password holding a |
| 1506 |
// quote or a backslash is stored — and sent — wrong, so the |
| 1507 |
// customer is told their correct password is incorrect. |
| 1508 |
// Not autoloaded: it is only needed when authenticating. |
| 1509 |
// An empty submission keeps the current password, since the |
| 1510 |
// form never pre-fills the field with the stored secret. |
| 1511 |
$password = wp_unslash( $value ); |
| 1512 |
if ( $password !== '' ) { |
| 1513 |
update_option( 'wpstream_api_password', $password, false ); |
| 1514 |
} elseif ( |
| 1515 |
array_key_exists( 'api_username', $_POST ) |
| 1516 |
&& '' === sanitize_text_field( wp_unslash( $_POST['api_username'] ) ) |
| 1517 |
) { |
| 1518 |
// Clearing the account is an explicit disconnect, not a |
| 1519 |
// password-only edit. Do not retain an orphaned secret. |
| 1520 |
delete_option( 'wpstream_api_password' ); |
| 1521 |
} |
| 1522 |
break; |
| 1523 |
} |
| 1524 |
} |
| 1525 |
} |
| 1526 |
} |
| 1527 |
|
| 1528 |
|
| 1529 |
|
| 1530 |
// Invalidate any cached token/quota so the new credentials are used immediately. |
| 1531 |
update_option('wpstream_token_expire',0); |
| 1532 |
update_option('wpstream_current_token',' '); |
| 1533 |
delete_transient( 'wpstream_token_api'); |
| 1534 |
delete_transient('wpstream_token_request_30'); |
| 1535 |
$this->main->quota_manager->invalidate(); |
| 1536 |
|
| 1537 |
// Announce the account change. Only the username travels: the |
| 1538 |
// password and any token stay server-side. |
| 1539 |
$username = (string) get_option( 'wpstream_api_username', '' ); |
| 1540 |
if ( '' !== $username ) { |
| 1541 |
/** |
| 1542 |
* Fires after WpStream account credentials were saved and the |
| 1543 |
* cached token/quota dropped; the connection itself is verified |
| 1544 |
* on the next API call. |
| 1545 |
* |
| 1546 |
* @since 4.14.0 |
| 1547 |
* |
| 1548 |
* @param string $username WpStream.net username or e-mail. |
| 1549 |
*/ |
| 1550 |
do_action( 'wpstream_account_connected', $username ); |
| 1551 |
} elseif ( '' !== $previous_username ) { |
| 1552 |
/** |
| 1553 |
* Fires after the WpStream account username was cleared. |
| 1554 |
* |
| 1555 |
* @since 4.14.0 |
| 1556 |
* |
| 1557 |
* @param string $username The username that was removed. |
| 1558 |
*/ |
| 1559 |
do_action( 'wpstream_account_disconnected', $previous_username ); |
| 1560 |
} |
| 1561 |
} |
| 1562 |
} |
| 1563 |
|
| 1564 |
|
| 1565 |
$allowed_html = array(); |
| 1566 |
|
| 1567 |
|
| 1568 |
// Field definitions for the credentials form (username + password). |
| 1569 |
$wpstream_options_array =array( |
| 1570 |
2 => array( |
| 1571 |
'label' => 'WpStream.net Username or Email', |
| 1572 |
'name' => 'api_username', |
| 1573 |
'type' => 'text', |
| 1574 |
), |
| 1575 |
3 => array( |
| 1576 |
'label' => 'WpStream.net Password', |
| 1577 |
'name' => 'api_password', |
| 1578 |
'type' => 'password', |
| 1579 |
), |
| 1580 |
|
| 1581 |
); |
| 1582 |
|
| 1583 |
|
| 1584 |
// Existing installs saved the password as an autoloaded option; flip it |
| 1585 |
// to non-autoloaded so the secret is not loaded on every request. |
| 1586 |
if ( function_exists( 'wp_set_option_autoload' ) ) { |
| 1587 |
wp_set_option_autoload( 'wpstream_api_password', false ); |
| 1588 |
} |
| 1589 |
|
| 1590 |
// Connectivity and quota data drive the connection-status banner below. |
| 1591 |
$connected = $this->main->wpstream_live_connection->is_connected(); |
| 1592 |
$pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_set_wpstream_credentials' ); |
| 1593 |
|
| 1594 |
$this->main->show_user_data($pack_details); |
| 1595 |
|
| 1596 |
print '<form method="post" action="" >'; |
| 1597 |
// CSRF nonce for the credentials save handled at the top of this method. |
| 1598 |
wp_nonce_field( 'wpstream_save_credentials', 'wpstream_credentials_nonce' ); |
| 1599 |
print '<div class="theme_options_tab_wpstream" style="display:block;" > |
| 1600 |
<h1>'.__('WpStream Credentials','wpstream').'</h1>'; |
| 1601 |
|
| 1602 |
// Credentials supplied via wp-config.php constants take |
| 1603 |
// precedence over anything saved here — tell the admin. |
| 1604 |
if( $credentials_from_constants ){ |
| 1605 |
echo '<div class="api_conected">'.esc_html__('Your WpStream credentials are defined in wp-config.php; values saved on this screen are ignored.','wpstream').'</div>'; |
| 1606 |
} |
| 1607 |
|
| 1608 |
// Connection status banner: no credentials, bad credentials, connected, or CURL failure. |
| 1609 |
if( wpstream_get_api_username()=='' || wpstream_get_api_password()== '' ){ |
| 1610 |
echo '<div class="api_not_conected wpstream_orange">'; |
| 1611 |
$admin_url_onboard=get_admin_url().'admin.php?page=wpstream_onboard'; |
| 1612 |
printf ( __('To connect your plugin, enter your WpStream credentials below or go <a href="%s" target="_blank">here</a> to create an account.','wpstream'),$admin_url_onboard); |
| 1613 |
echo '</div>'; |
| 1614 |
|
| 1615 |
}else if(!$connected){ |
| 1616 |
// Translate the literals at definition — gettext cannot extract variable strings. |
| 1617 |
$text = get_option('wpstream_curl_failed') === "0" ? |
| 1618 |
__( ' Incorrect username or password. Please check your credentials or go <a href="https://wpstream.net/my-account/edit-account/" target="_blank">here</a> to reset your password.', 'wpstream' ) : |
| 1619 |
__( 'Not connected to WpStream. Please note the errors above and contact support.', 'wpstream' ); |
| 1620 |
echo '<div class="api_not_conected">'.wp_kses_post($text).'</div>'; |
| 1621 |
}else{ |
| 1622 |
// Credentials present and a token was obtained: connected. |
| 1623 |
echo '<div class="api_conected">'.__('Connected to WpStream.net!','wpstream').'</div>'; |
| 1624 |
} |
| 1625 |
// Render the credential inputs. The username is pre-filled; |
| 1626 |
// the password field is always left blank so the stored |
| 1627 |
// secret is never echoed into the page source (an empty |
| 1628 |
// submission keeps the saved password). Inputs are disabled |
| 1629 |
// when the wp-config constants are in charge. |
| 1630 |
$inputs_disabled = $credentials_from_constants ? ' disabled' : ''; |
| 1631 |
print '<div class="wpstream_option_wrapper">'; |
| 1632 |
foreach ($wpstream_options_array as $key=>$option){ |
| 1633 |
print '<div class="wpstream_option">'; |
| 1634 |
|
| 1635 |
print '<label for="'.$option['name'].'">'.$option['label'].'</label>'; |
| 1636 |
if( $option['type']==='password' ){ |
| 1637 |
$placeholder = get_option('wpstream_'.$option['name'],'') !== '' ? '*****' : ''; |
| 1638 |
print '<input id="'.$option['name'].'" type="password" size="36" name="'.$option['name'].'" value="" autocomplete="new-password" placeholder="'.$placeholder.'"'.$inputs_disabled.' />'; |
| 1639 |
}else{ |
| 1640 |
$options_value = esc_html( get_option('wpstream_'.$option['name'],'') ); |
| 1641 |
print '<input id="'.$option['name'].'" type="'.$option['type'].'" size="36" name="'.$option['name'].'" value="'.esc_html($options_value).'"'.$inputs_disabled.' />'; |
| 1642 |
} |
| 1643 |
|
| 1644 |
print '</div>'; |
| 1645 |
} |
| 1646 |
print '</div>'; |
| 1647 |
|
| 1648 |
|
| 1649 |
print '<input type="submit"'.$inputs_disabled.' name="submit" class="wpstream_button wpstream_button_action" value="'.__('Save Changes','wpstream').'" />'; |
| 1650 |
|
| 1651 |
print '<h3>Video Tutorials</h3>'; |
| 1652 |
|
| 1653 |
print '<a class="how_to_videos" target="_blank" href="https://youtu.be/9DQrxsKcpmQ">How to Live Stream to WordPress with OBS</a>'; |
| 1654 |
print '<a class="how_to_videos" target="_blank" href="https://youtu.be/qMSjJCskAfM">How to Live Stream to WordPress in less than 3 Minutes</a>'; |
| 1655 |
print '<a class="how_to_videos" target="_blank" href="https://youtu.be/h6myD_vhKcg">How to Live-Stream to WordPress using your iPhone</a>'; |
| 1656 |
|
| 1657 |
print '<a style="margin-top:10px;" href="https://www.youtube.com/channel/UCIjItiJc4Z7aJApj3W6ArJA" target="_blank" class="how_to_videos">More Tutorials On Our YouTube Channel</a>'; |
| 1658 |
|
| 1659 |
|
| 1660 |
print '</div>'; |
| 1661 |
print '</form>'; |
| 1662 |
|
| 1663 |
// Quick-action links: create free / paid channel, or jump to the channels list. |
| 1664 |
print '<div class="theme_options_tab_wpstream" style="display:block;" >'; |
| 1665 |
$link_new = admin_url('admin.php?page=wpstream_live_channels'); |
| 1666 |
$link_new_paid = admin_url('post-new.php?post_type=product').'&new_stream='. rawurlencode('new'); |
| 1667 |
$link_new_free = admin_url('post-new.php?post_type=wpstream_product'); |
| 1668 |
|
| 1669 |
|
| 1670 |
print '<a href="'.esc_url($link_new_free).'" class="wpstream_no_chanel_add_channel">'.esc_html__('Create new Free-To-View channel','wpstream').'</a>'; |
| 1671 |
print '<a href="'.esc_url($link_new_paid).'" class="wpstream_no_chanel_add_channel">'.esc_html__('Create Pay-Per-View channel','wpstream').'</a>'; |
| 1672 |
print '<a href="'.esc_url($link_new).'" class="wpstream_no_chanel_add_channel">'.esc_html__('My Channels','wpstream').'</a>'; |
| 1673 |
print '</div>'; |
| 1674 |
|
| 1675 |
|
| 1676 |
} |
| 1677 |
|
| 1678 |
|
| 1679 |
|
| 1680 |
/** |
| 1681 |
* Media Management |
| 1682 |
* |
| 1683 |
* Render the Recordings admin screen: the quota summary, the upload widget, |
| 1684 |
* and the list of existing recordings. Echoes HTML. |
| 1685 |
* |
| 1686 |
* @since 3.0.1 |
| 1687 |
* @return void |
| 1688 |
*/ |
| 1689 |
public function wpstream_media_management(){ |
| 1690 |
// Storage/streaming quota data for the summary header. |
| 1691 |
$pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_media_management' ); |
| 1692 |
|
| 1693 |
$this->main->show_user_data($pack_details); |
| 1694 |
|
| 1695 |
|
| 1696 |
// Upload widget section. |
| 1697 |
print '<div id="wpstream_media_upload"><h3>'.__('Upload New Recording','wpstream').'</h3>'.$this->wpstream_present_media_upload().'</div>'; |
| 1698 |
|
| 1699 |
// Existing recordings list section. |
| 1700 |
print '<div id="wpstream_file_management"><h3 id="video_management_title">'.__('Your Recordings','wpstream').'</h3>'.$this->wpstream_present_file_management().'</div>'; |
| 1701 |
|
| 1702 |
|
| 1703 |
} |
| 1704 |
|
| 1705 |
|
| 1706 |
|
| 1707 |
|
| 1708 |
/** |
| 1709 |
* |
| 1710 |
* |
| 1711 |
* WpStream Pagination |
| 1712 |
* |
| 1713 |
* @since 3.0.1 |
| 1714 |
* |
| 1715 |
* |
| 1716 |
*/ |
| 1717 |
|
| 1718 |
/** |
| 1719 |
* Build a numeric pager (first/prev/window/next/last) for list screens. |
| 1720 |
* |
| 1721 |
* @param int $pages Total number of pages. |
| 1722 |
* @param int $range How many page links to show on each side of the current page. |
| 1723 |
* @return string Pager HTML, or '' when there is a single page / no pages. |
| 1724 |
*/ |
| 1725 |
public function wpstream_pagination($pages , $range = 2) { |
| 1726 |
$return=''; |
| 1727 |
// Total visible window width around the current page. |
| 1728 |
$showitems = ($range * 2) + 1; |
| 1729 |
// Current page from the query string (defaults to 1). |
| 1730 |
$paged = ( isset( $_GET['paged'] ) ) ? intval($_GET['paged']) : 1; |
| 1731 |
|
| 1732 |
|
| 1733 |
// Only render a pager when there is more than one page. |
| 1734 |
if (1 != $pages && $pages != 0) { |
| 1735 |
$return.= '<ul class="pagination wpstream_pagination">'; |
| 1736 |
// "Previous" arrow. |
| 1737 |
$return.= "<li class=\"roundleft\"><a href='" . get_pagenum_link($paged - 1) . "'><</a></li>"; |
| 1738 |
|
| 1739 |
$last_page = get_pagenum_link($pages); |
| 1740 |
// Emit page-number links, but only those within the visible window (or all if they fit). |
| 1741 |
for ($i = 1; $i <= $pages; $i++) { |
| 1742 |
if (1 != $pages && (!($i >= $paged + $range + 1 || $i <= $paged - $range - 1) || $pages <= $showitems )) { |
| 1743 |
if ($paged == $i) { |
| 1744 |
// Current page marker. |
| 1745 |
$return.= '<li class="active"><a href="' . esc_url(get_pagenum_link($i)) . '" >' . $i . '</a><li>'; |
| 1746 |
} else { |
| 1747 |
$return.= '<li><a href="' . esc_url(get_pagenum_link($i)) . '" >' . $i . '</a><li>'; |
| 1748 |
} |
| 1749 |
} |
| 1750 |
} |
| 1751 |
|
| 1752 |
// "Next" target, clamped to the last page. |
| 1753 |
$prev_page = get_pagenum_link($paged + 1); |
| 1754 |
if (($paged + 1) > $pages) { |
| 1755 |
$prev_page = get_pagenum_link($paged); |
| 1756 |
} else { |
| 1757 |
$prev_page = get_pagenum_link($paged + 1); |
| 1758 |
} |
| 1759 |
|
| 1760 |
|
| 1761 |
// "Next" and "Last" arrows, then close the list. |
| 1762 |
$return.= "<li class=\"roundright\"><a href='" . $prev_page . "'>></a><li>"; |
| 1763 |
$return.= "<li class=\"roundright\"><a href='" . $last_page . "'>>><li>"; |
| 1764 |
$return.= "</ul>"; |
| 1765 |
} |
| 1766 |
return $return; |
| 1767 |
} |
| 1768 |
|
| 1769 |
|
| 1770 |
|
| 1771 |
|
| 1772 |
|
| 1773 |
|
| 1774 |
/** |
| 1775 |
* Media upload |
| 1776 |
* |
| 1777 |
* Build the S3 direct-upload widget for new recordings: checks storage |
| 1778 |
* quota and API connectivity, then renders a multipart form pre-populated |
| 1779 |
* with the signed S3 upload fields. |
| 1780 |
* |
| 1781 |
* @since 3.0.1 |
| 1782 |
* @return string Upload widget HTML, or an alert/notice when unavailable. |
| 1783 |
*/ |
| 1784 |
public function wpstream_present_media_upload(){ |
| 1785 |
$to_return=''; |
| 1786 |
|
| 1787 |
// Refuse when the account is out of storage/data quota. |
| 1788 |
if ( ! $this->main->quota_manager->has_storage_quota( null, 'recordings_screen' ) ) { |
| 1789 |
return '<div class="wpstream_upload_alert">'.esc_html__('You don\'t have enough cloud storage or data to upload a new item. Please delete some videos or upgrade your plan.','wpstream').'</div>'; |
| 1790 |
} |
| 1791 |
|
| 1792 |
// Request the signed S3 POST fields from the cloud API. |
| 1793 |
$formInputs=$this->main->wpstream_live_connection->wpstream_get_signed_form_upload_data(); |
| 1794 |
|
| 1795 |
// On failure, show either a "not connected" or an "out of quota" message. |
| 1796 |
if( !$formInputs['success'] ){ |
| 1797 |
if ($formInputs['error'] == 'not_connected'){ |
| 1798 |
$to_return.='<div class="wpstream_upload_container">'.esc_html__('Not connected. Please connect to WpStream to upload videos.','wpstream').'</div>'; |
| 1799 |
} |
| 1800 |
else { |
| 1801 |
$to_return.='<div class="wpstream_upload_alert">'.esc_html__('You don\'t have enough cloud storage and data to upload a new item. Please delete some videos or upgrade your plan.','wpstream').'</div>'; |
| 1802 |
} |
| 1803 |
return $to_return; |
| 1804 |
} |
| 1805 |
|
| 1806 |
// Signed data obtained: render the direct-to-S3 upload form. |
| 1807 |
if($formInputs['success'] ===true){ |
| 1808 |
|
| 1809 |
|
| 1810 |
|
| 1811 |
$to_return.='<div class="wpstream_upload_container">'; |
| 1812 |
$to_return.='<div id="wpstream_uploaded_mes">'.esc_html__('Please select or drop a video file. Do not close this window during the upload!','wpstream').'</div>'; |
| 1813 |
$to_return.='<form action="https://wpstream-video.s3.amazonaws.com/" |
| 1814 |
method="POST" |
| 1815 |
enctype="multipart/form-data" |
| 1816 |
data-singleFileUploads="true" |
| 1817 |
data-limitMultiFileUploads="1" |
| 1818 |
data-limitConcurrentUploads="1" |
| 1819 |
class="direct-upload">'; |
| 1820 |
|
| 1821 |
$to_return.='<input id="wpstream_upload" type="file" class="inputfile inputfile-1" value="Pick a video file" name="file" multiple>'; |
| 1822 |
$to_return.='<label for="wpstream_upload"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" viewBox="0 0 20 17"><path d="M10 0l-5.2 4.9h3.3v5.1h3.8v-5.1h3.3l-5.2-4.9zm9.3 11.5l-3.2-2.1h-2l3.4 2.6h-3.5c-.1 0-.2.1-.2.1l-.8 2.3h-6l-.8-2.2c-.1-.1-.1-.2-.2-.2h-3.6l3.4-2.6h-2l-3.2 2.1c-.4.3-.7 1-.6 1.5l.6 3.1c.1.5.7.9 1.2.9h16.3c.6 0 1.1-.4 1.3-.9l.6-3.1c.1-.5-.2-1.2-.7-1.5z"/></svg> <span id="wpstream_label_action">' . esc_html__('Choose a file…','wpstream') . '</span></label>'; |
| 1823 |
|
| 1824 |
|
| 1825 |
$to_return.='<div class="wpstream_file_drop_color">'; |
| 1826 |
$to_return.='<div class="wpstream_form_ex">'.esc_html__('Drop a video file here!','wpstream').'</div>'; |
| 1827 |
$to_return.='<div class="wpstream_form_ex_details">'.__('The Video File must be encoded with the following settings:<br> |
| 1828 |
|
| 1829 |
Container: <strong>MP4</strong>,<br> |
| 1830 |
Video codec: <strong>H264</strong>,<br> |
| 1831 |
Audio codec: <strong>AAC</strong>.<br> |
| 1832 |
Media will fail to play if it does not follow the above settings. |
| 1833 |
You may use a tool like MediaInfo to verify your file. Also you may convert it with specialized software like HandBrake.','wpstream').'<strong> '.esc_html__('Accepted file extensions: .mp4, .mov','wpstream').'</strong></div>'; |
| 1834 |
// Inject each signed S3 field as a hidden input so the browser POST is authorized. |
| 1835 |
if(is_array($formInputs)){ |
| 1836 |
foreach ($formInputs['ref'] as $name => $value) { |
| 1837 |
$to_return.='<input type="hidden" name="'. $name.'" value="'.$value.'">'; |
| 1838 |
} |
| 1839 |
} |
| 1840 |
|
| 1841 |
$to_return.=' |
| 1842 |
<div class="progress-bar-area"></div></div> |
| 1843 |
</form>'; |
| 1844 |
|
| 1845 |
$to_return.='</div>'; |
| 1846 |
} |
| 1847 |
|
| 1848 |
return $to_return; |
| 1849 |
|
| 1850 |
} |
| 1851 |
|
| 1852 |
|
| 1853 |
|
| 1854 |
|
| 1855 |
|
| 1856 |
/** |
| 1857 |
* Display movie list |
| 1858 |
* |
| 1859 |
* Fetch the account's recordings from the cloud API and render them: |
| 1860 |
* still-processing ("pending") items first, then completed items with |
| 1861 |
* delete/download links and "create VOD from this recording" actions. |
| 1862 |
* |
| 1863 |
* @since 3.0.1 |
| 1864 |
* @return string Recordings list HTML, or a notice when not connected / empty. |
| 1865 |
*/ |
| 1866 |
public function wpstream_present_file_management(){ |
| 1867 |
// Pull the raw recordings payload from the API. |
| 1868 |
$video_list_raw = $this->main->wpstream_live_connection->wpstream_get_videos_from_api(); |
| 1869 |
|
| 1870 |
// false means the API call failed / not connected. |
| 1871 |
if ( $video_list_raw === false ) { |
| 1872 |
return '<div class="wpstream_upload_container">'.esc_html__('Not connected. Please connect to WpStream to upload videos.','wpstream').'</div>'; |
| 1873 |
} |
| 1874 |
|
| 1875 |
// Normalise the completed-items list. |
| 1876 |
$video_list_raw_array = []; |
| 1877 |
if( isset( $video_list_raw['items'] ) ){ |
| 1878 |
$video_list_raw_array = $video_list_raw['items']; |
| 1879 |
} |
| 1880 |
|
| 1881 |
// Sort completed items newest-first by their 'time' field. |
| 1882 |
$keys = array_column( $video_list_raw_array, 'time' ); |
| 1883 |
array_multisort($keys, SORT_DESC , $video_list_raw_array); |
| 1884 |
|
| 1885 |
$to_return=''; |
| 1886 |
|
| 1887 |
// show pending items |
| 1888 |
// Render still-processing uploads with a "processing" badge. |
| 1889 |
if ( key_exists( 'pending', $video_list_raw ) && is_array( $video_list_raw['pending'] ) ) { |
| 1890 |
foreach ( $video_list_raw['pending'] as $key => $video ) { |
| 1891 |
$video_size = intval($video['size']/1048576); |
| 1892 |
$video_name = esc_html($video['name']); |
| 1893 |
if($video_name!=''): |
| 1894 |
$to_return.='<div class="wpstream_video_wrapper">'; |
| 1895 |
$to_return.='<div class="wpstream_video_title">'; |
| 1896 |
$to_return.='<div class="wpstream_video_notice"></div></div>'; |
| 1897 |
$to_return.='<div class="wpstream_video_title"><strong class="storage_file_name">'.esc_html__('File Name :','wpstream').'</strong>'.'<span class="storage_file_name_real">'.$video_name.'</span><span class="storage_file_size">'.$video_size.' MB </span></div>'; |
| 1898 |
$to_return.='<div class="wpstream_video_pending">' . esc_html__( 'The video is still processing', 'wpstream') . '</div>'; |
| 1899 |
$to_return.='</div>'; |
| 1900 |
endif; |
| 1901 |
|
| 1902 |
} |
| 1903 |
} |
| 1904 |
|
| 1905 |
// show uploaded items |
| 1906 |
// Render each completed recording with its size, delete/download controls and VOD-creation links. |
| 1907 |
if( is_array($video_list_raw['items'] ) ) { |
| 1908 |
foreach ($video_list_raw_array as $key =>$video){ |
| 1909 |
// Keep the service-provided Source Recording identity |
| 1910 |
// separate from its context-specific HTML rendering. |
| 1911 |
$video_size = intval($video['size']/1048576); |
| 1912 |
$video_source_name = (string) $video['name']; |
| 1913 |
$video_name = esc_html($video_source_name); |
| 1914 |
if($video_name!=''): |
| 1915 |
$to_return.='<div class="wpstream_video_wrapper">'; |
| 1916 |
|
| 1917 |
$to_return.='<div class="wpstream_video_title">'; |
| 1918 |
$to_return.='<div class="wpstream_video_notice"></div></div>'; |
| 1919 |
$to_return.='<div class="wpstream_video_title"><strong class="storage_file_name">'.esc_html__('File Name:','wpstream').'</strong>'.'<span class="storage_file_name_real">'.$video_name.'</span><span class="storage_file_size">'.$video_size.' MB </span></div>'; |
| 1920 |
// Delete control. The shared JS utility owns the |
| 1921 |
// confirmation; the renderer supplies inert data |
| 1922 |
// only so any valid recording name remains safe. |
| 1923 |
$to_return.='<div class="wpstream_delete_media" data-filename="'.esc_attr($video_source_name).'">'.esc_html__('delete file','wpstream').'</div>'; |
| 1924 |
// Download trigger (JS fetches a time-limited signed URL) and the resulting link. |
| 1925 |
$to_return.='<div class="wpstream_get_download_link" data-filename="'.$video_name.'">'.esc_html__('download','wpstream').'</div>'; |
| 1926 |
$to_return.='<a href="" class="wpstream_download_link">'.esc_html__('Click to download! The url will work for the next 20 minutes!','wpstream').'</a>'; |
| 1927 |
|
| 1928 |
// Pre-fill new-VOD links (free and, if WooCommerce present, paid) with this recording's name. |
| 1929 |
$add_free_video_url=admin_url('post-new.php?post_type=wpstream_product_vod').'&new_video_name='. rawurlencode($video_source_name); |
| 1930 |
$add_paid_video_url=admin_url('post-new.php?post_type=product').'&new_video_name='. rawurlencode($video_source_name); |
| 1931 |
|
| 1932 |
|
| 1933 |
|
| 1934 |
$to_return .='<a class="create_new_free_video" href="'.esc_url($add_free_video_url).'">'.esc_html__('Create new Free-To-View VOD from this recording').'</a>'; |
| 1935 |
if (class_exists('WooCommerce')) { |
| 1936 |
$to_return .='<a class="create_new_ppv_video" href="'.esc_url($add_paid_video_url).'">'.esc_html__('Create new Pay-Per-View VOD from this recording').'</a>'; |
| 1937 |
} |
| 1938 |
|
| 1939 |
$to_return.='</div>'; |
| 1940 |
endif; |
| 1941 |
|
| 1942 |
} |
| 1943 |
$current_page= get_current_screen(); |
| 1944 |
} |
| 1945 |
|
| 1946 |
// no items to show |
| 1947 |
// Empty-state message when there are neither completed nor pending items. |
| 1948 |
if ( !is_array( $video_list_raw['items'] ) || ( key_exists( 'pending', $video_list_raw ) && !is_array( $video_list_raw['pending'] ) ) ) { |
| 1949 |
$to_return.= '<div class="wpstream_video_wrapper">'.esc_html__('You don\'t have any videos.','wpstream').'</div>'; |
| 1950 |
} |
| 1951 |
return $to_return; |
| 1952 |
} |
| 1953 |
|
| 1954 |
|
| 1955 |
/** |
| 1956 |
* Preserve the legacy free-Channel publish marker. |
| 1957 |
* |
| 1958 |
* Channel Settings initialization is coordinated by the Streaming Content |
| 1959 |
* Creation Module; this compatibility hook only maintains historical meta. |
| 1960 |
* |
| 1961 |
* @since 3.0.1 |
| 1962 |
* @param int $post_id Post ID being published. |
| 1963 |
* @param WP_Post $post Post object being published. |
| 1964 |
* @return void |
| 1965 |
*/ |
| 1966 |
public function wpstream_publish_wpstream_product($post_id,$post){ |
| 1967 |
// Only applies to free channel posts. |
| 1968 |
if( $post->post_type == 'wpstream_product' ){ |
| 1969 |
// Preserve the legacy marker used by existing installations. |
| 1970 |
update_post_meta ($post_id,'local_event_options_test','working_on_'.$post_id); |
| 1971 |
} |
| 1972 |
} |
| 1973 |
|
| 1974 |
/** |
| 1975 |
* Initialize existing Streaming Content when it is published or saved. |
| 1976 |
* |
| 1977 |
* The shared Module performs safe legacy repair and retries cloud provisioning |
| 1978 |
* for Live Channels. VODs receive only their applicable local initialization. |
| 1979 |
* |
| 1980 |
* @param int $post_id Post ID being published. |
| 1981 |
* @param WP_Post $post Post object being published. |
| 1982 |
* @return void |
| 1983 |
*/ |
| 1984 |
public function wpstream_create_remote_channel_on_publish( $post_id, $post ) { |
| 1985 |
// Skip autosaves and revisions (they are not real publishes). |
| 1986 |
if ( defined( 'DOING_AUTOSAVE') && DOING_AUTOSAVE ) { |
| 1987 |
return; |
| 1988 |
} |
| 1989 |
if ( wp_is_post_revision( $post_id ) ) { |
| 1990 |
return; |
| 1991 |
} |
| 1992 |
$this->main->streaming_content_creation->initialize_existing( $post_id ); |
| 1993 |
} |
| 1994 |
|
| 1995 |
/** |
| 1996 |
* Initialize a manually saved paid streaming product after WooCommerce |
| 1997 |
* has persisted its product-type taxonomy. The earlier publish hook may |
| 1998 |
* run while a new product still looks like `simple`; this later adapter |
| 1999 |
* enters the same idempotent creation policy once `live_stream` or |
| 2000 |
* `video_on_demand` is observable. |
| 2001 |
* |
| 2002 |
* @param int $post_id WooCommerce product ID. |
| 2003 |
* @return void |
| 2004 |
*/ |
| 2005 |
public function wpstream_initialize_paid_streaming_product( $post_id ) { |
| 2006 |
if ( 'product' !== get_post_type( $post_id ) ) { |
| 2007 |
return; |
| 2008 |
} |
| 2009 |
if ( ! has_term( array( 'live_stream', 'video_on_demand' ), 'product_type', $post_id ) ) { |
| 2010 |
return; |
| 2011 |
} |
| 2012 |
$this->main->streaming_content_creation->initialize_existing( $post_id ); |
| 2013 |
} |
| 2014 |
|
| 2015 |
/** |
| 2016 |
* Delete the remote channel when its Live Channel post is deleted for good. |
| 2017 |
* |
| 2018 |
* Hooked to before_delete_post: fires only on permanent deletion, never on |
| 2019 |
* trash, so trashing stays reversible and a restored channel keeps its |
| 2020 |
* cloud identity. Only a Live Channel that was actually Provisioned |
| 2021 |
* (`channelId` meta present) has anything at the service to remove. |
| 2022 |
* |
| 2023 |
* The local deletion proceeds whatever the service answers — this hook |
| 2024 |
* cannot cancel it, and a channel the customer removed must leave the |
| 2025 |
* site either way. |
| 2026 |
* |
| 2027 |
* @param int $post_id Post ID being permanently deleted. |
| 2028 |
* @param WP_Post $post Post object being permanently deleted. |
| 2029 |
* @return void |
| 2030 |
*/ |
| 2031 |
public function wpstream_delete_remote_channel_on_delete( $post_id, $post ) { |
| 2032 |
// Only Live Channel posts: the free post type, or a WooCommerce |
| 2033 |
// product of the live_stream type (paid channels). |
| 2034 |
$is_live_channel = $post instanceof WP_Post |
| 2035 |
&& ( 'wpstream_product' === $post->post_type |
| 2036 |
|| ( 'product' === $post->post_type && has_term( 'live_stream', 'product_type', $post_id ) ) ); |
| 2037 |
if ( ! $is_live_channel ) { |
| 2038 |
return; |
| 2039 |
} |
| 2040 |
|
| 2041 |
// A channel that never obtained a cloud identity has nothing to delete. |
| 2042 |
if ( '' === (string) get_post_meta( $post_id, 'channelId', true ) ) { |
| 2043 |
return; |
| 2044 |
} |
| 2045 |
|
| 2046 |
$this->main->wpstream_live_connection->wpstream_delete_channel( $post_id ); |
| 2047 |
} |
| 2048 |
|
| 2049 |
/** |
| 2050 |
* Restore a paid Live Channel as a draft instead of immediately putting |
| 2051 |
* it back on sale. WooCommerce restores ordinary products to their |
| 2052 |
* previous status; a channel requires the seller to republish it. |
| 2053 |
* |
| 2054 |
* @param string $new_status Status selected by earlier filters. |
| 2055 |
* @param int $post_id Restored post ID. |
| 2056 |
* @param string $previous_status Status before trashing. |
| 2057 |
* @return string |
| 2058 |
*/ |
| 2059 |
public function wpstream_paid_live_untrash_status( $new_status, $post_id, $previous_status ) { |
| 2060 |
if ( 'product' === get_post_type( $post_id ) && has_term( 'live_stream', 'product_type', $post_id ) ) { |
| 2061 |
return 'draft'; |
| 2062 |
} |
| 2063 |
return $new_status; |
| 2064 |
} |
| 2065 |
|
| 2066 |
/** Invalidate VOD DRM lookup state whenever publication visibility changes. */ |
| 2067 |
public function wpstream_invalidate_vod_drm_on_status_change( $new_status, $old_status, $post ) { |
| 2068 |
if ( $post instanceof WP_Post && 'wpstream_product_vod' === $post->post_type && $new_status !== $old_status ) { |
| 2069 |
$this->main->drm_key_delivery->invalidate_vod_lookup( $post->ID ); |
| 2070 |
} |
| 2071 |
} |
| 2072 |
|
| 2073 |
/** Invalidate VOD DRM lookup state before its key-index metadata is deleted. */ |
| 2074 |
public function wpstream_invalidate_vod_drm_on_delete( $post_id, $post ) { |
| 2075 |
if ( $post instanceof WP_Post && 'wpstream_product_vod' === $post->post_type ) { |
| 2076 |
$this->main->drm_key_delivery->invalidate_vod_lookup( $post_id ); |
| 2077 |
} |
| 2078 |
} |
| 2079 |
|
| 2080 |
/** |
| 2081 |
* save meta options |
| 2082 |
* |
| 2083 |
* Persist the allow-listed free channel / VOD meta fields on save for the |
| 2084 |
* `wpstream_product` and `wpstream_product_vod` post types. Hooked to the |
| 2085 |
* post save action. |
| 2086 |
* |
| 2087 |
* @since 3.0.1 |
| 2088 |
* @param int $post_id Post ID being saved. |
| 2089 |
* @param WP_Post $post Post object being saved. |
| 2090 |
* @return void |
| 2091 |
*/ |
| 2092 |
public function wpstream_free_product_update_post($post_id,$post){ |
| 2093 |
|
| 2094 |
// Guard against non-post callers. |
| 2095 |
if(!is_object($post) || !isset($post->post_type)) { |
| 2096 |
return; |
| 2097 |
} |
| 2098 |
|
| 2099 |
|
| 2100 |
|
| 2101 |
|
| 2102 |
|
| 2103 |
// Only handle the free channel and VOD post types. |
| 2104 |
if( $post->post_type == 'wpstream_product' || |
| 2105 |
$post->post_type == 'wpstream_product_vod' ): |
| 2106 |
$previous_vod_type = 'wpstream_product_vod' === $post->post_type |
| 2107 |
? intval( get_post_meta( $post_id, 'wpstream_product_type', true ) ) |
| 2108 |
: 0; |
| 2109 |
$previous_recording = 'wpstream_product_vod' === $post->post_type |
| 2110 |
? (string) get_post_meta( $post_id, 'wpstream_free_video', true ) |
| 2111 |
: ''; |
| 2112 |
|
| 2113 |
// Meta keys this handler is allowed to write. |
| 2114 |
$allowed_keys=array( |
| 2115 |
'wpstream_product_type', |
| 2116 |
'wpstream_free_video', |
| 2117 |
'wpstream_free_video_external', |
| 2118 |
'wpstream_closed_captions_file' |
| 2119 |
); |
| 2120 |
|
| 2121 |
|
| 2122 |
// Save each allow-listed, scalar POST field as sanitized post meta. |
| 2123 |
foreach ($_POST as $key => $value) { |
| 2124 |
if( !is_array ($value) ){ |
| 2125 |
if (in_array ($key, $allowed_keys)) { |
| 2126 |
$postmeta = sanitize_text_field ( $value ); |
| 2127 |
update_post_meta($post_id, sanitize_key($key), $postmeta ); |
| 2128 |
} |
| 2129 |
} |
| 2130 |
} |
| 2131 |
|
| 2132 |
if ( 'wpstream_product_vod' === $post->post_type && isset( $_POST['wpstream_product_type'] ) ) { |
| 2133 |
$new_vod_type = intval( $_POST['wpstream_product_type'] ); |
| 2134 |
if ( 3 === $new_vod_type ) { |
| 2135 |
$this->wpstream_retire_vod_recording_identity( $post_id ); |
| 2136 |
delete_post_meta( $post_id, 'wpstream_free_video' ); |
| 2137 |
} elseif ( 2 === $new_vod_type ) { |
| 2138 |
$new_recording = isset( $_POST['wpstream_free_video'] ) && is_scalar( $_POST['wpstream_free_video'] ) |
| 2139 |
? sanitize_text_field( wp_unslash( $_POST['wpstream_free_video'] ) ) |
| 2140 |
: $previous_recording; |
| 2141 |
if ( 2 !== $previous_vod_type || $new_recording !== $previous_recording ) { |
| 2142 |
$this->wpstream_retire_vod_recording_identity( $post_id ); |
| 2143 |
} |
| 2144 |
delete_post_meta( $post_id, 'wpstream_free_video_external' ); |
| 2145 |
} |
| 2146 |
} |
| 2147 |
|
| 2148 |
endif; |
| 2149 |
|
| 2150 |
} |
| 2151 |
|
| 2152 |
/** Retire key and hosted-player metadata owned by a VOD's old recording. */ |
| 2153 |
private function wpstream_retire_vod_recording_identity( $post_id ) { |
| 2154 |
$this->main->drm_key_delivery->invalidate_vod_lookup( $post_id ); |
| 2155 |
foreach ( array( 'hlsDecryptionKey', 'hlsDecryptionKeyIndex', 'wpstream_vod_video_data', 'wpstream_vod_embed_key', 'wpstream_vod_embed_url' ) as $meta_key ) { |
| 2156 |
delete_post_meta( $post_id, $meta_key ); |
| 2157 |
} |
| 2158 |
} |
| 2159 |
|
| 2160 |
|
| 2161 |
/** |
| 2162 |
* save meta options |
| 2163 |
* |
| 2164 |
* Register the plugin's post metaboxes: VOD settings on `wpstream_product_vod`, |
| 2165 |
* the video collection box on `wpstream_bundles`, and (for WooCommerce bundle |
| 2166 |
* products) the video-collection options box on `product`. Hooked to add_meta_boxes. |
| 2167 |
* |
| 2168 |
* @since 3.0.1 |
| 2169 |
* @return void |
| 2170 |
*/ |
| 2171 |
public function add_wpstream_product_metaboxes() { |
| 2172 |
global $post; |
| 2173 |
$post_id = $post->ID; |
| 2174 |
|
| 2175 |
|
| 2176 |
// VOD settings metabox and the bundle video-collection metabox. |
| 2177 |
add_meta_box( 'add_wpstream_product_metaboxes-sectionid', esc_html__( 'Video On Demand Settings', 'wpstream' ),array($this,'display_meta_options'),'wpstream_product_vod' ,'normal','default'); |
| 2178 |
add_meta_box( 'custom_metabox_video_collection', esc_html__( 'Video Collection', 'wpstream' ), 'wpstream_bundle_custom_metabox_callback', 'wpstream_bundles', 'normal', 'high' ); |
| 2179 |
|
| 2180 |
// For WooCommerce bundle products, add the video-collection options box on the product screen. |
| 2181 |
if(function_exists('wc_get_product')): |
| 2182 |
$product = wc_get_product( $post_id ); |
| 2183 |
if ( $product ) { |
| 2184 |
|
| 2185 |
if ( $product->get_type() === 'wpstream_bundle' ) { |
| 2186 |
add_meta_box( |
| 2187 |
'wpstream_woo_custom_metabox', |
| 2188 |
esc_html__( 'Video Collection Options', 'wpstream' ), |
| 2189 |
'wpstream_bundle_custom_metabox_callback', |
| 2190 |
'product', |
| 2191 |
'normal', |
| 2192 |
'default' |
| 2193 |
); |
| 2194 |
} |
| 2195 |
} |
| 2196 |
endif; |
| 2197 |
|
| 2198 |
/** |
| 2199 |
* Fires after the plugin registered its metaboxes for the edit screen. |
| 2200 |
* |
| 2201 |
* @since 4.14.0 |
| 2202 |
* |
| 2203 |
* @param int $post_id Post being edited. |
| 2204 |
* @param WP_Post $post The post. |
| 2205 |
*/ |
| 2206 |
do_action( 'wpstream_registered_metaboxes', intval( $post_id ), $post ); |
| 2207 |
} |
| 2208 |
|
| 2209 |
|
| 2210 |
/** |
| 2211 |
* make woocomerce virtual products |
| 2212 |
* |
| 2213 |
* Force WpStream WooCommerce product types (live_stream, video_on_demand, |
| 2214 |
* wpstream_bundle) to be virtual so they need no shipping. Hooked to save. |
| 2215 |
* |
| 2216 |
* @since 3.0.1 |
| 2217 |
* @param int $post_id Post ID being saved (unused; uses global $post). |
| 2218 |
* @param WP_Post $post Post object (overwritten by global $post). |
| 2219 |
* @return void |
| 2220 |
*/ |
| 2221 |
public function wpstream_make_product_virtual($post_id,$post){ |
| 2222 |
global $post; |
| 2223 |
if(isset($post->ID)){ |
| 2224 |
// Only WooCommerce products are relevant. |
| 2225 |
if ( $post->post_type !== 'product' ) return; |
| 2226 |
// Mark WpStream product types as virtual. |
| 2227 |
$term_list = wp_get_post_terms($post->ID, 'product_type'); |
| 2228 |
if( !empty($term_list) && |
| 2229 |
isset($term_list[0]->slug) && |
| 2230 |
in_array($term_list[0]->slug, ['live_stream', 'video_on_demand', 'wpstream_bundle']) |
| 2231 |
){ |
| 2232 |
update_post_meta( $post->ID, '_virtual', 'yes' ); |
| 2233 |
} |
| 2234 |
} |
| 2235 |
} |
| 2236 |
|
| 2237 |
|
| 2238 |
|
| 2239 |
/** |
| 2240 |
* render meta options |
| 2241 |
* |
| 2242 |
* Render the "Video On Demand Settings" metabox: media-type selector, |
| 2243 |
* recording chooser, optional captions (.vtt) picker, and the self-hosted/ |
| 2244 |
* external video URL field. Echoes HTML. |
| 2245 |
* |
| 2246 |
* @since 3.0.1 |
| 2247 |
* @param WP_Post $post Post being edited (overwritten by global $post). |
| 2248 |
* @return void |
| 2249 |
*/ |
| 2250 |
public function display_meta_options( $post ) { |
| 2251 |
// Nonce for the metabox save + use the global post object. |
| 2252 |
wp_nonce_field( plugin_basename( __FILE__ ), 'estate_agent_noncename' ); |
| 2253 |
global $post; |
| 2254 |
|
| 2255 |
// Determine the pre-selected media type / video. |
| 2256 |
$is_live = ''; |
| 2257 |
$is_video = ''; |
| 2258 |
$is_video_external = ''; |
| 2259 |
// When arriving from "create VOD from recording", preselect the recording. |
| 2260 |
if( isset( $_GET['new_video_name']) && $_GET['new_video_name']!='' ){ |
| 2261 |
$is_video = ' selected '; |
| 2262 |
$wpstream_free_video = esc_html( $_GET['new_video_name']); |
| 2263 |
}else{ |
| 2264 |
// Otherwise read the saved type/video from post meta and select accordingly. |
| 2265 |
$wpstream_product_type = esc_html(get_post_meta($post->ID, 'wpstream_product_type', true)); |
| 2266 |
$wpstream_free_video = esc_html(get_post_meta($post->ID, 'wpstream_free_video', true)); |
| 2267 |
|
| 2268 |
if($wpstream_product_type==1){ |
| 2269 |
$is_live = ' selected '; |
| 2270 |
} |
| 2271 |
|
| 2272 |
if($wpstream_product_type==2){ |
| 2273 |
$is_video = ' selected '; |
| 2274 |
} |
| 2275 |
|
| 2276 |
if($wpstream_product_type==3){ |
| 2277 |
$is_video_external = ' selected '; |
| 2278 |
} |
| 2279 |
} |
| 2280 |
|
| 2281 |
// Media-type dropdown (recording vs self-hosted/external). |
| 2282 |
print' |
| 2283 |
<p class="meta-options"> |
| 2284 |
<label for="wpstream_product_type">'.__('Media Type:','wpstream').' </label><br /> |
| 2285 |
<select id="wpstream_product_type" name="wpstream_product_type"> |
| 2286 |
<option value="2" '.$is_video.'>'.__('Recording','wpstream').'</option> |
| 2287 |
<option value="3" '.$is_video_external.'>'.__('Self Hosted or External Video','wpstream').'</option> |
| 2288 |
</select> |
| 2289 |
</p> |
| 2290 |
'; |
| 2291 |
|
| 2292 |
|
| 2293 |
// Fetch the account's recordings to populate the chooser. |
| 2294 |
$video_list = $this->main->wpstream_live_connection->wpstream_get_videos(); |
| 2295 |
|
| 2296 |
|
| 2297 |
// Recording chooser dropdown, pre-selecting the saved recording. |
| 2298 |
print '<div class="meta-options video_free">'; |
| 2299 |
print '<p class="meta-option wpstream_free_video">'; |
| 2300 |
print '<label for="wpstream_free_video">'.__('Choose video:','wpstream').' </label><br /> |
| 2301 |
<select id="wpstream_free_video" name="wpstream_free_video">'; |
| 2302 |
|
| 2303 |
if( is_array( $video_list ) ) { |
| 2304 |
foreach ($video_list as $key=>$value){ |
| 2305 |
print '<option value="'.$key.'"'; |
| 2306 |
if($wpstream_free_video === $key){ |
| 2307 |
print ' selected '; |
| 2308 |
} |
| 2309 |
print '>'.$value.'</option>'; |
| 2310 |
} |
| 2311 |
} |
| 2312 |
print'</select>'; |
| 2313 |
print '</p> '; |
| 2314 |
print '</div>'; |
| 2315 |
|
| 2316 |
// Optional captions (.vtt) picker; hide the select button when a file is already set. |
| 2317 |
$wpstream_closed_captions_file = get_post_meta($post->ID, 'wpstream_closed_captions_file', true); |
| 2318 |
|
| 2319 |
$button_style = $wpstream_closed_captions_file ? 'style="display:none;"' : ''; |
| 2320 |
|
| 2321 |
print '<p class="meta-option wpstream_vod_captions_url">'; |
| 2322 |
print '<label for="wpstream_vod_captions_url_button">'.__('Captions file (optional):','wpstream').' </label><br /> |
| 2323 |
<input type="hidden" id="wpstream_closed_captions_file" name="wpstream_closed_captions_file" value="'.esc_attr($wpstream_closed_captions_file).'" /> |
| 2324 |
<input id="wpstream_vod_captions_url_button" type="button" class="upload_button button" value="'.esc_html__('Select .vtt Captions File','wpstream').'" '.$button_style.' /> |
| 2325 |
<span class="wpstream_caption_file_display">'.( $wpstream_closed_captions_file ? esc_html( basename( $wpstream_closed_captions_file ) ) : '' ).'</span>'; |
| 2326 |
if ( $wpstream_closed_captions_file ) { |
| 2327 |
print '<input type="button" class="button wpstream_remove_caption" value="'.esc_html__('Remove','wpstream').'" style="margin-left: 5px;" />'; |
| 2328 |
} |
| 2329 |
print '</p> '; |
| 2330 |
|
| 2331 |
// Self-hosted / external video URL field with a media-library select button. |
| 2332 |
$wpstream_free_video_external= esc_html(get_post_meta($post->ID, 'wpstream_free_video_external', true)); |
| 2333 |
print '<div class="meta-options1 video_free_external"> |
| 2334 |
<label for="wpstream_free_video_external">'.__('Video:','wpstream').' </label><br /> |
| 2335 |
|
| 2336 |
<input id="wpstream_free_video_external" type="text" size="36" name="wpstream_free_video_external" value="'.$wpstream_free_video_external.'" /> |
| 2337 |
<input id="wpstream_free_video_external_button" type="button" size="40" class="upload_button button" value="'.esc_html__('Select Video','wpstream').'" />'; |
| 2338 |
// Show the recording hint vs the external hint depending on the saved media type. |
| 2339 |
if($wpstream_product_type==2){ |
| 2340 |
$show_recording=''; |
| 2341 |
$show_external='style="display:none"'; |
| 2342 |
}else{ |
| 2343 |
$show_recording='style="display:none"'; |
| 2344 |
$show_external=''; |
| 2345 |
} |
| 2346 |
print '<p '.$show_recording.' class="wpstream_option_vod_source wpstream_show_recording">'.esc_html__('Choose one of your existing recordings.','wpstream').'</p>'; |
| 2347 |
print '<p '. $show_external.' class="wpstream_option_vod_source wpstream_show_external">'.esc_html__('Upload a video from your computer or paste the URL of a YouTube/external video.','wpstream').'</p>'; |
| 2348 |
|
| 2349 |
print '</div> '; |
| 2350 |
|
| 2351 |
/** |
| 2352 |
* Fires at the end of the Video On Demand Settings metabox. |
| 2353 |
* |
| 2354 |
* Callbacks echo their own fields and own their escaping; saving |
| 2355 |
* them is the callback's job (hook `save_post`). |
| 2356 |
* |
| 2357 |
* @since 4.14.0 |
| 2358 |
* |
| 2359 |
* @param int $post_id VOD post ID. |
| 2360 |
* @param WP_Post $post The post. |
| 2361 |
*/ |
| 2362 |
do_action( 'wpstream_vod_meta_options_after', intval( $post->ID ), $post ); |
| 2363 |
} |
| 2364 |
|
| 2365 |
|
| 2366 |
|
| 2367 |
|
| 2368 |
|
| 2369 |
|
| 2370 |
|
| 2371 |
/** |
| 2372 |
* Add new product types to Woocommerce select product type |
| 2373 |
* |
| 2374 |
* Register the "Live Channel" and "Video On Demand" WooCommerce product |
| 2375 |
* types in the product-type dropdown. Filter callback. |
| 2376 |
* |
| 2377 |
* @since 3.0.1 |
| 2378 |
* @param array $types Existing product-type => label map. |
| 2379 |
* @return array The map with the WpStream types added. |
| 2380 |
*/ |
| 2381 |
public function wpstream_add_products( $types ){ |
| 2382 |
$types[ 'live_stream' ] = __( 'Live Channel','wpstream' ); |
| 2383 |
$types[ 'video_on_demand' ] = __( 'Video On Demand','wpstream' ); |
| 2384 |
|
| 2385 |
return $types; |
| 2386 |
} |
| 2387 |
|
| 2388 |
/** |
| 2389 |
* Map the WpStream product-type slugs to their WC_Product subclasses. |
| 2390 |
* Filter callback for woocommerce_product_class. |
| 2391 |
* |
| 2392 |
* @param string $classname Default product class name. |
| 2393 |
* @param string $product_type The product type slug being instantiated. |
| 2394 |
* @return string The resolved product class name. |
| 2395 |
*/ |
| 2396 |
public function wpstream_add_products_class( $classname, $product_type ) { |
| 2397 |
/** |
| 2398 |
* Filter the product-type slug => WC_Product subclass map. |
| 2399 |
* |
| 2400 |
* A mapped class must exist; unknown classes leave WooCommerce's own |
| 2401 |
* resolution in place. |
| 2402 |
* |
| 2403 |
* @since 4.14.0 |
| 2404 |
* |
| 2405 |
* @param array $map Slug => class name. |
| 2406 |
*/ |
| 2407 |
$map = (array) apply_filters( |
| 2408 |
'wpstream_product_class_map', |
| 2409 |
array( |
| 2410 |
'live_stream' => 'WC_Product_Live_Stream', |
| 2411 |
'video_on_demand' => 'WC_Product_Video_On_Demand', |
| 2412 |
) |
| 2413 |
); |
| 2414 |
if ( isset( $map[ $product_type ] ) && class_exists( $map[ $product_type ] ) ) { |
| 2415 |
$classname = $map[ $product_type ]; |
| 2416 |
} |
| 2417 |
return $classname; |
| 2418 |
} |
| 2419 |
|
| 2420 |
/** |
| 2421 |
* Load the custom WC_Product subclass files when WooCommerce is active. |
| 2422 |
* |
| 2423 |
* @return void |
| 2424 |
*/ |
| 2425 |
public function wpstream_add_custom_wc_products() { |
| 2426 |
// Require the live-stream and VOD product classes (WooCommerce only). |
| 2427 |
if( class_exists( 'WooCommerce' ) ){ |
| 2428 |
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-wc-product-live-stream.php'; |
| 2429 |
require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-wc-product-video-on-demand.php'; |
| 2430 |
} |
| 2431 |
} |
| 2432 |
|
| 2433 |
/** |
| 2434 |
* Js action to do when user pick live stream or video on demand |
| 2435 |
* |
| 2436 |
* @since 3.0.1 |
| 2437 |
*/ |
| 2438 |
|
| 2439 |
/** |
| 2440 |
* Whether WpStream WooCommerce product UI should load for this product. |
| 2441 |
* |
| 2442 |
* Skips simple, course, and other non-WpStream types so LearnDash and |
| 2443 |
* other integrations can use the General product data tab without conflict. |
| 2444 |
* |
| 2445 |
* @param int $post_id Product post ID. Uses global $post when 0. |
| 2446 |
* @return bool |
| 2447 |
*/ |
| 2448 |
public function wpstream_is_wpstream_wc_product_context( $post_id = 0 ) { |
| 2449 |
// Creating a new stream/VOD from a query-string action always counts. |
| 2450 |
if ( isset( $_GET['new_stream'] ) || isset( $_GET['new_video_name'] ) ) { |
| 2451 |
return true; |
| 2452 |
} |
| 2453 |
|
| 2454 |
// On the first product save WooCommerce has not necessarily made |
| 2455 |
// the selected taxonomy term observable through wc_get_product() |
| 2456 |
// yet. Trust only its allow-listed product-type form field here; |
| 2457 |
// the save handler remains scoped to WooCommerce product metadata. |
| 2458 |
if ( isset( $_POST['product-type'] ) && is_scalar( $_POST['product-type'] ) ) { |
| 2459 |
$submitted_type = sanitize_key( wp_unslash( $_POST['product-type'] ) ); |
| 2460 |
if ( in_array( $submitted_type, array( 'live_stream', 'video_on_demand', 'wpstream_bundle', 'subscription' ), true ) ) { |
| 2461 |
return true; |
| 2462 |
} |
| 2463 |
} |
| 2464 |
|
| 2465 |
// Fall back to the current global post when no ID was passed. |
| 2466 |
if ( ! $post_id ) { |
| 2467 |
global $post; |
| 2468 |
$post_id = ( $post && isset( $post->ID ) ) ? (int) $post->ID : 0; |
| 2469 |
} |
| 2470 |
|
| 2471 |
// Need a product ID and WooCommerce to resolve the type. |
| 2472 |
if ( ! $post_id || ! function_exists( 'wc_get_product' ) ) { |
| 2473 |
return false; |
| 2474 |
} |
| 2475 |
|
| 2476 |
$product = wc_get_product( $post_id ); |
| 2477 |
if ( ! $product ) { |
| 2478 |
return false; |
| 2479 |
} |
| 2480 |
|
| 2481 |
// True only for WpStream (and subscription) product types. |
| 2482 |
return in_array( |
| 2483 |
$product->get_type(), |
| 2484 |
array( 'live_stream', 'video_on_demand', 'wpstream_bundle', 'subscription' ), |
| 2485 |
true |
| 2486 |
); |
| 2487 |
} |
| 2488 |
|
| 2489 |
/** |
| 2490 |
* WooCommerce product types that use the core Regular price field. |
| 2491 |
* |
| 2492 |
* @return string[] |
| 2493 |
*/ |
| 2494 |
public function wpstream_get_wc_product_types_with_pricing() { |
| 2495 |
return array( 'live_stream', 'video_on_demand', 'wpstream_bundle' ); |
| 2496 |
} |
| 2497 |
|
| 2498 |
/** |
| 2499 |
* Add show_if_* classes to the pricing panel for WpStream product types. |
| 2500 |
* |
| 2501 |
* WooCommerce renders .options_group.pricing with show_if_simple only. Custom |
| 2502 |
* types rely on show_if_{type} classes so meta-boxes-product.js can toggle them |
| 2503 |
* when the product type dropdown changes, including on unsaved new products. |
| 2504 |
* |
| 2505 |
* @param string $hook Admin screen hook suffix. |
| 2506 |
*/ |
| 2507 |
public function wpstream_enqueue_wc_product_pricing_visibility( $hook ) { |
| 2508 |
// Only on the add/edit-post admin screens. |
| 2509 |
if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) { |
| 2510 |
return; |
| 2511 |
} |
| 2512 |
|
| 2513 |
// Only on the WooCommerce product post type. |
| 2514 |
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; |
| 2515 |
if ( ! $screen || 'product' !== $screen->post_type ) { |
| 2516 |
return; |
| 2517 |
} |
| 2518 |
|
| 2519 |
// Need WooCommerce's product meta-boxes script to attach to. |
| 2520 |
if ( ! wp_script_is( 'wc-admin-product-meta-boxes', 'registered' ) ) { |
| 2521 |
return; |
| 2522 |
} |
| 2523 |
|
| 2524 |
// Pass the WpStream pricing types to JS and add show_if_{type} classes so the pricing panel toggles correctly. |
| 2525 |
$types_json = wp_json_encode( array_values( $this->wpstream_get_wc_product_types_with_pricing() ) ); |
| 2526 |
|
| 2527 |
wp_add_inline_script( |
| 2528 |
'wc-admin-product-meta-boxes', |
| 2529 |
"jQuery( function( $ ) { |
| 2530 |
var wpstreamPricingProductTypes = {$types_json}; |
| 2531 |
var \$pricing = $( '.options_group.pricing' ); |
| 2532 |
var \$soldIndividually = $( '._sold_individually_field' ).parent(); |
| 2533 |
|
| 2534 |
wpstreamPricingProductTypes.forEach( function( type ) { |
| 2535 |
\$pricing.addClass( 'show_if_' + type ); |
| 2536 |
\$soldIndividually.addClass( 'show_if_' + type ); |
| 2537 |
} ); |
| 2538 |
|
| 2539 |
if ( wpstreamPricingProductTypes.indexOf( $( '#product-type' ).val() ) !== -1 ) { |
| 2540 |
\$pricing.show(); |
| 2541 |
\$soldIndividually.show(); |
| 2542 |
} |
| 2543 |
} );" |
| 2544 |
); |
| 2545 |
} |
| 2546 |
|
| 2547 |
|
| 2548 |
/** |
| 2549 |
* Add custom classes to the product types |
| 2550 |
* |
| 2551 |
* Adjust the WooCommerce product-data tab visibility classes so WpStream |
| 2552 |
* types hide the Shipping tab and show the Inventory tab. Filter callback. |
| 2553 |
* |
| 2554 |
* @since 3.0.1 |
| 2555 |
* @param array $tabs WooCommerce product data tabs config. |
| 2556 |
* @return array The modified tabs config. |
| 2557 |
*/ |
| 2558 |
public function wpstream_hide_attributes_data_panel( $tabs) { |
| 2559 |
|
| 2560 |
// Hide Shipping and show Inventory for WpStream product types. |
| 2561 |
$tabs['shipping']['class'][] = 'hide_if_live_stream hide_if_video_on_demand hide_if_wpstream_bundle'; |
| 2562 |
$tabs['inventory']['class'][] = 'show_if_live_stream show_if_video_on_demand show_if_wpstream_bundle'; |
| 2563 |
|
| 2564 |
return $tabs; |
| 2565 |
} |
| 2566 |
|
| 2567 |
|
| 2568 |
|
| 2569 |
/** |
| 2570 |
* Hide buy now on products if Netflix mode |
| 2571 |
* |
| 2572 |
* When global subscription ("Netflix") mode is on, mark WpStream media |
| 2573 |
* products as not individually purchasable (access comes via the |
| 2574 |
* subscription instead). Filter callback for is_purchasable. |
| 2575 |
* |
| 2576 |
* @since 3.12 |
| 2577 |
* @param bool $purchaseable_product_wpblog Current purchasable flag. |
| 2578 |
* @param WC_Product $product The product being checked. |
| 2579 |
* @return bool False for WpStream media in global-sub mode, else the original flag. |
| 2580 |
*/ |
| 2581 |
public function wpstream_hide_buy_now_subscription_mode( $purchaseable_product_wpblog,$product){ |
| 2582 |
$product_id=$product->get_id(); |
| 2583 |
|
| 2584 |
// Stable type slug is '' when the product has no term (never a crash). |
| 2585 |
$product_type_slug = wpstream_get_product_type_slug($product_id); |
| 2586 |
|
| 2587 |
// Global subscription ("Netflix") mode flag. |
| 2588 |
$subscription_model = intval( get_option('wpstream_global_sub','')) ; |
| 2589 |
|
| 2590 |
if($subscription_model==1){ // if we have Neflix mode |
| 2591 |
// WpStream media types are not directly purchasable in this mode. |
| 2592 |
if( $product_type_slug=='live_stream' || $product_type_slug=='video_on_demand' || $product_type_slug=='wpstream_bundle' ){ |
| 2593 |
return false; |
| 2594 |
} |
| 2595 |
} |
| 2596 |
|
| 2597 |
return $purchaseable_product_wpblog; |
| 2598 |
} |
| 2599 |
|
| 2600 |
|
| 2601 |
|
| 2602 |
|
| 2603 |
|
| 2604 |
|
| 2605 |
/** |
| 2606 |
* Add custom fields to custom product types |
| 2607 |
* |
| 2608 |
* Render the extra WooCommerce "General" product fields for WpStream types: |
| 2609 |
* the subscription-live flag, the recording/video chooser, and the |
| 2610 |
* "attach to subscription" multi-select. Echoes HTML. |
| 2611 |
* |
| 2612 |
* @since 3.0.1 |
| 2613 |
* @return void |
| 2614 |
*/ |
| 2615 |
public function wpstream_add_custom_general_fields() { |
| 2616 |
global $woocommerce, $post; |
| 2617 |
|
| 2618 |
// A new WooCommerce product is still an auto-draft `simple` product |
| 2619 |
// when this hook renders. Emit the type-scoped controls there so |
| 2620 |
// WooCommerce can reveal them after the administrator selects a |
| 2621 |
// WpStream type; the show_if_* classes keep them hidden otherwise. |
| 2622 |
$post_id = isset( $post->ID ) ? (int) $post->ID : 0; |
| 2623 |
$is_new_product = $post_id && 'auto-draft' === get_post_status( $post_id ); |
| 2624 |
if ( ! $is_new_product && ! $this->wpstream_is_wpstream_wc_product_context( $post_id ) ) { |
| 2625 |
return; |
| 2626 |
} |
| 2627 |
// Subscription-based live channel toggle (only when WC Subscriptions is active). |
| 2628 |
if(function_exists('wcs_user_has_subscription')){ |
| 2629 |
echo '<div class="options_group show_if_subscription">'; |
| 2630 |
woocommerce_wp_select( |
| 2631 |
array( |
| 2632 |
'id' => '_subscript_live_event', |
| 2633 |
'label' => __( 'Is a subscription based live channel ?', 'woocommerce' ), |
| 2634 |
'options' => array("yes"=>"yes","no"=>"no", "none" => "none") |
| 2635 |
) |
| 2636 |
); |
| 2637 |
echo '</div>'; |
| 2638 |
} |
| 2639 |
|
| 2640 |
echo '<div class="options_group show_if_live_stream" style="border:none;"></div>'; |
| 2641 |
// VOD video chooser section. |
| 2642 |
echo '<div class="options_group show_if_video_on_demand">'; |
| 2643 |
|
| 2644 |
|
| 2645 |
// Pre-selected video: from the new-video query arg, else the saved _movie_url meta. |
| 2646 |
$selected=''; |
| 2647 |
if( isset( $_GET['new_video_name']) && $_GET['new_video_name']!='' ){ |
| 2648 |
$selected=esc_html($_GET['new_video_name']); |
| 2649 |
} |
| 2650 |
if($selected==''){ |
| 2651 |
$selected= get_post_meta($post->ID,'_movie_url',true); |
| 2652 |
} |
| 2653 |
// Only administrators may assign videos. |
| 2654 |
if( !current_user_can('administrator') ){ |
| 2655 |
print '<div style="margin:10px;">'.esc_html__('You need to be an administrator in order to assign videos','wpstream').'</div>'; |
| 2656 |
}else{ |
| 2657 |
woocommerce_wp_select( |
| 2658 |
array( |
| 2659 |
'id' => '_movie_url', |
| 2660 |
'label' => __( 'Choose video', 'woocommerce' ), |
| 2661 |
'options' => $this->main->wpstream_live_connection->wpstream_get_videos(), |
| 2662 |
'selected'=> true, |
| 2663 |
'value' => $selected |
| 2664 |
) |
| 2665 |
); |
| 2666 |
} |
| 2667 |
|
| 2668 |
|
| 2669 |
|
| 2670 |
echo '</div>'; |
| 2671 |
|
| 2672 |
// "Attach to subscription" multi-select (only when WC Subscriptions is active). |
| 2673 |
if(function_exists('wcs_user_has_subscription')){ |
| 2674 |
$selected_sub=''; |
| 2675 |
echo '<div class="options_group show_if_video_on_demand show_if_live_stream">'; |
| 2676 |
if( isset( $_GET['wpstream_parent_sub']) && $_GET['wpstream_parent_sub']!='' ){ |
| 2677 |
$selected_sub=esc_html($_GET['wpstream_parent_sub']); |
| 2678 |
} |
| 2679 |
if($selected_sub==''){ |
| 2680 |
$selected_sub= get_post_meta($post->ID,'_wpstream_parent_sub',true); |
| 2681 |
} |
| 2682 |
woocommerce_wp_select( |
| 2683 |
array( |
| 2684 |
'id' => '_wpstream_parent_sub', |
| 2685 |
'name' => '_wpstream_parent_sub[]', |
| 2686 |
'label' => __( 'Attach to subscription', 'woocommerce' ), |
| 2687 |
'options' => $this->wpstream_return_subscriptions_created(), |
| 2688 |
'selected'=> true, |
| 2689 |
'value' => $selected_sub, |
| 2690 |
'custom_attributes' => array('multiple' => 'multiple') |
| 2691 |
) |
| 2692 |
); |
| 2693 |
|
| 2694 |
echo '</div>'; |
| 2695 |
|
| 2696 |
} |
| 2697 |
} |
| 2698 |
|
| 2699 |
|
| 2700 |
/** |
| 2701 |
* Build an id => title map of all WooCommerce subscription products, |
| 2702 |
* for the "attach to subscription" selector. Includes a "none" option. |
| 2703 |
* |
| 2704 |
* @return array Map of subscription product ID => title (0 => 'none'). |
| 2705 |
*/ |
| 2706 |
public function wpstream_return_subscriptions_created(){ |
| 2707 |
// Seed with the "none" choice. |
| 2708 |
$return=array('0'=>'none'); |
| 2709 |
|
| 2710 |
// Query all subscription-type products, title-ordered. |
| 2711 |
$args = array( |
| 2712 |
'post_type' => 'product', |
| 2713 |
'posts_per_page' => -1, |
| 2714 |
'orderby' => 'title', |
| 2715 |
'order' => 'ASC', |
| 2716 |
'tax_query' => array( |
| 2717 |
'relation' => 'AND', |
| 2718 |
array( |
| 2719 |
'taxonomy' => 'product_type', |
| 2720 |
'field' => 'slug', |
| 2721 |
'terms' => array( 'subscription'), |
| 2722 |
) |
| 2723 |
) |
| 2724 |
); |
| 2725 |
|
| 2726 |
// Collect each subscription's id => title. |
| 2727 |
$subscriptions = new WP_Query($args); |
| 2728 |
if($subscriptions->have_posts()): |
| 2729 |
while ($subscriptions->have_posts()): $subscriptions->the_post(); |
| 2730 |
$return[ get_the_ID() ] = get_the_title(); |
| 2731 |
endwhile; |
| 2732 |
endif; |
| 2733 |
|
| 2734 |
// Restore the main query and return the map. |
| 2735 |
wp_reset_postdata(); |
| 2736 |
return $return; |
| 2737 |
|
| 2738 |
} |
| 2739 |
|
| 2740 |
|
| 2741 |
|
| 2742 |
|
| 2743 |
|
| 2744 |
|
| 2745 |
/** |
| 2746 |
* Save custom fields |
| 2747 |
* |
| 2748 |
* Persist the WpStream WooCommerce product fields (_movie_url, |
| 2749 |
* _subscript_live_event, _wpstream_parent_sub) on save and reset the |
| 2750 |
* event_passed flag. Hooked to the product save action. |
| 2751 |
* |
| 2752 |
* @since 3.0.1 |
| 2753 |
* @param int $post_id Product ID being saved. |
| 2754 |
* @return void |
| 2755 |
*/ |
| 2756 |
public function wpstream_add_custom_general_fields_save( $post_id ){ |
| 2757 |
// Only handle WpStream product contexts. |
| 2758 |
if ( ! $this->wpstream_is_wpstream_wc_product_context( (int) $post_id ) ) { |
| 2759 |
return; |
| 2760 |
} |
| 2761 |
|
| 2762 |
// A Source Recording owns the cached DRM and hosted-player identity. |
| 2763 |
// Retire that identity before replacing the source so its old key index |
| 2764 |
// is still available to invalidate the lookup caches. |
| 2765 |
if ( |
| 2766 |
has_term( 'video_on_demand', 'product_type', $post_id ) |
| 2767 |
&& ! empty( $_POST['_movie_url'] ) |
| 2768 |
&& is_scalar( $_POST['_movie_url'] ) |
| 2769 |
) { |
| 2770 |
$previous_recording = (string) get_post_meta( $post_id, '_movie_url', true ); |
| 2771 |
$new_recording = sanitize_text_field( wp_unslash( $_POST['_movie_url'] ) ); |
| 2772 |
if ( $new_recording !== $previous_recording ) { |
| 2773 |
$this->wpstream_retire_vod_recording_identity( $post_id ); |
| 2774 |
} |
| 2775 |
} |
| 2776 |
|
| 2777 |
// Meta keys this handler may write. |
| 2778 |
$permited_values = array( |
| 2779 |
'_movie_url', |
| 2780 |
'_subscript_live_event', |
| 2781 |
'_wpstream_parent_sub', |
| 2782 |
|
| 2783 |
); |
| 2784 |
|
| 2785 |
|
| 2786 |
|
| 2787 |
// Reset event_passed and save each allow-listed field. |
| 2788 |
foreach($_POST as $key=>$value){ |
| 2789 |
update_post_meta( $post_id, 'event_passed', 0 ); |
| 2790 |
if( in_array($key, $permited_values) ){ |
| 2791 |
if( !empty( $_POST[$key] ) ){ |
| 2792 |
$key = sanitize_key($key); |
| 2793 |
$value = sanitize_text_field($_POST[$key]); |
| 2794 |
|
| 2795 |
// The parent-subscription field is an array; sanitize each element. |
| 2796 |
if($key=='_wpstream_parent_sub'){ |
| 2797 |
$value= $_POST[$key]; |
| 2798 |
$value = array_map("sanitize_text_field", $value); |
| 2799 |
|
| 2800 |
} |
| 2801 |
update_post_meta( $post_id, $key, $value ); |
| 2802 |
} |
| 2803 |
} |
| 2804 |
} |
| 2805 |
//die(); |
| 2806 |
|
| 2807 |
} |
| 2808 |
|
| 2809 |
/** |
| 2810 |
* Add to cart redirect |
| 2811 |
* |
| 2812 |
* Render the simple add-to-cart template for WpStream product types. |
| 2813 |
* |
| 2814 |
* @since 3.0.1 |
| 2815 |
* @return void |
| 2816 |
*/ |
| 2817 |
public function wpstream_add_to_cart() { |
| 2818 |
wc_get_template( 'single-product/add-to-cart/simple.php' ); |
| 2819 |
} |
| 2820 |
|
| 2821 |
|
| 2822 |
/** |
| 2823 |
* Replace add to cart button |
| 2824 |
* |
| 2825 |
* For live_stream / video_on_demand products, swap the loop add-to-cart |
| 2826 |
* button for a direct shop add-to-cart link. Filter callback. |
| 2827 |
* |
| 2828 |
* @since 3.0.1 |
| 2829 |
* @param string $button Original button HTML. |
| 2830 |
* @param WC_Product $product The product (overwritten by global $product). |
| 2831 |
* @return string The (possibly replaced) button HTML. |
| 2832 |
*/ |
| 2833 |
public function replacing_add_to_cart_button( $button, $product ) { |
| 2834 |
global $product; |
| 2835 |
$product_type = $product->get_type(); |
| 2836 |
|
| 2837 |
// Only WpStream media types get the custom link; others keep the default button. |
| 2838 |
if($product_type==='live_stream' || $product_type=='video_on_demand'){ |
| 2839 |
return $button = '<a class="button" href="'.get_site_url().'/shop/?add-to-cart=' .$product->get_id(). '&quantity=1">' . __( 'Add to Cart', 'woocommerce' ) . '</a>'; |
| 2840 |
}else{ |
| 2841 |
return $button; |
| 2842 |
} |
| 2843 |
} |
| 2844 |
|
| 2845 |
|
| 2846 |
/** |
| 2847 |
* Admin notices |
| 2848 |
* |
| 2849 |
* Print global admin notices: (commented-out) WooCommerce-missing notice |
| 2850 |
* and an error if the PHP cURL extension is unavailable, plus the dismiss |
| 2851 |
* nonce. Echoes HTML. |
| 2852 |
* |
| 2853 |
* @since 3.0.1 |
| 2854 |
* @return void |
| 2855 |
*/ |
| 2856 |
public function wpstream_admin_notice() { |
| 2857 |
global $pagenow; |
| 2858 |
global $typenow; |
| 2859 |
|
| 2860 |
// Stored dismissed-notice flags. |
| 2861 |
$wpstream_notices = get_option('wpstream_notices'); |
| 2862 |
|
| 2863 |
/* |
| 2864 |
if ( !in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { |
| 2865 |
if( !is_array($wpstream_notices) || |
| 2866 |
!isset($wpstream_notices['wpstream_woo_notice']) || |
| 2867 |
( isset($wpstream_notices['wpstream_woo_notice']) && $wpstream_notices['wpstream_woo_notice']!='yes') ){ |
| 2868 |
|
| 2869 |
|
| 2870 |
print '<div class="notice wpstream_notices notice-error is-dismissible" data-notice-type="wpstream_woo_notice" > |
| 2871 |
<p>'.__( 'WpStream Pay-Per-View Live Streaming and VOD only works with WooCommerce - Please enable and activate the WooCommerce plugin if you want to monetize your Live Events or Recorded Videos', 'wpstream' ).'</p> |
| 2872 |
</div>'; |
| 2873 |
} |
| 2874 |
} |
| 2875 |
*/ |
| 2876 |
|
| 2877 |
// Hard requirement: warn when the PHP cURL extension is missing. |
| 2878 |
if( !in_array ('curl', get_loaded_extensions())) { |
| 2879 |
print '<div class="notice notice-error is-dismissible"> |
| 2880 |
<p>'.__( 'The php CURL library is not enabled on your server. WpStream plugin needs this library in order to work. Please address this issue with your hosting provider.', 'wpstream' ).'</p> |
| 2881 |
</div>'; |
| 2882 |
} |
| 2883 |
|
| 2884 |
|
| 2885 |
// Nonce used by the JS that records notice dismissals. |
| 2886 |
$ajax_nonce = wp_create_nonce( "wpstream_notice_nonce" ); |
| 2887 |
print '<input type="hidden" id="wpstream_notice_nonce" value="'.esc_html($ajax_nonce).'"/>'; |
| 2888 |
|
| 2889 |
} |
| 2890 |
|
| 2891 |
/** |
| 2892 |
* Get plugin latest update release date from WordPress.org |
| 2893 |
* @param $plugin_slug |
| 2894 |
* @param $version |
| 2895 |
* |
| 2896 |
* @return bool |
| 2897 |
*/ |
| 2898 |
public function get_plugin_release_date( $plugin_slug, $version = null ) { |
| 2899 |
// This runs on every admin page while an update is pending, so the |
| 2900 |
// answer is cached for 12h — including a negative one, otherwise a |
| 2901 |
// failed lookup would retry per page load. Keyed by version so a |
| 2902 |
// newer release triggers a fresh lookup. |
| 2903 |
$cache_key = 'wpstream_release_date_' . md5( $plugin_slug . '|' . (string) $version ); |
| 2904 |
$cached = get_transient( $cache_key ); |
| 2905 |
if ( false !== $cached ) { |
| 2906 |
// 'none' is the stored form of a negative result. |
| 2907 |
return 'none' === $cached ? false : $cached; |
| 2908 |
} |
| 2909 |
|
| 2910 |
// Query the WordPress.org plugins info API (short timeout: this is |
| 2911 |
// cosmetic notice logic and must never stall the admin). |
| 2912 |
$api_url = 'https://api.wordpress.org/plugins/info/1.0/' . $plugin_slug . '.json'; |
| 2913 |
$response = wp_remote_get( $api_url, array( 'timeout' => 3 ) ); |
| 2914 |
|
| 2915 |
// Resolve the answer (false = no usable date), then cache it once. |
| 2916 |
$release_date = false; |
| 2917 |
|
| 2918 |
if ( ! is_wp_error( $response ) ) { |
| 2919 |
// Decode the JSON body; a date needs a versions list to validate against. |
| 2920 |
$data = json_decode( wp_remote_retrieve_body( $response ), true ); |
| 2921 |
|
| 2922 |
if ( $data && isset( $data['versions'] ) ) { |
| 2923 |
// no version provided, get the latest version |
| 2924 |
if ( ! $version ) { |
| 2925 |
$version = $data['version']; |
| 2926 |
} |
| 2927 |
|
| 2928 |
// The version must exist in the versions array. |
| 2929 |
// (note: last_updated is the plugin's overall date, not per-version) |
| 2930 |
if ( isset( $data['versions'][ $version ] ) && isset( $data['last_updated'] ) ) { |
| 2931 |
$release_date = date( 'Y-m-d', strtotime( $data['last_updated'] ) ); |
| 2932 |
} |
| 2933 |
} |
| 2934 |
} |
| 2935 |
|
| 2936 |
set_transient( $cache_key, false === $release_date ? 'none' : $release_date, 12 * HOUR_IN_SECONDS ); |
| 2937 |
return $release_date; |
| 2938 |
} |
| 2939 |
|
| 2940 |
/** |
| 2941 |
* Adds notice for the WpStream update availability |
| 2942 |
* when the update is not older than 30 days |
| 2943 |
*/ |
| 2944 |
public function wpstream_plugin_update_available_notice() { |
| 2945 |
// Only show to users who can update plugins. |
| 2946 |
if (!current_user_can('update_plugins')) { |
| 2947 |
return; |
| 2948 |
} |
| 2949 |
|
| 2950 |
// Look up a pending update for this plugin in WP's transient. |
| 2951 |
$plugin_slug = 'wpstream/wpstream.php'; |
| 2952 |
$update_data = get_site_transient('update_plugins'); |
| 2953 |
|
| 2954 |
if ( is_object( $update_data ) && |
| 2955 |
property_exists( $update_data, 'response' ) && |
| 2956 |
is_array($update_data->response) && |
| 2957 |
key_exists($plugin_slug, $update_data->response) |
| 2958 |
) { |
| 2959 |
$new_version = $update_data->response[$plugin_slug]->new_version; |
| 2960 |
|
| 2961 |
// Suppress the notice for very fresh releases (grace period). |
| 2962 |
$release_date = $this->get_plugin_release_date( 'wpstream', $new_version ); |
| 2963 |
|
| 2964 |
if ( $release_date ) { |
| 2965 |
$days_since_release = ( time() - strtotime( $release_date ) ) / DAY_IN_SECONDS; |
| 2966 |
|
| 2967 |
// if there's an update newer than 7 days, do not show the notice |
| 2968 |
if ( $days_since_release < 7 ) { |
| 2969 |
return; |
| 2970 |
} |
| 2971 |
} |
| 2972 |
// Build the one-click update URL (nonce-protected) and print the notice. |
| 2973 |
$update_url = wp_nonce_url( |
| 2974 |
self_admin_url('update.php?action=upgrade-plugin&plugin=' . urlencode($plugin_slug)), |
| 2975 |
'upgrade-plugin_' . $plugin_slug |
| 2976 |
); |
| 2977 |
|
| 2978 |
echo '<div class="notice notice-warning is-dismissible">'; |
| 2979 |
echo '<p><strong>' . __('WpStream Plugin Update Available', 'wpstream') . '</strong></p>'; |
| 2980 |
echo '<p>' . sprintf( |
| 2981 |
__('Version %s is available. Please update to the latest version for new features and security improvements.', 'wpstream'), |
| 2982 |
'<strong>' . esc_html($new_version) . '</strong>' |
| 2983 |
) . '</p>'; |
| 2984 |
echo '<p><a href="' . esc_url($update_url) . '" class="button button-primary">' . |
| 2985 |
__('Update Now', 'wpstream') . '</a></p>'; |
| 2986 |
echo '</div>'; |
| 2987 |
} |
| 2988 |
} |
| 2989 |
|
| 2990 |
/** |
| 2991 |
* Admin notices |
| 2992 |
* |
| 2993 |
* AJAX handler that records a dismissed notice: marks the posted notice |
| 2994 |
* type as 'yes' in the wpstream_notices option so it stops showing. |
| 2995 |
* |
| 2996 |
* @since 3.0.1 |
| 2997 |
* @return void Ends the request with die(). |
| 2998 |
*/ |
| 2999 |
public function wpstream_update_cache_notice(){ |
| 3000 |
|
| 3001 |
// CSRF gate: the dismiss JS sends the nonce printed next to the notice. |
| 3002 |
check_ajax_referer( 'wpstream_notice_nonce', 'security' ); |
| 3003 |
// Dismissals are stored in a site-wide option, so require admin rights. |
| 3004 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 3005 |
die(); |
| 3006 |
} |
| 3007 |
|
| 3008 |
// Which notice was dismissed. |
| 3009 |
$notice_type = esc_html($_POST['notice_type']); |
| 3010 |
$notices = get_option('wp_stream_notices'); |
| 3011 |
|
| 3012 |
// Normalise to an array. |
| 3013 |
if(! is_array($notices) ){ |
| 3014 |
$notices=array(); |
| 3015 |
} |
| 3016 |
|
| 3017 |
// Flag this notice as dismissed and persist. |
| 3018 |
$notices[$notice_type]='yes'; |
| 3019 |
|
| 3020 |
update_option('wpstream_notices',$notices); |
| 3021 |
die(); |
| 3022 |
} |
| 3023 |
|
| 3024 |
|
| 3025 |
|
| 3026 |
|
| 3027 |
/** |
| 3028 |
* Activate metaboxes for Streaming controls on sidebar |
| 3029 |
* |
| 3030 |
* Register the "Live Streaming" sidebar metabox on free channel posts, and |
| 3031 |
* also on WooCommerce products that are live_stream (or a subscription |
| 3032 |
* flagged as a live event). Hooked to add_meta_boxes. |
| 3033 |
* |
| 3034 |
* @since 3.0.1 |
| 3035 |
* @return void |
| 3036 |
*/ |
| 3037 |
public function wpstream_startstreaming_sidebar_meta() { |
| 3038 |
return $this->main->get_live_channel_presentation()->register_metaboxes(); |
| 3039 |
} |
| 3040 |
public function wpstream_start_stream_meta(){ |
| 3041 |
return $this->main->get_live_channel_presentation()->render_sidebar(); |
| 3042 |
} |
| 3043 |
public function wpstream_is_basic_streaming_mode(){ |
| 3044 |
return $this->main->quota_manager->is_basic_streaming_mode( null, 'wpstream_is_basic_streaming_mode' ); |
| 3045 |
} |
| 3046 |
|
| 3047 |
/** |
| 3048 |
* Whether the account's plan meters streaming by hours (vs. data). |
| 3049 |
* |
| 3050 |
* @return bool |
| 3051 |
*/ |
| 3052 |
public function wpstream_is_use_streaming_hours() { |
| 3053 |
$pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_start_channel' ); |
| 3054 |
return $this->main->quota_manager->uses_streaming_hours( $pack_details ); |
| 3055 |
} |
| 3056 |
|
| 3057 |
/** |
| 3058 |
* Cached-only flags for start_streaming.js localization (no API on cold cache). |
| 3059 |
* |
| 3060 |
* @return array{is_basic_streaming: bool, use_streaming_hours: bool} |
| 3061 |
*/ |
| 3062 |
public function wpstream_get_start_streaming_localization_flags() { |
| 3063 |
return $this->main->get_live_channel_presentation()->get_localization_flags(); |
| 3064 |
} |
| 3065 |
|
| 3066 |
|
| 3067 |
|
| 3068 |
/** |
| 3069 |
* Register a hidden dashboard page used as the onboarding wizard endpoint. |
| 3070 |
* |
| 3071 |
* @return void |
| 3072 |
*/ |
| 3073 |
public function add_dashboard_page() { |
| 3074 |
$this->main->get_onboarding()->render_part( 'dashboard_page' ); |
| 3075 |
} |
| 3076 |
|
| 3077 |
|
| 3078 |
|
| 3079 |
|
| 3080 |
/** |
| 3081 |
* Bootstrap the full-screen onboarding wizard screen (non-AJAX requests). |
| 3082 |
* |
| 3083 |
* @return void |
| 3084 |
*/ |
| 3085 |
public function wpstream_load_onboarding_wizard() { |
| 3086 |
$this->main->get_onboarding()->render_part( 'standalone' ); |
| 3087 |
} |
| 3088 |
|
| 3089 |
|
| 3090 |
/* |
| 3091 |
* Add on boarding to footer |
| 3092 |
*/ |
| 3093 |
/** |
| 3094 |
* On the onboarding admin page, print the onboarding modal into the footer. |
| 3095 |
* |
| 3096 |
* @return void |
| 3097 |
*/ |
| 3098 |
public function wpstream_admin_footer_onboarding(){ |
| 3099 |
$this->main->get_onboarding()->render_part( 'page_footer' ); |
| 3100 |
} |
| 3101 |
|
| 3102 |
|
| 3103 |
/* |
| 3104 |
* On Board Display |
| 3105 |
* |
| 3106 |
*/ |
| 3107 |
/** |
| 3108 |
* Render the Quick Start landing screen (logo, intro, "Start the Guide" |
| 3109 |
* button and the script that opens the onboarding wizard). Echoes HTML. |
| 3110 |
* |
| 3111 |
* @return void |
| 3112 |
*/ |
| 3113 |
public function wpstream_pre_onboard_display(){ |
| 3114 |
echo $this->main->get_onboarding()->render(); |
| 3115 |
} |
| 3116 |
|
| 3117 |
|
| 3118 |
|
| 3119 |
|
| 3120 |
|
| 3121 |
|
| 3122 |
|
| 3123 |
|
| 3124 |
/* |
| 3125 |
* |
| 3126 |
* On Boarding Content |
| 3127 |
* |
| 3128 |
*/ |
| 3129 |
/** |
| 3130 |
* Emit the full onboarding wizard markup by rendering each step in order |
| 3131 |
* (header, account, path choice, live/VOD branches, footer). Echoes HTML. |
| 3132 |
* |
| 3133 |
* @return void |
| 3134 |
*/ |
| 3135 |
public function wpstream_onboard_display() { |
| 3136 |
$this->main->get_onboarding()->render_part( 'wizard' ); |
| 3137 |
} |
| 3138 |
|
| 3139 |
|
| 3140 |
/* |
| 3141 |
* |
| 3142 |
* On Boarding Step 1 - the login/register |
| 3143 |
* |
| 3144 |
*/ |
| 3145 |
/** |
| 3146 |
* Onboarding step 1: WpStream account login/registration UI, including the |
| 3147 |
* ALTCHA captcha widget (on HTTPS) and a hidden generated registration |
| 3148 |
* password. Emits a marker div when a token already exists. Echoes HTML. |
| 3149 |
* |
| 3150 |
* @return void |
| 3151 |
*/ |
| 3152 |
public function wpstream_onboarding_step1(){ |
| 3153 |
$this->main->get_onboarding()->render_part( 'account' ); |
| 3154 |
} |
| 3155 |
|
| 3156 |
public function randomPassword() { |
| 3157 |
return $this->main->get_onboarding()->render_part( 'password' ); |
| 3158 |
} |
| 3159 |
|
| 3160 |
public function wpstream_onboarding_step2(){ |
| 3161 |
$this->main->get_onboarding()->render_part( 'path' ); |
| 3162 |
} |
| 3163 |
|
| 3164 |
public function wpstream_onboarding_step3_live_streaming(){ |
| 3165 |
$this->main->get_onboarding()->render_part( 'live' ); |
| 3166 |
} |
| 3167 |
|
| 3168 |
public function wpstream_onboarding_step_3_A_live_streaming_free_view(){ |
| 3169 |
$this->main->get_onboarding()->render_part( 'live_free' ); |
| 3170 |
} |
| 3171 |
|
| 3172 |
public function wpstream_onboarding_step_3_B_live_streaming_pay_per_view(){ |
| 3173 |
$this->main->get_onboarding()->render_part( 'live_ppv' ); |
| 3174 |
} |
| 3175 |
|
| 3176 |
public function wpstream_onboarding_woo_warning(){ |
| 3177 |
$this->main->get_onboarding()->render_part( 'woo_warning' ); |
| 3178 |
} |
| 3179 |
|
| 3180 |
public function wpstream_onboarding_step4_vod(){ |
| 3181 |
$this->main->get_onboarding()->render_part( 'vod' ); |
| 3182 |
} |
| 3183 |
|
| 3184 |
public function wpstream_onboarding_step_4_free_vod(){ |
| 3185 |
$this->main->get_onboarding()->render_part( 'vod_free' ); |
| 3186 |
} |
| 3187 |
|
| 3188 |
public function wpstream_obboarding_file_warning(){ |
| 3189 |
$this->main->get_onboarding()->render_part( 'file_warning' ); |
| 3190 |
} |
| 3191 |
|
| 3192 |
public function wpstream_onboarding_step_4_ppv_vod(){ |
| 3193 |
$this->main->get_onboarding()->render_part( 'vod_ppv' ); |
| 3194 |
} |
| 3195 |
|
| 3196 |
public function onboarding_wizard_header() { |
| 3197 |
$this->main->get_onboarding()->render_part( 'wizard_header' ); |
| 3198 |
} |
| 3199 |
|
| 3200 |
|
| 3201 |
|
| 3202 |
|
| 3203 |
|
| 3204 |
/* |
| 3205 |
* |
| 3206 |
* On Boarding Footer |
| 3207 |
* |
| 3208 |
*/ |
| 3209 |
/** |
| 3210 |
* Close the onboarding wizard wrapper and print its modal background. Echoes HTML. |
| 3211 |
* |
| 3212 |
* @return void |
| 3213 |
*/ |
| 3214 |
public function onboarding_wizard_footer() { |
| 3215 |
$this->main->get_onboarding()->render_part( 'wizard_footer' ); |
| 3216 |
} |
| 3217 |
|
| 3218 |
|
| 3219 |
|
| 3220 |
/* |
| 3221 |
* |
| 3222 |
* On Boarding create PPV channel |
| 3223 |
* |
| 3224 |
*/ |
| 3225 |
|
| 3226 |
|
| 3227 |
/** |
| 3228 |
* AJAX: create a Pay-Per-View live channel during onboarding. |
| 3229 |
* |
| 3230 |
* Inserts a WooCommerce `product`, sets its price, marks it live_stream, |
| 3231 |
* and returns the edit link (with onboarding query args) as JSON. |
| 3232 |
* Nonce- and administrator-gated. |
| 3233 |
* |
| 3234 |
* @return void Ends with die(). |
| 3235 |
*/ |
| 3236 |
public function wpstream_on_board_create_channel_ppv(){ |
| 3237 |
return $this->main->get_onboarding()->handle_ajax( 'wpstream_on_board_create_channel_ppv' ); |
| 3238 |
} |
| 3239 |
|
| 3240 |
/* |
| 3241 |
* |
| 3242 |
* On Boarding create channel |
| 3243 |
* |
| 3244 |
*/ |
| 3245 |
|
| 3246 |
|
| 3247 |
/** |
| 3248 |
* AJAX: create a Free-To-View live channel during onboarding. |
| 3249 |
* |
| 3250 |
* Inserts a `wpstream_product` post and returns its edit link (with |
| 3251 |
* onboarding query args) as JSON. Nonce- and administrator-gated. |
| 3252 |
* |
| 3253 |
* @return void Ends with die(). |
| 3254 |
*/ |
| 3255 |
public function wpstream_on_board_create_channel(){ |
| 3256 |
return $this->main->get_onboarding()->handle_ajax( 'wpstream_on_board_create_channel' ); |
| 3257 |
} |
| 3258 |
|
| 3259 |
/* |
| 3260 |
* |
| 3261 |
* On Boarding create free vod |
| 3262 |
* |
| 3263 |
*/ |
| 3264 |
/** |
| 3265 |
* AJAX: create a Free-To-View VOD during onboarding. |
| 3266 |
* |
| 3267 |
* Inserts a `wpstream_product_vod` post, links the chosen recording, and |
| 3268 |
* returns the edit link (with onboarding query args). Nonce/admin gated. |
| 3269 |
* |
| 3270 |
* @return void Ends with die(). |
| 3271 |
*/ |
| 3272 |
public function wpstream_on_board_create_free_vod(){ |
| 3273 |
return $this->main->get_onboarding()->handle_ajax( 'wpstream_on_board_create_free_vod' ); |
| 3274 |
} |
| 3275 |
|
| 3276 |
|
| 3277 |
/* |
| 3278 |
* |
| 3279 |
* On Boarding create ppv vod |
| 3280 |
* |
| 3281 |
*/ |
| 3282 |
|
| 3283 |
|
| 3284 |
/** |
| 3285 |
* AJAX: create a Pay-Per-View VOD during onboarding. |
| 3286 |
* |
| 3287 |
* Inserts a WooCommerce `product`, sets its price, links the recording, |
| 3288 |
* marks it video_on_demand, and returns the edit link (with onboarding |
| 3289 |
* query args). Nonce- and administrator-gated. |
| 3290 |
* |
| 3291 |
* @return void Ends with die(). |
| 3292 |
*/ |
| 3293 |
public function wpstream_on_board_create_ppv_vod(){ |
| 3294 |
return $this->main->get_onboarding()->handle_ajax( 'wpstream_on_board_create_ppv_vod' ); |
| 3295 |
} |
| 3296 |
|
| 3297 |
|
| 3298 |
/* |
| 3299 |
* |
| 3300 |
* On Boarding login |
| 3301 |
* |
| 3302 |
*/ |
| 3303 |
/** |
| 3304 |
* AJAX: log in to WpStream during onboarding. |
| 3305 |
* |
| 3306 |
* Stores the submitted credentials, clears the token cache, then attempts |
| 3307 |
* to fetch a token; returns success/failure JSON without ever exposing the |
| 3308 |
* token to the client. Nonce- and administrator-gated. |
| 3309 |
* |
| 3310 |
* @return void Ends with die(). |
| 3311 |
*/ |
| 3312 |
public function wpstream_on_board_login(){ |
| 3313 |
return $this->main->get_onboarding()->handle_ajax( 'wpstream_on_board_login' ); |
| 3314 |
} |
| 3315 |
|
| 3316 |
/** |
| 3317 |
* AJAX proxy: fetch a captcha challenge from the baker API and return it. |
| 3318 |
* Used on HTTP sites where the Altcha widget cannot run (requires Web Crypto / HTTPS). |
| 3319 |
* The JS side solves the PoW locally and sends back the full base64 Altcha payload. |
| 3320 |
* Logged-in administrators only: every call proxies an outbound request, so the |
| 3321 |
* endpoint is gated like the rest of the onboarding wizard. |
| 3322 |
*/ |
| 3323 |
public function wpstream_get_captcha_challenge() { |
| 3324 |
return $this->main->get_onboarding()->handle_ajax( 'wpstream_get_captcha_challenge' ); |
| 3325 |
} |
| 3326 |
|
| 3327 |
/* |
| 3328 |
* |
| 3329 |
* On Boarding login |
| 3330 |
* |
| 3331 |
*/ |
| 3332 |
/** |
| 3333 |
* AJAX: register a new WpStream account during onboarding. |
| 3334 |
* |
| 3335 |
* Validates the email/password, requires an ALTCHA solution, calls the |
| 3336 |
* account-create API, and on success stores the credentials and fetches a |
| 3337 |
* token. Returns JSON. Nonce- and administrator-gated. |
| 3338 |
* |
| 3339 |
* @return void Ends with die(). |
| 3340 |
*/ |
| 3341 |
public function wpstream_on_board_register(){ |
| 3342 |
return $this->main->get_onboarding()->handle_ajax( 'wpstream_on_board_register' ); |
| 3343 |
} |
| 3344 |
|
| 3345 |
|
| 3346 |
/* |
| 3347 |
* |
| 3348 |
* Validate for register |
| 3349 |
* |
| 3350 |
*/ |
| 3351 |
/** |
| 3352 |
* Validate the onboarding registration email + password. |
| 3353 |
* |
| 3354 |
* Checks for a non-empty, well-formed email whose domain has DNS records, |
| 3355 |
* and a password of at least 5 characters. |
| 3356 |
* |
| 3357 |
* @param string $wpstream_register_email Submitted email. |
| 3358 |
* @param string $wpstream_register_password Submitted password. |
| 3359 |
* @return array{success: bool, message?: string} Validation result. |
| 3360 |
*/ |
| 3361 |
public function wpstream_validate_onboard_register($wpstream_register_email,$wpstream_register_password){ |
| 3362 |
return $this->main->get_onboarding()->validate_registration( $wpstream_register_email, $wpstream_register_password ); |
| 3363 |
} |
| 3364 |
|
| 3365 |
/** |
| 3366 |
* Handle the AJAX request to initiate a multipart upload |
| 3367 |
* |
| 3368 |
* Validates the request, builds a clean filename, and asks the cloud API to |
| 3369 |
* open a multipart S3 upload; returns the upload id and per-part pre-signed |
| 3370 |
* URLs as JSON. Nonce- and administrator-gated. |
| 3371 |
* |
| 3372 |
* @since 3.0.1 |
| 3373 |
* @return void Responds via wp_send_json_*. |
| 3374 |
*/ |
| 3375 |
public function handle_initiate_multipart_upload() { |
| 3376 |
check_ajax_referer( 'wpstream_multipart_upload_nonce', 'security' ); |
| 3377 |
|
| 3378 |
// Security check - only admins can do this |
| 3379 |
if (!current_user_can('administrator')) { |
| 3380 |
wp_send_json_error('Unauthorized access'); |
| 3381 |
return; |
| 3382 |
} |
| 3383 |
|
| 3384 |
// Get file details from request |
| 3385 |
$file_name = sanitize_text_field($_POST['file_name']); |
| 3386 |
$file_size = intval($_POST['file_size']); |
| 3387 |
$content_type = sanitize_text_field($_POST['content_type']); |
| 3388 |
$num_parts = intval($_POST['parts']); |
| 3389 |
|
| 3390 |
// Reject incomplete/invalid file metadata. |
| 3391 |
if (empty($file_name) || $file_size <= 0 || $num_parts <= 0) { |
| 3392 |
wp_send_json_error('Invalid file information'); |
| 3393 |
return; |
| 3394 |
} |
| 3395 |
|
| 3396 |
// The part count is caller-supplied: refuse a split the backend can |
| 3397 |
// never assemble before spending a cloud call on it. |
| 3398 |
if ($num_parts > self::MULTIPART_MAX_PARTS) { |
| 3399 |
wp_send_json_error( |
| 3400 |
sprintf( |
| 3401 |
/* translators: 1: requested part count, 2: maximum part count */ |
| 3402 |
__('The file would be split into %1$d parts but at most %2$d are allowed. Reload the page to get the current uploader and try again.', 'wpstream'), |
| 3403 |
$num_parts, |
| 3404 |
self::MULTIPART_MAX_PARTS |
| 3405 |
) |
| 3406 |
); |
| 3407 |
return; |
| 3408 |
} |
| 3409 |
|
| 3410 |
// Prepare a clean filename (similar to the standard upload process) |
| 3411 |
// Keep the extension, slugify the base name (spaces -> _, strip non-word chars). |
| 3412 |
$file_name_array = explode(".", $file_name); |
| 3413 |
$file_extension = $file_name_array[count($file_name_array) - 1]; |
| 3414 |
$temp_file_name = $file_name_array[0]; |
| 3415 |
$temp_file_name = str_replace(' ', '_', $temp_file_name); |
| 3416 |
$temp_file_name = preg_replace('/\W/', '', $temp_file_name); |
| 3417 |
$clean_file_name = $temp_file_name . '.' . $file_extension; |
| 3418 |
|
| 3419 |
// Make API call to initiate multipart upload; the auth token is |
| 3420 |
// injected by the transport (not-connected surfaces as a WP_Error |
| 3421 |
// whose message is the legacy "Not connected to WPStream service"). |
| 3422 |
$api_params = array( |
| 3423 |
'size' => $file_size, |
| 3424 |
'name' => $clean_file_name, |
| 3425 |
'content_type' => $content_type, |
| 3426 |
'parts' => $num_parts |
| 3427 |
); |
| 3428 |
|
| 3429 |
$response_data = $this->main->wpstream_live_connection->authorized_request('video/upload', $api_params); |
| 3430 |
|
| 3431 |
// Bubble up any API error. |
| 3432 |
if (is_wp_error($response_data)) { |
| 3433 |
wp_send_json_error($response_data->get_error_message()); |
| 3434 |
return; |
| 3435 |
} |
| 3436 |
if (!isset($response_data['success']) || $response_data['success'] !== true) { |
| 3437 |
$error_message = isset($response_data['error']) ? $response_data['error'] : 'Failed to initiate multipart upload'; |
| 3438 |
wp_send_json_error($error_message); |
| 3439 |
return; |
| 3440 |
} |
| 3441 |
|
| 3442 |
// Return the upload ID and pre-signed URLs for each part |
| 3443 |
wp_send_json_success($response_data); |
| 3444 |
} |
| 3445 |
|
| 3446 |
/** |
| 3447 |
* Handle the AJAX request to complete a multipart upload |
| 3448 |
* |
| 3449 |
* Validates the request and tells the cloud API to finalize the multipart S3 |
| 3450 |
* upload for the given parts/handle. Nonce- and administrator-gated. |
| 3451 |
* |
| 3452 |
* @since 3.0.1 |
| 3453 |
* @return void Responds via wp_send_json_*. |
| 3454 |
*/ |
| 3455 |
public function handle_complete_multipart_upload() { |
| 3456 |
check_ajax_referer( 'wpstream_multipart_upload_nonce', 'security' ); |
| 3457 |
|
| 3458 |
// Security check - only admins can do this |
| 3459 |
if (!current_user_can('administrator')) { |
| 3460 |
wp_send_json_error('Unauthorized access'); |
| 3461 |
return; |
| 3462 |
} |
| 3463 |
|
| 3464 |
// Get completion details. The client sends 'parts' as the plain part |
| 3465 |
// COUNT (not a part list), so parse it as a positive integer. |
| 3466 |
$parts = isset($_POST['parts']) ? absint(wp_unslash($_POST['parts'])) : 0; |
| 3467 |
$file_name = sanitize_text_field($_POST['file_name']); |
| 3468 |
$handle = sanitize_text_field($_POST['handle']); |
| 3469 |
|
| 3470 |
// Reject incomplete completion metadata: no parts uploaded or no file name. |
| 3471 |
if ($parts < 1 || empty($file_name)) { |
| 3472 |
wp_send_json_error('Invalid completion information'); |
| 3473 |
return; |
| 3474 |
} |
| 3475 |
|
| 3476 |
// Send the finalize ("complete") request with the uploaded part list; |
| 3477 |
// the auth token is injected by the transport (not-connected surfaces |
| 3478 |
// as a WP_Error whose message is the legacy "Not connected to WPStream |
| 3479 |
// service"). |
| 3480 |
$api_params = array( |
| 3481 |
'parts' => $parts, |
| 3482 |
'name' => $file_name, |
| 3483 |
'handle' => $handle, |
| 3484 |
'action' => 'complete' |
| 3485 |
); |
| 3486 |
|
| 3487 |
$response_data = $this->main->wpstream_live_connection->authorized_request('video/upload', $api_params); |
| 3488 |
|
| 3489 |
// Bubble up any API error. |
| 3490 |
if (is_wp_error($response_data)) { |
| 3491 |
wp_send_json_error($response_data->get_error_message()); |
| 3492 |
return; |
| 3493 |
} |
| 3494 |
if (!isset($response_data['success']) || $response_data['success'] !== true) { |
| 3495 |
$error_message = isset($response_data['error']) ? $response_data['error'] : 'Failed to complete multipart upload'; |
| 3496 |
wp_send_json_error($error_message); |
| 3497 |
return; |
| 3498 |
} |
| 3499 |
|
| 3500 |
/** |
| 3501 |
* Fires after the cloud accepted a completed multipart VOD upload. |
| 3502 |
* |
| 3503 |
* @since 4.14.0 |
| 3504 |
* |
| 3505 |
* @param string $file_name Uploaded file name. |
| 3506 |
* @param string $handle Upload handle issued at initiation. |
| 3507 |
* @param int $parts Number of parts uploaded. |
| 3508 |
*/ |
| 3509 |
do_action( 'wpstream_multipart_upload_completed', $file_name, $handle, $parts ); |
| 3510 |
|
| 3511 |
// Return success |
| 3512 |
wp_send_json_success(); |
| 3513 |
} |
| 3514 |
|
| 3515 |
/** |
| 3516 |
* AJAX: update the WpStream plugin in place from the Settings/Support tab. |
| 3517 |
* |
| 3518 |
* Loads the upgrader APIs, initializes WP_Filesystem, runs the plugin |
| 3519 |
* upgrade, re-activates the plugin, and returns success/error JSON. |
| 3520 |
* Requires the update_plugins capability. |
| 3521 |
* |
| 3522 |
* @return void Responds via wp_send_json_*. |
| 3523 |
*/ |
| 3524 |
public function wpstream_settings_tab_update_plugin() { |
| 3525 |
// CSRF gate: the update button JS sends the settings-page nonce as 'security'. |
| 3526 |
check_ajax_referer( 'wpstream-settings-nonce', 'security' ); |
| 3527 |
// Capability gate. |
| 3528 |
if ( !current_user_can( 'update_plugins' ) ) { |
| 3529 |
wp_send_json_error( __( 'Not enough permissions to make this change', 'wpstream' ) ); |
| 3530 |
} |
| 3531 |
|
| 3532 |
// Load the core upgrade/filesystem APIs. |
| 3533 |
include_once ABSPATH . 'wp-admin/includes/plugin-install.php'; |
| 3534 |
include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; |
| 3535 |
include_once ABSPATH . 'wp-admin/includes/file.php'; |
| 3536 |
|
| 3537 |
// Initialize the filesystem abstraction (needed to write plugin files). |
| 3538 |
$credentials = request_filesystem_credentials(''); |
| 3539 |
if ( !WP_Filesystem( $credentials ) ) { |
| 3540 |
wp_send_json_error( __( 'Failed to connect to the filesystem', 'wpstream' ) ); |
| 3541 |
} |
| 3542 |
|
| 3543 |
// Run the in-place upgrade for this plugin, then re-activate it. |
| 3544 |
$upgrader = new Plugin_Upgrader(new Automatic_Upgrader_Skin()); |
| 3545 |
$plugin_path = plugin_basename( WPSTREAM_PLUGIN_PATH . 'wpstream.php' ); |
| 3546 |
$result = $upgrader->upgrade( $plugin_path ); |
| 3547 |
activate_plugin( $plugin_path ); |
| 3548 |
|
| 3549 |
if ( is_wp_error( $result ) ) { |
| 3550 |
wp_send_json_error( __( 'Update failed due to', 'wpstream' ) . $result->get_error_message() ); |
| 3551 |
} |
| 3552 |
|
| 3553 |
wp_send_json_success(); |
| 3554 |
} |
| 3555 |
} |
| 3556 |
|