PluginProbe
WpStream – Live Streaming, Video on Demand, Pay Per View / 4.14.0
WpStream – Live Streaming, Video on Demand, Pay Per View v4.14.0
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 / public / class-wpstream-public.php

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

1,732 lines 74.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The public-facing functionality of the plugin.
5 *
6 * @link http://wpstream.net
7 * @since 3.0.1
8 *
9 * @package Wpstream
10 * @subpackage Wpstream/public
11 */
12
13
14 // Exit if accessed directly.
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 /**
20 * The public-facing functionality of the plugin.
21 *
22 * This class wires up everything WpStream needs on the front end:
23 * - Registering/enqueuing the player and streaming CSS/JS (and their
24 * localized i18n + config data).
25 * - WooCommerce "My Account" endpoints (event-list, video-list, start-streaming)
26 * and menu items.
27 * - Shortcodes ([wpstream_player], [wpstream_go_live], list
28 * shortcodes) and the matching Visual Composer / WPBakery `vc_map` definitions.
29 * - Search/archive/sidebar filter callbacks that teach the theme about the
30 * plugin's custom post types (wpstream_product, wpstream_product_vod, wpstream_bundles).
31 * - The HLS/DRM key delivery endpoints for live and VOD playback, including the
32 * remote key fetch (cached in transients) and the WooCommerce purchase/subscription
33 * entitlement gate that protects paid content.
34 * - Low-level request plumbing: CORS preflight response and the session cookie
35 * used to bind DRM key requests to a viewer.
36 *
37 * @package Wpstream
38 * @subpackage Wpstream/public
39 * @author wpstream <office@wpstream.net>
40 */
41 class Wpstream_Public {
42
43
44
45 /**
46 * Store plugin main class to allow public access.
47 *
48 * @since 20180622
49 * @var object The main class.
50 */
51 public $main;
52 /**
53 * The ID of this plugin.
54 *
55 * @since 3.0.1
56 * @access private
57 * @var string $plugin_name The ID of this plugin.
58 */
59 private $plugin_name;
60
61 /**
62 * The version of this plugin.
63 *
64 * @since 3.0.1
65 * @access private
66 * @var string $version The current version of this plugin.
67 */
68 private $version;
69
70 /**
71 * Initialize the class and set its properties.
72 *
73 * @since 3.0.1
74 * @param string $plugin_name The name of the plugin.
75 * @param string $version The version of this plugin.
76 * @param object $plugin_main The main plugin class, kept for access to helpers (player, quota_manager, etc.).
77 */
78 public function __construct( $plugin_name, $version ,$plugin_main) {
79 // Keep a reference to the main plugin object so its sub-objects (player, quota manager) are reachable.
80 $this->main = $plugin_main;
81 // Remember the plugin slug/name.
82 $this->plugin_name = $plugin_name;
83 // Remember the plugin version (used for cache-busting enqueues).
84 $this->version = $version;
85
86 }
87
88 /**
89 * Register/enqueue the stylesheets for the public-facing side of the site.
90 *
91 * Only the core plugin CSS is enqueued site-wide; the player CSS is
92 * registered here and enqueued at player render time (see
93 * wpstream_enqueue_player_assets()), and the integrations CSS only loads
94 * when BuddyPress is present. The player-skin file is cache-busted with
95 * its own filemtime so skin tweaks show up immediately.
96 *
97 * @since 3.0.1
98 * @return void
99 */
100 public function enqueue_styles() {
101
102 // Core plugin front-end styles (cards, lists, notices — can appear on any page).
103 wp_enqueue_style('wpstream-style', plugin_dir_url( __FILE__ ) .'/css/wpstream_style.css',array(), WPSTREAM_PLUGIN_VERSION, 'all' );
104 // Base Video.js player CSS — registered only; player render sites enqueue it.
105 wp_register_style('video-js.min', plugin_dir_url( __FILE__ ).'css/video-js.css', array(), WPSTREAM_PLUGIN_VERSION, 'all');
106 // WpStream player skin on top of Video.js; registered only, filemtime cache-buster.
107 wp_register_style(
108 'videojs-wpstream-player',
109 plugin_dir_url( __FILE__ ).'css/videojs-wpstream.css',
110 array(),
111 WPSTREAM_PLUGIN_VERSION . '.' . filemtime( plugin_dir_path(__FILE__) . 'css/videojs-wpstream.css' ),
112 'all'
113 );
114 // BuddyBoss/BuddyPress integration styling — only useful when BuddyPress runs.
115 if ( class_exists( 'BuddyPress' ) ) {
116 wp_enqueue_style('wpstream-integrations', plugin_dir_url( __DIR__ ) .'integrations/css/integrations.css',array(), WPSTREAM_PLUGIN_VERSION, 'all' );
117 }
118
119 }
120
121 /**
122 * Register the JavaScript for the public-facing side of the site.
123 *
124 * Registers/enqueues Video.js and its quality-selector + logo plugins, the
125 * WpStream player bootstrap/controls, and the
126 * start-streaming and integrations scripts. Each script is paired with a
127 * `wp_localize_script` payload that ships translated UI strings, feature
128 * flags and a per-page player status nonce down to the browser.
129 *
130 * @since 3.0.1
131 * @return void
132 */
133 public function enqueue_scripts() {
134
135 // Register the VideoJS script
136 // Enqueuing is happing directly wherever is used
137 // Register (not enqueue) the bundled core Video.js library; templates enqueue it on demand.
138 wp_register_script('video.min', plugin_dir_url( __FILE__ ) . 'js/vendor/video.min.js', array(), '8.20.0', true);
139
140 // Quality selector dependency (Video.js plugin)
141 // Bundled Video.js contrib "quality levels" plugin, required by the WpStream quality selector below.
142 wp_register_script(
143 'videojs-contrib-quality-levels',
144 plugin_dir_url( __FILE__ ) . 'js/vendor/videojs-contrib-quality-levels.min.js',
145 array('video.min'),
146 '4.0.0',
147 true
148 );
149
150 // WpStream quality selector (Video.js 8 compatible)
151 // WpStream's own quality-selector UI; filemtime cache-buster keeps the served file fresh.
152 wp_register_script(
153 'wpstream-quality-selector',
154 plugin_dir_url( __FILE__ ) . 'js/wpstream-quality-selector.js',
155 array('video.min', 'videojs-contrib-quality-levels'),
156 WPSTREAM_PLUGIN_VERSION . '.' . filemtime(plugin_dir_path(__FILE__) . 'js/wpstream-quality-selector.js'),
157 true
158 );
159
160 // Register the VideoJS Logo plugin script
161 // Bundled Video.js "logo" plugin used to overlay the configurable player watermark.
162 // Registered only — it is a dependency of wpstream-player, so it loads exactly
163 // where the player loads.
164 wp_register_script(
165 'videojs-logo',
166 plugin_dir_url( __FILE__ ) . 'js/vendor/videojs-logo.min.js',
167 array('video.min'),
168 '3.0.0',
169 true
170 );
171
172 // YouTube tech for Video.js, used when a channel restreams from YouTube.
173 wp_register_script('youtube.min',
174 plugin_dir_url( __FILE__ ).'js/youtube.min.js',
175 array('video.min'),
176 WPSTREAM_PLUGIN_VERSION, true);
177
178 // Player bootstrap: sets up the Video.js instance before wpstream-player runs.
179 wp_register_script(
180 'wpstream-player-bootstrap',
181 plugin_dir_url( __FILE__ ) . 'js/wpstream-player-bootstrap.js',
182 array(),
183 WPSTREAM_PLUGIN_VERSION . '.' . filemtime(plugin_dir_path(__FILE__) . 'js/wpstream-player-bootstrap.js'),
184 true
185 );
186
187 // Main WpStream player script (live status polling, state messages, chat glue).
188 // videojs-logo is a hard dependency: the player calls player.logo() for the
189 // configurable watermark, so it must load wherever the player loads.
190 wp_register_script(
191 'wpstream-player',
192 plugin_dir_url( __FILE__ ).'js/wpstream-player.js',
193 array('video.min','wpstream-quality-selector','wpstream-player-bootstrap','videojs-logo'),
194 WPSTREAM_PLUGIN_VERSION . '.' . filemtime(plugin_dir_path(__FILE__) . 'js/wpstream-player.js'),
195 true
196 );
197
198 // Player custom controls layer.
199 wp_register_script(
200 'wpstream-player-controls',
201 plugin_dir_url( __FILE__ ) . 'js/player-controls.js',
202 array( 'jquery', 'video.min' ),
203 WPSTREAM_PLUGIN_VERSION . '.' . filemtime(plugin_dir_path(__FILE__) . 'js/player-controls.js'),
204 true
205 );
206 // Hand the controls script its translated live-UI status strings.
207 wp_localize_script(
208 'wpstream-player-controls',
209 'wpstreamLiveUiConfig',
210 $this->wpstream_get_player_i18n_config()
211 );
212
213 // Resolve adaptive bitrate through the Channel Settings Module.
214 $abr_enabled = false;
215 $post_id = get_the_ID();
216 $post_meta = $post_id ? $this->main->channel_settings->read( $post_id )['options'] : array();
217 if ( ! empty( $post_meta ) && isset( $post_meta['adaptive_bitrate'] ) && 1 === (int) $post_meta['adaptive_bitrate'] ) {
218 $abr_enabled = true;
219 }
220 // Localize the main player script with UI strings, player theme/logo, streamify flag, status nonce and ABR flag.
221 wp_localize_script( 'wpstream-player', 'wpstream_player_vars', $this->wpstream_get_player_vars_config( $abr_enabled ) );
222
223 // The built-in SockJS/WebSocket chat client was removed (issue #110):
224 // live chat is provided by the Better Messages integration in the
225 // hello-wpstream theme, so no chat_lib assets are registered anymore.
226
227 // Bundled QR generator (SEC-05): the Larix QR is built locally so the
228 // RTMP URL + secret stream key never reach a third-party image service.
229 wp_register_script('wpstream-qrcode-generator', plugin_dir_url( __FILE__ ) . 'js/vendor/qrcode-generator.js', array(), '1.4.4', true);
230 wp_register_script('wpstream-qr', plugin_dir_url( __FILE__ ) . 'js/wpstream-qr.js', array('wpstream-qrcode-generator'), WPSTREAM_PLUGIN_VERSION, true);
231
232 // Register the start-streaming (channel on/off) script, cache-busted by its file mtime.
233 // Depends on wpstream-qr so wpstreamRenderQr() is defined before this runs.
234 // Registered only — wpstream_live_stream_unit_wrapper() enqueues it when the
235 // go-live unit actually renders on a page.
236 $modified_start_streaming_file_time = gmdate( 'YmdHi', filemtime( WPSTREAM_PLUGIN_PATH . 'public/js/start_streaming.js' ) );
237 wp_register_script('wpstream-start-streaming', plugin_dir_url( __FILE__ ) .'js/start_streaming.js',array('wpstream-qr'), $modified_start_streaming_file_time, true);
238 // Cached-only streaming feature flags (see wpstream_get_start_streaming_localization_flags).
239 $streaming_localization_flags = $this->wpstream_get_start_streaming_localization_flags();
240 // Localize the start-streaming script with all the translated button labels, tooltips and warnings.
241 wp_localize_script('wpstream-start-streaming', 'wpstream_start_streaming_vars',
242 array(
243 'admin_url' => get_admin_url(),
244 'loading_url' => WPSTREAM_PLUGIN_DIR_URL.'/img/loading.gif',
245 'download_mess' => esc_html__('Click to download!','wpstream'),
246 'uploading' => esc_html__('We are uploading your file.Do not close this window!','wpstream'),
247 'upload_complete2' => esc_html__('Upload Complete! You can upload another file!','wpstream'),
248 'not_accepted' => esc_html__('The file is not an accepted video format','wpstream'),
249 'upload_complete' => esc_html__('Upload Complete!','wpstream'),
250 'upload_failed' => esc_html__('Upload Failed!','wpstream'),
251 'upload_failed2' => esc_html__('Upload Failed! Please Try again!','wpstream'),
252 'no_band' => esc_html__('Not enough streaming data.','wpstream'),
253 'no_band_no_store' => esc_html__('Not enough streaming data or storage.','wpstream'),
254
255 'start_streaming_action'=> esc_html__('TURNING ON','wpstream'),
256 'stop_streaming_action' => esc_html__('TURNING OFF','wpstream'),
257 'start_streaming' => esc_html__('TURN ON','wpstream'),
258 'stop_streaming' => esc_html__('TURN OFF','wpstream'),
259 'failed_fetching' => esc_html__('Failed to get channel info. Please try again.','wpstream'),
260 'turned_on_tooltip' => esc_html__('Channel is now OFF. Click to turn ON.','wpstream'),
261 'turned_off_tooltip' => esc_html__('Click to turn channel off. This will interrupt any ongoing broadcast.','wpstream'),
262 'turning_on_tooltip' => esc_html__('Turning a channel on may take 1-2 minutes or more. Please be patient.','wpstream'),
263 'turning_off_tooltip' => esc_html__('This may take a few minutes.','wpstream'),
264 'error1' => esc_html__('You don\'t have enough data to start a new event!','wpstream'),
265 'failed_event_creation' => esc_html__('Failed to start the channel. Please try again in a few minutes.','wpstream'),
266 'channel_turning_on' => esc_html__('Channel is turning on','wpstream'),
267 'channel_turning_off' => esc_html__('Channel is turning off','wpstream'),
268 'channel_on' => esc_html__('Channel is ON','wpstream'),
269 'channel_off' => esc_html__('Channel is OFF','wpstream'),
270 'turn_off_confirm' => esc_html__('ARE YOU SURE you\'d like to TURN OFF the channel now? '.PHP_EOL.PHP_EOL.'Channels TURN OFF automatically after 1 hour of inactivity (no active broadcast).'.PHP_EOL.PHP_EOL.'Manual TURN OFF is only useful if you require to change the channel settings immediately.'.PHP_EOL.PHP_EOL.'Statistics may be unavailable or incomplete for up to an hour.'.PHP_EOL.PHP_EOL.'If your channel is configured with Auto TURN ON, it will turn back on as soon as there is a broadcast.','wpstream'),
271 'is_basic_streaming' => $streaming_localization_flags['is_basic_streaming'],
272 'use_streaming_hours' => $streaming_localization_flags['use_streaming_hours'],
273 'basic_streaming_warning' => esc_html__(
274 'You’ve used all available broadcast or viewer hours.' . PHP_EOL . PHP_EOL .
275 'Some live channel features will be limited, including recording, viewer count, browser broadcasting, and content protection.' . PHP_EOL . PHP_EOL .
276 'Top up your resources or upgrade your plan to use all features, or choose OK to start anyway.',
277 'wpstream'
278 ),
279 'basic_streaming_warning_traffic' => esc_html__(
280 'Your account is now in BASIC STREAMING mode.' . PHP_EOL . PHP_EOL .
281 '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 .
282 'Certain features, such as recording, viewer count, browser broadcasting, and content protection are unavailable.' . PHP_EOL . PHP_EOL .
283 '- To take advantage of all features, please choose Cancel and upgrade your plan.' . PHP_EOL .
284 '- Otherwise, choose OK to start your channel with these limitations.' . PHP_EOL . PHP_EOL .
285 'ARE YOU SURE you want to continue with Basic Streaming?',
286 'wpstream'
287 ),
288 'broadcaster_url' => esc_url( esc_url(home_url('/broadcaster-page/') ) ),
289
290 ));
291
292
293 // The integrations script only carries BuddyBoss/BuddyPress glue
294 // (profile channel picker, timeline player) — skip it entirely when
295 // BuddyPress is not running. start_streaming.js guards its call with
296 // typeof, so the missing global is safe.
297 if ( class_exists( 'BuddyPress' ) ) {
298 // Build the integrations payload for the BuddyBoss JS.
299 $integrations_array=array(
300 'admin_url' => get_admin_url(),
301 // CSRF nonce for the BuddyBoss AJAX handlers (select channel / timeline player).
302 'buddy_nonce' => wp_create_nonce( 'wpstream_buddyboss' ),
303 'is_buddyboss' => 'yes',
304 );
305
306 // Enqueue and localize the integrations script (stable plugin-version cache-buster).
307 wp_enqueue_script('wpstream-integrations', plugin_dir_url( __DIR__ ) .'integrations/js/integrations.js',array(), WPSTREAM_PLUGIN_VERSION, true);
308 wp_localize_script('wpstream-integrations', 'wpstream_integrations_vars', $integrations_array );
309 }
310
311
312 // Admin stylesheet reused on the front end by the go-live unit (channel card,
313 // broadcast modal). Registered only — the unit wrapper enqueues it at render time.
314 wp_register_style( 'wpstream_front_style', plugin_dir_url( __DIR__ ) . 'admin/css/wpstream-admin.css', array(), WPSTREAM_PLUGIN_VERSION, 'all' );
315
316 // Theme-side helper script (comment form validation, watch-later, messaging).
317 // Registered for on-demand use; enqueued site-wide only under the hello-wpstream
318 // theme (parent or child), whose templates rely on it everywhere. Other themes get
319 // it at render time from the markup that needs it (watch-later icons, contact form).
320 wp_register_script( 'wpstream-plugin-scripts', WPSTREAM_PLUGIN_DIR_URL . '/hello-wpstream/js/wpstream-plugin-script.js', array( 'jquery' ), '1.0', true );
321 if ( 'hello-wpstream' === get_template() || 'hello-wpstream' === get_stylesheet() ) {
322 wp_enqueue_script( 'wpstream-plugin-scripts' );
323 }
324
325 // Localize that helper with the AJAX URL, login state and translated form-validation messages.
326 wp_localize_script(
327 'wpstream-plugin-scripts',
328 'wpstreamPluginScriptsVars',
329 array(
330 'ajaxurl' => admin_url( 'admin-ajax.php' ), // WordPress AJAX URL.
331 'watch_later_nonce' => wp_create_nonce( 'wpstream_watch_later' ), // CSRF nonce for the watch-later toggle.
332 'processing' => esc_html__('sending...','wpstream'),
333 'send_mess' => esc_html__('Send Message','wpstream'),
334 'is_user_logged_in' => is_user_logged_in() ? '1' : '0',
335 'comment_text_empty' => esc_html__('Please type your comment.','wpstream'),
336 'comment_author_empty' => esc_html__('Please enter your name.', 'wpstream'),
337 'comment_email_empty' => esc_html__('Please enter your email.', 'wpstream'),
338 'comment_email_invalid' => esc_html__('Please enter a valid email address.', 'wpstream'),
339 'gdpr_agree' => esc_html__('You need to agree with GDPR terms.', 'wpstream'),
340 )
341 );
342 }
343
344
345 /**
346 * Build the `wpstream_player_vars` object for the main player script.
347 *
348 * @param bool $abr_enabled Whether adaptive bitrate is on for the current page's channel.
349 * @return array Filtered config with canonical nonces.
350 */
351 public function wpstream_get_player_vars_config( $abr_enabled = false ) {
352 // Everything the main player script reads from `wpstream_player_vars`.
353 $config = array(
354 'admin_url' => get_admin_url(),
355 'chat_not_connected' => esc_html__('Inactive Channel - Chat is disabled.','wpstream'),
356
357 'server_up' => esc_html__('The live stream is paused and may resume shortly.','wpstream'),
358
359 // Translate the literal default only; a user-saved custom message is escaped as-is (variable strings can't go through gettext).
360
361 'wpstream_player_state_stopped_msg' => esc_html( wpstream_not_live_message() ),
362
363 'wpstream_player_state_init_msg' => esc_html__('The live stream has not yet started','wpstream'),
364
365 'wpstream_player_state_startup_msg' => esc_html__('The live stream is starting...','wpstream'),
366
367 'wpstream_player_state_paused_msg' => esc_html__('The live stream is paused','wpstream'),
368
369 'wpstream_player_state_ended_msg' => esc_html__('The live stream has ended','wpstream'),
370
371 'wpstream_player_state_error_msg' => esc_html__('Something went wrong','wpstream'),
372
373 'wpstream_player_theme' => get_option('wpstream_video_player_theme'),
374
375 'playerLogoSettings' => array(
376
377 'imageUrl' => $this->main->wpstream_player->wpstream_get_video_player_logo( get_the_ID() ),
378
379 'position' => get_option( 'wpstream_player_logo_position', 'top-left' ),
380
381 'opacity' => get_option('wpstream_player_logo_opacity', '100'),
382
383 ),
384
385 'wpstream_is_streamify_user' => $this->main->wpstream_player->wpstream_is_streamify_user( get_the_ID() ),
386
387 // Nonce the player uses when AJAX-polling live status; verified server-side on each status check.
388
389 'player_check_status_nonce' => wp_create_nonce( 'wpstream_player_check_status_nonce'),
390
391 'is_abr_enabled' => $abr_enabled,
392 /**
393 * Filter how often the player polls the channel status when no
394 * WebSocket is available, in seconds. Clamped to [1, 300].
395 *
396 * @since 4.14.0
397 *
398 * @param int $seconds Poll interval; default 30.
399 */
400 'status_poll_interval' => wpstream_tunable( 'wpstream_player_status_poll_interval', 30, 5 * MINUTE_IN_SECONDS ),
401
402 // Gates the player's diagnostic console logging: silent in production,
403
404 // verbose only when the site opts into SCRIPT_DEBUG.
405
406 'debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG,
407
408 );
409 return $this->filter_player_js_config( $config, 'wpstream_player_vars' );
410 }
411
412 /**
413 * Run a localized player object through `wpstream_player_js_config` with
414 * every nonce withheld.
415 *
416 * Keys whose name contains `nonce` are removed before the filter and the
417 * canonical values re-applied afterwards, so a callback can neither read
418 * nor replace them.
419 *
420 * @param array $config Localized object.
421 * @param string $object_name JS object name (`wpstream_player_vars`, `wpstreamLiveUiConfig`).
422 * @return array Filtered object with canonical nonces.
423 */
424 private function filter_player_js_config( array $config, $object_name ) {
425 $is_nonce = static function ( $key ) {
426 return false !== stripos( (string) $key, 'nonce' );
427 };
428 $nonces = array_filter( $config, $is_nonce, ARRAY_FILTER_USE_KEY );
429
430 /**
431 * Filter a localized player config object.
432 *
433 * Nonce keys are not in `$config` and cannot be set: the canonical
434 * values are re-applied after the filter.
435 *
436 * @since 4.14.0
437 *
438 * @param array $config Object without its nonce keys.
439 * @param string $object_name JS object name (`wpstream_player_vars` or `wpstreamLiveUiConfig`).
440 */
441 $filtered = (array) apply_filters( 'wpstream_player_js_config', array_diff_key( $config, $nonces ), $object_name );
442
443 // Drop any nonce key the callback set and restore the canonical ones.
444 $filtered = array_filter( $filtered, static function ( $key ) use ( $is_nonce ) {
445 return ! $is_nonce( $key );
446 }, ARRAY_FILTER_USE_KEY );
447 return array_merge( $filtered, $nonces );
448 }
449
450 /**
451 * Build the `wpstreamLiveUiConfig` object for the player controls script:
452 * the translated live-UI status strings plus an isThemeActive flag.
453 *
454 * @return array Filtered config.
455 */
456 public function wpstream_get_player_i18n_config() {
457 // Each entry maps a player state to the localized, escaped message shown to viewers.
458 $config = array(
459 'wpstream_player_state_stopped_msg' => esc_html( wpstream_not_live_message() ),
460 'wpstream_player_state_init_msg' => esc_html__( 'The live stream has not yet started', 'wpstream' ),
461 'wpstream_player_state_startup_msg' => esc_html__( 'The live stream is starting...', 'wpstream' ),
462 'wpstream_player_state_paused_msg' => esc_html__( 'The live stream is paused', 'wpstream' ),
463 'wpstream_player_state_ended_msg' => esc_html__( 'The live stream has ended', 'wpstream' ),
464 'wpstream_player_state_error_msg' => esc_html__( 'Something went wrong', 'wpstream' ),
465 'wpstream_player_offair_default_msg' => esc_html__( 'Live stream is not on air.', 'wpstream' ),
466 'wpstream_player_max_viewers_msg' => esc_html__( 'Max viewers reached. Please wait', 'wpstream' ),
467 'wpstream_player_max_viewers_wait_msg' => esc_html__( 'Max viewers reached. Please wait for %d to leave', 'wpstream' ),
468 'wpstream_player_invalid_session_msg' => esc_html__( 'Invalid session. Please refresh and start playback again.', 'wpstream' ),
469 'isThemeActive' => get_template() === 'hello-wpstream',
470 );
471 return $this->filter_player_js_config( $config, 'wpstreamLiveUiConfig' );
472 }
473
474 /**
475 * Whether the current account's live plan is metered in streaming hours.
476 *
477 * @return bool True when the resolved live quota pack uses streaming hours.
478 */
479 public function wpstream_is_use_streaming_hours() {
480 // Resolve the live quota pack for the start-channel action.
481 $pack_details = $this->main->quota_manager->get_live_quota_data( 'wpstream_start_channel' );
482 // Ask the quota manager whether that pack is measured in streaming hours.
483 return $this->main->quota_manager->uses_streaming_hours( $pack_details );
484 }
485
486 /**
487 * Cached-only flags for start_streaming.js (no API on cold cache).
488 * Skips work entirely for visitors who cannot stream.
489 *
490 * @return array{is_basic_streaming: bool, use_streaming_hours: bool}
491 */
492 public function wpstream_get_start_streaming_localization_flags() {
493 // Skip all quota work for logged-out visitors or users who cannot stream: return inert flags.
494 if ( ! is_user_logged_in() || ! $this->main->wpstream_check_user_can_stream() ) {
495 return array(
496 'is_basic_streaming' => false,
497 'use_streaming_hours' => false,
498 );
499 }
500
501 // Otherwise return the cached streaming UI flags (no live API call on a cold cache).
502 return $this->main->quota_manager->get_streaming_ui_flags_from_cache();
503 }
504
505
506 /**
507 * add custom end points for woocomerce
508 *
509 * @since 3.0.1
510 * @return nothing
511 */
512 public function wpstream_my_custom_endpoints() {
513 // Register the "My Videos" account endpoint on the site root and pages.
514 add_rewrite_endpoint( 'video-list', EP_ROOT | EP_PAGES );
515 // Register the "My Live Streams" account endpoint on the site root and pages.
516 add_rewrite_endpoint( 'event-list', EP_ROOT | EP_PAGES );
517 }
518
519 /**
520 * Remove specific wpstream filters.
521 *
522 * Used to detach the plugin's content/product hooks (e.g. on pages where the
523 * default player injection is not wanted).
524 *
525 * @return void
526 */
527 function wpstream_remove_wpstream_filter() {
528 // Access the global plugin instance for its main object.
529 global $wpstream_plugin;
530
531 if ( class_exists( 'Wpstream_Player' ) ) {
532 // Instantiate the Wpstream_Player class if it exists.
533 $pstream_player = new Wpstream_Player( $wpstream_plugin->main );
534 // Remove filters applied by wpstream.
535 // Drop the title/content filter that prepends player markup.
536 remove_filter( 'the_content', 'wpstream_filter_the_title' );
537 // Drop the "already bought" notice that renders before a single WooCommerce product.
538 remove_filter( 'woocommerce_before_single_product', array( $pstream_player, 'wpstream_user_logged_in_product_already_bought' ) );
539 }
540 }
541
542 /**
543 * add custom query vars
544 *
545 * @since 3.0.1
546 * @param array $vars Existing public query vars.
547 * @return array Query vars with the plugin's account endpoints appended.
548 */
549 public function wpstream_my_custom_query_vars( $vars ) {
550 // Make the video-list endpoint readable as a query var.
551 $vars[] = 'video-list';
552 // Make the event-list endpoint readable as a query var.
553 $vars[] = 'event-list';
554 return $vars;
555 }
556
557
558 /**
559 * Hust flush rewrite rules
560 *
561 * @since 3.0.1
562 *
563 */
564 public function wpstream_custom_flush_rewrite_rules() {
565 // Rebuild WordPress rewrite rules so the newly registered endpoints resolve.
566 flush_rewrite_rules();
567 }
568
569
570 /**
571 * Add new sections in woocomerce account
572 *
573 * @since 3.0.1
574 * @param array $items Existing WooCommerce My Account menu items (endpoint => label).
575 * @return array Menu items with the plugin's entries inserted just before logout.
576 */
577 public function wpstream_custom_my_account_menu_items( $items ) {
578 // Theme may provide extra endpoints (start-streaming, watch-later); only add those if present.
579 $has_theme_endpoints = function_exists( 'wpstream_theme_my_custom_endpoints' );
580
581 // Keep WooCommerce defaults and remove only plugin-specific entries before rebuilding plugin order.
582 unset( $items['event-list'], $items['video-list'], $items['start-streaming'], $items['watch-later'] );
583
584 // Build the ordered list of plugin menu entries.
585 $custom_items = array();
586 $custom_items['event-list'] = __( 'My Live Streams', 'wpstream' );
587 $custom_items['video-list'] = __( 'My Videos', 'wpstream' );
588 if ( $has_theme_endpoints ) {
589 // Theme-provided endpoints only appear when the theme registered them.
590 $custom_items['start-streaming'] = esc_html__( 'Start Streaming', 'wpstream' );
591 $custom_items['watch-later'] = esc_html__( 'Watch Later', 'wpstream' );
592 }
593
594 // Nothing to add: hand the menu back untouched.
595 if ( empty( $custom_items ) ) {
596 return $items;
597 }
598
599 // Insert custom items before logout to preserve WooCommerce/account plugin menu entries.
600 $result = array();
601 foreach ( $items as $endpoint => $label ) {
602 // Splice the plugin entries in right before the logout link.
603 if ( 'customer-logout' === $endpoint ) {
604 foreach ( $custom_items as $custom_endpoint => $custom_label ) {
605 $result[ $custom_endpoint ] = $custom_label;
606 }
607 }
608
609 // Preserve the original item order.
610 $result[ $endpoint ] = $label;
611 }
612
613 // No logout entry existed to anchor on; append the plugin items at the end.
614 if ( ! isset( $items['customer-logout'] ) ) {
615 foreach ( $custom_items as $custom_endpoint => $custom_label ) {
616 $result[ $custom_endpoint ] = $custom_label;
617 }
618 }
619
620 return $result;
621 }
622
623
624 /**
625 * Function that adds additional post types for the search template
626 *
627 * @param $post_types_array
628 * @return array
629 */
630 public function wpstream_search_template_add_item_post_type( $post_types_array ) {
631 // Append the plugin's product/VOD/bundle post types so they surface in the search template.
632 return array_merge( $post_types_array, array( 'wpstream_product_vod', 'wpstream_product', 'product', 'wpstream_bundles' ) );
633 }
634
635 /**
636 * Function that changes the sidebar id based on the post type
637 *
638 * @param $sidebar_id
639 * @return mixed|string
640 */
641 public function wpstream_sidebar_id_by_post_type( $sidebar_id ) {
642 // Determine the post type of the item currently being rendered.
643 $current_post_type = get_post_type( get_the_ID() );
644
645 // VOD items get the dedicated VOD sidebar.
646 if( $current_post_type == 'wpstream_product_vod' ) {
647 return 'sidebar-vod';
648 }
649 // WooCommerce products get the products sidebar.
650 if( $current_post_type == 'product' ) {
651 return 'sidebar-products';
652 }
653 // Live channels get the live sidebar.
654 if( $current_post_type == 'wpstream_product' ) {
655 return 'sidebar-live';
656 }
657 // Anything else keeps the incoming sidebar id.
658 return $sidebar_id;
659 }
660
661 /**
662 * Function to add new items to the header search dropdown post type list
663 *
664 * @param $search_list_values
665 * @return array
666 */
667 public function wpstream_header_search_values( $search_list_values ) {
668 // Add labelled entries for the plugin post types to the header search dropdown.
669 return array_merge($search_list_values, [
670 'wpstream_product' => esc_html__( 'Live Events', 'wpstream' ),
671 'wpstream_product_vod' => esc_html__( 'Video on Demand', 'wpstream' ),
672 'wpstream_bundles' => esc_html__( 'Video Bundles', 'wpstream' ),
673 ]);
674 }
675
676 /**
677 * Function to add new items to the category archive query
678 *
679 * @param $post_types
680 * @return array
681 */
682 public function wpstream_extend_category_archive_query_filter_callback( $post_types ) {
683 // Include the plugin post types in category archive queries.
684 return array_merge( $post_types, array( 'product', 'wpstream_bundles', 'wpstream_product_vod', 'wpstream_product' ) );
685 }
686
687 /**
688 * Function to add new labels to the list of taxonomies
689 *
690 * @param $taxonomy_labels
691 * @return mixed
692 */
693 public function wpstream_archives_lists_taxonomy_labels_callback( $taxonomy_labels ) {
694 // Provide human-readable labels for each plugin post type in the archives list.
695 $taxonomy_labels['product'] = esc_html__( 'Video Products', 'wpstream' );
696 $taxonomy_labels['wpstream_bundles'] = esc_html__( 'Bundles', 'wpstream' );
697 $taxonomy_labels['wpstream_product'] = esc_html__( 'Free Events', 'wpstream' );
698 $taxonomy_labels['wpstream_product_vod'] = esc_html__( 'Free Vod', 'wpstream' );
699 return $taxonomy_labels;
700 }
701
702 /**
703 * Function to add new labels to the list of taxonomies for author archive
704 *
705 * @param $taxonomy_labels
706 * @return mixed
707 */
708 public function wpstream_author_archive_list_taxonomy_labels_callback( $taxonomy_labels ) {
709 // Same labels as the archives list, applied to the author archive.
710 $taxonomy_labels['product'] = esc_html__( 'Video Products', 'wpstream' );
711 $taxonomy_labels['wpstream_bundles'] = esc_html__( 'Bundles', 'wpstream' );
712 $taxonomy_labels['wpstream_product_vod'] = esc_html__( 'Free Vod', 'wpstream' );
713 $taxonomy_labels['wpstream_product'] = esc_html__( 'Free Events', 'wpstream' );
714 return $taxonomy_labels;
715 }
716
717 /**
718 * Function to add new post type to the vod attached to the channel
719 *
720 * @param $post_type_array
721 * @return array
722 */
723 public function wpstream_vod_attached_to_channel( $post_type_array ) {
724 // Allow VOD items to be listed among the recordings attached to a channel.
725 return array_merge( $post_type_array, array( 'wpstream_product_vod' ) );
726 }
727
728 /**
729 * Function to add new post type to the additional post type content
730 *
731 * @param $post_type_list
732 * @return array
733 */
734 public function wpstream_additional_content_post_type_callback( $post_type_list ) {
735 // Add the plugin post types to the "additional content" list.
736 return array_merge( $post_type_list, array( 'wpstream_product_vod', 'wpstream_product', 'wpstream_bundle_bcks' ) );
737 }
738
739 /**
740 * Add the plugin post types to the author-content post type list.
741 *
742 * @param array $post_type_list Existing post types.
743 * @return array Post types with the plugin types appended.
744 */
745 public function wpstream_post_author_content_post_type_list_callback( $post_type_list ) {
746 // Include live/VOD/bundle post types when listing an author's content.
747 return array_merge( $post_type_list, array( 'wpstream_product_vod', 'wpstream_product', 'wpstream_bundles' ) );
748 }
749
750 /**
751 * Verb shown before the date on an author's simple content listing, per post type.
752 *
753 * @param string $message Default message.
754 * @param string $post_type Post type being rendered.
755 * @return mixed|string The post-type-appropriate action verb, or the default.
756 */
757 public function wpstream_author_content_simple_post_type_message_callback( $message, $post_type ) {
758 // Pick the action verb that matches the content type.
759 switch ( $post_type ) {
760 case 'wpstream_product_vod':
761 // VOD items were "Published".
762 $message = esc_html__( 'Published ', 'wpstream' );
763 break;
764 case 'wpstream_product':
765 // Live channels "Started streaming".
766 $message = esc_html__( 'Started streaming ', 'wpstream' );
767 break;
768 case 'wpstream_bundles':
769 // Bundles were "Added".
770 $message = esc_html__( 'Added ', 'wpstream' );
771 break;
772 }
773 return $message;
774 }
775
776 /**
777 * Verb shown before the date on an author's content listing, per post type.
778 *
779 * @param string $message Default message.
780 * @param string $post_type Post type being rendered.
781 * @return mixed|string The post-type-appropriate action verb, or the default.
782 */
783 public function wpstream_author_content_post_type_message_callback( $message, $post_type ) {
784 // Pick the action verb that matches the content type.
785 switch ( $post_type ) {
786 case 'wpstream_product_vod':
787 // VOD items were "Published".
788 $message = esc_html__( 'Published ', 'wpstream' );
789 break;
790 case 'wpstream_product':
791 // Live channels "Started streaming".
792 $message = esc_html__( 'Started streaming ', 'wpstream' );
793 break;
794 case 'wpstream_bundles':
795 // Bundles were "Added".
796 $message = esc_html__( 'Added ', 'wpstream' );
797 break;
798 }
799 return $message;
800 }
801
802 /**
803 * Function to show the sidebar for the post type
804 *
805 * @param $default
806 * @param $post_type
807 * @return bool|mixed
808 */
809 public function wpstream_show_sidebar_for_post_type_callback( $default, $post_type ) {
810 // Each post type reads its own Customizer toggle to decide whether a sidebar shows.
811 switch ( $post_type ) {
812 case 'page':
813 return get_theme_mod( 'wpstream_page_sidebar', true );
814 case 'wpstream_product_vod':
815 return get_theme_mod( 'wpstream_video_on_demand_sidebar', true );
816 case 'wpstream_product':
817 case 'wpstream_bundles':
818 // Live channels and bundles share the "free to view live" sidebar toggle.
819 return get_theme_mod( 'wpstream_free_to_view_live_sidebar', true );
820 case 'product':
821 return get_theme_mod( 'wpstream_product_details_page_sidebar', true );
822 default:
823 // Unknown types keep the caller's default.
824 return $default;
825 }
826 }
827
828 /**
829 * Function to add new post types to the video episodes
830 *
831 * @param $post_type
832 * @return array
833 */
834 public function wpstream_video_episodes_post_type_callback( $post_type ) {
835 // Treat live, VOD and WooCommerce products as valid "video episode" post types.
836 return array_merge( $post_type, array( 'wpstream_product', 'wpstream_product_vod', 'product' ) );
837 }
838
839 /**
840 * Function to add new post types to the vod episodes
841 *
842 * @param $post_type
843 * @return array
844 */
845 public function wpstream_video_past_broadcast_post_type_callback( $post_type ) {
846 // Past broadcasts are stored as VOD items.
847 return array_merge( $post_type, array( 'wpstream_product_vod' ) );
848 }
849
850 /**
851 * Function to return the label for the additional content based on the post type
852 *
853 * @param string $label Default label.
854 * @param string $post_type Post type being rendered.
855 * @return array|string "watching" for live channels, "views" for other media, or the default for posts.
856 */
857 public function wpstream_additional_content_post_type_label_callback( $label, $post_type ) {
858 // Regular posts keep the incoming label.
859 if ( 'post' === $post_type ) {
860 return $label;
861 } elseif ( 'wpstream_product' === $post_type ) {
862 // Live channels count concurrent viewers ("watching").
863 return __( 'watching', 'wpstream' );
864 } else {
865 // Everything else counts total "views".
866 return __( 'views', 'wpstream' );
867 }
868 }
869
870 /**
871 * Add new endpoint
872 *
873 * @since 3.0.1
874 */
875 public function wpstream_custom_endpoint_content_event_list() {
876 // Render the "My Live Streams" account tab from its template.
877 include plugin_dir_path( __DIR__ ).'templates/myaccount/event_list.php';
878 }
879
880
881 /**
882 * Add new endpoint
883 *
884 * @since 3.0.1
885 */
886 public function wpstream_custom_endpoint_video_list() {
887 // Render the "My Videos" account tab from its template.
888 include plugin_dir_path( __DIR__ ).'templates/myaccount/video_list.php';
889 }
890
891
892
893
894
895
896 /**
897 * register shortcodes
898 *
899 * @since 3.0.1
900 *
901 */
902 public function wpstream_shortcodes(){
903 // [wpstream_player] renders the in-page player for a given product/user id.
904 add_shortcode('wpstream_player', array($this,'wpstream_insert_player_inpage_local') );
905 // [wpstream_chat] was removed with the built-in chat (issue #110).
906 // [wpstream_go_live] renders the start-streaming (channel on/off) unit.
907 add_shortcode('wpstream_go_live', array($this,'wpstream_start_streaming_shortocde') );
908
909 // [wpstream_list_media_channels] lists live channels (bypasses the page builder wrapper).
910 add_shortcode('wpstream_list_media_channels', array($this,'wpstream_media_list_bakery_bypass') );
911 // [wpstream_list_media_vod] lists VOD items (bypasses the page builder wrapper).
912 add_shortcode('wpstream_list_media_vod', array($this,'wpstream_media_list_bakery_vod_bypass') );
913 }
914
915
916 /**
917 * Register WpStream elements with Visual Composer / WPBakery and add the
918 * classic-editor (TinyMCE) shortcode buttons.
919 *
920 * Each vc_map() call describes one draggable element (player, chat, lists,
921 * start-streaming) and its editable params. The TinyMCE buttons are only
922 * added for users who can edit content and use the visual editor.
923 *
924 * @since 3.0.1
925 * @return void
926 */
927
928 public function wpstream_bakery_shortcodes(){
929 // register shortcodes for visual composer
930 // Only map anything when Visual Composer / WPBakery is present.
931 if( function_exists('vc_map') ):
932
933 // Map: Start Streaming button element.
934 vc_map(
935 array(
936 "name" => esc_html__( "WpStream Start Streaming Button","wpstream"),
937 "base" => "wpstream_go_live",
938 "class" => "",
939 "category" => esc_html__( 'WpStream','wpstream'),
940 'admin_enqueue_js' => array(get_template_directory_uri().'/vc_extend/bartag.js'),
941 'admin_enqueue_css' => array(get_template_directory_uri().'/vc_extend/bartag.css'),
942 'weight'=>100,
943 'icon' =>'',
944 'description'=>esc_html__( 'Insert WpStream Start Streaming Button','wpstream'),
945 "params" => array(
946 array(
947 "type" => "textfield",
948 "holder" => "div",
949 "class" => "",
950 "heading" => esc_html__( "Product/Free Product Id","wpstream"),
951 "param_name" => "id",
952 "value" => "",
953 "description" => esc_html__( "If you leave this option blank we will stream on the first free/paid channel for this user","wpstream")
954 ),
955
956
957 )
958 )
959 );
960
961
962
963 // Map: standard Player element.
964 vc_map(
965 array(
966 "name" => esc_html__( "WpStream Player","wpstream"),
967 "base" => "wpstream_player",
968 "class" => "",
969 "category" => esc_html__( 'WpStream','wpstream'),
970 'admin_enqueue_js' => array(get_template_directory_uri().'/vc_extend/bartag.js'),
971 'admin_enqueue_css' => array(get_template_directory_uri().'/vc_extend/bartag.css'),
972 'weight'=>100,
973 'icon' =>'',
974 'description'=>esc_html__( 'Insert WpStream Player','wpstream'),
975 "params" => array(
976 array(
977 "type" => "textfield",
978 "holder" => "div",
979 "class" => "",
980 "heading" => esc_html__( "Product/Free Product Id","wpstream"),
981 "param_name" => "id",
982 "value" => "0",
983 "description" => esc_html__( "Add here the live stream id or the video id","wpstream")
984 ),
985 array(
986 "type" => "textfield",
987 "holder" => "div",
988 "class" => "",
989 "heading" => esc_html__( "User Id","wpstream"),
990 "param_name" => "user_id",
991 "value" => "",
992 "description" => esc_html__( "We will use the first channel of this user id(product id will be ignored.).","wpstream")
993 ),
994
995 )
996 )
997 );
998
999
1000 // Shared dropdown options reused by the channel/VOD list maps below.
1001 // Free vs paid filter.
1002 $media_catalog = $this->main->media_list->catalog( 'live_channel' );
1003 $free_paid_type=array(
1004 0 => $media_catalog['choices']['access']['free'],
1005 1 => $media_catalog['choices']['access']['paid']
1006 );
1007
1008 // "Only show live channels" yes/no.
1009 $live_settings=array(
1010
1011 'no' => $media_catalog['choices']['active_only'][false],
1012 'yes' => $media_catalog['choices']['active_only'][true],
1013 );
1014
1015 // Ordering options.
1016 $order_by_id=array(
1017 0 => $media_catalog['choices']['order']['date_asc'],
1018 1 => $media_catalog['choices']['order']['date_desc'],
1019 2 => $media_catalog['choices']['order']['title_asc'],
1020 3 => $media_catalog['choices']['order']['title_desc'],
1021 );
1022
1023 // Map: Channel List element.
1024 vc_map(
1025 array(
1026 "name" => esc_html__( "WpStream Channel List","wpstream"),
1027 "base" => "wpstream_list_media_channels",
1028 "class" => "",
1029 "category" => esc_html__( 'WpStream','wpstream'),
1030 'admin_enqueue_js' => array(get_template_directory_uri().'/vc_extend/bartag.js'),
1031 'admin_enqueue_css' => array(get_template_directory_uri().'/vc_extend/bartag.css'),
1032 'weight'=>100,
1033 'icon' =>'',
1034 'description'=>esc_html__( ' List wpstream channels','wpstream'),
1035 "params" => array(
1036
1037
1038
1039 array(
1040 "type" => "dropdown",
1041 "holder" => "div",
1042 "class" => "",
1043 "heading" => esc_html__( "Show Free or Paid Media ?","wpstream"),
1044 "param_name" => "product_type_free_paid",
1045 "value" => $free_paid_type,
1046 "description" => esc_html__( "What type of media(free/paid) ","wpstream")
1047 ),
1048
1049 array(
1050 "type" => "dropdown",
1051 "holder" => "div",
1052 "class" => "",
1053 "heading" => esc_html__( "Only show active channels","wpstream"),
1054 "param_name" => "product_show_live",
1055 "value" => $live_settings,
1056 "description" => esc_html__( "Only show channels that are live streaming right now.","wpstream")
1057 ),
1058 array(
1059 "type" => "textfield",
1060 "holder" => "div",
1061 "class" => "",
1062 "heading" => esc_html__( "Number of Items per Page","wpstream"),
1063 "param_name" => "media_number",
1064 "value" => (string) $media_catalog['defaults']['items_per_page'],
1065 "description" => esc_html__( "How many items will be displayed per page","wpstream")
1066 ),
1067
1068 array(
1069 "type" => "textfield",
1070 "holder" => "div",
1071 "class" => "",
1072 "heading" => esc_html__( "Link Label for free items","wpstream"),
1073 "param_name" => "free_label",
1074 "value" => $media_catalog['builder_defaults']['free_label'],
1075 "description" => esc_html__( "Link Label for free items'","wpstream")
1076 ),
1077
1078 array(
1079 "type" => "dropdown",
1080 "holder" => "div",
1081 "class" => "",
1082 "heading" => esc_html__( "Order by","wpstream"),
1083 "param_name" => "order_by",
1084 "value" => $order_by_id,
1085 "description" => esc_html__( "Order type","wpstream")
1086 ),
1087
1088
1089 )
1090 )
1091 );
1092
1093
1094 // Map: VOD List element.
1095 vc_map(
1096 array(
1097 "name" => esc_html__( "WpStream VOD List","wpstream"),
1098 "base" => "wpstream_list_media_vod",
1099 "class" => "",
1100 "category" => esc_html__( 'WpStream','wpstream'),
1101 'admin_enqueue_js' => array(get_template_directory_uri().'/vc_extend/bartag.js'),
1102 'admin_enqueue_css' => array(get_template_directory_uri().'/vc_extend/bartag.css'),
1103 'weight'=>100,
1104 'icon' =>'',
1105 'description'=>esc_html__( ' List wpstream video on demand','wpstream'),
1106 "params" => array(
1107
1108
1109
1110 array(
1111 "type" => "dropdown",
1112 "holder" => "div",
1113 "class" => "",
1114 "heading" => esc_html__( "Show Free or Paid Media ?","wpstream"),
1115 "param_name" => "product_type_free_paid",
1116 "value" => $free_paid_type,
1117 "description" => esc_html__( "What type of media(free/paid) ","wpstream")
1118 ),
1119
1120
1121 array(
1122 "type" => "textfield",
1123 "holder" => "div",
1124 "class" => "",
1125 "heading" => esc_html__( "Number of Items per Page","wpstream"),
1126 "param_name" => "media_number",
1127 "value" => (string) $media_catalog['defaults']['items_per_page'],
1128 "description" => esc_html__( "How many items will be displayed per page","wpstream")
1129 ),
1130
1131 array(
1132 "type" => "textfield",
1133 "holder" => "div",
1134 "class" => "",
1135 "heading" => esc_html__( "Link Label for free items","wpstream"),
1136 "param_name" => "free_label",
1137 "value" => $media_catalog['builder_defaults']['free_label'],
1138 "description" => esc_html__( "Link Label for free items'","wpstream")
1139 ),
1140
1141 array(
1142 "type" => "dropdown",
1143 "holder" => "div",
1144 "class" => "",
1145 "heading" => esc_html__( "Order by","wpstream"),
1146 "param_name" => "order_by",
1147 "value" => $order_by_id,
1148 "description" => esc_html__( "Order type","wpstream")
1149 ),
1150
1151
1152 )
1153 )
1154 );
1155
1156
1157 endif;
1158
1159
1160 // add shorcotes to editor interface
1161 // Only wire the TinyMCE buttons for users who can edit posts or pages.
1162 if (!current_user_can('edit_posts') && !current_user_can('edit_pages')) {
1163 return;
1164 }
1165
1166 // Register the TinyMCE plugin + toolbar button only when the user is on the visual editor.
1167 if (get_user_option('rich_editing') == 'true') {
1168 add_filter('mce_external_plugins', array( $this,'wpstream_add_plugin') );
1169 add_filter('mce_buttons_2', array($this,'wpstream_register_button') );
1170 }
1171 }
1172
1173 /**
1174 * list channels - shortcode function
1175 *
1176 * Adapts the [wpstream_list_media_channels] shortcode attributes and hands
1177 * them to the shared Elementor list renderer.
1178 *
1179 * @since 3.0.1
1180 * @param array $attributes Raw shortcode attributes.
1181 * @return string Rendered channel-list HTML.
1182 */
1183
1184 public function wpstream_media_list_bakery_bypass($attributes){
1185 $attributes = shortcode_atts(
1186 array(
1187 'media_number' => 3,
1188 'product_type_free_paid' => '0',
1189 'product_show_live' => 'no',
1190 'free_label' => '',
1191 'order_by' => '0'
1192 ),
1193 is_array( $attributes ) ? $attributes : array()
1194 );
1195 return $this->main->media_list->render( 'live_channel', $this->filter_media_list_shortcode_atts( $attributes, 'live_channel' ) );
1196 }
1197
1198 /**
1199 * list vod - shortcode function
1200 *
1201 * Adapts the [wpstream_list_media_vod] shortcode attributes and hands them
1202 * to the shared Elementor list renderer.
1203 *
1204 * @since 3.0.1
1205 * @param array $attributes Raw shortcode attributes.
1206 * @return string Rendered VOD-list HTML.
1207 */
1208
1209 public function wpstream_media_list_bakery_vod_bypass($attributes){
1210 $attributes = shortcode_atts(
1211 array(
1212 'media_number' => 3,
1213 'product_type_free_paid' => '0',
1214 'product_show_live' => 'no',
1215 'free_label' => '',
1216 'order_by' => '0'
1217 ),
1218 is_array( $attributes ) ? $attributes : array()
1219 );
1220 return $this->main->media_list->render( 'vod', $this->filter_media_list_shortcode_atts( $attributes, 'vod' ) );
1221 }
1222
1223 /**
1224 * Run the normalised attributes of a Media List shortcode through its filter.
1225 *
1226 * @param array $attributes Attributes after shortcode_atts() defaults.
1227 * @param string $kind `live_channel` or `vod`.
1228 * @return array
1229 */
1230 private function filter_media_list_shortcode_atts( array $attributes, $kind ) {
1231 /**
1232 * Filter the attributes of the [wpstream_list_media_channels] and
1233 * [wpstream_list_media_vod] shortcodes before the list renders.
1234 *
1235 * @since 4.14.0
1236 *
1237 * @param array $attributes Shortcode attributes with defaults applied.
1238 * @param string $kind `live_channel` or `vod`.
1239 */
1240 return (array) apply_filters( 'wpstream_media_list_shortcode_atts', $attributes, $kind );
1241 }
1242
1243 /**
1244 * [wpstream_player] shortcode - render the in-page player.
1245 *
1246 * @since 3.0.1
1247 * @param array $attributes Shortcode attributes ('id' = product id, 'user_id' = fallback owner).
1248 * @param string|null $content Enclosed content (unused).
1249 * @return string Buffered player HTML.
1250 */
1251
1252
1253 public function wpstream_insert_player_inpage_local($attributes, $content = null){
1254 // Initialise the target product id and output buffer.
1255 $product_id = '';
1256 $return_string = '';
1257 // Merge shortcode attributes with defaults.
1258 $attributes = shortcode_atts(
1259 array(
1260 'id' => 0,
1261 'user_id' => 0,
1262 ), $attributes) ;
1263
1264
1265 // Sanitize the explicit product id.
1266 if ( isset($attributes['id']) ){
1267 $product_id = intval( $attributes['id'] );
1268 }
1269
1270 // Sanitize the optional user id.
1271 if ( isset($attributes['user_id']) ){
1272 $user_id = intval( $attributes['user_id'] );
1273 }
1274
1275 // If no product id was given but a user id was, use that user's first channel.
1276 if(intval($product_id)==0 && $user_id!=0 ){
1277 $product_id= $this->main->wpstream_player_retrieve_first_id($user_id);
1278 }
1279
1280 // Capture the player markup into the return string.
1281 ob_start();
1282 $this->main->wpstream_player->wpstream_video_player_shortcode($product_id);
1283 $return_string= ob_get_contents();
1284 ob_end_clean();
1285
1286 return $return_string;
1287 }
1288
1289
1290 /**
1291 * [wpstream_go_live] shortcode - render the start-streaming (channel on/off) unit.
1292 *
1293 * @since 3.7
1294 * @param array $attributes Shortcode attributes ('id' = channel/product id).
1295 * @param string|null $content Enclosed content (unused).
1296 * @return string Buffered start-streaming unit HTML.
1297 */
1298
1299
1300 public function wpstream_start_streaming_shortocde($attributes, $content = null){
1301 // Initialise the target product id and output buffer.
1302 $product_id = '';
1303 $return_string = '';
1304
1305 // Merge shortcode attributes with defaults.
1306 $attributes = shortcode_atts(
1307 array(
1308 'id' => 0,
1309 ), $attributes) ;
1310
1311
1312 // Sanitize the channel/product id.
1313 if ( isset($attributes['id']) ){
1314 $product_id=intval($attributes['id']);
1315 }
1316
1317
1318 // Capture the front-end streaming unit markup from the main plugin object.
1319 ob_start();
1320 global $wpstream_plugin;
1321 $wpstream_plugin->wpstream_live_stream_unit_wrapper( $product_id,'front' );
1322 $return_string= ob_get_contents();
1323 ob_end_clean();
1324
1325 return $return_string;
1326 }
1327
1328
1329
1330 /**
1331 * register shortcodes - add buttons in js (mce_external_plugins filter).
1332 *
1333 * @since 3.0.1
1334 * @param array $plugin_array Registered TinyMCE external plugins.
1335 * @return array Plugins with the WpStream editor plugin added.
1336 */
1337
1338 public function wpstream_add_plugin($plugin_array) {
1339 $plugin_array['wpstream_player'] = plugin_dir_url( __FILE__ ). '/js/shortcodes.js';
1340 return $plugin_array;
1341 }
1342
1343 /**
1344 * register shortcodes - add buttons (mce_buttons_2 filter).
1345 *
1346 * @since 3.0.1
1347 * @param array $buttons Existing TinyMCE toolbar buttons.
1348 * @return array Buttons with the WpStream shortcode buttons appended.
1349 */
1350 public function wpstream_register_button($buttons) {
1351 array_push($buttons, "|", "wpstream_player");
1352 return $buttons;
1353 }
1354
1355
1356
1357 /**
1358 * Answer CORS preflight (OPTIONS) requests.
1359 *
1360 * Responds to a browser preflight with the allowed methods/headers and exits,
1361 * so cross-origin key/API calls can proceed.
1362 *
1363 * @since 3.0.1
1364 * @return void
1365 */
1366
1367 public function wpstream_cors_check_and_response(){
1368 // Only handle the preflight OPTIONS verb; other methods fall through.
1369 if ($_SERVER['REQUEST_METHOD'] == "OPTIONS") {
1370 // Advertise the permitted methods and headers, then send an empty 200 and stop.
1371 header('Access-Control-Allow-Methods: POST, GET');
1372 header('Access-Control-Allow-Headers: Authorization');
1373 header('Access-Control-Max-Age: 1'); //1728000
1374 header("Content-Length: 0");
1375 header("Content-Type: text/plain charset=UTF-8");
1376 exit(0);
1377 }
1378 }
1379
1380 /**
1381 * Seed the per-visitor session id used to bind DRM key requests.
1382 *
1383 * When session encryption is enabled globally, ensures a PHP session exists
1384 * and assigns a unique 'wpstream_id' - but not during an actual key request
1385 * (livedrm/voddrm/keys2), so the id is minted on a normal page view first.
1386 *
1387 * @since 3.0.1
1388 * @return void
1389 */
1390
1391 public function wpstream_set_cookies(){
1392 // Read the global streaming channel options.
1393 $local_event_options = get_option('wpstream_user_streaming_global_channel_options') ;
1394
1395 // Only proceed when session-based encryption is switched on.
1396 if( !isset($local_event_options['ses_encrypt']) || intval($local_event_options['ses_encrypt'])!=1 ) {
1397 return;
1398 }
1399
1400 // Cron and REST requests never render a player nor request a key: no session at all.
1401 if ( wp_doing_cron() || ( defined('REST_REQUEST') && REST_REQUEST ) ) {
1402 return;
1403 }
1404
1405 // Key-delivery requests only READ the previously minted id: load the
1406 // session data and release the file lock in one step, never minting.
1407 if( isset( $_GET[ 'wpstream_livedrm' ]) || isset( $_GET[ 'wpstream_voddrm' ]) || isset( $_REQUEST[ 'keys2' ]) ) {
1408 if (session_status() == PHP_SESSION_NONE) {
1409 session_start( array( 'read_and_close' => true ) );
1410 }
1411 return;
1412 }
1413
1414 // Ordinary page view: mint the id once, then close the session
1415 // immediately — holding the lock open would serialize every
1416 // concurrent request from this visitor for the whole page load.
1417 if (session_status() == PHP_SESSION_NONE) {
1418 session_start();
1419 }
1420 if( !isset($_SESSION['wpstream_id']) ){
1421 $_SESSION['wpstream_id']= uniqid();
1422 }
1423 session_write_close();
1424 }
1425
1426 /**
1427 * editd 4.0
1428 *
1429 * HLS key-delivery endpoint for LIVE streams.
1430 *
1431 * Fired on the wpstream_livedrm request. Resolves the stream name to a post,
1432 * then either serves the key for a free event, or - for paid events - only
1433 * after verifying the logged-in user bought the product or holds an active
1434 * subscription. Free-event and paid-event post lookups are cached in
1435 * transients; the remote key itself is fetched and cached by the DRM Key
1436 * Delivery Module. The compatibility Adapter emits the returned result.
1437 *
1438 * Note: when session encryption is on, a missing session id aborts the request.
1439 *
1440 * @since 3.0.1
1441 * @return void Emits the key and exits, or returns early when no livedrm request.
1442 */
1443
1444
1445 public function wpstream_live_streaming_key() {
1446 $this->wpstream_emit_drm_key_request( 'live', 'wpstream_livedrm' );
1447 }
1448
1449 /**
1450 * Return restream (YouTube/Twitch) RTMP URLs for a third-party publisher.
1451 *
1452 * Fired on the thirdkeys request. Looks up the post whose live_event_carnat2
1453 * meta matches the supplied key and returns its configured YouTube/Twitch
1454 * RTMP targets as JSON. Emits '{}' when nothing matches. Prints and dies.
1455 *
1456 * @since 3.0.1
1457 * @return void Emits JSON and exits, or returns early when no thirdkeys request.
1458 */
1459
1460
1461 public function wpstream_live_streaming_key_for_3rdparty(){
1462
1463 // Only act when a non-empty thirdkeys request is present.
1464 if( isset( $_REQUEST[ 'thirdkeys' ]) && $_REQUEST[ 'thirdkeys' ]!='' ) {
1465
1466 // Sanitize the supplied third-party key.
1467 $thirdkeys = esc_html($_REQUEST[ 'thirdkeys' ]);
1468
1469 //live_event_carnat2
1470
1471 // Find the live/product post whose live_event_carnat2 meta matches the key.
1472 $args = array(
1473 'post_type' => array('product','wpstream_product'),
1474 'post_status' => 'publish',
1475 'meta_query' =>array(
1476 array(
1477 'key' => 'live_event_carnat2',
1478 'value' => $thirdkeys,
1479 'compare' => '=',
1480 ),
1481 ),
1482
1483
1484 );
1485
1486
1487 $media_list= new WP_Query($args);
1488 if($media_list->have_posts()){
1489 // Return the first match's restream URLs as JSON.
1490 while($media_list->have_posts()):$media_list->the_post();
1491
1492 // Collect this post's YouTube and Twitch RTMP targets.
1493 $media_id = get_the_ID();
1494 $replay_array = array(
1495 // '', // fb will be here
1496 stripslashes( get_post_meta($media_id,'wpstream_youtube_rtmp',true )),
1497 stripslashes( get_post_meta($media_id,'wpstream_twich_rtmp',true) ),
1498 );
1499
1500 // Emit the URLs as JSON and stop.
1501 $reply_final=array('rtmp_urls'=>$replay_array);
1502 header('Content-Type: application/json;charset=utf-8');
1503 print json_encode($reply_final,JSON_UNESCAPED_SLASHES);
1504 die();
1505
1506
1507 endwhile;
1508 }else{
1509 // No match: return an empty JSON object.
1510 print'{}';
1511 die('');
1512 }
1513
1514 }
1515
1516 }
1517
1518
1519
1520 /**
1521 * HLS key-delivery endpoint for VOD (video-on-demand) items.
1522 *
1523 * Fired on the wpstream_voddrm request. Mirrors the live endpoint: resolves
1524 * the decryption key index to a post, serves the base64-decoded key for a
1525 * free VOD, or - for paid VOD - only after verifying the logged-in user
1526 * bought the product or holds an active subscription. Post lookups are
1527 * cached in transients. Prints the key and dies.
1528 *
1529 * Note: when session encryption is on, a missing session id aborts the request.
1530 *
1531 * @since 3.0.1
1532 * @return void Emits the key and exits, or returns when no voddrm request.
1533 */
1534 public function wpstream_live_streaming_key_vod() {
1535 $this->wpstream_emit_drm_key_request( 'vod', 'wpstream_voddrm' );
1536 }
1537
1538 /**
1539 * Translate one legacy WordPress request into the DRM Key Delivery Interface.
1540 *
1541 * @param string $kind `live` or `vod`.
1542 * @param string $parameter Legacy GET parameter name.
1543 * @return void
1544 */
1545 private function wpstream_emit_drm_key_request( $kind, $parameter ) {
1546 $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( (string) $_SERVER['REQUEST_METHOD'] ) : 'GET';
1547 if ( 'GET' !== $method || ! isset( $_GET[ $parameter ] ) || ! is_scalar( $_GET[ $parameter ] ) ) {
1548 return;
1549 }
1550
1551 $identifier = sanitize_text_field( wp_unslash( (string) $_GET[ $parameter ] ) );
1552 if ( '' === $identifier ) {
1553 return;
1554 }
1555
1556 $result = $this->main->drm_key_delivery->deliver(
1557 array(
1558 'kind' => $kind,
1559 'identifier' => $identifier,
1560 'has_session' => isset( $_SESSION['wpstream_id'] ),
1561 )
1562 );
1563 if ( ! is_array( $result ) || empty( $result['handled'] ) ) {
1564 return;
1565 }
1566
1567 status_header( isset( $result['status'] ) ? intval( $result['status'] ) : 200 );
1568 foreach ( (array) ( $result['headers'] ?? array() ) as $name => $value ) {
1569 header( $name . ': ' . $value, true );
1570 }
1571 print isset( $result['body'] ) ? $result['body'] : '';
1572 die();
1573 }
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589 /**
1590 * Open a wrapper div around WooCommerce product content, keyed to purchase state.
1591 *
1592 * Emits a different wrapper id depending on whether the logged-in viewer has
1593 * bought the current product, so CSS/JS can show or gate the content.
1594 *
1595 * @since 3.0.1
1596 * @return void Echoes an opening <div>.
1597 */
1598
1599
1600 public function wpstream_non_image_content_wrapper_start() {
1601 // Only wrap for logged-in viewers (purchase check needs a user).
1602 if ( is_user_logged_in() ) {
1603 global $product;
1604 $current_user = wp_get_current_user();
1605 // Resolve the current WooCommerce product.
1606 $product = wc_get_product();
1607
1608 if($product){
1609 $product_id = $product->get_id();
1610 // Buyers get the standard wrapper; non-buyers get the "no buy" wrapper.
1611 if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $product_id) ){
1612 echo '<div id="wpstream_product_wrap">';
1613 }else{
1614 echo '<div id="wpstream_product_wrap_no_buy">';
1615 }
1616 }
1617 }
1618
1619 }
1620
1621 /**
1622 * Close the wrapper opened by wpstream_non_image_content_wrapper_start().
1623 *
1624 * The closing div is currently commented out (left intentionally).
1625 *
1626 * @since 3.0.1
1627 * @return void
1628 */
1629
1630
1631 function wpstream_non_image_content_wrapper_end() {
1632 // echo '</div>';
1633 }
1634
1635
1636 /**
1637 * Append the WpStream access message to the WooCommerce thank-you page.
1638 *
1639 * @since 3.12
1640 * @param mixed $var Existing thank-you text passed by the filter.
1641 * @param WC_Order $order The completed order.
1642 * @return string The ordered-items access message.
1643 */
1644
1645 function wpstream_thankyou_extra($var,$order){
1646 // Reuse the shared message builder for the order.
1647 return $this->wpstream_get_ordered_items($order);
1648 }
1649
1650
1651 /**
1652 * Print the WpStream access message inside WooCommerce order emails.
1653 *
1654 * @since 3.12
1655 * @param WC_Order $order The order the email is for.
1656 * @param bool $sent_to_admin Whether the email is going to the admin.
1657 * @param bool $plain_text Whether the email is plain text.
1658 * @param WC_Email $email The email object.
1659 * @return void Echoes the access message.
1660 */
1661 function wpstream_email_order_details($order, $sent_to_admin, $plain_text, $email){
1662 // Output the shared message followed by a line break.
1663 print $this->wpstream_get_ordered_items($order).'</br>';
1664 }
1665
1666
1667
1668 /**
1669 * Compose the access message shown for a completed order / thank-you page.
1670 *
1671 * Builds a comma-separated list of purchased item links and substitutes it
1672 * into the configurable thank-you template - but only for streamable items
1673 * (live stream, VOD or subscription); other product types get a generic
1674 * "order received" message instead.
1675 *
1676 * @since 3.12
1677 * @param WC_Order $order The order to describe.
1678 * @return string The access or generic confirmation message.
1679 */
1680
1681 function wpstream_get_ordered_items($order){
1682
1683 // WooCommerce templates pass WC_Order|false (core order-received.php)
1684 // or null (thankyou.php no-order branch) - bail to the generic
1685 // message before dereferencing the order.
1686 if ( ! $order instanceof WC_Order ) {
1687 return esc_html__( 'Thank you. Your order has been received', 'wpstream' );
1688 }
1689
1690 // Start from the admin-configured thank-you template (contains the {item_link} placeholder).
1691 $message = esc_html( get_option('wpstream_product_thankyou','Thanks for your purchase. You can access your item at any time by visiting the following page: {item_link}')) ;
1692 $list = '';
1693 $product_id = 0;
1694
1695 // Build a comma-separated list of links to each purchased product.
1696 foreach( $order->get_items() as $line_item ) {
1697 // get_product() returns false for a deleted product or a line item
1698 // with no real product (e.g. the WooCommerce email-preview dummy
1699 // order carries product_id 0) - skip those instead of fataling.
1700 $product = $line_item->get_product();
1701 if ( ! $product ) {
1702 continue;
1703 }
1704 $list .= '<a href="'.esc_url( $product->get_permalink() ).'">'.esc_html( $product->get_title() ).'</a>, ';
1705 $product_id = $product->get_id();
1706 }
1707 // Trim the trailing separator and inject the list into the template.
1708 $list = trim($list,', ');
1709 $message = str_replace('{item_link}', $list, $message);
1710
1711 // No resolvable product (empty order, all products deleted, or the
1712 // email-preview dummy order): fall back to the generic confirmation.
1713 $product = $product_id ? wc_get_product( $product_id ) : false;
1714 if ( ! $product ) {
1715 return esc_html__( 'Thank you. Your order has been received', 'wpstream' );
1716 }
1717
1718 // Streamable purchases (live stream, VOD, subscription) keep the
1719 // access message; anything else gets a generic confirmation.
1720 // get_type() resolves from the product_type term SLUG, so it stays
1721 // correct even when a site renames the term's display name.
1722 $product_type = $product->get_type();
1723 if ( in_array( $product_type, array( 'live_stream', 'video_on_demand', 'subscription' ), true ) ) {
1724 return $message;
1725 }
1726
1727 return esc_html__( 'Thank you. Your order has been received', 'wpstream' );
1728
1729
1730 }
1731 }
1732