PluginProbe
WpStream – Live Streaming, Video on Demand, Pay Per View / 4.13.2
WpStream – Live Streaming, Video on Demand, Pay Per View v4.13.2
4.14.1 4.14.0 4.13.2 4.13.1 4.13 4.12.5 4.12.4 4.12.3 4.12.2 4.12.1 4.12 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5 4.5.1 4.5.11 4.5.11.1 4.5.11.2 4.5.11.4 4.5.11.5 4.5.11.6 All 181 releases
wpstream / admin / class-wpstream-admin.php

class-wpstream-admin.php in WpStream – Live Streaming, Video on Demand, Pay Per View 4.13.2, at admin/class-wpstream-admin.php

5,517 lines 285.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 * The admin-specific functionality of the plugin.
15 *
16 * This is the admin "god class": it wires up the whole wp-admin surface of the
17 * plugin. Responsibilities include registering admin CSS/JS, building the
18 * top-level WpStream menu and its sub-pages (Credentials, Channels, Recordings,
19 * Settings, Quick Start), rendering channel/settings/onboarding screens and
20 * their modal dialogs, defining the per-event global streaming options, hooking
21 * into WooCommerce to register the custom stream/VOD product types and their
22 * pricing/metabox behaviour, rendering post metaboxes, driving the multipart S3
23 * upload AJAX endpoints, and emitting the various admin notices (plugin update,
24 * cache flush, theme, etc.). Many methods echo HTML directly.
25 *
26 * @package Wpstream
27 * @subpackage Wpstream/admin
28 * @author wpstream <office@wpstream.net>
29 */
30 class Wpstream_Admin {
31
32
33 /**
34 * Store plugin main class to allow public access.
35 *
36 * @since 20180622
37 * @var object The main class.
38 */
39 public $main;
40
41
42 /**
43 * The ID of this plugin.
44 *
45 * @since 3.0.1
46 * @access private
47 * @var string $plugin_name The ID of this plugin.
48 */
49 private $plugin_name;
50
51 /**
52 * The version of this plugin.
53 *
54 * @since 3.0.1
55 * @access private
56 * @var string $version The current version of this plugin.
57 */
58 private $version;
59
60 /**
61 * @var array Definitions of the per-event "global" streaming options (record,
62 * view count, autoplay, encryption, etc.). Each entry holds a
63 * translated label, a help "details" string, and a default value.
64 * Populated lazily on `init` by load_global_event_options().
65 */
66 public $global_event_options ;
67
68
69
70
71
72 /**
73 * Initialize the class and set its properties.
74 *
75 * @since 3.0.1
76 * @param string $plugin_name The name (ID) of this plugin.
77 * @param string $version The version of this plugin.
78 * @param object $plugin_main The main plugin class instance, kept for public access.
79 * @return void
80 */
81 public function __construct( $plugin_name, $version,$plugin_main ) {
82
83 // Remember the plugin identity and the main class for later use.
84 $this->plugin_name = $plugin_name;
85 $this->version = $version;
86 $this->main = $plugin_main;
87
88 // Build the translatable global event options once WP (and its i18n) is ready.
89 add_action('init', array($this, 'load_global_event_options'));
90 }
91
92 /**
93 * Populate $global_event_options with the translatable per-event settings.
94 *
95 * Deferred to the `init` hook so the esc_html__() translations resolve
96 * against a loaded text domain.
97 *
98 * @return void
99 */
100 public function load_global_event_options() {
101 // Each key maps to a label/details/default triple used when rendering the event option toggles.
102 $this->global_event_options = array(
103 'record' => array(
104 'name' => esc_html__('Record Live Stream','wpstream'),
105 'details' => esc_html__('If enabled, live streams will be recorded and saved to your library.','wpstream'),
106 'defaults' => 'no',
107 ),
108 'view_count' => array(
109 'name' => esc_html__('Display Viewer Count','wpstream'),
110 'details' => esc_html__('If enabled, the live viewer count will show up in the player.','wpstream'),
111 'defaults' => 'yes',
112 ),
113 'domain_lock' => array(
114 'name' => esc_html__('Lock To Website','wpstream'),
115 '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') ),
116 'defaults' => 'no',
117 ),
118 'autoplay' => array(
119 'name' => esc_html__('Autoplay','wpstream'),
120 'details' => esc_html__('If enabled, live video will attempt to start playing automatically. This is only achievable in some browsers.','wpstream'),
121 'defaults' => 'yes',
122 ),
123 'mute' => array(
124 'name' => esc_html__('Start Muted','wpstream'),
125 'details' => esc_html__('If enabled, live video will start muted. This may increase the rate of autoplay in some browsers. ','wpstream'),
126 'defaults' => 'no',
127 ),
128 'low_latency' => array(
129 'name' => esc_html__('Low Latency (beta)','wpstream'),
130 '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'),
131 'defaults' => 'no',
132 ),
133 'adaptive_bitrate' => array(
134 'name' => esc_html__('Adaptive Bitrate (beta)','wpstream'),
135 'details' => esc_html__('Ensures a smooth and uninterrupted viewing experience by adjusting video quality for viewers with reduced network speed or device capabilities.','wpstream'),
136 'defaults' => 'no',
137 ),
138 'encrypt' =>array(
139 'name' => esc_html__('Encrypt Live Stream','wpstream'),
140 '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'),
141 'defaults' => 'no',
142 ),
143 'ses_encrypt'=>array(
144 'name' => esc_html__('Use Sessions with Encryption','wpstream'),
145 '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'),
146 'defaults' => 'no',
147 ),
148 // 'autostart' =>array(
149 // 'name' => esc_html__('Auto TURN ON','wpstream'),
150 // 'details' => esc_html__('If enabled, channel will TURN ON automatically when broadcasting with an External Streaming App (RTMP Encoder/Broadcaster)','wpstream'),
151 // 'defaults' => 'no',
152 // ),
153 );
154 }
155
156 /**
157 * Register and enqueue the plugin's admin-area stylesheets.
158 *
159 * Hooked (via Wpstream_Loader) to admin_enqueue_scripts.
160 *
161 * @since 3.0.1
162 * @return void
163 */
164 public function enqueue_styles() {
165
166 /**
167 * This function is provided for demonstration purposes only.
168 *
169 * An instance of this class should be passed to the run() function
170 * defined in Wpstream_Loader as all of the hooks are defined
171 * in that particular class.
172 *
173 * The Wpstream_Loader will then create the relationship
174 * between the defined hooks and the functions defined in this
175 * class.
176 */
177 // Google "Roboto" webfont used across the admin UI.
178 wp_enqueue_style( 'wpstream-roboto', "https://fonts.googleapis.com/css?family=Roboto:300,400,500,600,700,900&display=swap&subset=latin-ext" );
179 // Main admin stylesheet; filemtime() is used as the cache-busting version.
180 wp_enqueue_style(
181 $this->plugin_name,
182 plugin_dir_url( __FILE__ ) . 'css/wpstream-admin.css',
183 array(),
184 filemtime(plugin_dir_path(__FILE__) . 'css/wpstream-admin.css' ),
185 'all'
186 );
187
188 // Onboarding-specific styles, again cache-busted by file modification time.
189 wp_enqueue_style(
190 'wpstream-on-boarding-css',
191 plugin_dir_url( __FILE__ ) . 'css/wpstream-admin-onboarding.css',
192 array(),
193 filemtime(plugin_dir_path(__FILE__) . 'css/wpstream-admin-onboarding.css'),
194 );
195
196 // Ensure the WP media library scripts/styles are available (for logo/image pickers) if not already loaded.
197 if (!did_action('wp_enqueue_media')) {
198 wp_enqueue_media();
199 }
200 }
201
202 /**
203 * Register, enqueue and localize the plugin's admin-area JavaScript.
204 *
205 * Enqueues jQuery UI helpers, the fileupload lib, the admin control/upload
206 * scripts, the start-streaming and settings scripts, and the onboarding
207 * bundles. Each script is fed a localized vars array (translated strings,
208 * URLs, nonces, feature flags). Some bundles are only loaded on specific
209 * admin screens. Hooked (via Wpstream_Loader) to admin_enqueue_scripts.
210 *
211 * @since 3.0.1
212 * @return void
213 */
214 public function enqueue_scripts() {
215
216 // jQuery UI widgets used by the settings/date pickers and sliders.
217 wp_enqueue_script("jquery-ui-slider");
218 wp_enqueue_script("jquery-ui-datepicker");
219 // Blueimp-style file upload library backing the recordings/VOD uploader.
220 wp_enqueue_script('jquery.fileupload', plugin_dir_url( __FILE__ ) .'js/jquery.fileupload.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true);
221
222 // Shared admin utility helpers, then the main admin control script that drives uploads and channel actions.
223 wp_enqueue_script( 'wpstream-admin-utils', plugin_dir_url( __FILE__ ) .'js/utils/admin_utils.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true);
224 wp_enqueue_script('wpstream-admin-control', plugin_dir_url( __FILE__ ) .'js/admin_control.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true);
225 // Hand the control script all of its translated status strings, the admin URL, and the multipart upload nonce.
226 wp_localize_script('wpstream-admin-control', 'wpstream_admin_control_vars',
227 array(
228 'admin_url' => get_admin_url(),
229 'multipart_upload_nonce' => wp_create_nonce( 'wpstream_multipart_upload_nonce' ),
230 'loading_url' => WPSTREAM_PLUGIN_DIR_URL.'/img/loading.gif',
231 'download_mess' => esc_html__('Click to download!','wpstream'),
232 'uploading' => esc_html__('We are uploading your file. Do not close this window!','wpstream'),
233 'upload_complete2' => esc_html__('Upload Complete! You can upload another file!','wpstream'),
234 'not_accepted' => esc_html__('The file is not an accepted video format','wpstream'),
235 'upload_complete' => esc_html__('Upload Complete!','wpstream'),
236 'no_band' => esc_html__('Not enough streaming data.','wpsteam'),
237 'no_band_no_store' => esc_html__('Not enough streaming data or storage.','wpsteam'),
238 'no_streaming_hours' => esc_html__('Not enough streaming hours.','wpstream'),
239 'exceeding_limit' => esc_html__('File size exceeds 5GB. Initiating multipart upload...','wpsteam'),
240 'upload_failed' => esc_html__('Upload Failed!','wpstream'),
241 'upload_failed2' => esc_html__('Upload Failed! Please Try again!','wpstream'),
242 'choose_a_file' => esc_html__('Choose a file&hellip;','wpstream'),
243 'preparing_multipart' => esc_html__('Preparing multipart upload...','wpstream'),
244 'uploading_part' => esc_html__('Uploading part {part} of {total}...','wpstream'),
245 'upload_failed_part' => esc_html__('Failed to upload part {part}. Please try again.','wpstream'),
246 'completing_upload' => esc_html__('Completing upload. Please wait...','wpstream'),
247 'upload_failed_part_retry' => esc_html__('Failed to upload part {part}. Retrying...','wpstream'),
248 'choose_recording' => esc_html__( 'Choose Recording', 'wpstream' ),
249 'select_recording' => esc_html__( 'Please select a recording from the list', 'wpstream' ),
250 'invalid_response' => esc_html__('Invalid response from server. Missing required upload data.', 'wpstream'),
251 'video_processing' => esc_html__( 'The video is still processing', 'wpstream' ),
252 'file_name_text' => esc_html__('File Name:','wpstream'),
253 'channel_create_error' => esc_html__('Something did not work. Please try again.', 'wpstream'),
254 'select_caption_file' => esc_html__('Select .vtt Captions File', 'wpstream'),
255 'select_button' => esc_html__('Select', 'wpstream'),
256 'remove_button' => esc_html__('Remove', 'wpstream'),
257 'use_streaming_hours' => $this->wpstream_get_start_streaming_localization_flags()['use_streaming_hours'],
258 ));
259
260 // Recordings list script + its localized labels and the "create VOD from recording" links (free vs paid depending on WooCommerce).
261 wp_enqueue_script('wpstream-recordings-videos-list', plugin_dir_url( __FILE__ ) .'js/recordings_videos_list.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true);
262 wp_localize_script( 'wpstream-recordings-videos-list', 'wpstream_recordings_videos_list_vars',
263 array(
264 'delete_file' => esc_html__('Delete file', 'wpstream'),
265 'download' => esc_html__( 'Download', 'wpstream'),
266 'download_available' => esc_html__( 'Click to download! The url will work for the next 20 minutes!', 'wpstream'),
267 'add_free_video_url' => esc_url( admin_url( 'post-new.php?post_type=wpstream_product_vod') . '&new_video_name=' ),
268 'create_ftv_vod' => esc_html__( 'Create new Free-To-View VOD from this recording' , 'wpstream' ),
269 'woocommerce_exists' => class_exists( 'WooCommerce' ),
270 'add_paid_video_url' => esc_url( admin_url( 'post-new.php?post_type=product').'&new_video_name=' ),
271 'create_ptv_vod' => esc_html__( 'Create new Pay-Per-View VOD from this recording' , 'wpstream' ),
272 )
273 );
274
275 // Bundled QR generator (SEC-05): the Larix QR is built locally so the RTMP URL +
276 // secret stream key never reach a third-party image service. Registered here too
277 // because the admin loads start_streaming.js independently of the public enqueue.
278 wp_register_script('wpstream-qrcode-generator', plugin_dir_url( __DIR__ ) . 'public/js/vendor/qrcode-generator.js', array(), '1.4.4', true);
279 wp_register_script('wpstream-qr', plugin_dir_url( __DIR__ ) . 'public/js/wpstream-qr.js', array('wpstream-qrcode-generator'), WPSTREAM_PLUGIN_VERSION, true);
280
281 // Shared public start/stop-streaming script (reused in admin), cache-busted by its file mtime.
282 // Depends on wpstream-qr so wpstreamRenderQr() is defined before this runs.
283 $modified_start_streaming_file_time = gmdate( 'YmdHi', filemtime( WPSTREAM_PLUGIN_PATH . 'public/js/start_streaming.js' ) );
284 wp_enqueue_script('wpstream-start-streaming_admin', plugin_dir_url( __DIR__ ) .'public/js/start_streaming.js', array('wpstream-qr'), $modified_start_streaming_file_time, true);
285 // Feature flags (basic-streaming / streaming-hours mode) that tailor the confirm dialogs and warnings.
286 $streaming_localization_flags = $this->wpstream_get_start_streaming_localization_flags();
287 wp_localize_script('wpstream-start-streaming_admin', 'wpstream_start_streaming_vars',
288 array(
289 'admin_url' => get_admin_url(),
290 'loading_url' => WPSTREAM_PLUGIN_DIR_URL.'/img/loading.gif',
291 'download_mess' => esc_html__('Click to download!','wpstream'),
292 'uploading' => esc_html('We are uploading your file.Do not close this window!','wpstream'),
293 'upload_complete2' => esc_html('Upload Complete! You can upload another file!','wpstream'),
294 'not_accepted' => esc_html('The file is not an accepted video format','wpstream'),
295 'upload_complete' => esc_html('Upload Complete!','wpstream'),
296 'no_band' => esc_html('Not enough streaming data.','wpsteam'),
297 'no_band_no_store' => esc_html('Not enough streaming data or storage.','wpsteam'),
298
299 'start_streaming_action'=> esc_html__('TURNING ON','wpstream'),
300 'stop_streaming_action' => esc_html__('TURNING OFF','wpstream'),
301 'start_streaming' => esc_html__('TURN ON','wpstream'),
302 'stop_streaming' => esc_html__('TURN OFF','wpstream'),
303 'failed_fetching' => esc_html__('Failed to get channel info. Please try again.','wpstream'),
304 'turned_on_tooltip' => esc_html__('Channel is now OFF. Click to turn ON.','wpstream'),
305 'turned_off_tooltip' => esc_html__('Click to turn channel off. This will interrupt any ongoing broadcast.','wpstream'),
306 'turning_on_tooltip' => esc_html__('Turning a channel on may take 1-2 minutes or more. Please be patient.','wpstream'),
307 'turning_off_tooltip' => esc_html__('This may take a few minutes.','wpstream'),
308 'error1' => esc_html__('You don\'t have enough data to start a new event!','wpstream'),
309 'failed_event_creation' => esc_html__('Failed to start the channel. Please try again in a few minutes.','wpstream'),
310 'channel_turning_on' => esc_html__('Channel is turning on','wpstream'),
311 'channel_turning_off' => esc_html__('Channel is turning off','wpstream'),
312 'channel_on' => esc_html__('Channel is ON','wpstream'),
313 'channel_off' => esc_html__('Channel is OFF','wpstream'),
314 'turn_off_confirm' => esc_html__(
315 'ARE YOU SURE you\'d like to TURN OFF the channel now? ' . PHP_EOL . PHP_EOL .
316 '- Channels TURN OFF automatically after 1 hour of inactivity (no active broadcast).' . PHP_EOL .
317 '- Manual TURN OFF is only useful if you require to change the channel settings immediately.' . PHP_EOL .
318 '- If your channel is configured with Auto TURN ON, it will turn back on as soon as there is a broadcast.',
319 'wpstream'
320 ),
321 'is_basic_streaming' => $streaming_localization_flags['is_basic_streaming'],
322 'use_streaming_hours' => $streaming_localization_flags['use_streaming_hours'],
323 'basic_streaming_warning' => esc_html__(
324 'You’ve used all available broadcast or viewer hours.' . PHP_EOL . PHP_EOL .
325 'Some live channel features will be limited, including recording, viewer count, browser broadcasting, and content protection.' . PHP_EOL . PHP_EOL .
326 'Top up your resources or upgrade your plan to use all features, or choose OK to start anyway.',
327 'wpstream'
328 ),
329 'basic_streaming_warning_traffic' => esc_html__(
330 'Your account is now in BASIC STREAMING mode.' . PHP_EOL . PHP_EOL .
331 'Instead of offloading to the WpStream Cloud, this mode relies on WordPress and hosting resources to process and deliver video. In some WP environments, streaming may be unreliable.' . PHP_EOL .
332 'Certain features, such as recording, viewer count, browser broadcasting, and content protection are unavailable.' . PHP_EOL . PHP_EOL .
333 '- To take advantage of all features, please choose Cancel and upgrade your plan.' . PHP_EOL .
334 '- Otherwise, choose OK to start your channel with these limitations.' . PHP_EOL . PHP_EOL .
335 'ARE YOU SURE you want to continue with Basic Streaming?',
336 'wpstream'
337 ),
338 'broadcaster_url' => esc_url( esc_url(home_url('/broadcaster-page/') ) ),
339 'is_onboarding' => isset($_GET['onboard']) && $_GET['onboard'] === 'yes' ? 'yes' : 'no',
340 ));
341
342 // Settings-page script with its save/logo-picker strings and the broadcaster page URL.
343 wp_enqueue_script('wpstream-settings', plugin_dir_url( __DIR__ ) .'/admin/js/wpstream_settings.js?v='.time(),array(), WPSTREAM_PLUGIN_VERSION, true);
344 wp_localize_script('wpstream-settings', 'wpstream_settings_vars', array(
345 'error_message' => esc_html__( 'Failed to save settings. Please try again.', 'wpstream'),
346 'choose_image_text' => esc_html__( 'Choose Logo Image', 'wpstream'),
347 'select_image_text' => esc_html__( 'Select Image', 'wpstream'),
348 'update_successful' => esc_html__( 'Update Successful.', 'wpstream'),
349 'update_failed' => esc_html__( 'Something went wrong. Try again.', 'wpstream'),
350 'broadcaster_url' => esc_url( esc_url(home_url('/broadcaster-page/') ) ),
351 ));
352
353
354 // Optional ?branch= override (e.g. beta onboarding flow), sanitized from the query string.
355 $branch = isset($_GET['branch']) ? sanitize_text_field( wp_unslash( $_GET['branch'] ) ) : '';
356 // Core onboarding wizard script + its navigation URLs.
357 wp_enqueue_script('wpstream-on-boarding-js',plugin_dir_url( __DIR__ ) .'/admin/js/wpstream-onboarding2.js',array(), WPSTREAM_PLUGIN_VERSION, true);
358 wp_localize_script('wpstream-on-boarding-js', 'wpstreamonboarding_js_vars',
359 array(
360 'admin_url' => get_admin_url(),
361 'plugin_url' => get_dashboard_url().'/plugins.php',
362 'upload_url' => get_dashboard_url().'admin.php?page=wpstream_recordings',
363 'branch' => $branch
364 ));
365
366 // Identify the current admin screen so screen-specific bundles load only where needed.
367 $current_screen=get_current_screen();
368 // enqueue the file only on the on-boarding page and wpstream_product post type
369 // True on the dedicated Quick Start / onboarding admin page.
370 $is_wpstream_onboarding_page = $current_screen->base ==='wpstream_page_wpstream_onboard';
371 // True on a product edit screen reached from the onboarding flow (?onboard=yes).
372 $is_wpstream_onboarding_post_type_page = isset($_GET['onboard']) && $_GET['onboard'] === 'yes';
373 // Load the onboarding-page bundle in either onboarding context.
374 $onboarding_visible = $is_wpstream_onboarding_page || $is_wpstream_onboarding_post_type_page;
375 if( $onboarding_visible) {
376 // Onboarding telemetry/page script; current_page distinguishes wizard vs post-edit context.
377 wp_enqueue_script('wpstream-on-boarding-page-js', plugin_dir_url( __DIR__ ) .'admin/js/wpstream-onboarding-page.js',array(), WPSTREAM_PLUGIN_VERSION, true);
378 wp_localize_script( 'wpstream-on-boarding-page-js', 'wpstream_onboarding_page_vars',
379 array(
380 'admin_url' => get_admin_url(),
381 'request_url' => WPSTREAM_CLICK,
382 'wps_user' => get_option('wpstream_api_username_from_token'),
383 'current_page' => $is_wpstream_onboarding_post_type_page ? 'post_edit' : 'onboarding',
384 'plugin_version' => WPSTREAM_PLUGIN_VERSION,
385 'branch' => $branch,
386 )
387 );
388 }
389
390 // On the credentials/channels/recordings/onboard screens, load the quota widget updater.
391 if ( in_array( $current_screen->base, ['toplevel_page_wpstream_credentials', 'wpstream_page_wpstream_live_channels', 'wpstream_page_wpstream_recordings', 'wpstream_page_wpstream_onboard'] ) ) {
392 wp_enqueue_script( 'wpstream-user-quota-update', plugin_dir_url( __DIR__ ) . 'admin/js/wpstream-user-quota.js', array(), WPSTREAM_PLUGIN_VERSION, true );
393 wp_localize_script( 'wpstream-user-quota-update', 'wpstream_user_quota_vars', array(
394 'admin_url' => get_admin_url()
395 ));
396 }
397
398 // Add localized variables for broadcaster
399 // Provide the broadcaster script (registered elsewhere) with its AJAX URL and nonce.
400 wp_localize_script('wpstream-broadcaster', 'wpstream_broadcaster_vars', array(
401 'ajax_url' => admin_url('admin-ajax.php'),
402 'nonce' => wp_create_nonce('wpstream_broadcaster_nonce'),
403 'plugin_url' => plugin_dir_url(__FILE__),
404
405 ));
406
407 }
408
409
410 /**
411 * Add Plugin Administation menu
412 *
413 * Registers the top-level "WpStream" menu and its sub-pages (Credentials,
414 * All Channels, Recordings, Settings, Quick Start), each mapped to a render
415 * callback on this class and gated to the `administrator` capability.
416 *
417 * @since 3.0.1
418 * @return void
419 */
420 public function wpstream_manage_admin_menu() {
421
422 // Top-level menu; its page defaults to the Credentials screen and uses the WpStream icon at position 20.
423 add_menu_page( __('WpStream','wpestream'), __('WpStream ','wpstream'), 'administrator', 'wpstream_credentials', array($this,'wpstream_set_wpstream_credentials'), WPSTREAM_PLUGIN_DIR_URL.'img/wpstream-icon-menu_2.png',20 );
424 // Credentials sub-page (same slug as the parent so it is the default view).
425 add_submenu_page( 'wpstream_credentials', __('WpStream Credentials','wpestream'), __('Credentials','wpestream'), 'administrator', 'wpstream_credentials', array($this,'wpstream_set_wpstream_credentials') );
426 // All Channels listing.
427 add_submenu_page( 'wpstream_credentials', __('WpStream Live Channels','wpestream'), __('All Channels','wpestream'), 'administrator', 'wpstream_live_channels', array( $this,'wpstream_new_general_set'));
428 // Recordings / media management.
429 add_submenu_page( 'wpstream_credentials', __('WpStream Recordings','wpestream'), __('Recordings','wpestream'), 'administrator', 'wpstream_recordings', array($this,'wpstream_media_management'));
430 // Global plugin settings.
431 add_submenu_page( 'wpstream_credentials', __('WpStream Settings','wpestream'), __('Settings','wpestream'), 'administrator', 'wpstream_settings', array($this,'wpstream_settings'));
432
433 // Quick Start / onboarding entry point.
434 add_submenu_page( 'wpstream_credentials', __('WpStream Quick Start','wpestream'), __('WpStream Quick Start','wpestream'), 'administrator', 'wpstream_onboard', array($this,'wpstream_pre_onboard_display'));
435
436
437
438
439 }
440
441
442
443
444 /**
445 * Shows events wpstream
446 *
447 * Renders the "All Channels" admin screen: the Pay-Per-View (WooCommerce
448 * `product`) channel list, the Free-To-View (`wpstream_product`) list, the
449 * quota/pack summary, and the "no channels" call-to-action. Each channel is
450 * drawn via wpstream_live_stream_unit(). Echoes HTML directly.
451 *
452 * @since 3.0.1
453 * @return void
454 */
455 public function wpstream_new_general_set() {
456
457 // Assume no channels until a query proves otherwise.
458 $no_channel=1;
459
460 // Presence check for WooCommerce Subscriptions (branch intentionally left empty here).
461 if(class_exists ('WC_Subscription')){
462
463 }
464
465 //event_passed
466 // Query published PPV products that have not yet "passed" (event_passed != 1) and are of type live_stream/subscription.
467 $args = array(
468 'posts_per_page' => -1,
469 'post_type' => 'product',
470 'post_status' => 'publish',
471 'meta_query' => array(
472 array(
473 'key' => 'event_passed',
474 'value' => 1,
475 'compare' => '!=',
476 )
477 ),
478
479 'tax_query' => array(
480 'relation' => 'AND',
481 array(
482 'taxonomy' => 'product_type',
483 'field' => 'slug',
484 'terms' => array('live_stream','subscription')
485 )
486 ),
487 );
488
489
490
491 // Run the PPV channel query and load the current live-event map + quota/pack data for the user.
492 $event_list = new WP_Query($args);
493 global $live_event_for_user;
494 $live_event_for_user = $this->main->wpstream_live_connection->wpstream_get_live_event_for_user();
495 $pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_new_general_set' );
496
497 // Print the quota/pack summary header for the user.
498 $this->main->show_user_data($pack_details);
499 if( $event_list->have_posts()){
500
501
502 // Section header + "create new PPV channel" link.
503 print '<div class="pack_details_wrapper_transparent">
504 <h3>'.__('Your Pay-Per-View Channel List','wpstream').'</h3>';
505
506
507 $link_new = admin_url('post-new.php?post_type=product').'&new_stream='. rawurlencode('new');
508
509 print '<a href="'.esc_url($link_new).'" class="wpstream_create_new_product_link">'.esc_html__('Create new Pay-Per-View channel.','wpstream').'</a>';
510 print '</div>';
511
512 // Open the grid that will hold each channel card.
513 print '<div style="clear: both;"></div><div class="event_list_wrapper">';
514
515 // Loop every matched PPV product.
516 while ($event_list->have_posts()): $event_list->the_post();
517
518 // Gather the product id, whether a subscription is flagged as a live event, and its product type.
519 $the_id = get_the_ID();
520 $is_subscription_live_event = esc_html(get_post_meta($the_id,'_subscript_live_event',true));
521 $term_list = wp_get_post_terms($the_id, 'product_type');
522
523 // Skip plain subscriptions that are not marked as live events.
524 if( $term_list[0]->name=='subscription' && $is_subscription_live_event=='no'){
525 continue;
526 }
527
528 // Render the channel card for this product.
529 $this->wpstream_live_stream_unit($the_id);
530
531 endwhile;
532
533 // Close the grid; at least one PPV channel exists.
534 print'</div>';
535 $no_channel=1;
536 }else{
537 // No PPV channels found.
538 $no_channel=0;
539 }
540
541
542 // Nonce hidden input used by the JS "start event" action.
543 $ajax_nonce = wp_create_nonce( "wpstream_start_event_nonce" );
544 print '<input type="hidden" id="wpstream_start_event_nonce" value="'.$ajax_nonce.'">';
545
546 // Current user context (user's own live_shows meta collected but not directly used below).
547 $current_user = wp_get_current_user();
548 $allowded_html = array();
549 $userID = $current_user->ID;
550 $user_live_streams = get_user_meta($userID,'live_shows');
551
552
553 // Restore the main query after the custom WP_Query loop.
554 wp_reset_postdata();
555
556
557
558
559 // free
560
561 // Query all published Free-To-View channels (custom `wpstream_product` post type).
562 $args_free = array(
563 'posts_per_page' => -1,
564 'post_type' => 'wpstream_product',
565 'post_status' => 'publish',
566
567
568 );
569 $event_list_free = new WP_Query($args_free);
570
571
572 if( $event_list_free->have_posts()){
573 // Section header + "create new free channel" link.
574 print '<div class="pack_details_wrapper_transparent">
575 <h3>'.__('Free-To-View Channels','wpstream').'</h3>';
576
577 $link_new = admin_url('post-new.php?post_type=wpstream_product');
578 print '<a href="'.esc_url($link_new).'" class="wpstream_create_new_product_link">'.esc_html__('Create new Free-To-View channel.','wpstream').'</a>';
579 print '</div>';
580 // Open the free-channel grid.
581 print '<div style="clear: both;"></div><div class="event_list_wrapper">';
582
583 // Loop each free channel.
584 while ($event_list_free->have_posts()): $event_list_free->the_post();
585
586
587 $the_id = get_the_ID();
588
589 // Render only channels whose event has not passed.
590 if( get_post_meta ($the_id,'event_passed',true)!=1){
591 $this->wpstream_live_stream_unit($the_id);
592 }
593
594 endwhile;
595
596 // Close the grid and emit the shared modal background + error modal markup.
597 print'</div><div class="wpstream_modal_background"></div>';
598 print '<div class="wpstream_error_modal_notification"><div class="wpstream_error_content">er2</div>
599 <div class="wpstream_error_ok wpstream_button" type="button">'.esc_html__('Close','wpstream').'</div>
600 </div>';
601 $no_channel=1;
602 }else{
603 // No free channels found.
604 $no_channel=0;
605 }
606
607
608
609
610 // "No channels" call-to-action: build the two create links.
611 $link_new_paid = admin_url('post-new.php?post_type=product').'&new_stream='. rawurlencode('new');
612 $link_new_free = admin_url('post-new.php?post_type=wpstream_product');
613 print '<div class="no_events_warning"> ';
614 // Warn when the user has zero PPV channels.
615 if($event_list->found_posts==0){
616 print '<div class="no_events_warning_mes">'.__('* You do not have any Pay-Per-View channels!','wpstream').'</div>';
617 }
618 // Warn when the user has zero free channels.
619 if($event_list_free->found_posts==0){
620 print '<div class="no_events_warning_mes">'. __('* You do not have any free channels!','wpstream').'</div>';
621 }
622
623 // Offer buttons to create a free or paid channel.
624 print '<a href="'.esc_url($link_new_free).'" class="wpstream_no_chanel_add_channel">'.esc_html__('Add new Free-To-View channel ','wpstream').'</a>';
625 print '<a href="'.esc_url($link_new_paid).'" class="wpstream_no_chanel_add_channel">'.esc_html__('Add new Pay-Per-View channel ','wpstream').'</a>';
626
627 print '</div>';
628
629
630
631
632 }
633 // end wpstream_new_general_set
634
635
636 /**
637 * Social share
638 *
639 * Outputs the social-sharing icon row (Facebook, Twitter/X, Pinterest,
640 * WhatsApp, LinkedIn, Reddit, email) for a given channel/post. Echoes HTML.
641 *
642 * @since 3.0.1
643 * @param int $the_id Post/product ID whose permalink and title are shared.
644 * @return void
645 */
646 public function wpstream_social_share($the_id){
647 // Match the current request scheme so share URLs are not mixed-content.
648 $protocol = is_ssl() ? 'https' : 'http';
649 // Featured image (used as the Pinterest "media") and the canonical permalink/title.
650 $pinterest = wp_get_attachment_image_src(get_post_thumbnail_id($the_id), 'full');
651 $link = esc_url ( get_permalink($the_id) );
652 $title = get_the_title($the_id);
653 // Pre-encode the Twitter status text and the mailto subject/body query.
654 $twiter_status = urlencode( $title.' '.$link);
655 $email_link = 'subject='.urlencode ( $title ) .'&body='. urlencode( esc_url($link));
656
657 // Pre-build the Facebook sharer URL.
658 $facebook_link = esc_html($protocol).'://www.facebook.com/sharer.php?u='. esc_url($link) .'&amp;t='. urlencode(get_the_title());
659
660 // Below: HTML markup for the share bar; each <a> targets one social network's share endpoint.
661 ?>
662 <div class="wpstream_social_share_wrapper">
663
664 <a href="<?php print esc_url( $facebook_link); ?>" target="_blank" class="social_facebook wpstream_sharing_social">
665 <span class="dashicons dashicons-facebook-alt"></span>
666 </a>
667
668 <a href="<?php print esc_html($protocol);?>://twitter.com/intent/tweet?text=<?php echo esc_html($twiter_status); ?>" class="social_tweet wpstream_sharing_social" target="_blank">
669 <span class="dashicons dashicons-twitter"></span>
670 </a>
671
672 <a href="<?php print esc_html($protocol);?>://pinterest.com/pin/create/button/?url=<?php echo esc_url($link); ?>&amp;media=<?php if (isset( $pinterest[0])){ echo esc_url($pinterest[0]); }?>&amp;description=<?php echo urlencode(get_the_title()); ?>" target="_blank" class="social_pinterest wpstream_sharing_social">
673 <span class="dashicons dashicons-pinterest"></span>
674 </a>
675
676 <a href="<?php print esc_html($protocol);?>://api.whatsapp.com/send?text=<?php echo urlencode( get_the_title().' '. esc_url( $link )); ?>" class="social_whatsup wpstream_sharing_social" target="_blank">
677 <span class="dashicons dashicons-whatsapp"></span>
678 </a>
679
680 <a href="<?php print esc_html($protocol);?>://www.linkedin.com/sharing/share-offsite/?url=<?php echo urlencode(esc_url($link)); ?>" class="social_linkedin wpstream_sharing_social" target="_blank">
681 <span class="dashicons dashicons-linkedin"></span>
682 </a>
683
684 <a href="<?php print esc_html($protocol);?>:///www.reddit.com/submit?url==<?php echo urlencode(esc_url($link)); ?>" class="social_linkedin wpstream_sharing_social" target="_blank">
685 <span class="dashicons dashicons-reddit"></span>
686 </a>
687
688 <a href="mailto:email@email.com?<?php echo trim(esc_html($email_link));?>" data-action="share email" class="social_email wpstream_sharing_social">
689 <span class="dashicons dashicons-email-alt"></span>
690 </a>
691
692 <div class="wpstream_modal_explanations"> <?php print esc_html__('Spread the word! To let people know about your channel, click on the corresponding icon and share on the social platforms of your choice. The more the merrier!','wpstream'); ?> </div>
693 <?php
694 print '</div>';
695 }
696
697
698
699
700 /**
701 * Shows event unit card in admin
702 *
703 * Renders one channel "card" (thumbnail, title, ON/OFF button, and the
704 * webcam / external-app / stats / settings / edit / view / share icon
705 * controls) plus its settings/share/broadcast modals. Bails with a notice
706 * if the user lacks the administrator capability or streaming permission.
707 * Echoes HTML.
708 *
709 * @since 3.0.1
710 * @param int $the_id Channel product/post ID.
711 * @param string $is_front Non-empty (e.g. 'front') when rendered on the front end.
712 * @return void
713 */
714 public function wpstream_live_stream_unit($the_id,$is_front=''){
715 global $live_event_for_user;
716 global $wpstream_plugin;
717 $current_user = wp_get_current_user();
718
719 // Non-admins get a "not allowed" notice in the admin (back-end) context.
720 if( !current_user_can('administrator')){
721 if($is_front=='' ){
722 print '<div class="event_list_unit">';
723 esc_html_e('You are not allowed to broadcast.','wpstream');
724 print '</div>';
725 return;
726 }
727
728 }
729
730 // Enforce the plugin's own streaming-permission gate as well.
731 if( !$this->main->wpstream_check_user_can_stream() ){
732 print '<div class="event_list_unit">';
733 esc_html_e('You are not allowed to broadcast','wpstream');
734 print '</div>';
735 return;
736 }
737
738
739 // Flag this card as "live" if the channel currently has an active event.
740 $live_class='';
741 if(isset($live_event_for_user[$the_id])) {
742 $live_class=" wpstream_show_started";
743 }
744
745
746 // Thumbnail: the post's featured image, or the plugin logo as a fallback.
747 if(has_post_thumbnail($the_id)){
748 $thumb = get_the_post_thumbnail_url($the_id,'thumbnail');
749 }else{
750 $thumb= plugin_dir_url( dirname( __FILE__ ) ). 'img/plugin-logo.png';
751 }
752
753 // Default streaming display state (hidden data, empty OBS credentials/stats URL).
754 $pending_streaming_class = 'hide_stream_data';
755 $external_software_streaming_class = '';
756 $obs_uri = '';
757 $obs_stream = '';
758 $live_data_url = '';
759
760 // On the front end, lazily fetch the live-event map if it was not preloaded.
761 if( $live_event_for_user=='' && $is_front=='front' ){
762 $live_event_for_user = $this->main->wpstream_live_connection->wpstream_get_live_event_for_user();
763 }
764 // Default the status/button to spinners; resolved below based on live state.
765 $channel_status = '<div class="spinner" style="visibility: visible"></div>';
766 $button_status = '<div class="spinner" style="visibility: visible"></div>';
767 if(is_array($live_event_for_user) && isset($live_event_for_user[$the_id])) {
768 // Channel is live: reveal stream data and grab the QoS/stats URL.
769 $pending_streaming_class = 'pending_trigger';
770 $live_data_url = get_post_meta($the_id,'qos_url',true);
771 // $channel_status = esc_html__('Channel is on','wpstream');
772 } else {
773 // Channel is off: show OFF status and a TURN ON button.
774 $channel_status = esc_html__('Channel is OFF','wpstream');
775 $button_status = esc_html__('TURN ON','wpstream');
776 }
777
778 // Load the channel's server id and broadcasting credentials/URLs.
779 $server_id = get_post_meta($the_id,'server_id',true);
780 $obs_uri = get_post_meta($the_id,'obs_uri',true);
781 $obs_stream = get_post_meta($the_id,'obs_stream',true);
782 $webcaster_url = get_post_meta($the_id,'webcaster_url',true);
783 $rtmp_ip_uri = '';
784
785 // Emit the start-event nonce, then open the card wrapper with all its data-* attributes.
786 $ajax_nonce = wp_create_nonce( 'wpstream_start_event_nonce' );
787 print '<input type="hidden" id="wpstream_start_event_nonce" value="'.$ajax_nonce.'">';
788 print '<div class="event_list_unit '.$live_class.' '.$pending_streaming_class.' event_unit_style_'.esc_attr($is_front).'" data-show-id="'.intval($the_id).'" data-server-id="'.$server_id.'" data-server-url="'.$rtmp_ip_uri.'"">';
789
790 // Status line, notification slot, thumbnail and (trimmed) title with the channel ID.
791 print '<div class="wpstream_channel_status">'.$channel_status.'</div>';
792
793 print '<div class="server_notification"></div>';
794
795 print '<div class="event_thumb_wrapper" style="background-image:url('.$thumb.')"></div>';
796
797 print '<div class="event_title" data-prodid="'.$the_id.'">'.wp_trim_words(get_the_title($the_id),10);
798
799
800 /* print '***</br>'.get_post_meta($the_id,'obs_uri',true) .'***</br>'.
801 get_post_meta($the_id,'obs_stream',true).'***</br>'.
802 get_post_meta($the_id,'broadcast_url',true);
803 */
804 print '<div class="wpstream_channel_item_id">'.esc_html( '#ID' ).' '.$the_id.'</div>';
805 print '</div>';
806
807
808 // Primary ON/OFF toggle button (carries the start-event nonce).
809 print '<div class="start_event wpstream_button wpstream_tooltip_wrapper" data-show-id="'.$the_id.'" data-nonce="' . esc_attr( $ajax_nonce ) . '" > ' . $button_status;
810 print '<div class="wpstream_tooltip">'.esc_html__('Channel is now OFF. Click to turn ON.','wpestream').'</div>';
811 print '</div>';
812
813 // Column 1: broadcasting entry points (webcam, external app, live stats).
814 print '<div class="wpstream_options_col1 wpstream_stream_browser_wrapper">';
815
816 // Webcam ("go live in browser") icon button.
817 print '<div class="wpstream_inactive_icon start_webcaster wpstream_stream_browser wpstream-button-icon wpstream_tooltip_wrapper" data-webcaster-url="'.$webcaster_url.'" data-show-id="'.$the_id.'"">';
818
819 print '<div class="wpstream_tooltip_disabled">'.esc_html__('Turn ON the channel to go live.','wpestream').'</div>';
820 print '<div class="wpstream_tooltip">'.esc_html__('Go live with your webcam','wpestream').'</div>';
821
822 print '<svg width="41" height="51" viewBox="0 0 41 51" fill="none" xmlns="http://www.w3.org/2000/svg">
823 <path d="M40.7646 44.2989C39.8782 41.4216 38.3361 38.7814 36.181 36.4522C35.7274 35.9619 35.1629 35.3518 34.4858 34.8356C34.0448 34.4996 33.4262 34.5281 33.0182 34.9035C29.358 38.2714 25.2628 39.9088 20.4979 39.9088H20.4853C15.7103 39.9059 11.6117 38.264 7.95648 34.889C7.52404 34.4896 6.85874 34.484 6.41953 34.8764C3.30565 37.6569 1.20501 40.9206 0.175434 44.5778C-0.276291 46.1833 0.15652 47.8497 1.33328 49.0353C2.22709 49.9362 3.39869 50.4173 4.61958 50.4173C5.0357 50.4173 5.45813 50.3613 5.87645 50.247C10.147 49.081 14.0772 48.4108 17.8916 48.1983C21.1954 48.014 24.4465 48.1323 27.5541 48.5495C29.9559 48.8718 32.4237 49.3888 35.0996 50.1299C36.823 50.6076 38.459 50.1562 39.7059 48.86C40.9268 47.5901 41.2933 46.0132 40.7651 44.2994L40.7646 44.2989ZM38.0572 47.274C37.3825 47.9754 36.6367 48.1823 35.7092 47.9257C32.9321 47.1565 30.3638 46.6187 27.8578 46.2827C24.6079 45.8465 21.2117 45.723 17.7643 45.9151C13.7874 46.1365 9.70183 46.8319 5.27439 48.041C4.43435 48.2706 3.5673 48.0403 2.9568 47.425C2.35932 46.8227 2.14793 46.0108 2.37712 45.1975C3.21567 42.2171 4.7982 39.6162 7.2018 37.2734C11.0641 40.5378 15.5276 42.193 20.4845 42.196H20.4978C25.4557 42.196 29.9251 40.5386 33.7967 37.2667C34.024 37.4904 34.254 37.7359 34.5032 38.0059C36.4236 40.0816 37.7951 42.4256 38.5799 44.9728C38.8603 45.8811 38.6941 46.6125 38.0581 47.274L38.0572 47.274ZM20.4954 30.7538C13.8088 30.7523 8.54664 25.4128 8.54379 18.6256C8.5412 12.2624 13.9961 6.83353 20.3776 6.84872C27.1094 6.86467 32.4526 12.1652 32.4488 18.8231C32.4447 25.4243 27.1038 30.7547 20.4954 30.7538V30.7538ZM29.4401 18.8383C29.5559 13.9512 25.4358 9.8445 20.5347 9.83671C15.5301 9.82892 11.6488 13.9279 11.5328 18.5667C11.4086 23.5202 15.344 27.7103 20.3381 27.7926C25.5542 27.8783 29.554 23.5758 29.4401 18.8383V18.8383ZM20.4917 25.1342C17.2131 25.2436 14.1541 22.4349 14.1567 18.8924C14.1593 15.2627 16.9642 12.4191 20.5858 12.4662C24.0109 12.5111 26.8269 15.2466 26.8362 18.8471C26.8451 22.4293 23.782 25.2436 20.4917 25.1342V25.1342ZM18.5119 13.9982C17.3006 13.9908 15.6202 15.656 15.6295 16.8543C15.6328 17.3209 15.961 17.661 16.3983 17.6513C17.5973 17.6246 19.2281 16.0169 19.2696 14.8205C19.2881 14.2742 19.0371 14.0016 18.5119 13.9982L18.5119 13.9982ZM20.4964 0.568359C10.4428 0.568359 2.26333 8.74761 2.26333 18.8014C2.26333 28.8551 10.4426 37.0345 20.4964 37.0345C30.5502 37.0345 38.7295 28.8553 38.7295 18.8014C38.7291 8.7478 30.5502 0.568359 20.4964 0.568359V0.568359ZM20.4964 33.3886C12.4528 33.3886 5.90919 26.8449 5.90919 18.8014C5.90919 11.3105 11.585 5.12093 18.8624 4.30631C18.8561 4.36009 18.8528 4.41423 18.8528 4.46912C18.8468 5.37368 19.6112 6.16064 20.4927 6.15846C21.3636 6.15586 22.1283 5.39706 22.142 4.52143C22.1431 4.44874 22.1372 4.3768 22.1279 4.30596C29.4067 5.11929 35.0849 11.3092 35.0849 18.802C35.0842 26.8456 28.5404 33.3892 20.4968 33.3892L20.4964 33.3886Z" fill="black"/>
824 </svg>';
825 print '</div>';
826
827
828 print '<div class="wpstream_inactive_icon wpstream_stream_pro wpstream-button-icon wpstream-trigger-modal wpstream_tooltip_wrapper" data-modal="wpestate_broadcast_modal" data-show-id="'.$the_id.'"">';
829
830 print '<div class="wpstream_tooltip_disabled">'.esc_html__('Turn ON the channel to go live.','wpestream').'</div>';
831 print '<div class="wpstream_tooltip">'.esc_html__('Go Live with external streaming app','wpestream').'</div>';
832
833 print '<svg width="51" height="38" viewBox="0 0 51 38" fill="none" xmlns="http://www.w3.org/2000/svg">
834 <path d="M48.1266 13.9634L41.891 17.5343V13.6438C41.891 10.8723 39.6525 8.63384 36.8811 8.63384H33.1504L30.1124 3.62393C28.7268 1.3324 26.2751 0 23.6103 0H10.2863C8.84722 0 7.67471 1.17251 7.67471 2.6116C7.67471 4.05068 8.84722 5.22319 10.2863 5.22319H23.6103C24.4631 5.22319 25.2093 5.64961 25.6888 6.3957L27.0744 8.63413L5.00991 8.63376C2.23843 8.63376 0 10.8722 0 13.6437V32.9901C0 35.7616 2.23843 38 5.00991 38H36.9342C39.7057 38 41.9441 35.7616 41.9441 32.9901V29.0461L48.1797 32.6169C49.2991 33.2564 50.6846 32.4571 50.6846 31.1778L50.6842 15.4022C50.6311 14.123 49.2455 13.3237 48.1261 13.9632L48.1266 13.9634ZM38.1603 32.9368C38.1603 33.6297 37.574 34.1625 36.9345 34.1625L5.01029 34.1629C4.31733 34.1629 3.78458 33.5766 3.78458 32.9372V13.5907C3.78458 12.8978 4.37086 12.365 5.01029 12.365H36.9345C37.6275 12.365 38.1603 12.9513 38.1603 13.5907V32.9368Z" fill="black"/>
835 <path d="M22.4917 21.585H9.70066C8.84784 21.585 8.15527 22.2779 8.15527 23.1304V28.247C8.15527 29.0998 8.84823 29.7923 9.70066 29.7923H22.4917C23.3445 29.7923 24.0371 29.0994 24.0371 28.247V23.1304C24.0371 22.2775 23.3445 21.585 22.4917 21.585Z" fill="black"/>
836 </svg>';
837 print '</div>';
838
839
840
841
842 print '<a href="'.esc_url($live_data_url).'" target="_blank" class="wpstream_inactive_icon wpstream_live_data wpstream_statistics_channel wpstream-button-icon wpstream_tooltip_wrapper" data-show-id="'.$the_id.'" >';
843
844 print '<div class="wpstream_tooltip_disabled">'.esc_html__('Turn ON the channel to see live stats.','wpestream').'</div>';
845 print '<div class="wpstream_tooltip">'.esc_html__('Live Statistics','wpestream').'</div>';
846
847 print'<svg width="50" height="42" viewBox="0 0 50 42" fill="none" xmlns="http://www.w3.org/2000/svg">
848 <path fill-rule="evenodd" clip-rule="evenodd" d="M48.9001 9.02344H40.3342C39.7288 9.02344 39.2344 9.51791 39.2344 10.1233V40.4275C39.2344 41.033 39.7288 41.5274 40.3342 41.5274H48.9001C49.5055 41.5274 50 41.0329 50 40.4275V10.1233C50 9.51791 49.5055 9.02344 48.9001 9.02344V9.02344Z" fill="black"/>
849 <path fill-rule="evenodd" clip-rule="evenodd" d="M35.821 22.4067H27.2551C26.6497 22.4067 26.1553 22.9012 26.1553 23.5066V40.4277C26.1553 41.0332 26.6497 41.5276 27.2551 41.5276H35.821C36.4264 41.5276 36.9209 41.0332 36.9209 40.4277V23.5066C36.9209 22.9012 36.4264 22.4067 35.821 22.4067V22.4067Z" fill="black"/>
850 <path fill-rule="evenodd" clip-rule="evenodd" d="M22.7439 0H14.178C13.5726 0 13.0781 0.494478 13.0781 1.09989V40.4275C13.0781 41.0329 13.5726 41.5274 14.178 41.5274H22.7439C23.3493 41.5274 23.8438 41.0329 23.8438 40.4275V1.09989C23.8438 0.49447 23.3493 0 22.7439 0V0Z" fill="black"/>
851 <path fill-rule="evenodd" clip-rule="evenodd" d="M9.66573 15.2559H1.09987C0.49445 15.2559 -2.24584e-05 15.7512 -2.24584e-05 16.3558V40.4285C-2.24584e-05 41.0331 0.495325 41.5284 1.09987 41.5284H9.66573C10.2703 41.5284 10.7656 41.034 10.7656 40.4285V16.3558C10.7656 15.7503 10.2711 15.2559 9.66573 15.2559V15.2559Z" fill="black"/>
852 </svg>';
853 print '</a>';
854 print '</div>';
855
856 // Column 2: management controls (settings + edit only for back-end admins, then view/share).
857 print '<div class="wpstream_options_col2 wpstream_show_settings_wrapper">';
858 if($is_front==''){
859 // Settings modal trigger (disabled while the channel is ON).
860 print '<div class="wpstream_show_settings wpstream-button-icon wpstream-trigger-modal wpstream_tooltip_wrapper" data-modal="wpestate_settings_modal" data-show-id="'.$the_id.'" value="'.esc_html__('Settings','wpstream').'">';
861
862 print '<div class="wpstream_tooltip_disabled">'.esc_html__('
863 Turn OFF the channel to change its settings.','wpestream').'</div>';
864 print '<div class="wpstream_tooltip">'.esc_html__('Channel Settings','wpestream').'</div>';
865
866 print '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
867 <path d="M49.22 21.8648C48.82 21.6099 45.665 19.7247 44.415 19.15L42.8651 15.4C43.3251 14.1601 44.2099 10.6849 44.3652 10.0798H44.3649C44.4911 9.52035 44.3213 8.93514 43.9149 8.52987L41.47 6.09995C41.0658 5.69189 40.4795 5.52168 39.9201 5.65003C39.4601 5.75501 35.92 6.64994 34.5999 7.15012L30.8499 5.60021C30.3048 4.40012 28.4699 1.32529 28.1351 0.795199V0.79485C27.8337 0.301688 27.298 0.00069643 26.7201 0H23.2798C22.7057 0.000348769 22.1718 0.294715 21.8648 0.779859C21.6098 1.1799 19.7247 4.33487 19.15 5.58487L15.4 7.13478C14.1601 6.67476 10.6849 5.78996 10.0798 5.63469V5.63504C9.52035 5.50878 8.93513 5.67864 8.52987 6.08496L6.09995 8.52988C5.69189 8.9341 5.52168 9.52042 5.65003 10.0798C5.75501 10.5398 6.64994 14.0799 7.15012 15.4L5.60021 19.15C4.40012 19.6951 1.32529 21.53 0.795198 21.8648H0.79485C0.301688 22.1662 0.00069643 22.7019 0 23.2798V26.7149C0.000348751 27.2893 0.294715 27.8233 0.779858 28.1299C1.1799 28.3849 4.33486 30.27 5.58487 30.8448L7.13478 34.5948C6.67475 35.8347 5.78996 39.3099 5.63469 39.915H5.63504C5.50878 40.4747 5.67863 41.06 6.08496 41.4649L8.51487 43.8948V43.8951C8.9191 44.3032 9.50541 44.4731 10.0648 44.3451C10.5248 44.2401 14.0649 43.3451 15.385 42.845L19.135 44.3949C19.6801 45.595 21.515 48.6698 21.8498 49.1999C22.1525 49.6997 22.6956 50.0035 23.2798 50H26.7149C27.2893 49.9996 27.8233 49.7053 28.1299 49.2201C28.3849 48.8201 30.27 45.6651 30.8447 44.4151L34.5947 42.8652C35.8347 43.3252 39.3098 44.21 39.9149 44.3653V44.3649C40.4747 44.4912 41.0599 44.3213 41.4649 43.915L43.8948 41.4851H43.8951C44.3032 41.0809 44.473 40.4945 44.345 39.9352C44.2401 39.4751 43.3451 35.9351 42.8449 34.615L44.3949 30.865C45.5949 30.3198 48.6698 28.485 49.1999 28.1501C49.6997 27.8474 50.0034 27.3044 49.9999 26.7201V23.2799C50.0045 22.7047 49.7087 22.1683 49.2201 21.8649L49.22 21.8648ZM24.9995 35.8845C22.1099 35.882 19.3399 34.7314 17.2985 32.6866C15.2572 30.6414 14.1118 27.8694 14.1146 24.9798C14.1171 22.0903 15.2676 19.3199 17.3125 17.2785C19.3577 15.2372 22.1297 14.0921 25.0193 14.0946C27.9088 14.0974 30.6792 15.2477 32.7205 17.2928C34.7619 19.338 35.907 22.1101 35.9045 24.9996C35.8978 27.8879 34.7462 30.6557 32.7021 32.6964C30.6576 34.7367 27.8876 35.8834 24.9994 35.8845H24.9995Z" fill="black"/>
868 </svg>
869 </div>';
870
871
872 // Edit-post link for the channel (admin only).
873 print '<a href="'.get_edit_post_link($the_id).'" class="wpstream_edit_channel wpstream-button-icon wpstream_tooltip_wrapper" target="_blank" data-show-id="'.$the_id.'"">';
874 print '<div class="wpstream_tooltip">'.esc_html__('Edit Channel','wpestream').'</div>';
875 print '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
876 <path d="M44.8203 1.79549C42.4263 -0.598498 38.5591 -0.598498 36.1651 1.79549L4.67494 33.2246C4.55203 33.3475 4.49079 33.4088 4.42954 33.5317C4.42954 33.5317 4.42954 33.5929 4.36829 33.5929C4.30705 33.7159 4.24539 33.7771 4.24539 33.9V33.9613L0.0711091 47.5886C-0.113044 48.264 0.0711092 48.9389 0.500659 49.4301C0.868966 49.7984 1.29852 49.9826 1.78973 49.9826C1.97388 49.9826 2.15804 49.9826 2.34219 49.9213L15.9085 45.747H15.9697C16.0926 45.6858 16.2151 45.6858 16.2768 45.6241C16.2768 45.6241 16.338 45.6241 16.338 45.5629C16.4609 45.5016 16.5834 45.44 16.6451 45.3175L48.0742 13.8884C50.4682 11.4944 50.4682 7.62715 48.0742 5.23316L44.8203 1.79549Z" fill="black"/>
877 </svg>';
878 print '</a>';
879 }
880
881 // Public "view channel" link (always shown).
882 print '<a href="'.get_permalink($the_id).'" target="_blank" class="wpstream_view_channel wpstream-button-icon wpstream_tooltip_wrapper" data-show-id="'.$the_id.'"">';
883 print '<div class="wpstream_tooltip">'.esc_html__('View Channel','wpestream').'</div>';
884 print '<svg width="50" height="32" viewBox="0 0 50 32" fill="none" xmlns="http://www.w3.org/2000/svg">
885 <path d="M18.6201 15.2828H21.8752C21.8752 14.069 22.8681 13.0207 24.1373 13.0207V9.76562C21.1032 9.76562 18.6201 12.2487 18.6201 15.2828Z" fill="black"/>
886 <path d="M24.855 0C12.9378 0 3.28272 10.9792 0.579292 14.3447C-0.193097 15.2826 -0.193097 16.662 0.579292 17.6553C3.28282 21.0208 12.9378 32 24.855 32C36.7722 32 46.4273 21.0208 49.1307 17.6553C49.9031 16.7174 49.9031 15.338 49.1307 14.3447C46.4275 10.9792 36.7722 0 24.855 0V0ZM24.855 25.8205C19.4482 25.8205 15.0344 21.4067 15.0344 15.9999C15.0344 10.5931 19.4482 6.17927 24.855 6.17927C30.2618 6.17927 34.6756 10.5931 34.6756 15.9999C34.6756 21.4067 30.2618 25.8205 24.855 25.8205Z" fill="black"/>
887 </svg>';
888 print '</a>';
889
890
891 // Share modal trigger.
892 print '<div class="wpstream_share_channel wpstream-button-icon wpstream-trigger-modal wpstream_tooltip_wrapper" data-modal="wpestate_share_modal" data-show-id="'.$the_id.'"">';
893 print '<div class="wpstream_tooltip">'.esc_html__('Share Channel','wpestream').'</div>';
894 print '<svg width="44" height="50" viewBox="0 0 44 50" fill="none" xmlns="http://www.w3.org/2000/svg">
895 <path d="M20.0777 20.5087L24.3481 17.9427C26.6432 16.5645 29.4554 16.2443 31.96 17.198C33.443 17.7642 35.1166 17.9363 36.852 17.6105C40.3163 16.9521 43.1162 14.1831 43.8115 10.7248C45.0606 4.51597 39.7751 -0.910703 33.5907 0.12835C30.2924 0.682061 27.5604 3.16211 26.5942 6.36168C26.4405 6.86646 26.3418 7.35835 26.2804 7.84466C25.9482 10.5028 24.2498 12.8044 21.9547 14.1827L19.3458 15.7456C17.1061 17.0869 14.3986 17.1792 11.9494 16.2748C10.9956 15.9241 9.96159 15.7271 8.88507 15.7271C3.39004 15.7271 -0.960663 20.7607 0.184097 26.4527C0.879447 29.911 3.67936 32.68 7.14364 33.3384C8.87902 33.6706 10.5525 33.492 12.0356 32.9259C14.5402 31.9721 17.3461 32.2919 19.6475 33.6706L23.906 36.2241C25.346 37.0855 26.2195 38.6424 26.2195 40.3159V40.3403C26.2195 45.8229 31.2287 50.1611 36.9018 49.0472C40.3602 48.3703 43.1415 45.583 43.8244 42.1246C44.9383 36.451 40.6 31.4423 35.1175 31.4423C34.0406 31.4423 33.007 31.6394 32.0532 31.9901C29.604 32.8944 26.9029 32.8022 24.6627 31.4608L20.0721 28.7038C18.6445 27.8484 17.7706 26.304 17.7706 24.6364V24.5875C17.7642 22.9208 18.6441 21.37 20.0777 20.5085L20.0777 20.5087Z" fill="black"/>
896 </svg>';
897 print '</div>';
898 print '</div>';
899
900 // Emit the hidden per-channel modals (settings, share, broadcast) referenced by the triggers above.
901 $this->wpstream_display_modal_seetings($the_id);
902 $this->wpstream_display_modal_share($the_id);
903 $this->wpstream_display_modal_broadcast($the_id,$external_software_streaming_class,$obs_uri,$obs_stream);
904
905 // Close the card wrapper.
906 print '</div>';
907
908 }
909
910
911
912
913 /**
914 * Shows event unit card in admin - theme version
915 *
916 * Front-end/theme variant of wpstream_live_stream_unit(): same controls
917 * (webcam, external app, stats, settings, edit, view, share, ON/OFF) but
918 * wrapped in theme-specific markup and with visible text labels under each
919 * icon. Echoes HTML.
920 *
921 * @since 3.0.1
922 * @param int $the_id Channel product/post ID.
923 * @param string $is_front Non-empty (e.g. 'front') when rendered on the front end.
924 * @return void
925 */
926 public function wpstream_live_stream_unit_for_theme($the_id,$is_front=''){
927 global $live_event_for_user;
928 global $wpstream_plugin;
929 $current_user = wp_get_current_user();
930
931 // Non-admins get a "not allowed" notice in the back-end context.
932 if( !current_user_can('administrator')){
933 if($is_front=='' ){
934 print '<div class="event_list_unit">';
935 esc_html_e('You are not allowed to broadcast.','wpstream');
936 print '</div>';
937 return;
938 }
939
940 }
941
942 // Enforce the plugin's streaming-permission gate.
943 if( !$this->main->wpstream_check_user_can_stream() ){
944 print '<div class="event_list_unit">';
945 esc_html_e('You are not allowed to broadcast','wpstream');
946 print '</div>';
947 return;
948 }
949
950
951 // Mark the card "live" when the channel currently has an active event.
952 $live_class='';
953 if(isset($live_event_for_user[$the_id])) {
954 $live_class=" wpstream_show_started";
955 }
956
957
958 // Thumbnail: featured image or plugin-logo fallback.
959 if(has_post_thumbnail($the_id)){
960 $thumb = get_the_post_thumbnail_url($the_id,'thumbnail');
961 }else{
962 $thumb= plugin_dir_url( dirname( __FILE__ ) ). 'img/plugin-logo.png';
963 }
964
965 // Default streaming state and empty broadcasting credentials/URLs.
966 $pending_streaming_class = 'hide_stream_data';
967 $external_software_streaming_class = '';
968 $obs_uri = '';
969 $obs_stream = '';
970 $live_data_url = '';
971
972 // Lazily load the live-event map on the front end if not preloaded.
973 if( $live_event_for_user=='' && $is_front=='front' ){
974 $live_event_for_user = $this->main->wpstream_live_connection->wpstream_get_live_event_for_user();
975 }
976
977 // Default the button to a spinner (WP admin spinner gif) and no status text.
978 $spinner_url = admin_url('images/spinner-2x.gif');
979 $button_status = '<div class="spinner" style="background-image: url(' . $spinner_url . ')"></div>';
980 $channel_status = '';
981 if(is_array($live_event_for_user) && isset($live_event_for_user[$the_id])) {
982 // Channel live: reveal data and grab the stats URL.
983 $pending_streaming_class = 'pending_trigger';
984 $live_data_url = get_post_meta($the_id,'qos_url',true);
985 // $channel_status = esc_html__('Channel is on','wpstream');
986 } else {
987 // Channel off: show OFF status and a TURN ON button.
988 $channel_status = esc_html__('Channel is OFF','wpstream');
989 $button_status = esc_html__('TURN ON', 'wpstream');
990 }
991
992 // Load the channel's server id and broadcasting credentials/URLs.
993 $server_id = get_post_meta($the_id,'server_id',true);
994 $obs_uri = get_post_meta($the_id,'obs_uri',true);
995 $obs_stream = get_post_meta($the_id,'obs_stream',true);
996 $webcaster_url = get_post_meta($the_id,'webcaster_url',true);
997 $rtmp_ip_uri = '';
998
999 // Open the theme card wrapper with its data-* attributes.
1000 print '<div class="wpstream_theme_event_list_unit event_list_unit '.$live_class.' '.$pending_streaming_class.' event_unit_style_'.esc_attr($is_front).'" data-show-id="'.intval($the_id).'" data-server-id="'.$server_id.'" data-server-url="'.$rtmp_ip_uri.'"">';
1001
1002 // Status line (hidden when empty), notification slot, thumbnail and title with the channel ID.
1003 print '<div class="wpstream_channel_status" style="' . ($channel_status ? '' : 'display: none') . '">'.$channel_status.'</div>';
1004
1005 print '<div class="server_notification"></div>';
1006
1007 print '<div class="wpstream_theme_event_thumb_wrapper">';
1008 print '<div class="event_thumb_wrapper" style="background-image:url('.$thumb.')"></div>';
1009 print '<div class="event_title" data-prodid="'.$the_id.'">'.wp_trim_words(get_the_title($the_id),10);
1010 print '<div class="wpstream_channel_item_id">'.esc_html('#ID','wpstream').' '.$the_id.'</div>';
1011 print '</div>';
1012 print '</div>';
1013
1014 // Control bar: column 1 = broadcasting entry points (webcam, external app, live stats).
1015 print '<div class="wpstream_theme_control_bar_wrapper">';
1016
1017
1018 print '<div class="wpstream_options_col1 wpstream_stream_browser_wrapper">';
1019
1020 print '<div class="wpstream_inactive_icon start_webcaster wpstream_stream_browser wpstream-button-icon wpstream_tooltip_wrapper" data-webcaster-url="'.$webcaster_url.'" data-show-id="'.$the_id.'"">';
1021
1022 print '<div class="wpstream_tooltip_disabled">'.esc_html__('Turn ON the channel to go live.','wpestream').'</div>';
1023 print '<div class="wpstream_tooltip">'.esc_html__('Go live with your webcam','wpestream').'</div>';
1024
1025 print '<svg width="41" height="51" viewBox="0 0 41 51" fill="none" xmlns="http://www.w3.org/2000/svg">
1026 <path d="M40.7646 44.2989C39.8782 41.4216 38.3361 38.7814 36.181 36.4522C35.7274 35.9619 35.1629 35.3518 34.4858 34.8356C34.0448 34.4996 33.4262 34.5281 33.0182 34.9035C29.358 38.2714 25.2628 39.9088 20.4979 39.9088H20.4853C15.7103 39.9059 11.6117 38.264 7.95648 34.889C7.52404 34.4896 6.85874 34.484 6.41953 34.8764C3.30565 37.6569 1.20501 40.9206 0.175434 44.5778C-0.276291 46.1833 0.15652 47.8497 1.33328 49.0353C2.22709 49.9362 3.39869 50.4173 4.61958 50.4173C5.0357 50.4173 5.45813 50.3613 5.87645 50.247C10.147 49.081 14.0772 48.4108 17.8916 48.1983C21.1954 48.014 24.4465 48.1323 27.5541 48.5495C29.9559 48.8718 32.4237 49.3888 35.0996 50.1299C36.823 50.6076 38.459 50.1562 39.7059 48.86C40.9268 47.5901 41.2933 46.0132 40.7651 44.2994L40.7646 44.2989ZM38.0572 47.274C37.3825 47.9754 36.6367 48.1823 35.7092 47.9257C32.9321 47.1565 30.3638 46.6187 27.8578 46.2827C24.6079 45.8465 21.2117 45.723 17.7643 45.9151C13.7874 46.1365 9.70183 46.8319 5.27439 48.041C4.43435 48.2706 3.5673 48.0403 2.9568 47.425C2.35932 46.8227 2.14793 46.0108 2.37712 45.1975C3.21567 42.2171 4.7982 39.6162 7.2018 37.2734C11.0641 40.5378 15.5276 42.193 20.4845 42.196H20.4978C25.4557 42.196 29.9251 40.5386 33.7967 37.2667C34.024 37.4904 34.254 37.7359 34.5032 38.0059C36.4236 40.0816 37.7951 42.4256 38.5799 44.9728C38.8603 45.8811 38.6941 46.6125 38.0581 47.274L38.0572 47.274ZM20.4954 30.7538C13.8088 30.7523 8.54664 25.4128 8.54379 18.6256C8.5412 12.2624 13.9961 6.83353 20.3776 6.84872C27.1094 6.86467 32.4526 12.1652 32.4488 18.8231C32.4447 25.4243 27.1038 30.7547 20.4954 30.7538V30.7538ZM29.4401 18.8383C29.5559 13.9512 25.4358 9.8445 20.5347 9.83671C15.5301 9.82892 11.6488 13.9279 11.5328 18.5667C11.4086 23.5202 15.344 27.7103 20.3381 27.7926C25.5542 27.8783 29.554 23.5758 29.4401 18.8383V18.8383ZM20.4917 25.1342C17.2131 25.2436 14.1541 22.4349 14.1567 18.8924C14.1593 15.2627 16.9642 12.4191 20.5858 12.4662C24.0109 12.5111 26.8269 15.2466 26.8362 18.8471C26.8451 22.4293 23.782 25.2436 20.4917 25.1342V25.1342ZM18.5119 13.9982C17.3006 13.9908 15.6202 15.656 15.6295 16.8543C15.6328 17.3209 15.961 17.661 16.3983 17.6513C17.5973 17.6246 19.2281 16.0169 19.2696 14.8205C19.2881 14.2742 19.0371 14.0016 18.5119 13.9982L18.5119 13.9982ZM20.4964 0.568359C10.4428 0.568359 2.26333 8.74761 2.26333 18.8014C2.26333 28.8551 10.4426 37.0345 20.4964 37.0345C30.5502 37.0345 38.7295 28.8553 38.7295 18.8014C38.7291 8.7478 30.5502 0.568359 20.4964 0.568359V0.568359ZM20.4964 33.3886C12.4528 33.3886 5.90919 26.8449 5.90919 18.8014C5.90919 11.3105 11.585 5.12093 18.8624 4.30631C18.8561 4.36009 18.8528 4.41423 18.8528 4.46912C18.8468 5.37368 19.6112 6.16064 20.4927 6.15846C21.3636 6.15586 22.1283 5.39706 22.142 4.52143C22.1431 4.44874 22.1372 4.3768 22.1279 4.30596C29.4067 5.11929 35.0849 11.3092 35.0849 18.802C35.0842 26.8456 28.5404 33.3892 20.4968 33.3892L20.4964 33.3886Z" fill="black"/>
1027 </svg>';
1028 esc_html_e('Webcam','wpstream');
1029 print '</div>';
1030
1031
1032 print '<div class="wpstream_inactive_icon wpstream_stream_pro wpstream-button-icon wpstream-trigger-modal wpstream_tooltip_wrapper" data-modal="wpestate_broadcast_modal" data-show-id="'.$the_id.'"">';
1033
1034 print '<div class="wpstream_tooltip_disabled">'.esc_html__('Turn ON the channel to go live.','wpestream').'</div>';
1035 print '<div class="wpstream_tooltip">'.esc_html__('Go Live with external streaming app','wpestream').'</div>';
1036
1037 print '<svg width="51" height="38" viewBox="0 0 51 38" fill="none" xmlns="http://www.w3.org/2000/svg">
1038 <path d="M48.1266 13.9634L41.891 17.5343V13.6438C41.891 10.8723 39.6525 8.63384 36.8811 8.63384H33.1504L30.1124 3.62393C28.7268 1.3324 26.2751 0 23.6103 0H10.2863C8.84722 0 7.67471 1.17251 7.67471 2.6116C7.67471 4.05068 8.84722 5.22319 10.2863 5.22319H23.6103C24.4631 5.22319 25.2093 5.64961 25.6888 6.3957L27.0744 8.63413L5.00991 8.63376C2.23843 8.63376 0 10.8722 0 13.6437V32.9901C0 35.7616 2.23843 38 5.00991 38H36.9342C39.7057 38 41.9441 35.7616 41.9441 32.9901V29.0461L48.1797 32.6169C49.2991 33.2564 50.6846 32.4571 50.6846 31.1778L50.6842 15.4022C50.6311 14.123 49.2455 13.3237 48.1261 13.9632L48.1266 13.9634ZM38.1603 32.9368C38.1603 33.6297 37.574 34.1625 36.9345 34.1625L5.01029 34.1629C4.31733 34.1629 3.78458 33.5766 3.78458 32.9372V13.5907C3.78458 12.8978 4.37086 12.365 5.01029 12.365H36.9345C37.6275 12.365 38.1603 12.9513 38.1603 13.5907V32.9368Z" fill="black"/>
1039 <path d="M22.4917 21.585H9.70066C8.84784 21.585 8.15527 22.2779 8.15527 23.1304V28.247C8.15527 29.0998 8.84823 29.7923 9.70066 29.7923H22.4917C23.3445 29.7923 24.0371 29.0994 24.0371 28.247V23.1304C24.0371 22.2775 23.3445 21.585 22.4917 21.585Z" fill="black"/>
1040 </svg>';
1041 esc_html_e('External App','wpstream');
1042 print '</div>';
1043
1044
1045
1046
1047 print '<a href="'.esc_url($live_data_url).'" target="_blank" class="wpstream_inactive_icon wpstream_live_data wpstream_statistics_channel wpstream-button-icon wpstream_tooltip_wrapper" data-show-id="'.$the_id.'" >';
1048
1049 print '<div class="wpstream_tooltip_disabled">'.esc_html__('Turn ON the channel to see live stats.','wpestream').'</div>';
1050 print '<div class="wpstream_tooltip">'.esc_html__('Live Statistics','wpestream').'</div>';
1051
1052 print'<svg width="50" height="42" viewBox="0 0 50 42" fill="none" xmlns="http://www.w3.org/2000/svg">
1053 <path fill-rule="evenodd" clip-rule="evenodd" d="M48.9001 9.02344H40.3342C39.7288 9.02344 39.2344 9.51791 39.2344 10.1233V40.4275C39.2344 41.033 39.7288 41.5274 40.3342 41.5274H48.9001C49.5055 41.5274 50 41.0329 50 40.4275V10.1233C50 9.51791 49.5055 9.02344 48.9001 9.02344V9.02344Z" fill="black"/>
1054 <path fill-rule="evenodd" clip-rule="evenodd" d="M35.821 22.4067H27.2551C26.6497 22.4067 26.1553 22.9012 26.1553 23.5066V40.4277C26.1553 41.0332 26.6497 41.5276 27.2551 41.5276H35.821C36.4264 41.5276 36.9209 41.0332 36.9209 40.4277V23.5066C36.9209 22.9012 36.4264 22.4067 35.821 22.4067V22.4067Z" fill="black"/>
1055 <path fill-rule="evenodd" clip-rule="evenodd" d="M22.7439 0H14.178C13.5726 0 13.0781 0.494478 13.0781 1.09989V40.4275C13.0781 41.0329 13.5726 41.5274 14.178 41.5274H22.7439C23.3493 41.5274 23.8438 41.0329 23.8438 40.4275V1.09989C23.8438 0.49447 23.3493 0 22.7439 0V0Z" fill="black"/>
1056 <path fill-rule="evenodd" clip-rule="evenodd" d="M9.66573 15.2559H1.09987C0.49445 15.2559 -2.24584e-05 15.7512 -2.24584e-05 16.3558V40.4285C-2.24584e-05 41.0331 0.495325 41.5284 1.09987 41.5284H9.66573C10.2703 41.5284 10.7656 41.034 10.7656 40.4285V16.3558C10.7656 15.7503 10.2711 15.2559 9.66573 15.2559V15.2559Z" fill="black"/>
1057 </svg>';
1058 esc_html_e('Statistics','wpstream');
1059 print '</a>';
1060 print '</div>';
1061
1062 // Column 2: management controls (settings + edit only for back-end admins, then view/share).
1063 print '<div class="wpstream_options_col2 wpstream_show_settings_wrapper">';
1064 if($is_front==''){
1065 // Settings modal trigger (disabled while the channel is ON).
1066 print '<div class="wpstream_show_settings wpstream-button-icon wpstream-trigger-modal wpstream_tooltip_wrapper" data-modal="wpestate_settings_modal" data-show-id="'.$the_id.'" value="'.esc_html__('Settings','wpstream').'">';
1067
1068 print '<div class="wpstream_tooltip_disabled">'.esc_html__('Turn OFF the channel to change its settings.','wpestream').'</div>';
1069 print '<div class="wpstream_tooltip">'.esc_html__('Channel Settings','wpestream').'</div>';
1070
1071 print '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
1072 <path d="M49.22 21.8648C48.82 21.6099 45.665 19.7247 44.415 19.15L42.8651 15.4C43.3251 14.1601 44.2099 10.6849 44.3652 10.0798H44.3649C44.4911 9.52035 44.3213 8.93514 43.9149 8.52987L41.47 6.09995C41.0658 5.69189 40.4795 5.52168 39.9201 5.65003C39.4601 5.75501 35.92 6.64994 34.5999 7.15012L30.8499 5.60021C30.3048 4.40012 28.4699 1.32529 28.1351 0.795199V0.79485C27.8337 0.301688 27.298 0.00069643 26.7201 0H23.2798C22.7057 0.000348769 22.1718 0.294715 21.8648 0.779859C21.6098 1.1799 19.7247 4.33487 19.15 5.58487L15.4 7.13478C14.1601 6.67476 10.6849 5.78996 10.0798 5.63469V5.63504C9.52035 5.50878 8.93513 5.67864 8.52987 6.08496L6.09995 8.52988C5.69189 8.9341 5.52168 9.52042 5.65003 10.0798C5.75501 10.5398 6.64994 14.0799 7.15012 15.4L5.60021 19.15C4.40012 19.6951 1.32529 21.53 0.795198 21.8648H0.79485C0.301688 22.1662 0.00069643 22.7019 0 23.2798V26.7149C0.000348751 27.2893 0.294715 27.8233 0.779858 28.1299C1.1799 28.3849 4.33486 30.27 5.58487 30.8448L7.13478 34.5948C6.67475 35.8347 5.78996 39.3099 5.63469 39.915H5.63504C5.50878 40.4747 5.67863 41.06 6.08496 41.4649L8.51487 43.8948V43.8951C8.9191 44.3032 9.50541 44.4731 10.0648 44.3451C10.5248 44.2401 14.0649 43.3451 15.385 42.845L19.135 44.3949C19.6801 45.595 21.515 48.6698 21.8498 49.1999C22.1525 49.6997 22.6956 50.0035 23.2798 50H26.7149C27.2893 49.9996 27.8233 49.7053 28.1299 49.2201C28.3849 48.8201 30.27 45.6651 30.8447 44.4151L34.5947 42.8652C35.8347 43.3252 39.3098 44.21 39.9149 44.3653V44.3649C40.4747 44.4912 41.0599 44.3213 41.4649 43.915L43.8948 41.4851H43.8951C44.3032 41.0809 44.473 40.4945 44.345 39.9352C44.2401 39.4751 43.3451 35.9351 42.8449 34.615L44.3949 30.865C45.5949 30.3198 48.6698 28.485 49.1999 28.1501C49.6997 27.8474 50.0034 27.3044 49.9999 26.7201V23.2799C50.0045 22.7047 49.7087 22.1683 49.2201 21.8649L49.22 21.8648ZM24.9995 35.8845C22.1099 35.882 19.3399 34.7314 17.2985 32.6866C15.2572 30.6414 14.1118 27.8694 14.1146 24.9798C14.1171 22.0903 15.2676 19.3199 17.3125 17.2785C19.3577 15.2372 22.1297 14.0921 25.0193 14.0946C27.9088 14.0974 30.6792 15.2477 32.7205 17.2928C34.7619 19.338 35.907 22.1101 35.9045 24.9996C35.8978 27.8879 34.7462 30.6557 32.7021 32.6964C30.6576 34.7367 27.8876 35.8834 24.9994 35.8845H24.9995Z" fill="black"/>
1073 </svg>
1074 </div>';
1075
1076
1077 print '<a href="'.get_edit_post_link($the_id).'" class="wpstream_edit_channel wpstream-button-icon wpstream_tooltip_wrapper" target="_blank" data-show-id="'.$the_id.'"">';
1078 print '<div class="wpstream_tooltip">'.esc_html__('Edit Channel','wpestream').'</div>';
1079 print '<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
1080 <path d="M44.8203 1.79549C42.4263 -0.598498 38.5591 -0.598498 36.1651 1.79549L4.67494 33.2246C4.55203 33.3475 4.49079 33.4088 4.42954 33.5317C4.42954 33.5317 4.42954 33.5929 4.36829 33.5929C4.30705 33.7159 4.24539 33.7771 4.24539 33.9V33.9613L0.0711091 47.5886C-0.113044 48.264 0.0711092 48.9389 0.500659 49.4301C0.868966 49.7984 1.29852 49.9826 1.78973 49.9826C1.97388 49.9826 2.15804 49.9826 2.34219 49.9213L15.9085 45.747H15.9697C16.0926 45.6858 16.2151 45.6858 16.2768 45.6241C16.2768 45.6241 16.338 45.6241 16.338 45.5629C16.4609 45.5016 16.5834 45.44 16.6451 45.3175L48.0742 13.8884C50.4682 11.4944 50.4682 7.62715 48.0742 5.23316L44.8203 1.79549Z" fill="black"/>
1081 </svg>';
1082 print '</a>';
1083 }
1084
1085 print '<a href="'.get_permalink($the_id).'" target="_blank" class="wpstream_view_channel wpstream-button-icon wpstream_tooltip_wrapper" data-show-id="'.$the_id.'"">';
1086 print '<div class="wpstream_tooltip">'.esc_html__('View Channel','wpestream').'</div>';
1087 print '<svg width="50" height="32" viewBox="0 0 50 32" fill="none" xmlns="http://www.w3.org/2000/svg">
1088 <path d="M18.6201 15.2828H21.8752C21.8752 14.069 22.8681 13.0207 24.1373 13.0207V9.76562C21.1032 9.76562 18.6201 12.2487 18.6201 15.2828Z" fill="black"/>
1089 <path d="M24.855 0C12.9378 0 3.28272 10.9792 0.579292 14.3447C-0.193097 15.2826 -0.193097 16.662 0.579292 17.6553C3.28282 21.0208 12.9378 32 24.855 32C36.7722 32 46.4273 21.0208 49.1307 17.6553C49.9031 16.7174 49.9031 15.338 49.1307 14.3447C46.4275 10.9792 36.7722 0 24.855 0V0ZM24.855 25.8205C19.4482 25.8205 15.0344 21.4067 15.0344 15.9999C15.0344 10.5931 19.4482 6.17927 24.855 6.17927C30.2618 6.17927 34.6756 10.5931 34.6756 15.9999C34.6756 21.4067 30.2618 25.8205 24.855 25.8205Z" fill="black"/>
1090 </svg>';
1091 esc_html_e('View','wpstream');
1092 print '</a>';
1093
1094
1095 print '<div class="wpstream_share_channel wpstream-button-icon wpstream-trigger-modal wpstream_tooltip_wrapper" data-modal="wpestate_share_modal" data-show-id="'.$the_id.'"">';
1096 print '<div class="wpstream_tooltip">'.esc_html__('Share Channel','wpestream').'</div>';
1097 print '<svg width="44" height="50" viewBox="0 0 44 50" fill="none" xmlns="http://www.w3.org/2000/svg">
1098 <path d="M20.0777 20.5087L24.3481 17.9427C26.6432 16.5645 29.4554 16.2443 31.96 17.198C33.443 17.7642 35.1166 17.9363 36.852 17.6105C40.3163 16.9521 43.1162 14.1831 43.8115 10.7248C45.0606 4.51597 39.7751 -0.910703 33.5907 0.12835C30.2924 0.682061 27.5604 3.16211 26.5942 6.36168C26.4405 6.86646 26.3418 7.35835 26.2804 7.84466C25.9482 10.5028 24.2498 12.8044 21.9547 14.1827L19.3458 15.7456C17.1061 17.0869 14.3986 17.1792 11.9494 16.2748C10.9956 15.9241 9.96159 15.7271 8.88507 15.7271C3.39004 15.7271 -0.960663 20.7607 0.184097 26.4527C0.879447 29.911 3.67936 32.68 7.14364 33.3384C8.87902 33.6706 10.5525 33.492 12.0356 32.9259C14.5402 31.9721 17.3461 32.2919 19.6475 33.6706L23.906 36.2241C25.346 37.0855 26.2195 38.6424 26.2195 40.3159V40.3403C26.2195 45.8229 31.2287 50.1611 36.9018 49.0472C40.3602 48.3703 43.1415 45.583 43.8244 42.1246C44.9383 36.451 40.6 31.4423 35.1175 31.4423C34.0406 31.4423 33.007 31.6394 32.0532 31.9901C29.604 32.8944 26.9029 32.8022 24.6627 31.4608L20.0721 28.7038C18.6445 27.8484 17.7706 26.304 17.7706 24.6364V24.5875C17.7642 22.9208 18.6441 21.37 20.0777 20.5085L20.0777 20.5087Z" fill="black"/>
1099 </svg>';
1100 esc_html_e('Share','wpstream');
1101 print '</div>';
1102 print '</div>';
1103
1104
1105 // ON/OFF toggle button placed after the control columns in the theme layout.
1106 print '<div class="start_event wpstream_button wpstream_tooltip_wrapper" data-show-id="'.$the_id.'" > '. $button_status;
1107 print '<div class="wpstream_tooltip">'.esc_html__('Channel is now OFF. Click to turn ON.','wpestream').'</div>';
1108 print '</div>';
1109
1110 // Close the control bar wrapper.
1111 print '</div>';
1112
1113
1114
1115
1116 // Emit the hidden per-channel modals (settings, share, broadcast).
1117 $this->wpstream_display_modal_seetings($the_id);
1118 $this->wpstream_display_modal_share($the_id);
1119 $this->wpstream_display_modal_broadcast($the_id,$external_software_streaming_class,$obs_uri,$obs_stream);
1120
1121 // Close the theme card wrapper.
1122 print '</div>';
1123
1124 }
1125
1126
1127 /*
1128 *
1129 * Close modal button
1130 *
1131 */
1132 // Outputs the small "X" element used to dismiss the modal dialogs.
1133 public function wpstream_close_modal_button(){
1134 print '<div class="wpstream_close_modal"></div>';
1135 }
1136
1137 /**
1138 * Render the "edit settings for this channel" toggle plus the per-channel
1139 * event-options block on the product edit screen.
1140 *
1141 * When the toggle is off, the site-wide Default Channel Settings apply;
1142 * when on, the local per-channel options are used. In basic-stream mode a
1143 * warning is shown and the toggle is disabled. Echoes HTML.
1144 *
1145 * @param bool $is_basic_stream_mode True to render the basic-streaming warning and disable the toggle.
1146 * @return void
1147 */
1148 public function wpstream_local_event_options_toggle( $is_basic_stream_mode = false ) {
1149 // Read whether this post uses the global options and any stored per-channel overrides.
1150 $use_global_event_options = get_post_meta(get_the_ID(), 'use_global_event_options', true );
1151 $local_event_options = get_post_meta( get_the_ID(), 'local_event_options', true );
1152 // The local-options toggle is "on" when local options exist and global isn't forced, or global is explicitly 0.
1153 $use_local_event_options_enabled = ( is_array( $local_event_options ) && empty( $use_global_event_options ) ) ||
1154 ( !empty($use_global_event_options) && intval( $use_global_event_options ) === 0 );
1155
1156 // In basic-streaming mode, prepend the limitation notice.
1157 if ( $is_basic_stream_mode ) {
1158 $this->wpstream_basic_stream_mode_message();
1159 }
1160
1161 // Toggle row: label/description on the left, a switch checkbox on the right.
1162 print '<div class="wpstream_local_event_options_toggle_wrapper">';
1163 print '<div class="wpstream_local_event_options_toggle_info">';
1164 print '<label for="local_event_options_enabled" class="wpstream_local_event_options_label">'.esc_html__('Edit settings for this channel','wpstream').'</label>';
1165 print '<span>'.sprintf(esc_html__('When is OFF, the settings from %s will be applied','wpstream'), '<a href="' . admin_url('admin.php?page=wpstream_settings&tab=default_options') . '" target="_blank">'.esc_html__('Default Channel Settings','wpstream').'</a>').'</span>';
1166 print '</div>';
1167 // The switch checkbox reflects the current toggle state and is disabled in basic-stream mode.
1168 print '<label class="wpstream_switch">';
1169 print '<input id="local_event_options_enabled" type="checkbox" class="wpstream_local_event_options_toggle" ' . ($use_local_event_options_enabled === true ? 'checked' : '') . ' ' . ( $is_basic_stream_mode ? 'disabled' : '' ) . '>';
1170 print '<span class="wpstream_slider round"></span>';
1171 print '</label>';
1172 print '</div>';
1173 }
1174
1175 /**
1176 * Print the basic-streaming-mode notice shown above the channel settings.
1177 *
1178 * Wording differs depending on whether the account is out of streaming
1179 * hours vs. simply on a basic plan; both variants link to pricing. Echoes HTML.
1180 *
1181 * @return void
1182 */
1183 public function wpstream_basic_stream_mode_message() {
1184 print '<div class="basic-mode-notice">';
1185 // Out-of-hours variant: offer to top up or upgrade.
1186 if ( $this->wpstream_is_use_streaming_hours() ) {
1187 print sprintf(
1188 wp_kses(
1189 __(
1190 'You’ve used all available broadcast or viewer hours. Default channel settings will be used for now. To edit channel settings, <a href="%s" target="_blank">top up</a> your resources or <a href="%s" target="_blank">upgrade</a> your plan.',
1191 'wpstream'
1192 ),
1193 array( 'a' => array(
1194 'href' => array(),
1195 'target' => array()
1196 )
1197 )
1198 ),
1199 esc_url( 'https://wpstream.net/pricing/' ),
1200 esc_url( 'https://wpstream.net/pricing/' )
1201 );
1202 } else {
1203 // Plain basic-mode variant: offer to upgrade.
1204 print sprintf(
1205 wp_kses(
1206 __(
1207 'You are currently in Basic Streaming Mode. The default channel settings will be used instead. Please <a href="%s" target="_blank">upgrade</a> your plan to edit the channel settings.',
1208 'wpstream'
1209 ),
1210 array(
1211 'a' => array(
1212 'href' => array()
1213 )
1214 )
1215 ),
1216 esc_url( 'https://wpstream.net/pricing/' )
1217 );
1218 }
1219 print '</div>';
1220 }
1221
1222 /*
1223 *
1224 * Display modal settings
1225 *
1226 */
1227 /**
1228 * Render the per-channel "Channel Settings" modal (settings toggle plus
1229 * the streaming option controls). Echoes HTML.
1230 *
1231 * @param int $the_id Channel post/product ID.
1232 * @return void
1233 */
1234 public function wpstream_display_modal_seetings($the_id){
1235 // Whether the account is in basic-streaming mode (disables/locks some options).
1236 $is_basic_stream_mode = $this->wpstream_is_basic_streaming_mode();
1237
1238 // Modal shell with close button and title carrying the channel ID.
1239 print '<div class="wpstream_modal_form wpestate_settings_modal">';
1240 $this->wpstream_close_modal_button();
1241 print '<h3>';
1242 printf( esc_html__('Channel Settings (#ID %s)','wpstream'),$the_id);
1243 print '</h3>';
1244
1245 // The "edit settings for this channel" toggle.
1246 $this->wpstream_local_event_options_toggle( $is_basic_stream_mode );
1247
1248 // Resolve whether local per-channel options are in effect (same rule as the toggle).
1249 $local_event_options = get_post_meta($the_id,'local_event_options',true);
1250 $use_global_event_options = get_post_meta($the_id, 'use_global_event_options',true);
1251 $is_local_event_options_enabled = ( is_array( $local_event_options ) && empty( $use_global_event_options ) ) ||
1252 ( !empty($use_global_event_options) && intval( $use_global_event_options ) === 0 );
1253
1254 // Options that do not apply to the per-channel (local) context.
1255 $local_array_exclude=array('ses_encrypt','vod_domain_lock','vod_encrypt');
1256
1257 // Render the option controls; the 4th arg disables them when local options are off.
1258 print '<div class="wpstream_event_streaming_local">';
1259 $this->user_streaming_global_channel_options(
1260 '',
1261 $local_event_options,
1262 $local_array_exclude,
1263 !$is_local_event_options_enabled,
1264 $is_basic_stream_mode
1265 );
1266 print '</div>';
1267 print '</div>';
1268 }
1269
1270
1271 /*
1272 *
1273 * Display share settings
1274 *
1275 */
1276 /**
1277 * Render the per-channel "Share your Channel" modal (social-share bar). Echoes HTML.
1278 *
1279 * @param int $the_id Channel post/product ID.
1280 * @return void
1281 */
1282 public function wpstream_display_modal_share($the_id){
1283 // Modal shell with close button, title and the social-share icon row.
1284 print '<div class="wpstream_modal_form wpestate_share_modal">';
1285 $this->wpstream_close_modal_button();
1286 print '<h3>'.esc_html__('Share your Channel','wpstream').'</h3>';
1287 $this->wpstream_social_share($the_id);
1288 print '</div>';
1289 }
1290
1291
1292 /*
1293 *
1294 * Display broadcast modal
1295 *
1296 */
1297
1298 /**
1299 * Render the "Go Live with External Streaming App" modal: an encoder
1300 * picker (OBS, StreamYard, Restream, vMix, Wirecast, XSplit, Larix) plus
1301 * one settings panel per encoder showing the RTMP server URI and stream
1302 * key. Echoes HTML.
1303 *
1304 * @param int $the_id Channel post/product ID.
1305 * @param string $external_software_streaming_class Extra CSS class flag (unused in body).
1306 * @param string $obs_uri RTMP server/ingest URI for the channel.
1307 * @param string $obs_stream RTMP stream key for the channel.
1308 * @return void
1309 */
1310 public function wpstream_display_modal_broadcast($the_id,$external_software_streaming_class,$obs_uri,$obs_stream){
1311 // Modal shell with close button, title and instructions.
1312 print '<div class="wpstream_modal_form wpestate_broadcast_modal">';
1313 $this->wpstream_close_modal_button();
1314 print '<h3>'.esc_html__('Go Live with External Streaming App','wpstream').'</h3>';
1315
1316 print '<div class="wpstream_modal_explanations">'.esc_html__('Please choose your RTMP encoder/broadcaster','wpstream').'</div>';
1317
1318 // Dropdown selecting which encoder's settings panel to show (driven by JS).
1319 print '<select class="wpstream_external_broadcast_options">';
1320 print '<option value="wpstream_obs_settings">'.esc_html('OBS','wpstream').'</option>';
1321 print '<option value="wpstream_streamyard_settings">'.esc_html('StreamYard','wpstream').'</option>';
1322 print '<option value="wpstream_restream_settings">'.esc_html('Restream','wpstream').'</option>';
1323 print '<option value="wpstream_wimx_settings">'.esc_html('vMix','wpstream').'</option>';
1324 print '<option value="wpstream_wirecast_settings">'.esc_html('Wirecast','wpstream').'</option>';
1325 print '<option value="wpstream_xplit_settings">'.esc_html('XSplit','wpstream').'</option>';
1326 print '<option value="wpstream_larix_settings">'.esc_html('Larix Broadcaster','wpstream').'</option>';
1327
1328 print '</select>';
1329
1330 // Emit every encoder's settings panel; JS toggles the visible one.
1331 $this->wpstream_obs_settings($obs_uri,$obs_stream);
1332 $this->wpstream_streamyard_settings($obs_uri,$obs_stream);
1333 $this->wpstream_restream_settings($obs_uri,$obs_stream);
1334 $this->wpstream_wmix_settings($obs_uri,$obs_stream);
1335 $this->wpstream_wirecast_settings($obs_uri,$obs_stream);
1336 $this->wpstream_xplit_settings($obs_uri,$obs_stream);
1337 $this->wpstream_larix_settings($obs_uri,$obs_stream);
1338
1339 print '</div>';
1340 }
1341
1342
1343
1344
1345 /*
1346 *
1347 * Display OBS settings
1348 *
1349 */
1350
1351 /**
1352 * Render the OBS encoder settings panel (server URI + stream key + steps).
1353 * Shown by default (display:block). Echoes HTML.
1354 *
1355 * @param string $obs_uri RTMP server/ingest URI.
1356 * @param string $obs_stream RTMP stream key.
1357 * @return void
1358 */
1359 public function wpstream_obs_settings($obs_uri,$obs_stream){
1360
1361 print '<div class="external_software_streaming wpstream_obs_settings" style="display:block;">';
1362
1363 // Server URI and stream key, each with a "copy" affordance.
1364 print '<div class="external_software_streaming_details">';
1365 print '<div class="event_list_unit_notificationx"><strong>'.esc_html__('Server:').' </strong>';
1366 print '<div class="wpstream_live_uri_text">' . $obs_uri.'</div>
1367 <div class="copy_live_uri">'.__('copy','wpstream').'</div>';
1368
1369 print '<div class="event_list_stream_key_wrap"><strong>'.__('Stream Key:').' </strong>
1370 <div class="wpstream_live_key_text">'. $obs_stream.'</div><div class="copy_live_key">'.__('copy','wpstream').'</div></div>';
1371 print '</div>';
1372
1373 print'</div>';
1374
1375 print ' <div class="wpstream_modal_explanations">';
1376 print '<ul>
1377 <li>1. Click Settings in the OBS Window and then Select Stream.</li>
1378 <li>2. Choose Custom Streaming Server in the Stream Type dropdown menu.</li>
1379 <li>3. In the URL box, type/paste your Server.</li>
1380 <li>4. In the Stream key, type/paste your Stream key.</li>
1381 <li>5. Save changes.Close the Settings window and click on the "Start Streaming" button in the main window of OBS.</li></ul>';
1382
1383 print '</div>';
1384
1385
1386 print'</div>';
1387 }
1388
1389 /*
1390 *
1391 * Display StreamYard settings
1392 *
1393 */
1394
1395 /**
1396 * Render the StreamYard encoder settings panel (RTMP URL + stream key + steps). Echoes HTML.
1397 *
1398 * @param string $obs_uri RTMP server/ingest URI.
1399 * @param string $obs_stream RTMP stream key.
1400 * @return void
1401 */
1402 public function wpstream_streamyard_settings($obs_uri,$obs_stream){
1403
1404 print '<div class="external_software_streaming wpstream_streamyard_settings">';
1405
1406 // Server URI and stream key, each with a "copy" affordance.
1407 print '<div class="external_software_streaming_details">';
1408 print '<div class="event_list_unit_notificationx"><strong>'.esc_html__('RTMP server URL:').' </strong>';
1409 print '<div class="wpstream_live_uri_text">' . $obs_uri.'</div>
1410 <div class="copy_live_uri">'.__('copy','wpstream').'</div>';
1411
1412 print '<div class="event_list_stream_key_wrap"><strong>'.__('Stream key:').' </strong>
1413 <div class="wpstream_live_key_text">'. $obs_stream.'</div><div class="copy_live_key">'.__('copy','wpstream').'</div></div>';
1414 print '</div>';
1415
1416 print'</div>';
1417
1418 print ' <div class="wpstream_modal_explanations">';
1419 print '<ul>
1420 <li>1. Set up your destination by going to your StreamYard account.</li>
1421 <li>2. Choose "Custom RTMP" and add the RTMP server URL and Stream key from WpStream.</li>
1422 <li>3. Go to "Broadcasts" then "create a broadcast".</li>
1423 <li>4. Enter Live Studio.</li>
1424 <li>5. Adjust all your preferred settings and Click on "Go Live".</li></ul>';
1425
1426 print '</div>';
1427
1428
1429 print'</div>';
1430 }
1431
1432
1433
1434 /*
1435 *
1436 * Display StreamYard settings
1437 *
1438 */
1439
1440 /**
1441 * Render the Restream encoder settings panel (RTMP URL + stream key + steps). Echoes HTML.
1442 *
1443 * @param string $obs_uri RTMP server/ingest URI.
1444 * @param string $obs_stream RTMP stream key.
1445 * @return void
1446 */
1447 public function wpstream_restream_settings($obs_uri,$obs_stream){
1448
1449 print '<div class="external_software_streaming wpstream_restream_settings">';
1450
1451 // Server URI and stream key, each with a "copy" affordance.
1452 print '<div class="external_software_streaming_details">';
1453 print '<div class="event_list_unit_notificationx"><strong>'.esc_html__('RTMP URL:').' </strong>';
1454 print '<div class="wpstream_live_uri_text">' . $obs_uri.'</div>
1455 <div class="copy_live_uri">'.__('copy','wpstream').'</div>';
1456
1457 print '<div class="event_list_stream_key_wrap"><strong>'.__('Stream key:').' </strong>
1458 <div class="wpstream_live_key_text">'. $obs_stream.'</div><div class="copy_live_key">'.__('copy','wpstream').'</div></div>';
1459 print '</div>';
1460
1461 print'</div>';
1462
1463 print ' <div class="wpstream_modal_explanations">';
1464 print '<ul>
1465 <li>1. Go to your Restream account and set up a destination or channel.</li>
1466 <li>2. Choose "Custom RTMP" and add the RTMP URL and Stream Key from WpStream.</li>
1467 <li>3. Click on "Add Channel".</li>
1468 <li>4. Enter Live Studio.</li>
1469 <li>5. Adjust your preferred settings and Go Live.</li></ul>';
1470
1471 print '</div>';
1472
1473
1474 print'</div>';
1475 }
1476
1477
1478
1479 /*
1480 *
1481 * Display Xplit settings
1482 *
1483 */
1484
1485
1486 /**
1487 * Render the XSplit encoder settings panel (RTMP URL + stream key + steps).
1488 * Hidden by default (display:none). Echoes HTML.
1489 *
1490 * @param string $obs_uri RTMP server/ingest URI.
1491 * @param string $obs_stream RTMP stream key.
1492 * @return void
1493 */
1494 public function wpstream_xplit_settings($obs_uri,$obs_stream){
1495 print '<div class="external_software_streaming wpstream_xplit_settings" style="display:none;">';
1496 // Server URI and stream key, each with a "copy" affordance.
1497 print '<div class="external_software_streaming_details">';
1498 print '<div class="event_list_unit_notificationx"><strong>'.esc_html__('RTMP Url:').' </strong>';
1499 print '<div class="wpstream_live_uri_text">' . $obs_uri.'</div>
1500 <div class="copy_live_uri">'.__('copy','wpstream').'</div>';
1501
1502 print '<div class="event_list_stream_key_wrap"><strong>'.__('Stream Key:').' </strong>
1503 <div class="wpstream_live_key_text">'. $obs_stream.'</div><div class="copy_live_key">'.__('copy','wpstream').'</div></div>';
1504 print '</div>';
1505
1506 print'</div>';
1507
1508 print ' <div class="wpstream_modal_explanations">';
1509 print '<ul>
1510 <li>1. Click Broadcast in the XSplit Window and then click "Set up a new output".</li>
1511 <li>2. Choose Custom RTMP in the Set up a new output dropdown menu.</li>
1512 <li>3. In the URL box, type/paste your RTMP Url.</li>
1513 <li>4. In the Stream key, type/paste your Stream key.</li>
1514 <li>5. Save changes and click on the "Stream" button in the main window.</li></ul>';
1515 print '</div>';
1516
1517 print '</div>';
1518 }
1519
1520
1521
1522 /*
1523 *
1524 * Display WireCast settings
1525 *
1526 */
1527
1528
1529 /**
1530 * Render the Wirecast encoder settings panel (address + stream + steps).
1531 * Hidden by default (display:none). Echoes HTML.
1532 *
1533 * @param string $obs_uri RTMP server/ingest URI (shown as "Address").
1534 * @param string $obs_stream RTMP stream key (shown as "Stream").
1535 * @return void
1536 */
1537 public function wpstream_wirecast_settings($obs_uri,$obs_stream){
1538 print '<div class="external_software_streaming wpstream_wirecast_settings" style="display:none;">';
1539
1540 // Server address and stream, each with a "copy" affordance.
1541 print '<div class="external_software_streaming_details">';
1542 print '<div class="event_list_unit_notificationx"><strong>'.esc_html__('Address:').' </strong>';
1543 print '<div class="wpstream_live_uri_text">' . $obs_uri.'</div>
1544 <div class="copy_live_uri">'.__('copy','wpstream').'</div>';
1545
1546 print '<div class="event_list_stream_key_wrap"><strong>'.__('Stream:').' </strong>
1547 <div class="wpstream_live_key_text">'. $obs_stream.'</div><div class="copy_live_key">'.__('copy','wpstream').'</div></div>';
1548 print '</div>';
1549
1550 print'</div>';
1551
1552 print ' <div class="wpstream_modal_explanations">';
1553 print '<ul>
1554 <li>1. Click Output on the top of the screen and then Select Output Settings.</li>
1555 <li>2. In the destination, choose RTMP Server and click OK.</li>
1556 <li>3. In the Address box, type/paste your Address.</li>
1557 <li>4. In the Stream box, type/paste your Stream key.</li>
1558 <li>5. Click on OK to save changes just click the Output on the top of the screen and then Start/Stop Broadcasting.</li></ul>';
1559 print '</div>';
1560
1561 print '</div>';
1562 }
1563
1564
1565
1566 /*
1567 *
1568 * Display Larix settings
1569 *
1570 */
1571
1572
1573 /**
1574 * Render the Larix Broadcaster (mobile) settings panel: a combined RTMP
1575 * string, a QR code image (populated by JS), and, on mobile, a deep-link
1576 * button to launch Larix. Hidden by default. Echoes HTML.
1577 *
1578 * @param string $obs_uri RTMP server/ingest URI.
1579 * @param string $obs_stream RTMP stream key.
1580 * @return void
1581 */
1582 public function wpstream_larix_settings($obs_uri,$obs_stream){
1583 print '<div class="external_software_streaming wpstream_larix_settings" style="display:none;">';
1584 // Larix takes a single combined RTMP URL (server + key).
1585 $larix_rtmp=$obs_uri.$obs_stream;
1586 // RTMP value (filled in by JS) with a "copy" affordance.
1587 print '<div class="external_software_streaming_details">';
1588 print '<div class="event_list_unit_notificationx"><strong>'.esc_html__('RTMP:').'</strong>';
1589 print '<div class="wpstream_live_uri_text wpstream_larix_rtmp"></div>
1590 <div class="copy_live_uri">'.__('copy','wpstream').'</div>';
1591
1592 print '</div>';
1593
1594 print'</div>';
1595
1596 // Instructions: scan QR, configure manually, or (mobile only) tap the launch button.
1597 print ' <div class="wpstream_modal_explanations">';
1598 print '<ul>
1599 <li>A. Scan the QR code.</li>
1600 <li>or</li>
1601 <li>B. Manually configure Larix with the above RTMP</li>';
1602 if(wp_is_mobile() ){
1603 print '
1604 <li>or</li>
1605 <li>C. Click on the button below </li></ul>';
1606 }
1607
1608
1609 print '</div>';
1610
1611
1612 // QR image (src set by JS) and, on mobile, the Larix deep-link button.
1613 print '<img class="print_qrcode" src="" />';
1614 if(wp_is_mobile() ){
1615 print '<a href="" class="wpstream_start_with_larix_mobile" >Start Streaming with Larix</a>';
1616 }
1617
1618
1619
1620 print '</div>';
1621 }
1622
1623
1624
1625
1626 /*
1627 *
1628 * Display Wmix settings
1629 *
1630 */
1631
1632
1633 /**
1634 * Render the vMix encoder settings panel (URL + stream key + steps).
1635 * Hidden by default (display:none). Echoes HTML.
1636 *
1637 * @param string $obs_uri RTMP server/ingest URI.
1638 * @param string $obs_stream RTMP stream key.
1639 * @return void
1640 */
1641 public function wpstream_wmix_settings($obs_uri,$obs_stream){
1642 print '<div class="external_software_streaming wpstream_wimx_settings" style="display:none;">';
1643
1644 // Server URL and stream key, each with a "copy" affordance.
1645 print '<div class="external_software_streaming_details">';
1646 print '<div class="event_list_unit_notificationx"><strong>'.esc_html__('URL:').' </strong>';
1647 print '<div class="wpstream_live_uri_text">' . $obs_uri.'</div>
1648 <div class="copy_live_uri">'.__('copy','wpstream').'</div>';
1649
1650 print '<div class="event_list_stream_key_wrap"><strong>'.__('Stream Key:').' </strong>
1651 <div class="wpstream_live_key_text">'. $obs_stream.'</div><div class="copy_live_key">'.__('copy','wpstream').'</div></div>';
1652 print '</div>';
1653
1654 print'</div>';
1655
1656 print ' <div class="wpstream_modal_explanations">';
1657 print '<ul>
1658 <li>1. Click on the gear icon near the stream button on the bottom.</li>
1659 <li>2. Choose a custom RTMP Server in destination.</li>
1660 <li>3. In the URL box, type/paste your URL.</li>
1661 <li>4. In the Stream key, type/paste your Stream key.</li>
1662 <li>5. Save changes. You can start streaming by clicking on the stream button at the bottom of the dashboard.</li></ul>';
1663 print '</div>';
1664
1665 print '</div>';
1666 }
1667
1668
1669
1670 /*
1671 * Set Settings
1672 *
1673 *
1674 */
1675 /**
1676 * Render the WpStream Settings admin screen and persist submitted values.
1677 *
1678 * On POST it verifies the nonce, then stores each field as a `wpstream_*`
1679 * option (with special handling for the streamer role and the per-tab
1680 * global event options), and flushes rewrite rules. It then builds the
1681 * tabbed settings definition array and renders the form. Echoes HTML.
1682 *
1683 * @return void
1684 */
1685 public function wpstream_settings(){
1686
1687 // Admin-only screen.
1688 if( !current_user_can('administrator') ){
1689 die('Only for administrators');
1690 }
1691
1692
1693 // Handle a settings submission.
1694 if($_SERVER['REQUEST_METHOD'] === 'POST'){
1695 // CSRF protection.
1696 if( !wp_verify_nonce($_POST['wpstream-settings-nonce'],'wpstream-settings-nonce') ){
1697 die('Security check');
1698 }
1699
1700 $allowed_html = array();
1701 $exclude_array = array();
1702 $allowed_html = array();
1703
1704
1705
1706 // If the "channel type" section was shown but no streamer role was chosen, clear the stored role.
1707 if( isset($_POST['user_streaming_channel_type_hidden']) && intval($_POST['user_streaming_channel_type_hidden'])==1 && !isset($_POST['stream_role']) ){
1708 update_option( sanitize_key('wpstream_stream_role'), '' );
1709 }
1710
1711 // Persist every posted field (except the submit button) as a sanitized wpstream_* option.
1712 foreach($_POST as $variable=>$value){
1713 if ($variable!='submit'){
1714 if (!in_array($variable, $exclude_array) ){
1715 update_option( sanitize_key('wpstream_'.$variable), sanitize_text_field ($value) );
1716 }
1717
1718 // Streamer role is stored raw (may be an array of roles) rather than sanitized as text.
1719 if($variable=='stream_role'){
1720 update_option( sanitize_key('wpstream_stream_role'), $value );
1721 }
1722
1723 }
1724 }
1725
1726
1727 // On the "default options" tab, fold the per-option checkboxes into a single 1/0 map.
1728 if( isset($_GET['tab']) && $_GET['tab']=='default_options' ){
1729 $event_settings=array();
1730 foreach($this->global_event_options as $key=>$option){
1731 $event_settings[$key]='';
1732 if(isset($_POST['wpstream_event_set_'.$key]) && $_POST['wpstream_event_set_'.$key]=='on'){
1733 $event_settings[$key]=1;
1734 }else{
1735 $event_settings[$key]=0;
1736 }
1737 }
1738 update_option('wpstream_user_streaming_global_channel_options',$event_settings);
1739 }
1740
1741
1742
1743
1744 // reset permalinkgs
1745 // Slug settings can change permalinks, so drop and rebuild the rewrite rules.
1746 global $wp_rewrite;
1747
1748 update_option( "rewrite_rules", FALSE );
1749 $wp_rewrite->flush_rules( true );
1750
1751 }
1752
1753 // Definition of every settings field, keyed and grouped by 'tab' (general, subscription, messages, default options, VOD defaults, support).
1754 $wpstream_settings_array =array(
1755 1 => array(
1756 'tab' => 'general_options',
1757 'label' => esc_html__('Slug for free video/channel pages ','wpstream'),
1758 'name' => 'free_media_slug',
1759 'type' => 'text',
1760 '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'),
1761 ),
1762
1763 'free_vod_slug' => array(
1764 'tab' => 'general_options',
1765 'label' => esc_html__('Slug for free VOD pages ','wpstream'),
1766 'name' => 'free_media_slug_vod',
1767 'type' => 'text',
1768 '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'),
1769 ),
1770
1771 2 => array(
1772 'tab' => 'general_options',
1773 'label' => esc_html__('Non-Admin User Roles Allowed to Broadcast','wpstream'),
1774 'name' => 'stream_role',
1775 'type' => 'user_roles',
1776 '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'),
1777
1778 ),
1779 3 => array(
1780 'tab' => 'general_options',
1781 'label' => esc_html__('Non Admin Streamers Channel Type.','wpstream'),
1782 'name' => 'user_streaming_channel_type',
1783 'type' => 'select',
1784 'select_values'=>array(
1785 'free' => esc_html__('Free Live Channel','wpstream'),
1786 'paid' => esc_html__('Pay-Per-View','wpstream')
1787 ),
1788 'details' => esc_html__('Choose whether the channels assigned to non-admins are free-for-all or pay-per-view (WooCommerce product).','wpstream'),
1789 ),
1790
1791 4 => array(
1792 'tab' => 'general_options',
1793 'label' => esc_html__('Default Pay-Per-View Price','wpstream'),
1794 'name' => 'user_streaming_default_price',
1795 'type' => 'text',
1796 'details' => esc_html__('Default price of pay-per-view channels assigned to non-admins.','wpstream'),
1797 ),
1798
1799
1800
1801 6 => array(
1802 'tab' => 'subscription_options',
1803 'label' => esc_html__('Use Global Subscription Mode','wpstream'),
1804 'name' => 'global_sub',
1805 'type' => 'slidertoogle',
1806 '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'),
1807 ),
1808
1809 7 => array(
1810 'tab' => 'subscription_options',
1811 'label' => esc_html__('Subscription ID for Global Subscription Mode','wpstream'),
1812 'name' => 'global_sub_id',
1813 'type' => 'text',
1814 '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'),
1815 ),
1816 8 => array(
1817 'tab' => 'messages_options',
1818 'label' => esc_html__('PPV not logged in message','wpstream'),
1819 'name' => 'product_not_login',
1820 'type' => 'text',
1821 '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'),
1822 'default' => esc_html__('You must be logged in to watch this video.','wpstream'),
1823 ),
1824 9 => array(
1825 'tab' => 'messages_options',
1826 'label' => esc_html__('PPV not purchased message','wpstream'),
1827 'name' => 'product_not_bought',
1828 'type' => 'text',
1829 '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'),
1830 'default' => esc_html__('You did not yet purchase this item.','wpstream'),
1831 ),
1832 10 => array(
1833 'tab' => 'messages_options',
1834 'label' => esc_html__('Subscription not purchased message','wpstream'),
1835 'name' => 'product_not_subscribe',
1836 'type' => 'text',
1837 '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'),
1838 'default' => esc_html__(' You did not yet subscribe to this item.','wpstream'),
1839 ),
1840 11 => array(
1841 'tab' => 'messages_options',
1842 'label' => esc_html__('Thank you message','wpstream'),
1843 'name' => 'product_thankyou',
1844 'type' => 'text',
1845 'details' => esc_html__('This message will be displayed on the thank you page (after purchase) and the confirmation email.','wpstream'),
1846 'default' => esc_html__('Thanks for your purchase. You can access your item at any time by visiting the following page: {item_link}','wpstream'),
1847 ),
1848
1849 12 => array(
1850 'tab' => 'messages_options',
1851 'label' => esc_html__('Subscription Active message','wpstream'),
1852 'name' => 'subscription_active',
1853 'type' => 'text',
1854 'details' => esc_html__('This message will be displayed on subscription product page.','wpstream'),
1855 'default' => esc_html__('Your Subscription is Active','wpstream'),
1856 ),
1857 13 => array(
1858 'tab' => 'messages_options',
1859 'label' => esc_html__('You are not live message','wpstream'),
1860 'name' => 'you_are_not_live',
1861 'type' => 'text',
1862 'details' => esc_html__('This message will be displayed in player.','wpstream'),
1863 'default' => esc_html__('We are not live at this moment','wpstream'),
1864 ),
1865
1866 14 => array(
1867 'tab' => 'general_options',
1868 'label' => esc_html__('Video player theme','wpstream'),
1869 'name' => 'video_player_theme',
1870 'type' => 'select',
1871 'select_values'=>array(
1872 'default' => esc_html__('Default','wpstream'),
1873 'city' => esc_html__('City','wpstream'),
1874 'forest' => esc_html__('Forest','wpstream'),
1875 'fantasy' => esc_html__('Fantasy','wpstream'),
1876 'sea' => esc_html__('Sea','wpstream'),
1877 ),
1878 'details' => esc_html__('Choose the video player theme to have a different look for the player.','wpstream'),
1879 ),
1880 'wpstream_player_logo' => array(
1881 'tab' => 'general_options',
1882 'name' => 'player_logo',
1883 'label' => esc_html__('Logo for the video player','wpstream'),
1884 'type' => 'image',
1885 'details' => esc_html__('This logo will be displayed on the the video player.','wpstream'),
1886 'default' => '',
1887 'image_size' => 'thumbnail',
1888 ),
1889 // hide the video player logo opacity for now
1890 // 'wpstream_player_logo_opacity' => array(
1891 // 'tab' => 'general_options',
1892 // 'name' => 'player_logo_opacity',
1893 // 'label' => esc_html__('Opacity of the video player logo','wpstream'),
1894 // 'type' => 'range',
1895 // 'details' => esc_html__('Set the opacity of the logo','wpstream'),
1896 // 'default' => '',
1897 // 'image_size' => 'thumbnail',
1898 // ),
1899 'wpsteram_player_logo_position' => array(
1900 'tab' => 'general_options',
1901 'name' => 'player_logo_position',
1902 'label' => esc_html__('Position of the video player logo','wpstream'),
1903 'type' => 'select',
1904 'select_values'=>array(
1905 'top-left' => esc_html__('Top Left','wpstream'),
1906 'top-right' => esc_html__('Top Right','wpstream'),
1907 'bottom-left' => esc_html__('Bottom Left','wpstream'),
1908 'bottom-right' => esc_html__('Bottom Right','wpstream'),
1909 ),
1910 'details' => esc_html__('Choose the position of the logo on the video player.','wpstream'),
1911 'default' => '',
1912 ),
1913
1914
1915
1916 99 => array(
1917 'tab' => 'default_options',
1918 'label' => esc_html__('Events Options ','wpstream'),
1919 'name' => 'user_streaming_global_channel_options',
1920 'type' => 'user_streaming_global_channel_options',
1921 'details' => esc_html__('Global Options for live events.','wpstream'),
1922 ),
1923
1924 100 => array(
1925 'tab' => 'default_options_vod',
1926 'label' => esc_html__('Autoplay','wpstream'),
1927 'name' => 'vod_autoplay',
1928 'type' => 'slidertoogle',
1929 'details' => esc_html__('If enabled, video will attempt to start playing automatically. This is only achievable in some browsers.','wpstream'),
1930 ),
1931
1932
1933 101 => array(
1934 'tab' => 'default_options_vod',
1935 'label' => esc_html__('Start Muted','wpstream'),
1936 'name' => 'vod_start_muted',
1937 'type' => 'slidertoogle',
1938 'details' => esc_html__('If enabled, video will start muted. This may increase the rate of autoplay in some browsers.','wpstream'),
1939 ),
1940
1941 102 => array(
1942 'tab' => 'default_options_vod',
1943 'label' => esc_html__('Lock To Website','wpstream'),
1944 'name' => 'vod_domain_lock',
1945 'type' => 'slidertoogle',
1946 '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') ),
1947 ),
1948
1949 103 => array(
1950 'tab' => 'default_options_vod',
1951 'label' => esc_html__('Encrypt Video','wpstream'),
1952 'name' => 'vod_encrypt',
1953 'type' => 'slidertoogle',
1954 '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'),
1955 ),
1956
1957 /* 'vod_domain_lock' =>array(
1958 'name' => esc_html__('Video On Demand - Lock To Website','wpstream'),
1959 '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') ),
1960 'defaults' => 'no',
1961 ),
1962 'vod_encrypt' =>array(
1963 'name' => esc_html__('Encrypt Video on Demand','wpstream'),
1964 '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'),
1965 'defaults' => 'no',
1966 ),*/
1967
1968 104 => array(
1969 'tab' => 'support_tab',
1970 'label' => esc_html__('Logs','wpstream'),
1971 'name' => 'logs',
1972 'type' => 'logs_table',
1973 'details' => esc_html__('This is the error log of the plugin.','wpstream'),
1974 )
1975
1976 );
1977
1978 // Determine the active tab (defaults to General Options).
1979 $active_tab = 'general_options';
1980 if( isset( $_GET[ 'tab' ] ) ) {
1981 $active_tab = $_GET[ 'tab' ];
1982 }
1983
1984
1985 // Open the settings panel and the form.
1986 print '<div class="theme_options_tab_wpstream" style="display:block;" >
1987 <h1>'.__('WpStream Settings','wpstream').'</h1>
1988 <form method="post" action="" >';
1989
1990 // Tab navigation bar; the current tab gets the nav-tab-active class.
1991 print '<h2 class="nav-tab-wrapper">
1992 <a href="?page=wpstream_settings&tab=general_options" class="nav-tab '; echo $active_tab == 'general_options' ? 'nav-tab-active' : ''; echo '">'.esc_html__('General Options','wpstream').'</a>
1993 <a href="?page=wpstream_settings&tab=default_options" class="nav-tab '; echo $active_tab == 'default_options' ? 'nav-tab-active' : ''; echo '">'.esc_html__('Default Channel Settings','wpstream').'</a>
1994 <a href="?page=wpstream_settings&tab=default_options_vod" class="nav-tab '; echo $active_tab == 'default_options_vod' ? 'nav-tab-active' : ''; echo '">'.esc_html__('VOD Settings','wpstream').'</a>
1995 <a href="?page=wpstream_settings&tab=subscription_options" class="nav-tab '; echo $active_tab == 'subscription_options' ? 'nav-tab-active' : '';echo '">'.esc_html__('Subscription Options','wpstream').'</a>
1996 <a href="?page=wpstream_settings&tab=messages_options" class="nav-tab '; echo $active_tab == 'messages_options' ? 'nav-tab-active' : ''; echo '">'.esc_html__('Customize Messages','wpstream').'</a>
1997 <a href="?page=wpstream_settings&tab=support_tab" class="nav-tab '; echo $active_tab == 'support_tab' ? 'nav-tab-active' : ''; echo '">'.esc_html__('Support','wpstream').'</a>
1998 </h2>';
1999 $help_link='';
2000
2001 // Basic-stream mode disables/annotates some default-channel controls.
2002 $is_basic_stream_mode = $this->wpstream_is_basic_streaming_mode();
2003 print '<div class="wpstream_option_wrapper">';
2004
2005 // Pick the contextual "Video Help" docs link for the active tab.
2006 switch ($active_tab) {
2007 case 'general_options':
2008 $help_link='https://docs.wpstream.net/docs/general-settings/';
2009 break;
2010 case 'default_options':
2011 $help_link='https://docs.wpstream.net/docs/default-channel-settings/';
2012 break;
2013 case 'default_options_vod':
2014 $help_link='https://docs.wpstream.net/docs/vod-settings/';
2015 break;
2016 case 'subscription_options':
2017 $help_link='https://docs.wpstream.net/docs/subscription-options/';
2018 break;
2019 case 'messages_options':
2020 $help_link='https://docs.wpstream.net/docs/customize-messages/';
2021 break;
2022 }
2023
2024 print '<div class="options_wrapper">';
2025 // Render each field that belongs to the active tab.
2026 foreach ($wpstream_settings_array as $key=>$option){
2027 // Skip fields from other tabs.
2028 if($option['tab']!=$active_tab){
2029 continue;
2030 }
2031
2032 // Intro blurb (and basic-mode notice) shown above the global channel options control.
2033 if ( $option['type']=='user_streaming_global_channel_options' ) {
2034 print '<div class="default-channel-settings-info">';
2035 print esc_html__( 'These settings will apply to newly created channels; existing channels will not change settings if you change them here', 'wpstream');
2036 if ( $is_basic_stream_mode ) {
2037 $this->wpstream_basic_stream_mode_message();
2038 }
2039 print '</div>';
2040
2041 }
2042 print '<div class="wpstream_option">';
2043 // Load this option's stored value.
2044 $options_value = get_option('wpstream_'.$option['name']) ;
2045
2046
2047 // Render the appropriate control for this field's type.
2048 switch( $option['type'] ) {
2049 // Multi-select of WordPress user roles allowed to broadcast.
2050 case 'user_roles':
2051 print '<label for="'.$option['name'].'">'.$option['label'].'</label>';
2052 print $this->wpstream_select_user_roles($option['name'],$options_value);
2053 print '<div class="settings_details">'.$option['details'].'</div>';
2054 break;
2055 // The grid of default per-event streaming options.
2056 case 'user_streaming_global_channel_options':
2057 $exclude_array=array();
2058 $this->user_streaming_global_channel_options(
2059 $option['name'],
2060 $options_value,
2061 $exclude_array,
2062 $is_basic_stream_mode
2063 );
2064 break;
2065 // Plain text input; falls back to the field's default when unset.
2066 case 'text':
2067 if($options_value==''){
2068 $options_value='';
2069 if(isset($option['default'])){
2070 $options_value=$option['default'];
2071 }
2072 }
2073
2074 print '<label for="'.$option['name'].'">'.$option['label'].'</label>';
2075 print '<input class="wpstream-text-input-setting" id="'.$option['name'].'" type="'.$option['type'].'" size="36" name="'.$option['name'].'" value="'.esc_attr($options_value).'" />';
2076 print '<div class="settings_details">'.$option['details'].'</div>';
2077 break;
2078 // Dropdown built from the field's select_values map; a hidden mirror marks the field as present.
2079 case 'select':
2080 print '<label for="'.$option['name'].'">'.$option['label'].'</label>';
2081 print '<select id="'.$option['name'].'" name="'.$option['name'].'" >';
2082 foreach($option['select_values'] as $key=>$value){
2083 print '<option value="'.$key.'" ';
2084 // Pre-select the currently stored value.
2085 if( $key == esc_html($options_value) ){
2086 print ' selected ';
2087 }
2088 print '>'.$value.'</option>';
2089 }
2090 print '</select>';
2091 print '<input type="hidden" name="'.$option['name'].'_hidden" value="1" >';
2092 print '<div class="settings_details">'.$option['details'].'</div>';
2093 break;
2094 // On/off switch; a hidden 0-valued twin ensures "off" posts a value.
2095 case 'slidertoogle':
2096 print '<label for="'.$option['name'].'">'.$option['label'].'</label>';
2097 print '<div style="display: flex; gap: 25px; justify-content: space-between;">';
2098 print '<div class="settings_details">'.$option['details'].'</div>';
2099 print '<label class="wpstream_switch">
2100 <input type="hidden" class="wpstream_event_option_itemc" value="0" name="'.$option['name'].'" >
2101 <input type="checkbox" class="wpstream_event_option_itemc" value="1" name="'.$option['name'].'" ';
2102 if( intval($options_value) !==0 ){
2103 print ' checked ';
2104 }
2105 print '> <span class="wpstream_slider round"></span>';
2106 print '</label>';
2107 print '</div>';
2108 break;
2109 // Media-library image picker with preview + upload/remove buttons.
2110 case 'image':
2111 $image_url = $options_value ? esc_url($options_value) : '';
2112 $has_image = !empty($image_url);
2113
2114 print '<label for="' . $option['name'] . '">' . $option['label'] . '</label>';
2115 print '<div class="wpstream-image-upload-wrapper">';
2116 print '<input type="hidden" id="' . $option['name'] . '" name="' . $option['name'] . '" value="' . $image_url . '" />';
2117
2118 // Preview area
2119 print '<div class="wpstream-image-preview" style="' . (!$has_image ? 'display:none;' : '') . '">';
2120 print '<img src="' . $image_url . '" alt="Preview" />';
2121 print '</div>';
2122
2123 // Upload/remove buttons
2124 print '<div class="wpstream-image-upload-buttons">';
2125 print '<button type="button" class="wpstream-upload-image button">' . esc_html__('Upload Image', 'wpstream') . '</button>';
2126 print '<button type="button" class="wpstream-remove-image button" style="' . (!$has_image ? 'display:none;' : '') . '">' . esc_html__('Remove Image', 'wpstream') . '</button>';
2127 print '</div>';
2128
2129 print '</div>';
2130 print '<div class="settings_details">' . $option['details'] . '</div>';
2131 break;
2132 // 0-100 range slider.
2133 case 'range':
2134 print '<label for="'.$option['name'].'">'.$option['label'].'</label>';
2135 print '<input class="wpstream-range-input" type="range" id="'.$option['name'].'" name="'.$option['name'].'" min="0" max="100" step="10" value="'.esc_attr($options_value).'" />';
2136 print '<div class="settings_details">'.$option['details'].'</div>';
2137 break;
2138 // Delegates to the Support tab renderer (plugin error log table).
2139 case 'logs_table':
2140 $this->wpstream_support_tab();
2141 break;
2142 }
2143 print '</div>';
2144 }
2145 print '</div>'; // options wrapper
2146 // Contextual help link for tabs that have one.
2147 if($help_link!==''){
2148 print '<div class="wpstream_options_help"><a href="'.esc_url($help_link).'" target="_blank" >'.esc_html__('Video Help','wpstream').'</a></div>';
2149 }
2150 print '</div>';
2151
2152
2153 // Save button (hidden on the read-only Support tab).
2154 if ( $active_tab != 'support_tab') {
2155 print '<div class="wpstream-save-settings">';
2156 print '<input type="submit" name="submit" class="wpstream_button wpstream_button_action" value="'.__('Save Changes','wpstream').'" />';
2157 print '<div class="spinner"></div>';
2158 print '</div>';
2159 }
2160
2161 // CSRF nonce for the submission handled at the top of this method.
2162 print '<input id="wpstream-settings-nonce" name="wpstream-settings-nonce" type="hidden" value="'.wp_create_nonce('wpstream-settings-nonce').'" /> ';
2163 print '</form>';
2164 print '</div>';
2165
2166 }
2167
2168 /**
2169 * Get system information for support tab
2170 *
2171 * @return array System information
2172 */
2173 private function get_system_info() {
2174 global $wp_version;
2175
2176 // Environment facts: PHP/WP version, debug flag, memory limit and the plugin version.
2177 $php_version = phpversion();
2178 $wp_version_info = $wp_version;
2179 $site_debug_mode = (defined('WP_DEBUG') && WP_DEBUG);
2180 $wp_memory_limit = WP_MEMORY_LIMIT;
2181
2182 $wpstream_version = WPSTREAM_PLUGIN_VERSION;
2183 $wpstream_plugin_outdated = false;
2184
2185 // Check if plugin is outdated
2186 // Mark the plugin outdated if WP's update transient lists an available update for it.
2187 $update_plugins = get_site_transient('update_plugins');
2188 if (isset($update_plugins->response['plugin/wpstream.php'])) {
2189 $wpstream_plugin_outdated = true;
2190 }
2191
2192 // Check API status
2193 // API is "connected" when a WpStream cloud token is present.
2194 $api_status = false;
2195 if (method_exists($this->main->wpstream_live_connection, 'wpstream_get_token')) {
2196 $token = $this->main->wpstream_live_connection->wpstream_get_token();
2197 $api_status = !empty($token);
2198 }
2199
2200 // Return the collected diagnostics for the Support tab to render.
2201 return array(
2202 'php_version' => $php_version,
2203 'wp_version' => $wp_version_info,
2204 'site_debug_mode' => $site_debug_mode,
2205 'wp_memory_limit' => $wp_memory_limit,
2206 'wpstream_version' => $wpstream_version,
2207 'wpstream_plugin_outdated' => $wpstream_plugin_outdated,
2208 'api_status' => $api_status
2209 );
2210 }
2211
2212 /**
2213 * Render system information HTML
2214 *
2215 * Prints the Support-tab diagnostics table (PHP/WP versions, debug mode,
2216 * memory limit, plugin version + update button, API connection) with a
2217 * warning/OK dashicon per row. Echoes HTML.
2218 *
2219 * @return void
2220 */
2221 private function render_system_info() {
2222 // Pull the current environment diagnostics.
2223 $system_info = $this->get_system_info();
2224 // Below: diagnostics table; each row shows a value and a warning/OK indicator based on recommended thresholds.
2225 ?>
2226
2227 <div class="wpstream-system-info">
2228 <h3><?php esc_html_e('System Information', 'wpstream'); ?></h3>
2229 <table class="widefat">
2230 <tbody>
2231 <tr>
2232 <td><strong><?php esc_html_e('PHP Version', 'wpstream'); ?></strong></td>
2233 <td><?php echo esc_html($system_info['php_version']); ?></td>
2234 <td>
2235 <?php if (version_compare($system_info['php_version'], '7.4', '<')): ?>
2236 <span class="dashicons dashicons-warning" style="color: #ffb900;"></span>
2237 <?php esc_html_e('We recommend PHP 7.4 or higher', 'wpstream'); ?>
2238 <?php else: ?>
2239 <span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
2240 <?php endif; ?>
2241 </td>
2242 </tr>
2243 <tr>
2244 <td><strong><?php esc_html_e('WordPress Version', 'wpstream'); ?></strong></td>
2245 <td><?php echo esc_html($system_info['wp_version']); ?></td>
2246 <td>
2247 <?php if (version_compare($system_info['wp_version'], '5.6', '<')): ?>
2248 <span class="dashicons dashicons-warning" style="color: #ffb900;"></span>
2249 <?php esc_html_e('We recommend WordPress 5.6 or higher', 'wpstream'); ?>
2250 <?php else: ?>
2251 <span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
2252 <?php endif; ?>
2253 </td>
2254 </tr>
2255 <tr>
2256 <td><strong><?php esc_html_e('WP Debug Mode', 'wpstream'); ?></strong></td>
2257 <td><?php echo $system_info['site_debug_mode'] ? esc_html__('Enabled', 'wpstream') : esc_html__('Disabled', 'wpstream'); ?></td>
2258 <td>
2259 <?php if ($system_info['site_debug_mode']): ?>
2260 <span class="dashicons dashicons-info" style="color: #00a0d2;"></span>
2261 <?php esc_html_e('Debug mode should be disabled on production sites', 'wpstream'); ?>
2262 <?php else: ?>
2263 <span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
2264 <?php endif; ?>
2265 </td>
2266 </tr>
2267 <tr>
2268 <td><strong><?php esc_html_e('WP Memory Limit', 'wpstream'); ?></strong></td>
2269 <td><?php echo esc_html($system_info['wp_memory_limit']); ?></td>
2270 <td>
2271 <?php
2272 $memory_limit = wp_convert_hr_to_bytes($system_info['wp_memory_limit']);
2273 if ($memory_limit < 64 * 1024 * 1024): // 64MB
2274 ?>
2275 <span class="dashicons dashicons-warning" style="color: #ffb900;"></span>
2276 <?php esc_html_e('We recommend at least 64MB', 'wpstream'); ?>
2277 <?php else: ?>
2278 <span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
2279 <?php endif; ?>
2280 </td>
2281 </tr>
2282 <tr>
2283 <td><strong><?php esc_html_e('WpStream Version', 'wpstream'); ?></strong></td>
2284 <td><?php echo esc_html($system_info['wpstream_version']); ?></td>
2285 <td style="display: flex; align-items: center; gap: 5px;">
2286 <?php if ($system_info['wpstream_plugin_outdated']): ?>
2287 <span class="dashicons dashicons-warning" style="color: #ffb900;"></span>
2288 <?php esc_html_e('Update available', 'wpstream'); ?>
2289 <div class="update-button-wrapper">
2290 <button class="wpstream-update-plugin-button button button-primary" data-plugin="wpstream/wpstream.php">
2291 <?php esc_html_e('Update Now', 'wpstream'); ?>
2292 </button>
2293 </div>
2294 <?php else: ?>
2295 <span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
2296 <?php endif; ?>
2297 </td>
2298 </tr>
2299 <tr>
2300 <td><strong><?php esc_html_e('API Connection', 'wpstream'); ?></strong></td>
2301 <td><?php echo $system_info['api_status'] ? esc_html__('Connected', 'wpstream') : esc_html__('Disconnected', 'wpstream'); ?></td>
2302 <td>
2303 <?php if (!$system_info['api_status']): ?>
2304 <span class="dashicons dashicons-warning" style="color: #ffb900;"></span>
2305 <?php esc_html_e('API connection issue', 'wpstream'); ?>
2306 <?php else: ?>
2307 <span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
2308 <?php endif; ?>
2309 </td>
2310 </tr>
2311 </tbody>
2312 </table>
2313 </div>
2314 <?php
2315 }
2316
2317 /**
2318 * Render support tab content
2319 *
2320 * Prints the Support tab: the system-info table, a table of relevant active
2321 * plugins with update tooltips, and a table of the plugin's recent logs.
2322 * Echoes HTML.
2323 *
2324 * @return void
2325 */
2326 public function wpstream_support_tab() {
2327 // Below: Support tab markup (system info, active plugins table, recent logs table).
2328 ?>
2329 <div class="wrap">
2330 <div class="wpstream-support-tab-root">
2331 <?php $this->render_system_info(); ?>
2332
2333 <div class="wpstream-plugins-table-container">
2334 <h3><?php esc_html_e('Active Plugins', 'wpstream'); ?></h3>
2335 <table class="widefat wpstream-plugins-table">
2336 <thead>
2337 <tr>
2338 <th><?php esc_html_e('Plugin', 'wpstream'); ?></th>
2339 <th><?php esc_html_e('Version', 'wpstream'); ?></th>
2340 </tr>
2341 </thead>
2342 <tbody>
2343 <?php
2344 // Build the relevant-plugins list; show a placeholder row when none are found.
2345 $plugins_data = $this->wpstream_get_plugins_data();
2346 if (empty($plugins_data)) {
2347 echo '<tr><td colspan="3">' . esc_html__('No WPStream plugins found.', 'wpstream') . '</td></tr>';
2348 } else {
2349 // One row per plugin; append an "update available" tooltip when a newer version exists.
2350 foreach ($plugins_data as $plugin) {
2351 echo '<tr>';
2352 echo '<td>' . esc_html($plugin['name']) . '</td>';
2353 echo '<td>';
2354 echo esc_html($plugin['version']);
2355 if ( isset($plugin['new_version']) ) {
2356 echo '<div class="wpstream-tooltip-container">';
2357 echo '<span class="dashicons dashicons-info wpstream-tooltip" title="' . esc_attr($plugin['new_version']) . '">';
2358 echo '</span>';
2359 echo '<div class="wpstream-custom-tooltip">' . sprintf(
2360 esc_html__('A new version is available: %s', 'wpstream'),
2361 esc_html($plugin['new_version'])
2362 ) . '</div>';
2363 echo '</div>';
2364 }
2365 echo '</td>';
2366 echo '</tr>';
2367 }
2368 }
2369 ?>
2370 </tbody>
2371 </table>
2372 </div>
2373
2374 <div class="wpstream-logs-table-container">
2375 <h3><?php esc_html_e('Recent Logs', 'wpstream'); ?></h3>
2376 <table class="widefat wpstream-logs-table">
2377 <thead>
2378 <tr>
2379 <th><?php esc_html_e('Time', 'wpstream'); ?></th>
2380 <th><?php esc_html_e('Type', 'wpstream'); ?></th>
2381 <th><?php esc_html_e('Description', 'wpstream'); ?></th>
2382 </tr>
2383 </thead>
2384 <tbody>
2385 <?php
2386 // Load the stored plugin logs; show a placeholder row when empty.
2387 $logs = get_option('wpstream_logs');
2388 if ( !is_array($logs) || empty($logs) ) {
2389 echo '<tr><td colspan="3">' . esc_html__('No logs found.', 'wpstream') . '</td></tr>';
2390 } else {
2391 // One row per log entry (time, type, description).
2392 foreach ($logs as $log) {
2393 echo '<tr>';
2394 echo '<td>' . esc_html(date('Y-m-d H:i:s', $log['timestamp'] ) ) . '</td>';
2395 echo '<td>' . esc_html($log['type']) . '</td>';
2396 echo '<td>' . esc_html($log['description']) . '</td>';
2397 echo '</tr>';
2398 }
2399 }
2400 ?>
2401 </tbody>
2402 </table>
2403 </div>
2404 </div>
2405 </div>
2406 <?php
2407 }
2408
2409 /**
2410 * Get plugins data.
2411 *
2412 * Collects name/version/active/update info for a fixed allow-list of plugins
2413 * relevant to WpStream (WpStream, WooCommerce, Meta Box, One Click Demo
2414 * Import, Better Messages), used by the Support tab.
2415 *
2416 * @return array List of plugin info arrays (name, version, path, active, needs_update, [new_version]).
2417 */
2418 public function wpstream_get_plugins_data() {
2419 // Ensure get_plugins() is available in non-admin contexts.
2420 if (!function_exists('get_plugins')) {
2421 require_once ABSPATH . 'wp-admin/includes/plugin.php';
2422 }
2423
2424 // Get all installed plugins
2425 $all_plugins = get_plugins();
2426 $update_data = get_site_transient('update_plugins');
2427 $all_plugins_info = [];
2428
2429 // We want to get data only for the WpStream plugins
2430 $wpstream_plugins = array(
2431 'WpStream' => array(
2432 'path' => 'wpstream/wpstream.php',
2433 ),
2434 'WooCommerce' => array(
2435 'path' => 'woocommerce/woocommerce.php',
2436 ),
2437 'Meta Box' => array(
2438 'path' => 'meta-box/meta-box.php',
2439 ),
2440 'One Click Demo Import' => array(
2441 'path' => 'one-click-demo-import/one-click-demo-import.php',
2442 ),
2443 'Better Messages' => array(
2444 'path' => 'bp-better-messages/bp-better-messages.php',
2445 ),
2446
2447 );
2448
2449 // Build an info array for every installed plugin (filtered down afterwards).
2450 foreach ($all_plugins as $plugin_path => $plugin_data) {
2451 $is_active = is_plugin_active( $plugin_path );
2452 $has_update = isset( $update_data->response[$plugin_path] );
2453
2454 $plugin_info = [
2455 'name' => $plugin_data['Name'],
2456 'version' => $plugin_data['Version'],
2457 'path' => $plugin_path,
2458 'active' => $is_active ? 'Yes' : 'No',
2459 'needs_update' => $has_update ? 'Yes' : 'No'
2460 ];
2461
2462 // Record the offered version when an update is pending.
2463 if ($has_update) {
2464 $plugin_info['new_version'] = $update_data->response[$plugin_path]->new_version;
2465 }
2466
2467 $all_plugins_info[] = $plugin_info;
2468 }
2469
2470 // Filter out the elements from $all_plugins_info that are not in $wpstream_plugins
2471 foreach ( $all_plugins_info as $key => $plugin_info ) {
2472 // compare $plugin_info['path'] against the path property on each $wpstream_plugins element item
2473 // if the path is not in $wpstream_plugins, unset the element
2474 if ( !in_array( $plugin_info['path'], array_column( $wpstream_plugins, 'path' ) ) ) {
2475 unset( $all_plugins_info[$key] );
2476 }
2477 }
2478
2479 return $all_plugins_info;
2480 }
2481
2482 /**
2483 * Print a dismissible admin notice when a WpStream plugin update is pending.
2484 *
2485 * @return void
2486 */
2487 public function wpstream_render_outdated_plugin_notice() {
2488 // Only render when WP's update transient lists an update for the plugin.
2489 $has_update = get_site_transient('update_plugins');
2490 if (isset($has_update->response['plugin/wpstream.php'])) {
2491 // Below: warning notice linking to the WP updates page.
2492 ?>
2493 <div class="notice notice-warning is-dismissible">
2494 <p>
2495 <?php
2496 printf(
2497 /* translators: 1: Link to update page */
2498 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'),
2499 '<a href="' . esc_url(admin_url('update-core.php')) . '">' . esc_html__('updates page', 'wpstream') . '</a>'
2500 );
2501 ?>
2502 </p>
2503 </div>
2504 <?php
2505 }
2506 }
2507
2508
2509 /**
2510 * Set user roles
2511 *
2512 * Render the grid of per-event streaming option switches (from
2513 * $global_event_options). Reused for both the site-wide default settings
2514 * and the per-channel settings modal. Echoes HTML.
2515 *
2516 * @param string $name Field name prefix/context (unused in body; kept for callers).
2517 * @param array|string $value Stored 1/0 map of option states (empty falls back to defaults).
2518 * @param array|string $local_array Option keys to EXCLUDE from this render (per-channel context).
2519 * @param bool $disabled True to render the switches disabled.
2520 * @param bool $is_basic_stream_mode True to disable switches for basic-streaming accounts.
2521 * @return void
2522 */
2523 public function user_streaming_global_channel_options(
2524 $name,
2525 $value,
2526 $local_array='',
2527 $disabled = false,
2528 $is_basic_stream_mode = false
2529 ) {
2530
2531 // Render one switch per defined global event option.
2532 foreach($this->global_event_options as $key=>$option){
2533
2534 // Skip options listed in the exclude array (used by the per-channel modal).
2535 if( is_array($local_array) && !in_array($key,$local_array)){
2536 print '<div class="wpstream_setting_event_unit_wrapper wpstream-setting-'.esc_attr($key).' ">';
2537
2538 print '<label for="'.$option['name'].'">'.$option['name'].'</label>';
2539
2540 print '<div style="display: flex; gap: 25px; justify-content: space-between;">';
2541 print '<div class="settings_details">'.$option['details'].'</div>';
2542 print '
2543 <label class="wpstream_switch">
2544 <input type="checkbox" class="wpstream_event_option_item" data-attr-ajaxname="'.esc_attr($key).'" name="wpstream_event_set_'.esc_attr($key).'" ';
2545 // Check state: use the stored value if present, otherwise the option's default.
2546 if( isset($value[$key]) ){
2547 if( intval($value[$key]) !==0 ){
2548 print ' checked ';
2549 }
2550 }else{
2551 if($option['defaults']=='yes') {
2552 print ' checked ';
2553 }
2554 }
2555 // Disable the switch when requested or in basic-streaming mode.
2556 if ( $disabled || $is_basic_stream_mode ) {
2557 print ' disabled ';
2558 }
2559
2560
2561 print '> <span class="wpstream_slider round"></span>';
2562 print '</label>';
2563 print '</div>';
2564
2565
2566 print '</div>';
2567 }
2568 }
2569
2570
2571 }
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581 /*
2582 * Set user roles
2583 *
2584 * @since 3.0.1
2585 */
2586 /**
2587 * Build a multi-select of editable user roles (administrator excluded)
2588 * for the "roles allowed to broadcast" setting.
2589 *
2590 * @param string $name Field name (rendered as name="{name}[]").
2591 * @param array|string $value Currently selected role keys ('' becomes an empty array).
2592 * @return string HTML <select> markup.
2593 */
2594 public function wpstream_select_user_roles($name,$value){
2595 // Normalize an empty stored value to an array for in_array() below.
2596 if($value==''){
2597 $value=array();
2598 }
2599
2600 // All editable roles minus administrator (admins always can broadcast).
2601 $roles = get_editable_roles();
2602 $return = '<select id="wpstream_user_roles" name="'.esc_html($name).'[]" multiple>';
2603 unset( $roles['administrator'] );
2604
2605 // One option per role, pre-selecting those already chosen.
2606 foreach ($roles as $key=>$role){
2607 $return .= '<option value="'.$key.'" ';
2608 if( in_array($key, $value) ){
2609 $return .= ' selected ';
2610 }
2611 $return .= '>'.$role['name'].'</option>';
2612 }
2613 $return .= '</select>';
2614
2615
2616 return $return;
2617 }
2618
2619
2620 /*
2621 * Set credential admin function
2622 *
2623 * @since 3.0.1
2624 */
2625 /**
2626 * Render the Credentials admin screen and save submitted API credentials.
2627 *
2628 * On POST it stores the WpStream username/password options and clears the
2629 * cached token/quota transients so the next request re-authenticates. It
2630 * then shows the connection status and the credentials form. Echoes HTML.
2631 *
2632 * @return void
2633 */
2634 public function wpstream_set_wpstream_credentials(){
2635
2636 // Handle a credentials submission.
2637 if($_SERVER['REQUEST_METHOD'] === 'POST'){
2638 $allowed_html = array();
2639 $exclude_array = array();
2640 $allowed_html = array();
2641
2642 // Persist only the recognised credential fields.
2643 foreach($_POST as $variable=>$value){
2644 if ($variable!='submit'){
2645 if (!in_array($variable, $exclude_array) ){
2646 switch ( $variable ) {
2647 case 'api_username':
2648 // Username is sanitized as text.
2649 update_option( sanitize_key('wpstream_api_username'), sanitize_text_field($value) );
2650 break;
2651 case 'api_password':
2652 // Password stored raw (may contain characters sanitization would strip).
2653 update_option( sanitize_key('wpstream_api_password'), $value );
2654 break;
2655 }
2656 }
2657 }
2658 }
2659
2660
2661
2662 // Invalidate any cached token/quota so the new credentials are used immediately.
2663 update_option('wp_estate_token_expire',0);
2664 update_option('wp_estate_curent_token',' ');
2665 delete_transient( 'wpstream_token_api');
2666 delete_transient('wpstream_token_request_30');
2667 delete_transient('wpstream_request_pack_data_per_user_transient');
2668 }
2669
2670
2671 $allowed_html = array();
2672
2673
2674 // Field definitions for the credentials form (username + password).
2675 $wpstream_options_array =array(
2676 2 => array(
2677 'label' => 'WpStream.net Username or Email',
2678 'name' => 'api_username',
2679 'type' => 'text',
2680 ),
2681 3 => array(
2682 'label' => 'WpStream.net Password',
2683 'name' => 'api_password',
2684 'type' => 'password',
2685 ),
2686
2687 );
2688
2689
2690 // Current auth token and quota data drive the connection-status banner below.
2691 $token = $this->main->wpstream_live_connection->wpstream_get_token();
2692 $pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_set_wpstream_credentials' );
2693
2694 $this->main->show_user_data($pack_details);
2695
2696 print '<form method="post" action="" >';
2697 print '<div class="theme_options_tab_wpstream" style="display:block;" >
2698 <h1>'.__('WpStream Credentials','wpstream').'</h1>';
2699
2700 // Connection status banner: no credentials, bad credentials, connected, or CURL failure.
2701 if( get_option('wpstream_api_username')=='' || get_option('wpstream_api_password')== '' ){
2702 echo '<div class="api_not_conected wpstream_orange">';
2703 $admin_url_onboard=get_admin_url().'admin.php?page=wpstream_onboard';
2704 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);
2705 echo '</div>';
2706
2707 }else if($token==''){
2708 $text = get_option('wpstream_curl_failed') === "0" ?
2709 ' 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.' :
2710 'Not connected to WpStream. Please note the errors above and contact support.';
2711 echo '<div class="api_not_conected">'.__($text,'wpstream').'</div>';
2712 }else if( $this->main->wpstream_live_connection->wpstream_client_check_api_status() ){
2713 echo '<div class="api_conected">'.__('Connected to WpStream.net!','wpstream').'</div>';
2714 }else{
2715 echo '<div class="api_not_conected wpstream_brown">'.__('Failed to connect to WpStream.net. Please address CURL connectivity with your hosting provider.','wpstream').'</div>';
2716 }
2717 // Render each credential input pre-filled with its stored value.
2718 print '<div class="wpstream_option_wrapper">';
2719 foreach ($wpstream_options_array as $key=>$option){
2720 print '<div class="wpstream_option">';
2721
2722 $options_value = esc_html( get_option('wpstream_'.$option['name'],'') );
2723 print '<label for="'.$option['name'].'">'.$option['label'].'</label>';
2724 print '<input id="'.$option['name'].'" type="'.$option['type'].'" size="36" name="'.$option['name'].'" value="'.esc_html($options_value).'" />';
2725
2726 print '</div>';
2727 }
2728 print '</div>';
2729
2730
2731 print '<input type="submit" name="submit" class="wpstream_button wpstream_button_action" value="'.__('Save Changes','wpstream').'" />';
2732
2733 print '<h3>Video Tutorials</h3>';
2734
2735 print '<a class="how_to_videos" target="_blank" href="https://youtu.be/9DQrxsKcpmQ">How to Live Stream to WordPress with OBS</a>';
2736 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>';
2737 print '<a class="how_to_videos" target="_blank" href="https://youtu.be/h6myD_vhKcg">How to Live-Stream to WordPress using your iPhone</a>';
2738
2739 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>';
2740
2741
2742 print '</div>';
2743 print '</form>';
2744
2745 // Quick-action links: create free / paid channel, or jump to the channels list.
2746 print '<div class="theme_options_tab_wpstream" style="display:block;" >';
2747 $link_new = admin_url('admin.php?page=wpstream_live_channels');
2748 $link_new_paid = admin_url('post-new.php?post_type=product').'&new_stream='. rawurlencode('new');
2749 $link_new_free = admin_url('post-new.php?post_type=wpstream_product');
2750
2751
2752 print '<a href="'.esc_url($link_new_free).'" class="wpstream_no_chanel_add_channel">'.esc_html__('Create new Free-To-View channel','wpstream').'</a>';
2753 print '<a href="'.esc_url($link_new_paid).'" class="wpstream_no_chanel_add_channel">'.esc_html__('Create Pay-Per-View channel','wpstream').'</a>';
2754 print '<a href="'.esc_url($link_new).'" class="wpstream_no_chanel_add_channel">'.esc_html('My Channels','wpstream').'</a>';
2755 print '</div>';
2756
2757
2758 }
2759
2760
2761
2762 /**
2763 * Media Management
2764 *
2765 * Render the Recordings admin screen: the quota summary, the upload widget,
2766 * and the list of existing recordings. Echoes HTML.
2767 *
2768 * @since 3.0.1
2769 * @return void
2770 */
2771 public function wpstream_media_management(){
2772 // Storage/streaming quota data for the summary header.
2773 $pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_media_management' );
2774
2775 $this->main->show_user_data($pack_details);
2776
2777
2778 // Upload widget section.
2779 print '<div id="wpstream_media_upload"><h3>'.__('Upload New Recording','wpstream').'</h3>'.$this->wpstream_present_media_upload().'</div>';
2780
2781 // Existing recordings list section.
2782 print '<div id="wpstream_file_management"><h3 id="video_management_title">'.__('Your Recordings','wpstream').'</h3>'.$this->wpstream_present_file_management().'</div>';
2783
2784
2785 }
2786
2787
2788
2789
2790 /**
2791 *
2792 *
2793 * WpStream Pagination
2794 *
2795 * @since 3.0.1
2796 *
2797 *
2798 */
2799
2800 /**
2801 * Build a numeric pager (first/prev/window/next/last) for list screens.
2802 *
2803 * @param int $pages Total number of pages.
2804 * @param int $range How many page links to show on each side of the current page.
2805 * @return string Pager HTML, or '' when there is a single page / no pages.
2806 */
2807 public function wpstream_pagination($pages , $range = 2) {
2808 $return='';
2809 // Total visible window width around the current page.
2810 $showitems = ($range * 2) + 1;
2811 // Current page from the query string (defaults to 1).
2812 $paged = ( isset( $_GET['paged'] ) ) ? intval($_GET['paged']) : 1;
2813
2814
2815 // Only render a pager when there is more than one page.
2816 if (1 != $pages && $pages != 0) {
2817 $return.= '<ul class="pagination wpstream_pagination">';
2818 // "Previous" arrow.
2819 $return.= "<li class=\"roundleft\"><a href='" . get_pagenum_link($paged - 1) . "'><</a></li>";
2820
2821 $last_page = get_pagenum_link($pages);
2822 // Emit page-number links, but only those within the visible window (or all if they fit).
2823 for ($i = 1; $i <= $pages; $i++) {
2824 if (1 != $pages && (!($i >= $paged + $range + 1 || $i <= $paged - $range - 1) || $pages <= $showitems )) {
2825 if ($paged == $i) {
2826 // Current page marker.
2827 $return.= '<li class="active"><a href="' . esc_url(get_pagenum_link($i)) . '" >' . $i . '</a><li>';
2828 } else {
2829 $return.= '<li><a href="' . esc_url(get_pagenum_link($i)) . '" >' . $i . '</a><li>';
2830 }
2831 }
2832 }
2833
2834 // "Next" target, clamped to the last page.
2835 $prev_page = get_pagenum_link($paged + 1);
2836 if (($paged + 1) > $pages) {
2837 $prev_page = get_pagenum_link($paged);
2838 } else {
2839 $prev_page = get_pagenum_link($paged + 1);
2840 }
2841
2842
2843 // "Next" and "Last" arrows, then close the list.
2844 $return.= "<li class=\"roundright\"><a href='" . $prev_page . "'>></a><li>";
2845 $return.= "<li class=\"roundright\"><a href='" . $last_page . "'>>><li>";
2846 $return.= "</ul>";
2847 }
2848 return $return;
2849 }
2850
2851
2852
2853
2854
2855
2856 /**
2857 * Media upload
2858 *
2859 * Build the S3 direct-upload widget for new recordings: checks storage
2860 * quota and API connectivity, then renders a multipart form pre-populated
2861 * with the signed S3 upload fields.
2862 *
2863 * @since 3.0.1
2864 * @return string Upload widget HTML, or an alert/notice when unavailable.
2865 */
2866 public function wpstream_present_media_upload(){
2867 $to_return='';
2868
2869 // Refuse when the account is out of storage/data quota.
2870 if ( ! $this->main->quota_manager->has_storage_quota( null, 'recordings_screen' ) ) {
2871 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>';
2872 }
2873
2874 // Request the signed S3 POST fields from the cloud API.
2875 $formInputs=$this->main->wpstream_live_connection->wpstream_get_signed_form_upload_data();
2876
2877 // On failure, show either a "not connected" or an "out of quota" message.
2878 if( !$formInputs['success'] ){
2879 if ($formInputs['error'] == 'not_connected'){
2880 $to_return.='<div class="wpstream_upload_container">'.esc_html__('Not connected. Please connect to WpStream to upload videos.','wpstream').'</div>';
2881 }
2882 else {
2883 $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>';
2884 }
2885 return $to_return;
2886 }
2887
2888 // Signed data obtained: render the direct-to-S3 upload form.
2889 if($formInputs['success'] ===true){
2890
2891
2892
2893 $to_return.='<div class="wpstream_upload_container">';
2894 $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>';
2895 $to_return.='<form action="https://wpstream-video.s3.amazonaws.com/"
2896 method="POST"
2897 enctype="multipart/form-data"
2898 data-singleFileUploads="true"
2899 data-limitMultiFileUploads="1"
2900 data-limitConcurrentUploads="1"
2901 class="direct-upload">';
2902
2903 $to_return.='<input id="wpstream_upload" type="file" class="inputfile inputfile-1" value="Pick a video file" name="file" multiple>';
2904 $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&hellip;','wpstream') . '</span></label>';
2905
2906
2907 $to_return.='<div class="wpstream_file_drop_color">';
2908 $to_return.='<div class="wpstream_form_ex">'.esc_html__('Drop a video file here!','wpstream').'</div>';
2909 $to_return.='<div class="wpstream_form_ex_details">'.__('The Video File must be encoded with the following settings:<br>
2910
2911 Container: <strong>MP4</strong>,<br>
2912 Video codec: <strong>H264</strong>,<br>
2913 Audio codec: <strong>AAC</strong>.<br>
2914 Media will fail to play if it does not follow the above settings.
2915 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>';
2916 // Inject each signed S3 field as a hidden input so the browser POST is authorized.
2917 if(is_array($formInputs)){
2918 foreach ($formInputs['ref'] as $name => $value) {
2919 $to_return.='<input type="hidden" name="'. $name.'" value="'.$value.'">';
2920 }
2921 }
2922
2923 $to_return.='
2924 <div class="progress-bar-area"></div></div>
2925 </form>';
2926
2927 $to_return.='</div>';
2928 }
2929
2930 return $to_return;
2931
2932 }
2933
2934
2935
2936
2937
2938 /**
2939 * Display movie list
2940 *
2941 * Fetch the account's recordings from the cloud API and render them:
2942 * still-processing ("pending") items first, then completed items with
2943 * delete/download links and "create VOD from this recording" actions.
2944 *
2945 * @since 3.0.1
2946 * @return string Recordings list HTML, or a notice when not connected / empty.
2947 */
2948 public function wpstream_present_file_management(){
2949 // Pull the raw recordings payload from the API.
2950 $video_list_raw = $this->main->wpstream_live_connection->wpstream_get_videos_from_api();
2951
2952 // false means the API call failed / not connected.
2953 if ( $video_list_raw === false ) {
2954 return '<div class="wpstream_upload_container">'.esc_html__('Not connected. Please connect to WpStream to upload videos.','wpstream').'</div>';
2955 }
2956
2957 // Normalise the completed-items list.
2958 $video_list_raw_array = [];
2959 if( isset( $video_list_raw['items'] ) ){
2960 $video_list_raw_array = $video_list_raw['items'];
2961 }
2962
2963 // Sort completed items newest-first by their 'time' field.
2964 $keys = array_column( $video_list_raw_array, 'time' );
2965 array_multisort($keys, SORT_DESC , $video_list_raw_array);
2966
2967 $to_return='';
2968
2969 // show pending items
2970 // Render still-processing uploads with a "processing" badge.
2971 if ( key_exists( 'pending', $video_list_raw ) && is_array( $video_list_raw['pending'] ) ) {
2972 foreach ( $video_list_raw['pending'] as $key => $video ) {
2973 $video_size = intval($video['size']/1048576);
2974 $video_name = esc_html($video['name']);
2975 if($video_name!=''):
2976 $to_return.='<div class="wpstream_video_wrapper">';
2977 $to_return.='<div class="wpstream_video_title">';
2978 $to_return.='<div class="wpstream_video_notice"></div></div>';
2979 $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>';
2980 $to_return.='<div class="wpstream_video_pending">' . esc_html__( 'The video is still processing', 'wpstream') . '</div>';
2981 $to_return.='</div>';
2982 endif;
2983
2984 }
2985 }
2986
2987 // show uploaded items
2988 // Render each completed recording with its size, delete/download controls and VOD-creation links.
2989 if( is_array($video_list_raw['items'] ) ) {
2990 foreach ($video_list_raw_array as $key =>$video){
2991 // Size in MB and the (escaped) file name.
2992 $video_size = intval($video['size']/1048576);
2993 $video_name = esc_html($video['name']);
2994 if($video_name!=''):
2995 $to_return.='<div class="wpstream_video_wrapper">';
2996
2997 $to_return.='<div class="wpstream_video_title">';
2998 $to_return.='<div class="wpstream_video_notice"></div></div>';
2999 $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>';
3000 // Delete control with a JS confirm prompt.
3001 $to_return.=' <div class="wpstream_delete_media" ';
3002 $to_return.=' onclick="return confirm(\' Are you sure you wish to delete '.$video_name.'?\')" data-filename="'.$video_name.'">'.esc_html__('delete file','wpstream').'</div>';
3003 // Download trigger (JS fetches a time-limited signed URL) and the resulting link.
3004 $to_return.='<div class="wpstream_get_download_link" data-filename="'.$video_name.'">'.esc_html__('download','wpstream').'</div>';
3005 $to_return.='<a href="" class="wpstream_download_link">'.esc_html__('Click to download! The url will work for the next 20 minutes!','wpstream').'</a>';
3006
3007 // Pre-fill new-VOD links (free and, if WooCommerce present, paid) with this recording's name.
3008 $add_free_video_url=admin_url('post-new.php?post_type=wpstream_product_vod').'&new_video_name='. rawurlencode($video_name);
3009 $add_paid_video_url=admin_url('post-new.php?post_type=product').'&new_video_name='. rawurlencode($video_name);
3010
3011
3012
3013 $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>';
3014 if (class_exists('WooCommerce')) {
3015 $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>';
3016 }
3017
3018 $to_return.='</div>';
3019 endif;
3020
3021 }
3022 $current_page= get_current_screen();
3023 }
3024
3025 // no items to show
3026 // Empty-state message when there are neither completed nor pending items.
3027 if ( !is_array( $video_list_raw['items'] ) || ( key_exists( 'pending', $video_list_raw ) && !is_array( $video_list_raw['pending'] ) ) ) {
3028 $to_return.= '<div class="wpstream_video_wrapper">'.esc_html__('You don\'t have any videos.','wpstream').'</div>';
3029 }
3030 return $to_return;
3031 }
3032
3033
3034 /**
3035 * Set defualt channels values
3036 *
3037 * On publish of a free channel, seed its per-channel `local_event_options`
3038 * from the site-wide default options if it has none yet. Hooked to the
3039 * post publish/save action.
3040 *
3041 * @since 3.0.1
3042 * @param int $post_id Post ID being published.
3043 * @param WP_Post $post Post object being published.
3044 * @return void
3045 */
3046 public function wpstream_publish_wpstream_product($post_id,$post){
3047 // Only applies to free channel posts.
3048 if( $post->post_type == 'wpstream_product' ){
3049 // Marker meta + flag that global options are the source of truth.
3050 update_post_meta ($post_id,'local_event_options_test','working_on_'.$post_id);
3051 update_post_meta ($post_id, 'use_global_event_options', true);
3052 $to_save_option=array();
3053
3054 // Site-wide default event options and any existing per-channel overrides.
3055 $global_options= get_option('wpstream_user_streaming_global_channel_options');
3056
3057 $local_events = get_post_meta ($post_id ,'local_event_options',true) ;
3058
3059 // Only seed when this channel has no local options yet.
3060 if( $local_events =='' ){
3061 if( is_array($global_options) ){
3062 // Copy each global option (sanitized) into the per-channel meta.
3063 foreach($global_options as $key=>$value){
3064 $to_save_option[sanitize_key($key)]=sanitize_text_field($value);
3065 }
3066
3067 update_post_meta ($post_id,'local_event_options',$to_save_option);
3068
3069 }
3070 }
3071
3072
3073 }
3074 }
3075
3076 /**
3077 * Create the remote WpStream cloud channel when a channel post is published.
3078 *
3079 * Runs for free (`wpstream_product`) and paid live-stream (`product` with
3080 * the live_stream type) posts, skipping autosaves/revisions and posts that
3081 * already have a remote channel. On success it stores the channelId and
3082 * embedUrl meta. Hooked to the post publish/save action.
3083 *
3084 * @param int $post_id Post ID being published.
3085 * @param WP_Post $post Post object being published.
3086 * @return void
3087 */
3088 public function wpstream_create_remote_channel_on_publish( $post_id, $post ) {
3089 // Determine whether this is a channel post that needs a cloud channel.
3090 $is_free_channel = $post->post_type == 'wpstream_product';
3091 $is_paid_channel = $post->post_type == 'product' && has_term( 'live_stream', 'product_type', $post_id );
3092
3093 // Bail for non-channel post types.
3094 if ( !$is_free_channel && !$is_paid_channel ) {
3095 return;
3096 }
3097
3098 // Skip autosaves and revisions (they are not real publishes).
3099 if ( defined( 'DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
3100 return;
3101 }
3102 if ( wp_is_post_revision( $post_id ) ) {
3103 return;
3104 }
3105 // Idempotency: skip if a remote channel was already created.
3106 if ( get_post_meta( $post_id, '_wpstream_remote_channel_created', true ) ) {
3107 return;
3108 }
3109
3110 // Ask the cloud API to create the channel and log the raw response.
3111 $response = $this->main->wpstream_live_connection->wpstream_create_channel( $post_id );
3112 error_log('channel/create response for post ' . $post_id . ' is ' . print_r($response, true));
3113
3114 // On success, persist the returned channel id + player embed URL and mark it created.
3115 if ( $response && ! empty( $response['success'] ) ) {
3116 if ( isset($response['channel_id']) && $response['channel_id'] !== '' ) {
3117 update_post_meta( $post_id, 'channelId', $response['channel_id'] );
3118 update_post_meta( $post_id, 'embedUrl', LIVE_PLAYER_URL_PREFIX . '?' . http_build_query( array( 'channelId' => $response['channel_id'] ) ) );
3119 update_post_meta( $post_id, '_wpstream_remote_channel_created', 1 );
3120 }
3121 }
3122 }
3123
3124 /**
3125 * save meta options
3126 *
3127 * Persist the allow-listed free channel / VOD meta fields on save for the
3128 * `wpstream_product` and `wpstream_product_vod` post types. Hooked to the
3129 * post save action.
3130 *
3131 * @since 3.0.1
3132 * @param int $post_id Post ID being saved.
3133 * @param WP_Post $post Post object being saved.
3134 * @return void
3135 */
3136 public function wpstream_free_product_update_post($post_id,$post){
3137
3138 // Guard against non-post callers.
3139 if(!is_object($post) || !isset($post->post_type)) {
3140 return;
3141 }
3142
3143
3144
3145
3146
3147 // Only handle the free channel and VOD post types.
3148 if( $post->post_type == 'wpstream_product' ||
3149 $post->post_type == 'wpstream_product_vod' ):
3150
3151 // Meta keys this handler is allowed to write.
3152 $allowed_keys=array(
3153 'wpstream_product_type',
3154 'wpstream_free_video',
3155 'wpstream_free_video_external',
3156 'wpstream_closed_captions_file'
3157 );
3158
3159
3160 // Save each allow-listed, scalar POST field as sanitized post meta.
3161 foreach ($_POST as $key => $value) {
3162 if( !is_array ($value) ){
3163 if (in_array ($key, $allowed_keys)) {
3164 $postmeta = sanitize_text_field ( $value );
3165 update_post_meta($post_id, sanitize_key($key), $postmeta );
3166 }
3167 }
3168 }
3169
3170 endif;
3171
3172 }
3173
3174
3175 /**
3176 * save meta options
3177 *
3178 * Register the plugin's post metaboxes: VOD settings on `wpstream_product_vod`,
3179 * the video collection box on `wpstream_bundles`, and (for WooCommerce bundle
3180 * products) the video-collection options box on `product`. Hooked to add_meta_boxes.
3181 *
3182 * @since 3.0.1
3183 * @return void
3184 */
3185 public function add_wpstream_product_metaboxes() {
3186 global $post;
3187 $post_id = $post->ID;
3188
3189
3190 // VOD settings metabox and the bundle video-collection metabox.
3191 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');
3192 add_meta_box( 'custom_metabox_video_collection', esc_html__( 'Video Collection', 'wpstream' ), 'wpstream_bundle_custom_metabox_callback', 'wpstream_bundles', 'normal', 'high' );
3193
3194 // For WooCommerce bundle products, add the video-collection options box on the product screen.
3195 if(function_exists('wc_get_product')):
3196 $product = wc_get_product( $post_id );
3197 if ( $product ) {
3198
3199 if ( $product->get_type() === 'wpstream_bundle' ) {
3200 add_meta_box(
3201 'wpstream_woo_custom_metabox',
3202 esc_html__( 'Video Collection Options', 'wpstream' ),
3203 'wpstream_bundle_custom_metabox_callback',
3204 'product',
3205 'normal',
3206 'default'
3207 );
3208 }
3209 }
3210 endif;
3211
3212 }
3213
3214
3215 /**
3216 * make woocomerce virtual products
3217 *
3218 * Force WpStream WooCommerce product types (live_stream, video_on_demand,
3219 * wpstream_bundle) to be virtual so they need no shipping. Hooked to save.
3220 *
3221 * @since 3.0.1
3222 * @param int $post_id Post ID being saved (unused; uses global $post).
3223 * @param WP_Post $post Post object (overwritten by global $post).
3224 * @return void
3225 */
3226 public function wpstream_make_product_virtual($post_id,$post){
3227 global $post;
3228 if(isset($post->ID)){
3229 // Only WooCommerce products are relevant.
3230 if ( $post->post_type !== 'product' ) return;
3231 // Mark WpStream product types as virtual.
3232 $term_list = wp_get_post_terms($post->ID, 'product_type');
3233 if( !empty($term_list) &&
3234 isset($term_list[0]->name) &&
3235 in_array($term_list[0]->name, ['live_stream', 'video_on_demand', 'wpstream_bundle'])
3236 ){
3237 update_post_meta( $post->ID, '_virtual', 'yes' );
3238 }
3239 }
3240 }
3241
3242
3243
3244 /**
3245 * render meta options
3246 *
3247 * Render the "Video On Demand Settings" metabox: media-type selector,
3248 * recording chooser, optional captions (.vtt) picker, and the self-hosted/
3249 * external video URL field. Echoes HTML.
3250 *
3251 * @since 3.0.1
3252 * @param WP_Post $post Post being edited (overwritten by global $post).
3253 * @return void
3254 */
3255 public function display_meta_options( $post ) {
3256 // Nonce for the metabox save + use the global post object.
3257 wp_nonce_field( plugin_basename( __FILE__ ), 'estate_agent_noncename' );
3258 global $post;
3259
3260 // Determine the pre-selected media type / video.
3261 $is_live = '';
3262 $is_video = '';
3263 $is_video_external = '';
3264 // When arriving from "create VOD from recording", preselect the recording.
3265 if( isset( $_GET['new_video_name']) && $_GET['new_video_name']!='' ){
3266 $is_video = ' selected ';
3267 $wpstream_free_video = esc_html( $_GET['new_video_name']);
3268 }else{
3269 // Otherwise read the saved type/video from post meta and select accordingly.
3270 $wpstream_product_type = esc_html(get_post_meta($post->ID, 'wpstream_product_type', true));
3271 $wpstream_free_video = esc_html(get_post_meta($post->ID, 'wpstream_free_video', true));
3272
3273 if($wpstream_product_type==1){
3274 $is_live = ' selected ';
3275 }
3276
3277 if($wpstream_product_type==2){
3278 $is_video = ' selected ';
3279 }
3280
3281 if($wpstream_product_type==3){
3282 $is_video_external = ' selected ';
3283 }
3284 }
3285
3286 // Media-type dropdown (recording vs self-hosted/external).
3287 print'
3288 <p class="meta-options">
3289 <label for="wpstream_product_type">'.__('Media Type:','wpstream').' </label><br />
3290 <select id="wpstream_product_type" name="wpstream_product_type">
3291 <option value="2" '.$is_video.'>'.__('Recording','wpstream').'</option>
3292 <option value="3" '.$is_video_external.'>'.__('Self Hosted or External Video','wpstream').'</option>
3293 </select>
3294 </p>
3295 ';
3296
3297
3298 // Fetch the account's recordings to populate the chooser.
3299 $video_list = $this->main->wpstream_live_connection->wpstream_get_videos();
3300
3301
3302 // Recording chooser dropdown, pre-selecting the saved recording.
3303 print '<div class="meta-options video_free">';
3304 print '<p class="meta-option wpstream_free_video">';
3305 print '<label for="wpstream_free_video">'.__('Choose video:','wpstream').' </label><br />
3306 <select id="wpstream_free_video" name="wpstream_free_video">';
3307
3308 if( is_array( $video_list ) ) {
3309 foreach ($video_list as $key=>$value){
3310 print '<option value="'.$key.'"';
3311 if($wpstream_free_video === $key){
3312 print ' selected ';
3313 }
3314 print '>'.$value.'</option>';
3315 }
3316 }
3317 print'</select>';
3318 print '</p> ';
3319
3320 // Optional captions (.vtt) picker; hide the select button when a file is already set.
3321 $wpstream_closed_captions_file = get_post_meta($post->ID, 'wpstream_closed_captions_file', true);
3322
3323 $button_style = $wpstream_closed_captions_file ? 'style="display:none;"' : '';
3324
3325 print '<p class="meta-option wpstream_vod_captions_url">';
3326 print '<label for="wpstream_vod_captions_url_button">'.__('Captions file (optional):','wpstream').' </label><br />
3327 <input type="hidden" id="wpstream_closed_captions_file" name="wpstream_closed_captions_file" value="'.esc_attr($wpstream_closed_captions_file).'" />
3328 <input id="wpstream_vod_captions_url_button" type="button" class="upload_button button" value="'.esc_html__('Select .vtt Captions File','wpstream').'" '.$button_style.' />
3329 <span class="wpstream_caption_file_display">'.( $wpstream_closed_captions_file ? esc_html( basename( $wpstream_closed_captions_file ) ) : '' ).'</span>';
3330 if ( $wpstream_closed_captions_file ) {
3331 print '<input type="button" class="button wpstream_remove_caption" value="'.esc_html__('Remove','wpstream').'" style="margin-left: 5px;" />';
3332 }
3333 print '</p> ';
3334 print '</div>';
3335
3336 // Self-hosted / external video URL field with a media-library select button.
3337 $wpstream_free_video_external= esc_html(get_post_meta($post->ID, 'wpstream_free_video_external', true));
3338 print '<div class="meta-options1 video_free_external">
3339 <label for="wpstream_free_video_external">'.__('Video:','wpstream').' </label><br />
3340
3341 <input id="wpstream_free_video_external" type="text" size="36" name="wpstream_free_video_external" value="'.$wpstream_free_video_external.'" />
3342 <input id="wpstream_free_video_external_button" type="button" size="40" class="upload_button button" value="'.esc_html__('Select Video','wpstream').'" />';
3343 // Show the recording hint vs the external hint depending on the saved media type.
3344 if($wpstream_product_type==2){
3345 $show_recording='';
3346 $show_external='style="display:none"';
3347 }else{
3348 $show_recording='style="display:none"';
3349 $show_external='';
3350 }
3351 print '<p '.$show_recording.' class="wpstream_option_vod_source wpstream_show_recording">'.esc_html__('Choose one of your existing recordings.','wpstream').'</p>';
3352 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>';
3353
3354 print '</div> ';
3355 }
3356
3357
3358
3359
3360
3361
3362
3363 /**
3364 * Add new product types to Woocommerce select product type
3365 *
3366 * Register the "Live Channel" and "Video On Demand" WooCommerce product
3367 * types in the product-type dropdown. Filter callback.
3368 *
3369 * @since 3.0.1
3370 * @param array $types Existing product-type => label map.
3371 * @return array The map with the WpStream types added.
3372 */
3373 public function wpstream_add_products( $types ){
3374 $types[ 'live_stream' ] = __( 'Live Channel','wpestream' );
3375 $types[ 'video_on_demand' ] = __( 'Video On Demand','wpestream' );
3376
3377 return $types;
3378 }
3379
3380 /**
3381 * Map the WpStream product-type slugs to their WC_Product subclasses.
3382 * Filter callback for woocommerce_product_class.
3383 *
3384 * @param string $classname Default product class name.
3385 * @param string $product_type The product type slug being instantiated.
3386 * @return string The resolved product class name.
3387 */
3388 public function wpstream_add_products_class( $classname, $product_type ) {
3389 if ( 'live_stream' === $product_type ) {
3390 $classname = 'WC_Product_Live_Stream';
3391 }
3392 if ( 'video_on_demand' === $product_type ) {
3393 $classname = 'WC_Product_Video_On_Demand';
3394 }
3395 return $classname;
3396 }
3397
3398 /**
3399 * Load the custom WC_Product subclass files when WooCommerce is active.
3400 *
3401 * @return void
3402 */
3403 public function wpstream_add_custom_wc_products() {
3404 // Require the live-stream and VOD product classes (WooCommerce only).
3405 if( class_exists( 'WooCommerce' ) ){
3406 require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-wc_product_live_stream.php';
3407 require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-wc_product_video_on_demand.php';
3408 }
3409 }
3410
3411 /**
3412 * Js action to do when user pick live stream or video on demand
3413 *
3414 * @since 3.0.1
3415 */
3416
3417 /**
3418 * Whether WpStream WooCommerce product UI should load for this product.
3419 *
3420 * Skips simple, course, and other non-WpStream types so LearnDash and
3421 * other integrations can use the General product data tab without conflict.
3422 *
3423 * @param int $post_id Product post ID. Uses global $post when 0.
3424 * @return bool
3425 */
3426 public function wpstream_is_wpstream_wc_product_context( $post_id = 0 ) {
3427 // Creating a new stream/VOD from a query-string action always counts.
3428 if ( isset( $_GET['new_stream'] ) || isset( $_GET['new_video_name'] ) ) {
3429 return true;
3430 }
3431
3432 // Fall back to the current global post when no ID was passed.
3433 if ( ! $post_id ) {
3434 global $post;
3435 $post_id = ( $post && isset( $post->ID ) ) ? (int) $post->ID : 0;
3436 }
3437
3438 // Need a product ID and WooCommerce to resolve the type.
3439 if ( ! $post_id || ! function_exists( 'wc_get_product' ) ) {
3440 return false;
3441 }
3442
3443 $product = wc_get_product( $post_id );
3444 if ( ! $product ) {
3445 return false;
3446 }
3447
3448 // True only for WpStream (and subscription) product types.
3449 return in_array(
3450 $product->get_type(),
3451 array( 'live_stream', 'video_on_demand', 'wpstream_bundle', 'subscription' ),
3452 true
3453 );
3454 }
3455
3456 /**
3457 * WooCommerce product types that use the core Regular price field.
3458 *
3459 * @return string[]
3460 */
3461 public function wpstream_get_wc_product_types_with_pricing() {
3462 return array( 'live_stream', 'video_on_demand', 'wpstream_bundle' );
3463 }
3464
3465 /**
3466 * Add show_if_* classes to the pricing panel for WpStream product types.
3467 *
3468 * WooCommerce renders .options_group.pricing with show_if_simple only. Custom
3469 * types rely on show_if_{type} classes so meta-boxes-product.js can toggle them
3470 * when the product type dropdown changes, including on unsaved new products.
3471 *
3472 * @param string $hook Admin screen hook suffix.
3473 */
3474 public function wpstream_enqueue_wc_product_pricing_visibility( $hook ) {
3475 // Only on the add/edit-post admin screens.
3476 if ( ! in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
3477 return;
3478 }
3479
3480 // Only on the WooCommerce product post type.
3481 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
3482 if ( ! $screen || 'product' !== $screen->post_type ) {
3483 return;
3484 }
3485
3486 // Need WooCommerce's product meta-boxes script to attach to.
3487 if ( ! wp_script_is( 'wc-admin-product-meta-boxes', 'registered' ) ) {
3488 return;
3489 }
3490
3491 // Pass the WpStream pricing types to JS and add show_if_{type} classes so the pricing panel toggles correctly.
3492 $types_json = wp_json_encode( array_values( $this->wpstream_get_wc_product_types_with_pricing() ) );
3493
3494 wp_add_inline_script(
3495 'wc-admin-product-meta-boxes',
3496 "jQuery( function( $ ) {
3497 var wpstreamPricingProductTypes = {$types_json};
3498 var \$pricing = $( '.options_group.pricing' );
3499 var \$soldIndividually = $( '._sold_individually_field' ).parent();
3500
3501 wpstreamPricingProductTypes.forEach( function( type ) {
3502 \$pricing.addClass( 'show_if_' + type );
3503 \$soldIndividually.addClass( 'show_if_' + type );
3504 } );
3505
3506 if ( wpstreamPricingProductTypes.indexOf( $( '#product-type' ).val() ) !== -1 ) {
3507 \$pricing.show();
3508 \$soldIndividually.show();
3509 }
3510 } );"
3511 );
3512 }
3513
3514
3515 /**
3516 * Add custom classes to the product types
3517 *
3518 * Adjust the WooCommerce product-data tab visibility classes so WpStream
3519 * types hide the Shipping tab and show the Inventory tab. Filter callback.
3520 *
3521 * @since 3.0.1
3522 * @param array $tabs WooCommerce product data tabs config.
3523 * @return array The modified tabs config.
3524 */
3525 public function wpstream_hide_attributes_data_panel( $tabs) {
3526
3527 // Hide Shipping and show Inventory for WpStream product types.
3528 $tabs['shipping']['class'][] = 'hide_if_live_stream hide_if_video_on_demand hide_if_wpstream_bundle';
3529 $tabs['inventory']['class'][] = 'show_if_live_stream show_if_video_on_demand show_if_wpstream_bundle';
3530
3531 return $tabs;
3532 }
3533
3534
3535
3536 /**
3537 * Hide buy now on products if Netflix mode
3538 *
3539 * When global subscription ("Netflix") mode is on, mark WpStream media
3540 * products as not individually purchasable (access comes via the
3541 * subscription instead). Filter callback for is_purchasable.
3542 *
3543 * @since 3.12
3544 * @param bool $purchaseable_product_wpblog Current purchasable flag.
3545 * @param WC_Product $product The product being checked.
3546 * @return bool False for WpStream media in global-sub mode, else the original flag.
3547 */
3548 public function wpstream_hide_buy_now_subscription_mode( $purchaseable_product_wpblog,$product){
3549 $product_id=$product->get_id();
3550
3551 $term_list = wp_get_post_terms($product_id, 'product_type');
3552
3553 // Global subscription ("Netflix") mode flag.
3554 $subscription_model = intval( get_option('wpstream_global_sub','')) ;
3555
3556 if($subscription_model==1){ // if we have Neflix mode
3557 // WpStream media types are not directly purchasable in this mode.
3558 if( $term_list[0]->name=='live_stream' || $term_list[0]->name=='video_on_demand' || $term_list[0]->name=='wpstream_bundle' ){
3559 return false;
3560 }
3561 }
3562
3563 return $purchaseable_product_wpblog;
3564 }
3565
3566
3567
3568
3569
3570
3571 /**
3572 * Add custom fields to custom product types
3573 *
3574 * Render the extra WooCommerce "General" product fields for WpStream types:
3575 * the subscription-live flag, the recording/video chooser, and the
3576 * "attach to subscription" multi-select. Echoes HTML.
3577 *
3578 * @since 3.0.1
3579 * @return void
3580 */
3581 public function wpstream_add_custom_general_fields() {
3582 global $woocommerce, $post;
3583
3584 // Only render for WpStream product contexts (avoids clashing with other product types).
3585 if ( ! $this->wpstream_is_wpstream_wc_product_context( isset( $post->ID ) ? (int) $post->ID : 0 ) ) {
3586 return;
3587 }
3588 // Subscription-based live channel toggle (only when WC Subscriptions is active).
3589 if(function_exists('wcs_user_has_subscription')){
3590 echo '<div class="options_group show_if_subscription">';
3591 woocommerce_wp_select(
3592 array(
3593 'id' => '_subscript_live_event',
3594 'label' => __( 'Is a subscription based live channel ?', 'woocommerce' ),
3595 'options' => array("yes"=>"yes","no"=>"no", "none" => "none")
3596 )
3597 );
3598 echo '</div>';
3599 }
3600
3601 echo '<div class="options_group show_if_live_stream" style="border:none;"></div>';
3602 // VOD video chooser section.
3603 echo '<div class="options_group show_if_video_on_demand">';
3604
3605
3606 // Pre-selected video: from the new-video query arg, else the saved _movie_url meta.
3607 $selected='';
3608 if( isset( $_GET['new_video_name']) && $_GET['new_video_name']!='' ){
3609 $selected=esc_html($_GET['new_video_name']);
3610 }
3611 if($selected==''){
3612 $selected= get_post_meta($post->ID,'_movie_url',true);
3613 }
3614 // Only administrators may assign videos.
3615 if( !current_user_can('administrator') ){
3616 print '<div style="margin:10px;">'.esc_html('You need to be an administrator in order to assign videos','wpstream').'</div>';
3617 }else{
3618 woocommerce_wp_select(
3619 array(
3620 'id' => '_movie_url',
3621 'label' => __( 'Choose video', 'woocommerce' ),
3622 'options' => $this->main->wpstream_live_connection->wpstream_get_videos(),
3623 'selected'=> true,
3624 'value' => $selected
3625 )
3626 );
3627 }
3628
3629
3630
3631 echo '</div>';
3632
3633 // "Attach to subscription" multi-select (only when WC Subscriptions is active).
3634 if(function_exists('wcs_user_has_subscription')){
3635 $selected_sub='';
3636 echo '<div class="options_group show_if_video_on_demand show_if_live_stream">';
3637 if( isset( $_GET['wpstream_parent_sub']) && $_GET['wpstream_parent_sub']!='' ){
3638 $selected_sub=esc_html($_GET['wpstream_parent_sub']);
3639 }
3640 if($selected_sub==''){
3641 $selected_sub= get_post_meta($post->ID,'_wpstream_parent_sub',true);
3642 }
3643 woocommerce_wp_select(
3644 array(
3645 'id' => '_wpstream_parent_sub',
3646 'name' => '_wpstream_parent_sub[]',
3647 'label' => __( 'Attach to subscription', 'woocommerce' ),
3648 'options' => $this->wpstream_return_subscriptions_created(),
3649 'selected'=> true,
3650 'value' => $selected_sub,
3651 'custom_attributes' => array('multiple' => 'multiple')
3652 )
3653 );
3654
3655 echo '</div>';
3656
3657 }
3658 }
3659
3660
3661 /**
3662 * Build an id => title map of all WooCommerce subscription products,
3663 * for the "attach to subscription" selector. Includes a "none" option.
3664 *
3665 * @return array Map of subscription product ID => title (0 => 'none').
3666 */
3667 public function wpstream_return_subscriptions_created(){
3668 // Seed with the "none" choice.
3669 $return=array('0'=>'none');
3670
3671 // Query all subscription-type products, title-ordered.
3672 $args = array(
3673 'post_type' => 'product',
3674 'posts_per_page' => -1,
3675 'orderby' => 'title',
3676 'order' => 'ASC',
3677 'tax_query' => array(
3678 'relation' => 'AND',
3679 array(
3680 'taxonomy' => 'product_type',
3681 'field' => 'slug',
3682 'terms' => array( 'subscription'),
3683 )
3684 )
3685 );
3686
3687 // Collect each subscription's id => title.
3688 $subscriptions = new WP_Query($args);
3689 if($subscriptions->have_posts()):
3690 while ($subscriptions->have_posts()): $subscriptions->the_post();
3691 $return[ get_the_ID() ] = get_the_title();
3692 endwhile;
3693 endif;
3694
3695 // Restore the main query and return the map.
3696 wp_reset_postdata();
3697 return $return;
3698
3699 }
3700
3701
3702
3703
3704
3705
3706 /**
3707 * Save custom fields
3708 *
3709 * Persist the WpStream WooCommerce product fields (_movie_url,
3710 * _subscript_live_event, _wpstream_parent_sub) on save and reset the
3711 * event_passed flag. Hooked to the product save action.
3712 *
3713 * @since 3.0.1
3714 * @param int $post_id Product ID being saved.
3715 * @return void
3716 */
3717 public function wpstream_add_custom_general_fields_save( $post_id ){
3718 // Only handle WpStream product contexts.
3719 if ( ! $this->wpstream_is_wpstream_wc_product_context( (int) $post_id ) ) {
3720 return;
3721 }
3722
3723 // Meta keys this handler may write.
3724 $permited_values = array(
3725 '_movie_url',
3726 '_subscript_live_event',
3727 '_wpstream_parent_sub',
3728
3729 );
3730
3731
3732
3733 // Reset event_passed and save each allow-listed field.
3734 foreach($_POST as $key=>$value){
3735 update_post_meta( $post_id, 'event_passed', 0 );
3736 if( in_array($key, $permited_values) ){
3737 if( !empty( $_POST[$key] ) ){
3738 $key = sanitize_key($key);
3739 $value = sanitize_text_field($_POST[$key]);
3740
3741 // The parent-subscription field is an array; sanitize each element.
3742 if($key=='_wpstream_parent_sub'){
3743 $value= $_POST[$key];
3744 $value = array_map("sanitize_text_field", $value);
3745
3746 }
3747 update_post_meta( $post_id, $key, $value );
3748 }
3749 }
3750 }
3751 //die();
3752
3753 }
3754
3755 /**
3756 * Add to cart redirect
3757 *
3758 * Render the simple add-to-cart template for WpStream product types.
3759 *
3760 * @since 3.0.1
3761 * @return void
3762 */
3763 public function wpstream_add_to_cart() {
3764 wc_get_template( 'single-product/add-to-cart/simple.php' );
3765 }
3766
3767
3768 /**
3769 * Replace add to cart button
3770 *
3771 * For live_stream / video_on_demand products, swap the loop add-to-cart
3772 * button for a direct shop add-to-cart link. Filter callback.
3773 *
3774 * @since 3.0.1
3775 * @param string $button Original button HTML.
3776 * @param WC_Product $product The product (overwritten by global $product).
3777 * @return string The (possibly replaced) button HTML.
3778 */
3779 public function replacing_add_to_cart_button( $button, $product ) {
3780 global $product;
3781 $product_type = $product->get_type();
3782
3783 // Only WpStream media types get the custom link; others keep the default button.
3784 if($product_type==='live_stream' || $product_type=='video_on_demand'){
3785 return $button = '<a class="button" href="'.get_site_url().'/shop/?add-to-cart=' .$product->get_id(). '&quantity=1">' . __( 'Add to Cart', 'woocommerce' ) . '</a>';
3786 }else{
3787 return $button;
3788 }
3789 }
3790
3791
3792 /**
3793 * Admin notices
3794 *
3795 * Print global admin notices: (commented-out) WooCommerce-missing notice
3796 * and an error if the PHP cURL extension is unavailable, plus the dismiss
3797 * nonce. Echoes HTML.
3798 *
3799 * @since 3.0.1
3800 * @return void
3801 */
3802 public function wpstream_admin_notice() {
3803 global $pagenow;
3804 global $typenow;
3805
3806 // Stored dismissed-notice flags.
3807 $wpstream_notices = get_option('wpstream_notices');
3808
3809 /*
3810 if ( !in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
3811 if( !is_array($wpstream_notices) ||
3812 !isset($wpstream_notices['wpstream_woo_notice']) ||
3813 ( isset($wpestate_notices['wpstream_woo_notice']) && $wpestate_notices['wpstream_woo_notice']!='yes') ){
3814
3815
3816 print '<div class="notice wpstream_notices notice-error is-dismissible" data-notice-type="wpstream_woo_notice" >
3817 <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>
3818 </div>';
3819 }
3820 }
3821 */
3822
3823 // Hard requirement: warn when the PHP cURL extension is missing.
3824 if( !in_array ('curl', get_loaded_extensions())) {
3825 print '<div class="notice notice-error is-dismissible">
3826 <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>
3827 </div>';
3828 }
3829
3830
3831 // Nonce used by the JS that records notice dismissals.
3832 $ajax_nonce = wp_create_nonce( "wpstream_notice_nonce" );
3833 print '<input type="hidden" id="wpstream_notice_nonce" value="'.esc_html($ajax_nonce).'"/>';
3834
3835 }
3836
3837 /**
3838 * Get plugin latest update release date from WordPress.org
3839 * @param $plugin_slug
3840 * @param $version
3841 *
3842 * @return bool
3843 */
3844 public function get_plugin_release_date( $plugin_slug, $version = null ) {
3845 // Query the WordPress.org plugins info API.
3846 $api_url = 'https://api.wordpress.org/plugins/info/1.0/' . $plugin_slug . '.json';
3847 $response = wp_remote_get( $api_url );
3848
3849 // Bail on transport error.
3850 if ( is_wp_error( $response ) ) {
3851 return false;
3852 }
3853
3854 // Decode the JSON body; bail if there is no versions list.
3855 $body = wp_remote_retrieve_body( $response );
3856 $data = json_decode( $body, true );
3857
3858 if ( !$data || !isset( $data['versions'] ) ) {
3859 return false;
3860 }
3861
3862 // no version provided, get the latest version
3863 if ( !$version ) {
3864 $version = $data['version'];
3865 }
3866
3867 // check if the version exists in the versions array
3868 if ( !isset( $data['versions'][ $version ] ) ) {
3869 return false;
3870 }
3871
3872 // Return the plugin's last-updated date (note: this is the plugin's overall last_updated, not per-version).
3873 if ( isset( $data['last_updated'] ) ) {
3874 return date('Y-m-d', strtotime( $data['last_updated'] ) );
3875 }
3876
3877 return false;
3878 }
3879
3880 /**
3881 * Adds notice for the WpStream update availability
3882 * when the update is not older than 30 days
3883 */
3884 public function wpstream_plugin_update_available_notice() {
3885 // Only show to users who can update plugins.
3886 if (!current_user_can('update_plugins')) {
3887 return;
3888 }
3889
3890 // Look up a pending update for this plugin in WP's transient.
3891 $plugin_slug = 'wpstream/wpstream.php';
3892 $update_data = get_site_transient('update_plugins');
3893
3894 if ( is_object( $update_data ) &&
3895 property_exists( $update_data, 'response' ) &&
3896 is_array($update_data->response) &&
3897 key_exists($plugin_slug, $update_data->response)
3898 ) {
3899 $new_version = $update_data->response[$plugin_slug]->new_version;
3900
3901 // Suppress the notice for very fresh releases (grace period).
3902 $release_date = $this->get_plugin_release_date( 'wpstream', $new_version );
3903
3904 if ( $release_date ) {
3905 $days_since_release = ( time() - strtotime( $release_date ) ) / DAY_IN_SECONDS;
3906
3907 // if there's an update newer than 7 days, do not show the notice
3908 if ( $days_since_release < 7 ) {
3909 return;
3910 }
3911 }
3912 // Build the one-click update URL (nonce-protected) and print the notice.
3913 $update_url = wp_nonce_url(
3914 self_admin_url('update.php?action=upgrade-plugin&plugin=' . urlencode($plugin_slug)),
3915 'upgrade-plugin_' . $plugin_slug
3916 );
3917
3918 echo '<div class="notice notice-warning is-dismissible">';
3919 echo '<p><strong>' . __('WpStream Plugin Update Available', 'wpstream') . '</strong></p>';
3920 echo '<p>' . sprintf(
3921 __('Version %s is available. Please update to the latest version for new features and security improvements.', 'wpstream'),
3922 '<strong>' . esc_html($new_version) . '</strong>'
3923 ) . '</p>';
3924 echo '<p><a href="' . esc_url($update_url) . '" class="button button-primary">' .
3925 __('Update Now', 'wpstream') . '</a></p>';
3926 echo '</div>';
3927 }
3928 }
3929
3930 /**
3931 * Admin notices
3932 *
3933 * AJAX handler that records a dismissed notice: marks the posted notice
3934 * type as 'yes' in the wpstream_notices option so it stops showing.
3935 *
3936 * @since 3.0.1
3937 * @return void Ends the request with die().
3938 */
3939 public function wpstream_update_cache_notice(){
3940
3941 //check_ajax_referer( 'wpstream_notice_nonce', 'security' );
3942
3943 // Which notice was dismissed.
3944 $notice_type = esc_html($_POST['notice_type']);
3945 $notices = get_option('wp_stream_notices');
3946
3947 // Normalise to an array.
3948 if(! is_array($notices) ){
3949 $notices=array();
3950 }
3951
3952 // Flag this notice as dismissed and persist.
3953 $notices[$notice_type]='yes';
3954
3955 update_option('wpstream_notices',$notices);
3956 die();
3957 }
3958
3959
3960
3961
3962 /**
3963 * Activate metaboxes for Streaming controls on sidebar
3964 *
3965 * Register the "Live Streaming" sidebar metabox on free channel posts, and
3966 * also on WooCommerce products that are live_stream (or a subscription
3967 * flagged as a live event). Hooked to add_meta_boxes.
3968 *
3969 * @since 3.0.1
3970 * @return void
3971 */
3972 public function wpstream_startstreaming_sidebar_meta() {
3973 global $post;
3974 $term_list = wp_get_post_terms($post->ID, 'product_type');
3975
3976 // Always add the sidebar controls to the free channel post type.
3977 add_meta_box(
3978 'wpstream-sidebar-meta',
3979 esc_html__('Live Streaming', 'wpstream'),
3980 array($this,'wpstream_start_stream_meta'),
3981 'wpstream_product',
3982 'side',
3983 'high'
3984 );
3985
3986 // For WooCommerce products, add it only for live channels / subscription-live events.
3987 $is_subscription_live_event = esc_html(get_post_meta($post->ID,'_subscript_live_event',true));
3988 if(!is_wp_error( $term_list )){
3989 if( isset($term_list[0]->name) ){
3990 if( $term_list[0]->name=='live_stream' || ($term_list[0]->name=='subscription' && $is_subscription_live_event=='yes' ) ){
3991 add_meta_box('wpstream-sidebar-meta', esc_html__('Live Streaming', 'wpstream'), array($this,'wpstream_start_stream_meta'), 'product', 'side', 'high');
3992 }
3993 }
3994 }
3995
3996 }
3997
3998 /**
3999 * edited 4.0
4000 *
4001 * Show Streaming controls on sidebar
4002 *
4003 * Render the sidebar metabox contents: for published channels, the live
4004 * stream unit card (plus a basic-streaming flag when out of data) and the
4005 * error modal; otherwise a "publish first" prompt. Echoes HTML.
4006 *
4007 * @since 3.0.1
4008 * @return void
4009 */
4010 public function wpstream_start_stream_meta(){
4011 // Preload the current live-event map for the card renderer.
4012 global $live_event_for_user;
4013 $live_event_for_user = $this->main->wpstream_live_connection->wpstream_get_live_event_for_user();
4014
4015 global $post;
4016 $local_event_options = get_post_meta ($post->ID,'local_event_options','');
4017
4018
4019
4020 // Streaming controls only make sense once the channel is published.
4021 if( get_post_status( $post->ID ) === 'publish' ) {
4022 // Start-event nonce for the card's ON/OFF button.
4023 $ajax_nonce = wp_create_nonce( "wpstream_start_event_nonce" );
4024 print '<input type="hidden" id="wpstream_start_event_nonce" value="'.$ajax_nonce.'">';
4025
4026 // Flag basic-streaming mode when the account has no data left.
4027 $pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_new_general_set' );
4028 if( isset($pack_details['available_data_mb'])){
4029 if ($pack_details['available_data_mb'] <= 0){
4030 print '<input type="hidden" id="wpstream_basic_streaming" value="true">';
4031 }
4032 }
4033
4034 // Render the channel card plus the shared modal background and error modal.
4035 $this->wpstream_live_stream_unit($post->ID);
4036 print '<div class="wpstream_modal_background"></div>';
4037 print '<div class="wpstream_error_modal_notification"><div class="wpstream_error_content">er1</div>
4038 <div class="wpstream_error_ok wpstream_button" type="button">'.esc_html__('Close','wpstream').'</div>
4039 </div>';
4040 } else {
4041 // Not published yet.
4042 esc_html_e('To Go Live, please publish your channel first !','wpstream');
4043 }
4044 }
4045
4046
4047 /**
4048 * Whether the account is currently limited to "basic streaming" mode.
4049 *
4050 * @return bool
4051 */
4052 public function wpstream_is_basic_streaming_mode(){
4053 return $this->main->quota_manager->is_basic_streaming_mode( null, 'wpstream_is_basic_streaming_mode' );
4054 }
4055
4056 /**
4057 * Whether the account's plan meters streaming by hours (vs. data).
4058 *
4059 * @return bool
4060 */
4061 public function wpstream_is_use_streaming_hours() {
4062 $pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_start_channel' );
4063 return $this->main->quota_manager->uses_streaming_hours( $pack_details );
4064 }
4065
4066 /**
4067 * Cached-only flags for start_streaming.js localization (no API on cold cache).
4068 *
4069 * @return array{is_basic_streaming: bool, use_streaming_hours: bool}
4070 */
4071 public function wpstream_get_start_streaming_localization_flags() {
4072 return $this->main->quota_manager->get_streaming_ui_flags_from_cache();
4073 }
4074
4075
4076
4077 /**
4078 * Register a hidden dashboard page used as the onboarding wizard endpoint.
4079 *
4080 * @return void
4081 */
4082 public function add_dashboard_page() {
4083 add_dashboard_page( '', '', 'administrator', 'wpstream-onboarding', '' );
4084 }
4085
4086
4087
4088
4089 /**
4090 * Bootstrap the full-screen onboarding wizard screen (non-AJAX requests).
4091 *
4092 * @return void
4093 */
4094 public function wpstream_load_onboarding_wizard() {
4095
4096 // Check for wizard-specific parameter
4097 // Allow plugins to disable the onboarding wizard
4098 // Check if current user is allowed to save settings.
4099
4100
4101 // Don't load the interface if doing an ajax call.
4102 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
4103 return;
4104 }
4105
4106 // Ensure a current screen is set for the standalone wizard context.
4107 set_current_screen();
4108
4109 // Remove an action in the Gutenberg plugin ( not core Gutenberg ) which throws an error.
4110 remove_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' );
4111
4112 // Hand off to the actual wizard renderer.
4113 $this->actual_load_onboarding_wizard();
4114
4115 }
4116
4117
4118 /*
4119 * Add on boarding to footer
4120 */
4121 /**
4122 * On the onboarding admin page, print the onboarding modal into the footer.
4123 *
4124 * @return void
4125 */
4126 public function wpstream_admin_footer_onboarding(){
4127 if( isset($_GET['page']) && $_GET['page']==='wpstream_onboard') {
4128 $this->wpstream_onboard_display();
4129 }
4130 }
4131
4132
4133 /*
4134 * On Board Display
4135 *
4136 */
4137 /**
4138 * Render the Quick Start landing screen (logo, intro, "Start the Guide"
4139 * button and the script that opens the onboarding wizard). Echoes HTML.
4140 *
4141 * @return void
4142 */
4143 public function wpstream_pre_onboard_display(){
4144 // Quota summary header for the Quick Start page.
4145 $pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_pre_onboard_display' );
4146 $this->main->show_user_data($pack_details);
4147
4148 // Onboarding logo used in the intro card.
4149 $thumb= plugin_dir_url( dirname( __FILE__ ) ). 'img/logo_onboarding.svg';
4150 // Below: Quick Start intro markup plus a script that reveals the onboarding wizard modal.
4151 ?>
4152 <div id="wpstream-onboarding-root"></div>
4153 <div class="wpstream_quick_start_wrapper">
4154 <img class="wpstream_onboarding_logo" src="<?php echo esc_url($thumb); ?>" />
4155
4156 <h1><?php print esc_html__('WpStream','wpstream').' <span class="header_special">'.esc_html__('Quick Start','wpstream').'</span>';?></h1>
4157
4158
4159 <p>
4160 <?php esc_html_e('The quick start guide will help you set up Live Streaming, Video On Demand, and Monetization in a fun and interactive way. Give it a shot! ','wpstream');?>
4161 </p>
4162
4163 <div id="wpstream_trigger_quick_start" class="wpstream_button wpstream_button_action"><?php esc_html_e('Start the Guide','wpstream');?></div>
4164
4165
4166
4167 </div>
4168
4169
4170
4171 <script type="text/javascript">
4172 //<![CDATA[
4173 jQuery(document).ready(function(){
4174 jQuery(".wpstream_on_boarding_wrapper").show();
4175 jQuery(".wpstream_modal_background_onboard").show();
4176 });
4177
4178 //]]>
4179 </script>
4180
4181 <?php
4182
4183 }
4184
4185
4186
4187
4188
4189
4190
4191
4192 /*
4193 *
4194 * On Boarding Content
4195 *
4196 */
4197 /**
4198 * Emit the full onboarding wizard markup by rendering each step in order
4199 * (header, account, path choice, live/VOD branches, footer). Echoes HTML.
4200 *
4201 * @return void
4202 */
4203 public function wpstream_onboard_display() {
4204 // Render each wizard step sequentially into the modal.
4205 $this->onboarding_wizard_header();
4206 $this->wpstream_onboarding_step1();
4207 $this->wpstream_onboarding_step2();
4208 $this->wpstream_onboarding_step3_live_streaming();
4209 $this->wpstream_onboarding_step_3_A_live_streaming_free_view();
4210 $this->wpstream_onboarding_step_3_B_live_streaming_pay_per_view();
4211 $this->wpstream_onboarding_step4_vod();
4212 $this->wpstream_onboarding_step_4_free_vod();
4213 $this->wpstream_onboarding_step_4_ppv_vod();
4214 $this->onboarding_wizard_footer();
4215 }
4216
4217
4218 /*
4219 *
4220 * On Boarding Step 1 - the login/register
4221 *
4222 */
4223 /**
4224 * Onboarding step 1: WpStream account login/registration UI, including the
4225 * ALTCHA captcha widget (on HTTPS) and a hidden generated registration
4226 * password. Emits a marker div when a token already exists. Echoes HTML.
4227 *
4228 * @return void
4229 */
4230 public function wpstream_onboarding_step1(){
4231
4232 // Login form field definitions (username + password).
4233 $wpstream_options_array =array(
4234 2 => array(
4235 'label' => 'WpStream.net Username or Email',
4236 'name' => 'api_username',
4237 'type' => 'text',
4238 ),
4239 3 => array(
4240 'label' => 'WpStream.net Password',
4241 'name' => 'api_password',
4242 'type' => 'password',
4243 ),
4244
4245 );
4246 // Existing auth token (if already connected) checked at the end.
4247 $token = $this->main->wpstream_live_connection->wpstream_get_token();
4248
4249 // Below: the account step markup (login form, register form, captcha, nonce).
4250 ?>
4251 <div class="wpstream_step_wrapper wpstream_step_1" id="wpstream_step_1">
4252 <div class="wpstream_has_credential">
4253 <h1><?php esc_html_e('WpStream Account','wpstream');?></h1>
4254
4255 <div class="wpstream_on_board_login_wrapper_explanations">
4256 <?php esc_html_e('A WpStream account is required to make use of the plugin.','wpstream');?>
4257 </div>
4258
4259
4260 <div class="wpstream_check_account_status">
4261 <?php esc_html_e( 'Checking if you are already logged.....', 'wpstream' ); ?>
4262 </div>
4263
4264 <div class="wpstream_onboarding_notification"></div>
4265
4266 <div class="wpstream_option_wrapper wpstream_on_board_login_wrapper">
4267 <h2><?php esc_html_e('Login with your WpStream Account','wpstream');?></h2>
4268 <?php
4269 // Render each login field pre-filled with its stored value.
4270 foreach ($wpstream_options_array as $key=>$option){
4271 print '<div class="wpstream_option">';
4272
4273 $options_value = esc_html( get_option('wpstream_'.$option['name'],'') );
4274 print '<label for="'.$option['name'].'">'.$option['label'].'</label>';
4275 print '<input id="'.$option['name'].'" type="'.$option['type'].'" size="36" name="'.$option['name'].'" value="'.esc_html($options_value).'" />';
4276
4277 print '</div>';
4278 }
4279 ?>
4280 <input type="submit" name="submit" class="wpstream_button wpstream_button_action wpstream_onboard_login" value="<?php esc_html_e('Login','wpstream');?>" />
4281 </div>
4282
4283
4284
4285
4286 <div class="wpstream_on_board_register_wrapper">
4287 <h2><?php esc_html_e('Register for a WpStream Account','wpstream');?></h2>
4288 <div class="wpstream_option">
4289 <label for="wpstream_register_email"><?php esc_html_e('Your Email','wpstream');?></label>
4290 <input id="wpstream_register_email" type="text" size="36" name="wpstream_register_email" value="<?php echo get_option('admin_email'); ?>" />
4291
4292 </div>
4293
4294 <div class="wpstream_option">
4295 <!-- <label for="wpstream_register_password">--><?php //esc_html_e('Your Password','wpstream');?><!--</label>-->
4296 <input id="wpstream_register_password" hidden type="text" size="36" name="wpstream_register_password" value="<?php echo $this->randomPassword();?>" />
4297 <span class="" ><?php esc_html_e('We\'ll send the password to the email you attached. ', 'wpstream') ?></span>
4298 </div>
4299
4300
4301 <!-- Altcha Widget (requires HTTPS/secure context) -->
4302 <?php if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ) : ?>
4303 <script async defer src="https://cdn.jsdelivr.net/gh/altcha-org/altcha/dist/altcha.min.js" type="module"></script>
4304 <div class="wpstream_option" style="display:none;">
4305 <altcha-widget
4306 challengeurl="<?php echo esc_url( WPSTREAM_API . '/v2/user/getcaptcha' ); ?>"
4307 name="altcha"
4308 auto="onload"
4309 hidefooter
4310 hidelogo
4311 strings='{"label": "<?php esc_html_e('I am not a robot', 'wpstream'); ?>", "error": "<?php esc_html_e('Verification failed', 'wpstream'); ?>", "wait": "<?php esc_html_e('Verifying...', 'wpstream'); ?>"}'
4312 ></altcha-widget>
4313 </div>
4314 <?php endif; ?>
4315
4316 <div class="wpstream_option wpstream_terms_agreement">
4317 <!-- Add "by registering you agree to the privacy terms" checkbox-->
4318 <input id="wpstream_register_privacy" type="checkbox" name="wpstream_register_privacy" />
4319 <label for="wpstream_register_privacy">
4320 <?php printf(
4321 esc_html__('By registering you agree to the %sPrivacy Policy%s','wpstream'),
4322 '<a href="https://wpstream.net/privacy-policy/" target="_blank">',
4323 '</a>'
4324 );?>
4325 </label>
4326 </div>
4327 <input type="submit" name="submit" class="wpstream_button wpstream_button_action wpstream_onboard_register" value="<?php esc_html_e('register','wpstream');?>" />
4328 </div>
4329
4330
4331 <div id="wpstream_onboarding_action_login"><?php esc_html_e('I already have a WpStream Account','wpstream');?></div>
4332
4333 <div id="wpstream_onboarding_action_register"><?php esc_html_e('Back to Registration','wpstream');?></div>
4334
4335 </div>
4336
4337
4338 <?php
4339 // Nonce protecting the onboarding AJAX actions.
4340 $ajax_nonce = wp_create_nonce( "wpstream_onboarding_nonce");
4341 print'<input type="hidden" id="wpstream_onboarding_nonce" value="'.esc_html($ajax_nonce).'" /> ';
4342
4343 ?>
4344
4345
4346 <div id="wpstream_on_board" class="wpstream_action_next_step" data-nextthing="wpstream_step_2" style="display:none;" >Move to step 2</div>
4347 </div>
4348 <?php
4349
4350 // If already authenticated, emit a marker the wizard JS uses to skip login.
4351 if( !is_null( $token ) && trim( $token ) !== '' ) {
4352 print '<div id="wpstream_have_token"></div>';
4353 }
4354 }
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365 /*
4366 *
4367 * Generate random pass
4368 *
4369 */
4370 /**
4371 * Generate a random 16-character alphanumeric password (used to pre-fill
4372 * the onboarding registration form).
4373 *
4374 * @return string The generated password.
4375 */
4376 public function randomPassword() {
4377 // Character pool for the password.
4378 $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
4379 $pass = array(); //remember to declare $pass as an array
4380 $alphaLength = strlen($alphabet) - 1; //put the length -1 in cache
4381 // Pick 16 cryptographically-random characters.
4382 for ($i = 0; $i < 16; $i++) {
4383 $n = random_int(0, $alphaLength);
4384 $pass[] = $alphabet[$n];
4385 }
4386 return implode($pass); //turn the array into a string
4387 }
4388
4389
4390 /*
4391 *
4392 *
4393 *
4394 */
4395 /**
4396 * Onboarding step 2: choose a starting path (Go Live vs create a VOD). Echoes HTML.
4397 *
4398 * @return void
4399 */
4400 public function wpstream_onboarding_step2(){
4401 // Below: the path-choice step markup.
4402 ?>
4403
4404 <div class="wpstream_step_wrapper wpstream_step_2" id="wpstream_step_2">
4405 <h1>Welcome to <span class="header_special">WpStream</span>! How would you like to start?</h1>
4406
4407 <div class="wpstream_accordion_header wpstream_action_next_step" data-nextthing="wpstream_step_3a" ><?php esc_html_e('Go LIVE!','wpstream');?></div>
4408 <div class="wpstram_or">or</div>
4409 <div class="wpstream_accordion_header wpstream_action_next_step wpstream_step_2_create_vod" data-nextthing="wpstream_step_4a" ><?php esc_html_e('Create a Video-On-Demand (VOD)','wpstream');?></div>
4410
4411 </div>
4412
4413
4414 <?php
4415 }
4416
4417 /*
4418 *
4419 *
4420 *
4421 */
4422 /**
4423 * Onboarding step 3 (live): choose Free-To-View vs Pay-Per-View. Echoes HTML.
4424 *
4425 * @return void
4426 */
4427 public function wpstream_onboarding_step3_live_streaming(){
4428 // Below: the FTV/PPV choice step for live streaming.
4429 ?>
4430
4431 <div class="wpstream_step_wrapper wpstream_step_3 wpstream_onboarding_live" id="wpstream_step_3">
4432 <h1><?php esc_html_e('Do you want to charge a fee for watching?','wpstream'); ?></h1>
4433
4434 <div class="wpstream_accordion_header wpstream_action_next_step wpstream_step_3a" data-nextthing="wpstream_step_3a"><?php esc_html_e('No - Free-To-View (FTV)','wpstream');?></div>
4435 <div class="wpstram_or">or</div>
4436 <div class="wpstream_accordion_header wpstream_action_next_step" data-nextthing="wpstream_step_3b" ><?php esc_html_e('Yes - Pay-Per-View (PPV)','wpstream');?></div>
4437
4438 <div class="wpstream_initial_onboarding_controls_wrapper">
4439 <span class="wpstream_onboard_initial_bubble_prev" data-step="wpstream_step_2"><?php esc_html_e('Prev','wpstream');?></span>
4440 </div>
4441
4442 </div>
4443
4444
4445 <?php
4446 }
4447
4448
4449 /*
4450 *
4451 *
4452 *
4453 */
4454 /**
4455 * Onboarding step 3A: create the first Free-To-View live channel (name +
4456 * create button). Echoes HTML.
4457 *
4458 * @return void
4459 */
4460 public function wpstream_onboarding_step_3_A_live_streaming_free_view(){
4461 // Below: the create-FTV-channel step markup.
4462 ?>
4463
4464 <div class="wpstream_step_wrapper wpstream_step_3a wpstream_onboarding_live" id="wpstream_step_3a">
4465 <h1><?php esc_html_e('Let’s create your first FTV live channel','wpstream');?></h1>
4466
4467 <div id="wpstream_onboard_live_notice" class="wpstream_onboarding_notification"></div>
4468 <div id="wpstream_onboard_live" class="wpstream_accordion_container">
4469 <label>Name your first Free-To-View channel</label>
4470 <input type="text" name="channel_name" id="wpstream_onboarding_channel_name" class="wpstream_onboarding_channel_name" value="<?php esc_html_e('My first FTV channel','wpstream');?>" >
4471 <input type="submit" name="submit" id="wpstream_on_board_create_channel" class="wpstream_button wpstream_button_action wpstream_onboard_live_action" value="<?php esc_html_e('Create Channel','wpstream');?>" />
4472
4473 </div>
4474
4475 <div class="wpstream_initial_onboarding_controls_wrapper">
4476 <span class="wpstream_onboard_initial_bubble_prev" data-step="wpstream_step_2"><?php esc_html_e('Prev','wpstream');?></span>
4477 </div>
4478
4479 </div>
4480
4481
4482 <?php
4483 }
4484
4485
4486 /*
4487 *
4488 *
4489 *
4490 */
4491
4492 /**
4493 * Onboarding step 3B: create the first Pay-Per-View live channel (name +
4494 * price). Requires WooCommerce; otherwise shows the Woo warning. Echoes HTML.
4495 *
4496 * @return void
4497 */
4498 public function wpstream_onboarding_step_3_B_live_streaming_pay_per_view(){
4499 // Below: the create-PPV-channel step markup.
4500 ?>
4501
4502 <div class="wpstream_step_wrapper wpstream_step_3b wpstream_onboarding_live" id="wpstream_step_3b">
4503 <h1><?php esc_html_e('Make your live stream Pay Per View','wpstream');?></h1>
4504 <?php
4505 // PPV needs WooCommerce for the product; show a warning when missing.
4506 if ( class_exists( 'WooCommerce' ) ) {
4507 ?>
4508 <div id="wpstream_onboard_live_ppv_notice" class="wpstream_onboarding_notification"></div>
4509 <div id="wpstream_onboard_live_ppv" class="wpstream_accordion_container">
4510 <label><?php esc_html_e('Choose a name for your channel','wpstream');?></label>
4511 <input type="text" name="channel_name" id="wpstream_onboarding_channel_name_ppv" class="wpstream_onboarding_channel_name" value="<?php esc_html_e('My First PPV Channel','wpstream');?>">
4512 <label><?php esc_html_e('Pay-Per-View Price ($)','wpstream');?></label>
4513 <input type="text" name="channel_name" id="wpstream_onboarding_event_price_ppv" class="wpstream_onboarding_event_price" value="10">
4514 <input type="submit" name="submit" id="wpstream_onboard_live_ppv_action" class="wpstream_button wpstream_button_action wpstream_onboard_live_ppv_action" value="<?php esc_html_e('Create Channel','wpstream');?>" />
4515
4516 </div>
4517 <?php
4518 } else {
4519 $this->wpstream_onboarding_woo_warning();
4520 } ?>
4521
4522
4523 <div class="wpstream_initial_onboarding_controls_wrapper">
4524 <span class="wpstream_onboard_initial_bubble_prev" data-step="wpstream_step_2"><?php esc_html_e('Prev','wpstream');?></span>
4525 </div>
4526 </div>
4527 <?php
4528 }
4529
4530
4531
4532
4533
4534
4535 /*
4536 * WooCommerce not installed Warning
4537 *
4538 *
4539 */
4540 /**
4541 * Print the "PPV needs WooCommerce" warning with install / try-again actions.
4542 *
4543 * @return void
4544 */
4545 public function wpstream_onboarding_woo_warning(){
4546 print'<div class="wpstream_warning_onboarding">
4547 '.esc_html__('Pay-Per-View streaming requires WooCommerce. Please install the WooCommerce plugin and try again.','wpstream').'
4548 </br>
4549 <div class="wpstream_install_plugin">'. esc_html__('Install WooCommerce','wpstream').'</div>
4550
4551 <div class="wpstream_onboarding_tryagain">'.esc_html__('Try Again','wpstream').'</div>
4552 </div>';
4553 }
4554
4555
4556 /*
4557 *
4558 *
4559 *
4560 */
4561
4562 /**
4563 * Onboarding step 4 (VOD): choose Free-To-View vs Pay-Per-View. Echoes HTML.
4564 *
4565 * @return void
4566 */
4567 public function wpstream_onboarding_step4_vod(){
4568 // Below: the FTV/PPV choice step for VOD.
4569 ?>
4570 <div class="wpstream_step_wrapper wpstream_step_4 wpstream_onboarding_vod" id="wpstream_step_4">
4571 <h1><?php esc_html_e('Do you want to charge a fee for watching?','wpstream');?></h1>
4572
4573 <div class="wpstream_accordion_header wpstream_action_next_step wpstream_step_4a" data-control="wpstream_onboard_vod_free" data-nextthing="wpstream_step_4a" ><?php esc_html_e('No - Free-To-View (FTV)','wpstream'); ?></div>
4574 <div class="wpstram_or">or</div>
4575 <input type="hidden" id="wpstream_onboarding_video_list_nonce" value="<?php echo wp_create_nonce( "wpstream_onboarding_video_list_nonce" ); ?>">
4576 <div class="wpstream_accordion_header wpstream_action_next_step wpstream_step_4b" data-control="wpstream_onboard_vod_ppv" data-nextthing="wpstream_step_4b" ><?php esc_html_e('Yes - Pay-Per-View (PPV)','wpstream'); ?></div>
4577 <div class="wpstream_initial_onboarding_controls_wrapper">
4578 <span class="wpstream_onboard_initial_bubble_prev" data-step="wpstream_step_2"><?php esc_html_e('Prev','wpstream');?></span>
4579 </div>
4580
4581 </div>
4582 <?php
4583 }
4584
4585
4586 /*
4587 *
4588 *
4589 *
4590 */
4591 /**
4592 * Onboarding step 4A: create the first Free-To-View VOD (name + recording
4593 * chooser). Only rendered on the dedicated onboarding page. Echoes HTML.
4594 *
4595 * @return void
4596 */
4597 public function wpstream_onboarding_step_4_free_vod(){
4598
4599 // Only render on the standalone onboarding admin page.
4600 $current_screen=get_current_screen();
4601 if($current_screen->base !=='wpstream_page_wpstream_onboard'){
4602 return;
4603 }
4604 // Below: the create-FTV-VOD step markup.
4605 ?>
4606
4607 <div class="wpstream_step_wrapper wpstream_step_4a wpstream_onboarding_live" id="wpstream_step_4a">
4608 <h1><?php esc_html_e('Let’s create your first Free-To-View VOD','wpstream');?></h1>
4609 <div id="wpstream_onboard_vod_free_notice" class="wpstream_onboarding_notification"></div>
4610 <div id="wpstream_onboard_vod_free" class="wpstream_accordion_container">
4611 <div class="spinner"></div>
4612 <div class="wpstream-step-container" style="display: none">
4613 <label><?php esc_html_e('Name your FTV Video-On-Demand','wpstream')?></label>
4614 <input type="text" name="channel_name" class="wpstream_onboarding_vod_name" id="wpstream_onboarding_vod_name" value="<?php esc_html_e('My First FTV VOD','wpstream');?>" >
4615 <div id="wpstream_free_vod_dropdown_videos_list"></div>
4616 <input type="submit" name="submit" class="wpstream_button wpstream_button_action wpstream_onboard_vod_free_action" id="wpstream_onboard_vod_free_action" value="Create FTV VOD">
4617 </div>
4618 </div>
4619 <?php $this->wpstream_obboarding_file_warning(); ?>
4620
4621 <div class="wpstream_initial_onboarding_controls_wrapper">
4622 <span class="wpstream_onboard_initial_bubble_prev" data-step="wpstream_step_4"><?php esc_html_e('Prev','wpstream');?></span>
4623 </div>
4624
4625 </div>
4626
4627
4628 <?php
4629 }
4630
4631 /*
4632 *
4633 *
4634 *
4635 */
4636 /**
4637 * Print the "no recordings available for a VOD" warning with upload /
4638 * try-again actions (hidden until the JS detects no recordings).
4639 *
4640 * @return void
4641 */
4642 public function wpstream_obboarding_file_warning(){
4643 print '<div class="wpstream_warning_onboarding" style="display: none">
4644 '.esc_html__('A recording is needed to create a VOD from. There are no recordings under your account. You can create new recordings by recording a live channel or uploading video files directly.','wpstream').'
4645 </br>
4646 <div class="wpstream_upload_video">'.esc_html__('Upload Video','wpstream').'</div>
4647
4648 <div class="wpstream_onboarding_tryagain">'.esc_html__('Try Again','wpstream').'</div>
4649 </div>';
4650 }
4651
4652 /*
4653 *
4654 *
4655 *
4656 */
4657 /**
4658 * Onboarding step 4B: create the first Pay-Per-View VOD (name + recording
4659 * chooser + price). Requires WooCommerce and the onboarding page. Echoes HTML.
4660 *
4661 * @return void
4662 */
4663 public function wpstream_onboarding_step_4_ppv_vod(){
4664 // Only render on the standalone onboarding admin page.
4665 $current_screen=get_current_screen();
4666
4667 if($current_screen->base !=='wpstream_page_wpstream_onboard'){
4668 return;
4669 }
4670 // Below: the create-PPV-VOD step markup.
4671 ?>
4672
4673 <div class="wpstream_step_wrapper wpstream_step_4b wpstream_onboarding_live" id="wpstream_step_4b">
4674 <h1><?php esc_html_e('Let\'s create your first Pay-Per-View VOD','wpstream');?></h1>
4675
4676 <div id="wpstream_onboard_vod_ppv_notice" class="wpstream_onboarding_notification"></div>
4677 <?php
4678 // PPV VOD needs WooCommerce; otherwise show the Woo warning.
4679 if ( class_exists( 'WooCommerce' ) ) { ?>
4680 <div id="wpstream_onboard_vod_ppv" class="wpstream_accordion_container">
4681 <div class="spinner"></div>
4682 <div class="wpstream-step-container" style="display: none">
4683 <label><?php esc_html_e('Name your PPV Video-On-Demand','wpstream'); ?></label>
4684 <input type="text" name="channel_name" class="wpstream_onboarding_ppv_vod_name" id="wpstream_onboarding_ppv_vod_name" value="<?php esc_html_e('My First PPV VOD','wpstream');?>">
4685 <div id="wpstream_ppv_vod_dropdown_videos_list"></div>
4686 <label><?php esc_html_e('Pay-Per-View Price','wpstream');?></label>
4687 <input type="text" name="channel_name" class="wpstream_onboarding_vod_price" id="wpstream_onboarding_vod_price" value="10">
4688 <input type="submit" name="submit" class="wpstream_button wpstream_button_action wpstream_onboard_vod_ppv_action" id="wpstream_onboard_vod_ppv_action" value="<?php esc_html_e('Create PPV VOD','wpstream');?>" />
4689 </div>
4690 </div>
4691 <?php $this->wpstream_obboarding_file_warning(); ?>
4692 <?php } else {
4693 $this->wpstream_onboarding_woo_warning();
4694 } ?>
4695 <div class="wpstream_initial_onboarding_controls_wrapper">
4696 <span class="wpstream_onboard_initial_bubble_prev" data-step="wpstream_step_4"><?php esc_html_e('Prev','wpstream');?></span>
4697 </div>
4698 </div>
4699 <?php
4700 }
4701
4702
4703
4704
4705 /*
4706 *
4707 * On boarding Header
4708 *
4709 */
4710 /**
4711 * Open the onboarding wizard wrapper (logo + close controls). Echoes HTML.
4712 *
4713 * @return void
4714 */
4715 public function onboarding_wizard_header() {
4716 $thumb= plugin_dir_url( dirname( __FILE__ ) ). 'img/logo_onboarding.svg';
4717 // Below: the wizard wrapper opening markup (closed by onboarding_wizard_footer()).
4718 ?>
4719
4720 <div class="wpstream_on_boarding_wrapper">
4721 <div class="wpstream_close_onboarding wpstream_close_initial_onboarding"></div>
4722 <img class="wpstream_onboarding_logo" src="<?php echo esc_url($thumb); ?>" />
4723 <div class="wpstream_close_onboarding_warning"></div>
4724
4725 <?php
4726 }
4727
4728
4729
4730
4731
4732 /*
4733 *
4734 * On Boarding Footer
4735 *
4736 */
4737 /**
4738 * Close the onboarding wizard wrapper and print its modal background. Echoes HTML.
4739 *
4740 * @return void
4741 */
4742 public function onboarding_wizard_footer() {
4743 // Below: closes the wrapper opened by onboarding_wizard_header() and adds the modal backdrop.
4744 ?>
4745 </div>
4746 <div class="wpstream_modal_background_onboard"></div>
4747 <?php
4748 }
4749
4750
4751
4752 /*
4753 *
4754 * On Boarding create PPV channel
4755 *
4756 */
4757
4758
4759 /**
4760 * AJAX: create a Pay-Per-View live channel during onboarding.
4761 *
4762 * Inserts a WooCommerce `product`, sets its price, marks it live_stream,
4763 * and returns the edit link (with onboarding query args) as JSON.
4764 * Nonce- and administrator-gated.
4765 *
4766 * @return void Ends with die().
4767 */
4768 public function wpstream_on_board_create_channel_ppv(){
4769 check_ajax_referer( 'wpstream_onboarding_nonce', 'security' );
4770 $current_user = wp_get_current_user();
4771
4772 if(current_user_can('administrator')){
4773 // Sanitize the submitted channel name and price.
4774 $channel_name = sanitize_text_field($_POST['channel_name']);
4775 $channel_price = floatval($_POST['channel_price']);
4776 $my_post = array(
4777 'post_title' => $channel_name,
4778 'post_content' => '',
4779 'post_status' => 'publish',
4780 'post_type' => 'product',
4781 'post_author' => $current_user->ID
4782 );
4783
4784 // Insert the post into the database
4785 $post_id = wp_insert_post( $my_post );
4786
4787 if(is_wp_error($post_id)){
4788 // Log the failure and report it to the client.
4789 $logger = new WPStream_Logger();
4790 $log_entry = new WpStream_Log_Entry([
4791 'type' => 'error',
4792 'description' => 'Couldn\'t create channel during onboarding because of error: ' . $post_id->get_error_message(),
4793 ]);
4794 $logger->add( $log_entry );
4795 echo json_encode( array('succes'=>false) );
4796 }else{
4797
4798 // Apply the price to the new product.
4799 $product = wc_get_product($post_id);
4800 $price = wc_format_decimal($channel_price);
4801
4802 $product = wc_get_product( $post_id );
4803 $product->set_price( $price );
4804 $product->set_regular_price( $price ); // To be sure
4805 $product->save();
4806 // Reset event flag and mark it as a live stream product.
4807 update_post_meta ($post_id,'event_passed',0);
4808 wp_set_object_terms( $post_id, 'live_stream', 'product_type' );
4809
4810
4811
4812
4813 // Return the edit link carrying the onboarding continuation args.
4814 $permalink = get_edit_post_link($post_id);
4815
4816 $permalink= add_query_arg( 'onboard', 'yes', $permalink );
4817 $permalink= add_query_arg( 'branch', '2', $permalink );
4818
4819
4820 echo json_encode( array(
4821 'success'=> true,
4822 'link' => ($permalink)
4823 ));
4824 }
4825
4826 }
4827 die();
4828 }
4829
4830 /*
4831 *
4832 * On Boarding create channel
4833 *
4834 */
4835
4836
4837 /**
4838 * AJAX: create a Free-To-View live channel during onboarding.
4839 *
4840 * Inserts a `wpstream_product` post and returns its edit link (with
4841 * onboarding query args) as JSON. Nonce- and administrator-gated.
4842 *
4843 * @return void Ends with die().
4844 */
4845 public function wpstream_on_board_create_channel(){
4846 check_ajax_referer( 'wpstream_onboarding_nonce', 'security' );
4847 $current_user = wp_get_current_user();
4848
4849 if(current_user_can('administrator')){
4850 // Sanitize the submitted channel name.
4851 $channel_name=sanitize_text_field($_POST['channel_name']);
4852 $my_post = array(
4853 'post_title' => $channel_name,
4854 'post_content' => '',
4855 'post_status' => 'publish',
4856 'post_type' => 'wpstream_product',
4857 'post_author' => $current_user->ID
4858 );
4859
4860 // Insert the post into the database
4861 $post_id = wp_insert_post( $my_post );
4862
4863 if( is_wp_error( $post_id ) ) {
4864 // Log the failure and report it to the client.
4865 $logger = new WPStream_Logger();
4866 $log_entry = new WpStream_Log_Entry([
4867 'type' => 'error',
4868 'description' => 'Couldn\'t create channel during onboarding because of error: ' . $post_id->get_error_message(),
4869 ]);
4870 $logger->add( $log_entry );
4871 echo json_encode( array('succes'=>false) );
4872 } else {
4873 // Return the edit link carrying the onboarding continuation args.
4874 $permalink = get_edit_post_link($post_id);
4875
4876 $permalink= add_query_arg( 'onboard', 'yes', $permalink );
4877 $permalink= add_query_arg( 'branch', '1', $permalink );
4878
4879 echo json_encode( array(
4880 'success'=> true,
4881 'link' => ($permalink)
4882 ));
4883 }
4884 }
4885 die();
4886 }
4887
4888 /*
4889 *
4890 * On Boarding create free vod
4891 *
4892 */
4893 /**
4894 * AJAX: create a Free-To-View VOD during onboarding.
4895 *
4896 * Inserts a `wpstream_product_vod` post, links the chosen recording, and
4897 * returns the edit link (with onboarding query args). Nonce/admin gated.
4898 *
4899 * @return void Ends with die().
4900 */
4901 public function wpstream_on_board_create_free_vod(){
4902 $current_user = wp_get_current_user();
4903 check_ajax_referer( 'wpstream_onboarding_nonce', 'security' );
4904 if(current_user_can('administrator')){
4905 // Sanitize the VOD name and the source recording file name.
4906 $channel_name =sanitize_text_field($_POST['channel_name']);
4907 $file_name =sanitize_text_field($_POST['file_name']);
4908 $my_post = array(
4909 'post_title' => $channel_name,
4910 'post_content' => '',
4911 'post_status' => 'publish',
4912 'post_type' => 'wpstream_product_vod',
4913 'post_author' => $current_user->ID
4914 );
4915
4916 // Insert the post into the database
4917 $post_id = wp_insert_post( $my_post );
4918
4919 if(is_wp_error($post_id)){
4920 $logger = new WPStream_Logger();
4921 $log_entry = new WpStream_Log_Entry([
4922 'type' => 'error',
4923 'description' => 'Couldn\'t create free VOD during onboarding because of error: ' . $post_id->get_error_message(),
4924 ]);
4925 $logger->add( $log_entry );
4926 echo json_encode( array('succes'=>false) );
4927 }else{
4928 // Mark as a recording-type VOD and attach the chosen recording.
4929 update_post_meta($post_id, 'wpstream_product_type', 2);
4930 update_post_meta($post_id, 'wpstream_free_video', $file_name);
4931
4932
4933 // Return the edit link carrying the onboarding continuation args.
4934 $permalink = get_edit_post_link($post_id);
4935
4936 $permalink= add_query_arg( 'onboard', 'yes', $permalink );
4937 $permalink= add_query_arg( 'branch', '3', $permalink );
4938
4939
4940 echo json_encode( array(
4941 'success'=> true,
4942 'link' => ($permalink)
4943 ));
4944 }
4945
4946 }
4947 die();
4948 }
4949
4950
4951 /*
4952 *
4953 * On Boarding create ppv vod
4954 *
4955 */
4956
4957
4958 /**
4959 * AJAX: create a Pay-Per-View VOD during onboarding.
4960 *
4961 * Inserts a WooCommerce `product`, sets its price, links the recording,
4962 * marks it video_on_demand, and returns the edit link (with onboarding
4963 * query args). Nonce- and administrator-gated.
4964 *
4965 * @return void Ends with die().
4966 */
4967 public function wpstream_on_board_create_ppv_vod(){
4968 $current_user = wp_get_current_user();
4969 check_ajax_referer( 'wpstream_onboarding_nonce', 'security' );
4970
4971 if(current_user_can('administrator')){
4972 // Sanitize the VOD name, price and source recording file name.
4973 $channel_name = sanitize_text_field($_POST['channel_name']);
4974 $vod_price = floatval($_POST['vod_price']);
4975 $file_name = sanitize_text_field($_POST['file_name']);
4976
4977 $my_post = array(
4978 'post_title' => $channel_name,
4979 'post_content' => '',
4980 'post_status' => 'publish',
4981 'post_type' => 'product',
4982 'post_author' => $current_user->ID
4983 );
4984
4985 // Insert the post into the database
4986 $post_id = wp_insert_post( $my_post );
4987
4988 if( is_wp_error( $post_id ) ) {
4989 $logger = new WPStream_Logger();
4990 $log_entry = new WpStream_Log_Entry([
4991 'type' => 'error',
4992 'description' => 'Couldn\'t create PPV VOD during onboarding because of error: ' . $post_id->get_error_message(),
4993 ]);
4994 $logger->add( $log_entry );
4995 echo json_encode( array('succes'=>false) );
4996 } else {
4997
4998 // Apply the price to the new product.
4999 $product = wc_get_product($post_id);
5000 $price = wc_format_decimal($vod_price);
5001
5002 $product = wc_get_product( $post_id );
5003 $product->set_price( $price );
5004 $product->set_regular_price( $price ); // To be sure
5005 $product->save();
5006 // Reset event flag, attach the recording, and mark it VOD.
5007 update_post_meta ($post_id,'event_passed',0);
5008 update_post_meta ($post_id,'_movie_url', $file_name);
5009 wp_set_object_terms( $post_id, 'video_on_demand', 'product_type' );
5010
5011
5012
5013
5014 // Return the edit link carrying the onboarding continuation args.
5015 $permalink = get_edit_post_link($post_id);
5016
5017 $permalink= add_query_arg( 'onboard', 'yes', $permalink );
5018 $permalink= add_query_arg( 'branch', '4', $permalink );
5019
5020
5021 echo json_encode( array(
5022 'success'=> true,
5023 'link' => ($permalink) ,
5024 ' $file_name'=> $file_name,
5025 ));
5026 }
5027
5028 }
5029 die();
5030 }
5031
5032
5033 /*
5034 *
5035 * On Boarding login
5036 *
5037 */
5038 /**
5039 * AJAX: log in to WpStream during onboarding.
5040 *
5041 * Stores the submitted credentials, clears the token cache, then attempts
5042 * to fetch a token; returns success/failure JSON without ever exposing the
5043 * token to the client. Nonce- and administrator-gated.
5044 *
5045 * @return void Ends with die().
5046 */
5047 public function wpstream_on_board_login(){
5048 check_ajax_referer( 'wpstream_onboarding_nonce', 'security' );
5049
5050 if(current_user_can('administrator')){
5051 // Persist the submitted credentials (password stored raw).
5052 $username = sanitize_text_field($_POST['api_username']);
5053 $password = $_POST['api_password'];
5054 update_option('wpstream_api_username',$username);
5055 update_option('wpstream_api_password',$password);
5056
5057 // Force a fresh token fetch.
5058 delete_transient( 'wpstream_token_api' );
5059
5060 $token = $this->main->wpstream_live_connection->wpstream_get_token();
5061 $videos_list = $this->main->wpstream_live_connection->wpstream_get_videos();
5062 // cleanup any previous echo before sending json
5063 ob_end_clean();
5064 // !DO NOT SEND TOKEN TO THE CLIENT!
5065 if ($token){
5066 // Authentication succeeded.
5067 echo json_encode( array(
5068 'success'=> true
5069 ));
5070 }
5071 else {
5072 // Distinguish a hard CURL failure from bad credentials.
5073 $text = get_option('wpstream_curl_failed') ?
5074 'Login failed with critical error: ' . get_option('wpstream_curl_failed') :
5075 'Wrong username or password!';
5076 echo json_encode( array(
5077 'success'=> false,
5078 'error' => $text,
5079 ));
5080 }
5081
5082 }else{
5083 // Non-admins may not connect the account.
5084 echo json_encode( array(
5085 'success'=> false,
5086 'token' => esc_html('You are not an administrator','wpstream')
5087 ));
5088 }
5089 die();
5090 }
5091
5092 /**
5093 * Placeholder AJAX callback to refresh the captcha (currently a no-op).
5094 *
5095 * @return void
5096 */
5097 public function wpstream_register_refresh_capthca(){
5098
5099 if(current_user_can('administrator')){
5100
5101 }
5102 }
5103
5104 /**
5105 * AJAX proxy: fetch a captcha challenge from the baker API and return it.
5106 * Used on HTTP sites where the Altcha widget cannot run (requires Web Crypto / HTTPS).
5107 * The JS side solves the PoW locally and sends back the full base64 Altcha payload.
5108 */
5109 public function wpstream_get_captcha_challenge() {
5110 // Fetch a challenge from the captcha ("baker") API.
5111 $api_url = WPSTREAM_API . '/v2/user/getcaptcha';
5112 $response = wp_remote_get( $api_url, array( 'timeout' => 10 ) );
5113
5114 // On transport failure, return an error JSON.
5115 if ( is_wp_error( $response ) ) {
5116 echo json_encode( array( 'success' => false, 'error' => 'Could not reach captcha service.' ) );
5117 die();
5118 }
5119
5120 $body = wp_remote_retrieve_body( $response );
5121 // Forward the challenge JSON directly to the browser
5122 header( 'Content-Type: application/json' );
5123 echo $body;
5124 die();
5125 }
5126
5127 /**
5128 * Solve a simple proof-of-work: find a nonce whose sha256(challenge.nonce)
5129 * hash begins with $difficulty leading zeros. (Server-side helper.)
5130 *
5131 * @param string $challenge Challenge string to hash against.
5132 * @param int $difficulty Number of required leading "0" hex chars.
5133 * @return int|null The solving nonce, or null if none found within the cap.
5134 */
5135 private function solve_pow( $challenge, $difficulty ) {
5136 // Start at nonce 0 and require this many leading zeros.
5137 $nonce = 0;
5138 $target = str_repeat( "0", $difficulty );
5139
5140 // Brute-force up to a hard cap of attempts.
5141 while ( $nonce < 5000000 ) {
5142 $hash = hash( 'sha256', $challenge . $nonce );
5143 // Accept the first nonce whose hash starts with the target zeros.
5144 if ( strpos( $hash, $target ) === 0 ) {
5145 return $nonce;
5146 }
5147 $nonce++;
5148 }
5149 return null; // Return null if no solution is found within the max attempts
5150 }
5151
5152 /*
5153 *
5154 * On Boarding login
5155 *
5156 */
5157 /**
5158 * AJAX: register a new WpStream account during onboarding.
5159 *
5160 * Validates the email/password, requires an ALTCHA solution, calls the
5161 * account-create API, and on success stores the credentials and fetches a
5162 * token. Returns JSON. Nonce- and administrator-gated.
5163 *
5164 * @return void Ends with die().
5165 */
5166 public function wpstream_on_board_register(){
5167 check_ajax_referer( 'wpstream_onboarding_nonce', 'security' );
5168 if(current_user_can('administrator')){
5169 // Read submitted email/password (password kept raw).
5170 $wpstream_register_email = sanitize_text_field($_POST['wpstream_register_email']);
5171 $wpstream_register_password = $_POST['wpstream_register_password'];
5172
5173 // Field-level validation; bail with the error payload if invalid.
5174 $validate = $this->wpstream_validate_onboard_register($wpstream_register_email,$wpstream_register_password);
5175 if(!$validate['success']){
5176 // cleanup any previous echo before sending json
5177 ob_end_clean();
5178 echo json_encode($validate);
5179 die();
5180 }
5181
5182 // Require a captcha solution; message differs on HTTPS vs HTTP (widget readiness).
5183 $wpstream_altcha = isset($_POST['wpstream_altcha']) ? trim( $_POST['wpstream_altcha'] ) : '';
5184 $is_https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
5185
5186 if ( empty($wpstream_altcha) ) {
5187 if ( $is_https ) {
5188 $message = esc_html__('Captcha verification failed. Please try again.', 'wpstream');
5189 } else {
5190 $message = esc_html__('Security check not ready yet. Please wait a moment and try again.', 'wpstream');
5191 }
5192 echo json_encode( array( 'success' => false, 'message' => $message ) );
5193 die();
5194 }
5195
5196 // Call the account-create API with the captcha solution.
5197 $url='v2/user/create';
5198 $curl_post_fields=array(
5199 'email' => $wpstream_register_email,
5200 'password' => $wpstream_register_password,
5201 'solution' => $wpstream_altcha,
5202 'captcha_id' => '',
5203 );
5204
5205 $curl_response = $this->main->wpstream_live_connection->wpstream_baker_do_curl_base($url,$curl_post_fields,true);
5206 $curl_response_decoded = json_decode($curl_response,JSON_OBJECT_AS_ARRAY);
5207
5208 if($curl_response_decoded['success']){
5209
5210 if($curl_response_decoded['request']['registred']){
5211 // we are registerd
5212
5213 // Store the new credentials and obtain a token.
5214 update_option('wpstream_api_username',$wpstream_register_email);
5215 update_option('wpstream_api_password',$wpstream_register_password);
5216
5217 $token = $this->main->wpstream_live_connection->wpstream_get_token();
5218
5219 // cleanup any previous echo before sending json
5220 ob_end_clean();
5221 echo json_encode( array(
5222 'success' => true,
5223 'token' => $token ,
5224 'message' => esc_html__('Your Account was created. Please stand by...','wpstream'),
5225
5226 ));
5227 die();
5228 }else{
5229 // API reachable but registration was rejected (e.g. email taken).
5230
5231 // cleanup any previous echo before sending json
5232 ob_end_clean();
5233 echo json_encode( array(
5234 'success'=> false,
5235 'message'=> $curl_response_decoded['request']['message'],
5236 'curl'=>$curl_response_decoded,
5237
5238 ));
5239 die();
5240 }
5241
5242
5243
5244
5245 }else{
5246 // API call itself failed.
5247 // cleanup any previous echo before sending json
5248 ob_end_clean();
5249 echo json_encode( array(
5250 'success'=> false,
5251 'message'=> esc_html__('Registration could not be completed. Please try again or register on wpstream.net','wpstream'),
5252
5253 ));
5254 die();
5255 }
5256
5257
5258
5259
5260
5261
5262
5263 }else{
5264 // Non-admins may not register the account.
5265 // cleanup any previous echo before sending json
5266 ob_end_clean();
5267 echo json_encode( array(
5268 'success'=> false,
5269 'message' => esc_html('You are not an administrator','wpstream')
5270 ));
5271 die();
5272 }
5273
5274 die();
5275 }
5276
5277
5278 /*
5279 *
5280 * Validate for register
5281 *
5282 */
5283 /**
5284 * Validate the onboarding registration email + password.
5285 *
5286 * Checks for a non-empty, well-formed email whose domain has DNS records,
5287 * and a password of at least 5 characters.
5288 *
5289 * @param string $wpstream_register_email Submitted email.
5290 * @param string $wpstream_register_password Submitted password.
5291 * @return array{success: bool, message?: string} Validation result.
5292 */
5293 public function wpstream_validate_onboard_register($wpstream_register_email,$wpstream_register_password){
5294
5295 // Default to valid; each check below can override with an error.
5296 $return= array(
5297 'success'=>true
5298 );
5299
5300 // Email must not be empty.
5301 if ($wpstream_register_email=='' ){
5302 $return= array(
5303 'success'=> false,
5304 'message' => esc_html('The email Field is Empty','wpstream')
5305 );
5306 return $return;die();
5307 }
5308
5309 // Email must be syntactically valid.
5310 if(filter_var($wpstream_register_email,FILTER_VALIDATE_EMAIL) === false) {
5311 $return= array(
5312 'success'=> false,
5313 'message' => esc_html__('The email doesn\'t look right !','wpstream')
5314 );
5315 return $return;die();
5316 }
5317
5318
5319 // The email domain must resolve in DNS.
5320 $domain = mb_substr(strrchr($wpstream_register_email, "@"), 1);
5321 if( $domain!='' && !checkdnsrr ($domain) ){
5322 $return= array(
5323 'success'=> false,
5324 'message' => esc_html__('The email doesn\'t look right !','wpstream')
5325 );
5326 return $return;die();
5327 }
5328
5329
5330
5331 // Enforce a minimum password length.
5332 if(strlen($wpstream_register_password)<5){
5333 $return= array(
5334 'success'=> false,
5335 'message' => esc_html('The password is too short. Please use at least 5 characters.','wpstream')
5336 );
5337 return $return;die();
5338 }
5339
5340 return $return;die();
5341 }
5342
5343 /**
5344 * Handle the AJAX request to initiate a multipart upload
5345 *
5346 * Validates the request, builds a clean filename, and asks the cloud API to
5347 * open a multipart S3 upload; returns the upload id and per-part pre-signed
5348 * URLs as JSON. Nonce- and administrator-gated.
5349 *
5350 * @since 3.0.1
5351 * @return void Responds via wp_send_json_*.
5352 */
5353 public function handle_initiate_multipart_upload() {
5354 check_ajax_referer( 'wpstream_multipart_upload_nonce', 'security' );
5355
5356 // Security check - only admins can do this
5357 if (!current_user_can('administrator')) {
5358 wp_send_json_error('Unauthorized access');
5359 return;
5360 }
5361
5362 // Get file details from request
5363 $file_name = sanitize_text_field($_POST['file_name']);
5364 $file_size = intval($_POST['file_size']);
5365 $content_type = sanitize_text_field($_POST['content_type']);
5366 $num_parts = intval($_POST['parts']);
5367
5368 // Reject incomplete/invalid file metadata.
5369 if (empty($file_name) || $file_size <= 0 || $num_parts <= 0) {
5370 wp_send_json_error('Invalid file information');
5371 return;
5372 }
5373
5374 // Prepare a clean filename (similar to the standard upload process)
5375 // Keep the extension, slugify the base name (spaces -> _, strip non-word chars).
5376 $file_name_array = explode(".", $file_name);
5377 $file_extension = $file_name_array[count($file_name_array) - 1];
5378 $temp_file_name = $file_name_array[0];
5379 $temp_file_name = str_replace(' ', '_', $temp_file_name);
5380 $temp_file_name = preg_replace('/\W/', '', $temp_file_name);
5381 $clean_file_name = $temp_file_name . '.' . $file_extension;
5382
5383 // Make API call to initiate multipart upload
5384 $url = 'video/upload';
5385 $access_token = $this->main->wpstream_live_connection->wpstream_get_token();
5386
5387 // Need an authenticated token to talk to the cloud.
5388 if (!$access_token) {
5389 wp_send_json_error('Not connected to WPStream service');
5390 return;
5391 }
5392
5393 // Request the multipart upload initiation.
5394 $api_params = array(
5395 'access_token' => $access_token,
5396 'size' => $file_size,
5397 'name' => $clean_file_name,
5398 'content_type' => $content_type,
5399 'parts' => $num_parts
5400 );
5401
5402 $response = $this->main->wpstream_live_connection->wpstream_baker_do_curl_base($url, $api_params, true);
5403 $response_data = json_decode($response, true);
5404
5405 // Bubble up any API error.
5406 if (!isset($response_data['success']) || $response_data['success'] !== true) {
5407 $error_message = isset($response_data['error']) ? $response_data['error'] : 'Failed to initiate multipart upload';
5408 wp_send_json_error($error_message);
5409 return;
5410 }
5411
5412 // Return the upload ID and pre-signed URLs for each part
5413 wp_send_json_success($response_data);
5414 }
5415
5416 /**
5417 * Handle the AJAX request to complete a multipart upload
5418 *
5419 * Validates the request and tells the cloud API to finalize the multipart S3
5420 * upload for the given parts/handle. Nonce- and administrator-gated.
5421 *
5422 * @since 3.0.1
5423 * @return void Responds via wp_send_json_*.
5424 */
5425 public function handle_complete_multipart_upload() {
5426 check_ajax_referer( 'wpstream_multipart_upload_nonce', 'security' );
5427
5428 // Security check - only admins can do this
5429 if (!current_user_can('administrator')) {
5430 wp_send_json_error('Unauthorized access');
5431 return;
5432 }
5433
5434 // Get completion details
5435 $parts = json_decode(stripslashes($_POST['parts']), true);
5436 $file_name = sanitize_text_field($_POST['file_name']);
5437 $handle = sanitize_text_field($_POST['handle']);
5438
5439 // Reject incomplete completion metadata. (Note: is_numeric($parts) checks the decoded parts array — see report.)
5440 if (empty($parts) || !is_numeric($parts) || empty($file_name)) {
5441 wp_send_json_error('Invalid completion information');
5442 return;
5443 }
5444
5445 // Make API call to complete the multipart upload
5446 $url = 'video/upload';
5447 $access_token = $this->main->wpstream_live_connection->wpstream_get_token();
5448
5449 // Need an authenticated token to talk to the cloud.
5450 if (!$access_token) {
5451 wp_send_json_error('Not connected to WPStream service');
5452 return;
5453 }
5454
5455 // Send the finalize ("complete") request with the uploaded part list.
5456 $api_params = array(
5457 'access_token' => $access_token,
5458 'parts' => $parts,
5459 'name' => $file_name,
5460 'handle' => $handle,
5461 'action' => 'complete'
5462 );
5463
5464 $response = $this->main->wpstream_live_connection->wpstream_baker_do_curl_base($url, $api_params, true);
5465 $response_data = json_decode($response, true);
5466
5467 // Bubble up any API error.
5468 if (!isset($response_data['success']) || $response_data['success'] !== true) {
5469 $error_message = isset($response_data['error']) ? $response_data['error'] : 'Failed to complete multipart upload';
5470 wp_send_json_error($error_message);
5471 return;
5472 }
5473
5474 // Return success
5475 wp_send_json_success();
5476 }
5477
5478 /**
5479 * AJAX: update the WpStream plugin in place from the Settings/Support tab.
5480 *
5481 * Loads the upgrader APIs, initializes WP_Filesystem, runs the plugin
5482 * upgrade, re-activates the plugin, and returns success/error JSON.
5483 * Requires the update_plugins capability.
5484 *
5485 * @return void Responds via wp_send_json_*.
5486 */
5487 public function wpstream_settings_tab_update_plugin() {
5488 // Capability gate.
5489 if ( !current_user_can( 'update_plugins' ) ) {
5490 wp_send_json_error( __( 'Not enough permissions to make this change', 'wpstream' ) );
5491 }
5492
5493 // Load the core upgrade/filesystem APIs.
5494 include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
5495 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
5496 include_once ABSPATH . 'wp-admin/includes/file.php';
5497
5498 // Initialize the filesystem abstraction (needed to write plugin files).
5499 $credentials = request_filesystem_credentials('');
5500 if ( !WP_Filesystem( $credentials ) ) {
5501 wp_send_json_error( __( 'Failed to connect to the filesystem', 'wpstream' ) );
5502 }
5503
5504 // Run the in-place upgrade for this plugin, then re-activate it.
5505 $upgrader = new Plugin_Upgrader(new Automatic_Upgrader_Skin());
5506 $plugin_path = plugin_basename( WPSTREAM_PLUGIN_PATH . 'wpstream.php' );
5507 $result = $upgrader->upgrade( $plugin_path );
5508 activate_plugin( $plugin_path );
5509
5510 if ( is_wp_error( $result ) ) {
5511 wp_send_json_error( __( 'Update failed due to', 'wpstream' ) . $result->get_error_message() );
5512 }
5513
5514 wp_send_json_success();
5515 }
5516 }
5517