PluginProbe
WpStream – Live Streaming, Video on Demand, Pay Per View / 4.14.1
WpStream – Live Streaming, Video on Demand, Pay Per View v4.14.1
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 / includes / class-wpstream-player.php

class-wpstream-player.php in WpStream – Live Streaming, Video on Demand, Pay Per View 4.14.1, at includes/class-wpstream-player.php

708 lines 31.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Front-end video player for WpStream live channels and video-on-demand.
5 *
6 * This class is the WordPress and backwards-compatibility facade: it hooks
7 * the_content, decides whether the current user is entitled to watch, preserves
8 * the established public renderer methods, and exposes the status-poll AJAX
9 * endpoint. Wpstream_Playback_Presentation owns source resolution, engine
10 * selection, assets, and viewer-facing player markup.
11 *
12 * Live status is fetched from the WpStream cloud API and cached in a 45-second
13 * transient so repeated polls do not hammer the remote service.
14 *
15 * @package Wpstream
16 * @subpackage Wpstream/includes
17 * @author cretu
18 */
19
20 // Exit if accessed directly.
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit;
23 }
24
25 class Wpstream_Player{
26 /** @var object Main plugin instance; exposes shared services (live connection, quota manager). */
27 public $main;
28
29 /**
30 * @var Wpstream_Playback_Session Helper that issues/validates playback sessions for encrypted streams.
31 */
32 public $playback_session;
33
34 /**
35 * Wire up the player: keep a handle on the plugin, load the playback-session
36 * helper, and register the content filter, purchase check, and status-poll
37 * AJAX endpoints (both logged-in and nopriv).
38 *
39 * @param object $plugin_main Main plugin instance passed in by the loader.
40 */
41 public function __construct($plugin_main) {
42 // Keep a reference to the main plugin so we can reach its services later.
43 $this->main = $plugin_main;
44
45 // Instantiate the playback-session helper (used for encrypted streams).
46 $this->playback_session = new Wpstream_Playback_Session();
47
48 // Inject the player markup into single wpstream product/VOD pages.
49 add_filter( 'the_content',array($this, 'wpstream_filter_the_title') );
50 // On a WooCommerce single product, render the player only if the visitor already bought it.
51 add_action( 'woocommerce_before_single_product', array($this,'wpstream_user_logged_in_product_already_bought') );
52
53 // Front-end status poll endpoint for logged-in users.
54 add_action( 'wp_ajax_wpstream_player_check_status', array($this,'wpstream_player_check_status') );
55 // Same endpoint for logged-out visitors (nopriv) so public streams can poll too.
56 add_action('wp_ajax_nopriv_wpstream_player_check_status', array($this,'wpstream_player_check_status'));
57
58 }
59
60
61
62
63
64
65 /**
66 * AJAX endpoint: report whether a channel is currently live and, if so, its
67 * playback/stats/chat URLs. Called repeatedly by the front-end player JS.
68 *
69 * The posted id must be a published streaming channel or it is rejected
70 * before any work. Reads the channel status from a 45-second transient; on
71 * a cache miss it calls the WpStream cloud API and refreshes the transient.
72 * Liveness is public, but the playback/stats/chat URLs are returned only to
73 * callers who pass the player-render entitlement check; the response always
74 * carries exactly the five keys the player JS reads. Echoes a JSON payload
75 * and dies. No nonce check on purpose (see inline note) because the hosting
76 * page may be cached, which would invalidate the nonce.
77 *
78 * edited 4.0
79 *
80 * @return void Outputs JSON and terminates the request.
81 * @author cretu
82 */
83 public function wpstream_player_check_status(){
84 // did not add a nonce check here because this is a call done from the frontend
85 // and the page might be cached, so the nonce would not be valid
86 // Which channel/post the caller is asking about.
87 $channel_id = intval($_POST['channel_id']);
88
89 // Reject anything that is not a published streaming channel BEFORE any
90 // cache read, remote API call or meta write: the id must be a wpstream
91 // live/VOD post, or a WooCommerce product carrying one of the wpstream
92 // product types. This stops arbitrary-id probing from reaching the API
93 // or touching unrelated posts' meta.
94 $channel_post = get_post( $channel_id );
95 $is_valid_channel = false;
96 if ( $channel_post instanceof WP_Post && 'publish' === $channel_post->post_status ) {
97 if ( in_array( $channel_post->post_type, array( 'wpstream_product', 'wpstream_product_vod' ), true ) ) {
98 $is_valid_channel = true;
99 } elseif ( 'product' === $channel_post->post_type
100 && has_term( array( 'live_stream', 'video_on_demand', 'subscription' ), 'product_type', $channel_post ) ) {
101 $is_valid_channel = true;
102 }
103 }
104 if ( ! $is_valid_channel ) {
105 ob_end_clean();
106 wp_send_json_error( array( 'message' => esc_html__( 'Invalid channel.', 'wpstream' ) ), 400 );
107 }
108
109 // Channel state (cache, Baker fetch on miss, fresh-data meta sync) is
110 // owned by Wpstream_Channel_State — the poll only asks questions of it.
111 $state = Wpstream_Channel_State::for_channel( $channel_id, 'wpstream_player_check_status_note_from_js' );
112
113 // Live means playable: upstream status active AND a playback URL present.
114 if ( $state->is_live() ) {
115
116 // Entitlement gate: playback/stats/chat URLs go only to
117 // callers who pass the same check that decides whether the player
118 // renders at all (free wpstream post types for anyone; WooCommerce
119 // products only for buyers / active subscribers / bundle owners).
120 // Liveness itself stays visible so a paywall page can reflect it.
121 $viewer_is_entitled = wpstream_user_entitled_for_product( $channel_id );
122
123 // cleanup any previous echo before sending json
124 ob_end_clean();
125 // Both branches return exactly the keys the player JS reads —
126 // never the raw upstream payload or any debug field.
127 if ( $viewer_is_entitled ) {
128 echo json_encode( array(
129 'started' => 'yes',
130 'channel_id' => $channel_id,
131 'event_uri' => $state->playback_url(),
132 'live_conect_views' => $state->stats_url(),
133 'chat_url' => $state->chat_url(),
134 ));
135 } else {
136 // Unentitled caller: same JSON shape, real liveness, but every
137 // URL field empty.
138 echo json_encode( array(
139 'started' => 'yes',
140 'channel_id' => $channel_id,
141 'event_uri' => '',
142 'live_conect_views' => '',
143 'chat_url' => '',
144 ));
145 }
146
147 }else{
148 // Channel is not live: return a "started: no" payload with empty
149 // URLs, in the same five-key shape as the live branch.
150 // cleanup any previous echo before sending json
151 ob_end_clean();
152 echo json_encode( array(
153 'started' => 'no',
154 'channel_id' => $channel_id,
155 'event_uri' => '',
156 'live_conect_views' => '',
157 'chat_url' => '',
158 ));
159
160 }
161
162 // AJAX response is complete; stop execution so nothing else is appended.
163 die();
164 }
165
166
167
168 /**
169 * the_content filter: prepend the player markup to single live/VOD product pages.
170 *
171 * @param string $content The post content WordPress is about to render.
172 * @return string Content with the player div prepended, or unchanged.
173 * @author cretu
174 */
175 public function wpstream_filter_the_title( $content ) {
176 // Only inject on single WpStream live-product or VOD-product pages.
177 if( is_singular('wpstream_product') || is_singular('wpstream_product_vod') ){
178 global $post;
179 // The theme may own the player placement and switch the injection off.
180 if ( ! $this->auto_insert_enabled( intval( $post->ID ), 'the_content' ) ) {
181 return $content;
182 }
183 // Build the player HTML for the current post and prepend it to the content.
184 $args=array('id'=>$post->ID);
185 $custom_content = $this->wpstream_insert_player_inpage($args);
186 $content = '<div class="wpestream_inserted_player">'.$custom_content.'</div>'.$content;
187 return $content;
188 }else{
189 // Not a player page: return content untouched.
190 return $content;
191 }
192 }
193
194 /**
195 * Decide whether the plugin injects its player automatically into a page.
196 *
197 * Themes that render the player in their own templates switch this off.
198 * Replaces the `wpstream_remove_wpstream_filter()` function-name sniff,
199 * which is honoured as the default for one more release.
200 *
201 * @param int $post_id Post the page is about.
202 * @param string $context `the_content` or `woocommerce_before_single_product`.
203 * @return bool
204 */
205 private function auto_insert_enabled( $post_id, $context ) {
206 // One-release shim: a theme defining the legacy opt-out function still opts out.
207 $enabled = ! function_exists( 'wpstream_remove_wpstream_filter' );
208
209 /**
210 * Filter whether the plugin inserts its player into the page automatically.
211 *
212 * Only the automatic placements (post content, WooCommerce single product)
213 * are affected; shortcodes and builder widgets always render.
214 *
215 * @since 4.14.0
216 *
217 * @param bool $enabled Whether to insert the player.
218 * @param int $post_id Post the page is about.
219 * @param string $context `the_content` or `woocommerce_before_single_product`.
220 */
221 return (bool) apply_filters( 'wpstream_player_auto_insert', $enabled, $post_id, $context );
222 }
223
224 /**
225 * Build the access notice shown in place of the player.
226 *
227 * @param string $reason `nobuy`, `nolog` or `sub_active`.
228 * @param int $product_id Product the notice is about.
229 * @param string $message Configured wording for the reason.
230 * @param string $wrapper_class Extra class on the outer wrapper (shortcode placement).
231 * @param string $notice_style Inline style kept by the shortcode placement.
232 * @return string Notice markup.
233 */
234 private function not_entitled_notice( $reason, $product_id, $message, $wrapper_class = '', $notice_style = '' ) {
235 /**
236 * Filter the wording of the access notice shown in place of the player.
237 *
238 * @since 4.14.0
239 *
240 * @param string $message Plain-text message (not yet escaped).
241 * @param int $product_id Product the notice is about.
242 * @param string $reason `nobuy`, `nolog` or `sub_active` — the entitlement path,
243 * not the wording (global-subscription sites show the
244 * "not subscribed" text under `nobuy`).
245 */
246 $message = (string) apply_filters( 'wpstream_player_not_entitled_message', $message, $product_id, $reason );
247
248 // Assemble the block with the message escaped; the style is a plugin constant.
249 $style = '' !== $notice_style ? ' style="' . esc_attr( $notice_style ) . '"' : '';
250 $html = '<div class="wpstream_player_wrapper ' . esc_attr( trim( $wrapper_class . ' no_buy' ) ) . '"><div class="wpstream_player_container">'
251 . '<div class="wpstream_notice"' . $style . '>' . esc_html( $message ) . '</div>'
252 . '</div></div>';
253
254 /**
255 * Filter the whole access notice block shown in place of the player.
256 *
257 * The default markup is already escaped; callbacks own the escaping of
258 * anything they add. This decides presentation only — entitlement was
259 * refused before this point and is not revisited.
260 *
261 * @since 4.14.0
262 *
263 * @param string $html Notice markup.
264 * @param int $product_id Product the notice is about.
265 * @param string $reason `nobuy`, `nolog` or `sub_active` (the entitlement path).
266 */
267 return (string) apply_filters( 'wpstream_player_not_entitled_html', $html, $product_id, $reason );
268 }
269
270 /**
271 * Build the player HTML for a shortcode/embed and return it as a string.
272 *
273 * Buffers the echoed player markup so it can be returned instead of printed.
274 *
275 * @param array $attributes Shortcode attributes; 'id' selects the product.
276 * @param string|null $content Unused enclosed shortcode content.
277 * @return string The captured player markup.
278 * @author cretu
279 */
280 public function wpstream_insert_player_inpage($attributes, $content = null){
281 $product_id = '';
282 $return_string = '';
283 // Normalise the incoming attributes against the supported defaults.
284 $attributes = shortcode_atts(
285 array(
286 'id' => 0,
287 ), $attributes) ;
288
289
290 // Take the product id from the attributes when present.
291 if ( isset($attributes['id']) ){
292 $product_id=$attributes['id'];
293 }
294
295 // No explicit id: fall back to the first available product id. The
296 // lookup lives on the main plugin class, not on this player service.
297 if(intval($product_id)==0){
298 $product_id= $this->main->wpstream_player_retrieve_first_id();
299 }
300
301 // Capture everything the shortcode renderer echoes into a string.
302 ob_start();
303
304 $this->wpstream_video_player_shortcode($product_id);
305 $return_string= ob_get_contents();
306 ob_end_clean();
307
308 return $return_string;
309 }
310
311
312
313
314 /**
315 * Render the player for a product, gated by login and purchase entitlement.
316 *
317 * Picks the live or VOD renderer based on product type / taxonomy term, and
318 * shows a "not purchased" / "must log in" notice when the visitor is not
319 * entitled to watch.
320 *
321 * @param string|int $from_sh_id Product/post id to render.
322 * @return void Echoes the player markup or an access notice.
323 * @author cretu
324 */
325 public function wpstream_video_player_shortcode($from_sh_id='') {
326 // disable cache in order to be able to check the nonce
327 if ( !defined( 'DONOTCACHEPAGE' ) ) {
328 define( 'DONOTCACHEPAGE', true );
329 }
330
331 $product_id = intval( $from_sh_id );
332 $product_post = get_post( $product_id );
333 if ( ! $product_post instanceof WP_Post
334 || ( 'publish' !== $product_post->post_status && ! current_user_can( 'read_post', $product_id ) ) ) {
335 return;
336 }
337
338 // Player depends on Video.js plus the WpStream player glue script (+styles).
339 wpstream_enqueue_player_assets();
340
341 if ( is_user_logged_in() ) {
342 // Logged-in branch: gather the identifier needed to check entitlement.
343 // Entitlement gate: the single shared rule (free type / purchase /
344 // active subscription / bundle / Netflix mode).
345 if ( wpstream_user_entitled_for_product( $product_id ) ){
346 // Open the player wrapper markup.
347 echo '<div class="wpstream_player_wrapper wpstream_player_shortcode"><div class="wpstream_player_container">';
348 echo $this->main->playback_presentation->render( $product_id, 'auto' );
349 // Close the player wrapper markup.
350 echo '</div></div>';
351 }else{
352 // Logged in but not entitled: show a "not purchased" notice for paid products.
353 if( get_post_type($product_id) == 'product' ){
354 $message = (string) get_option( 'wpstream_product_not_bought', 'You did not yet purchase this item.' );
355 echo $this->not_entitled_notice( 'nobuy', $product_id, $message, 'wpstream_player_shortcode', 'background:#e16767;' );
356 }
357 }
358
359
360 }else{
361 // Logged-out branch. Stable type slug is '' when the product has no term.
362 $product_type_slug = wpstream_get_product_type_slug($product_id);
363
364 if( get_post_type($product_id) == 'product' && ($product_type_slug=='live_stream' || $product_type_slug=='video_on_demand') ){
365 // Paid live/VOD product requires login: show a "must log in" notice.
366 $message = (string) get_option( 'wpstream_product_not_login', 'You must be logged in to watch this video.' );
367 echo $this->not_entitled_notice( 'nolog', $product_id, $message, 'wpstream_player_shortcode', 'background:#e16767;' );
368 }elseif( get_post_type($product_id) == 'wpstream_product' ){
369 // Free live product is watchable without login.
370 echo '<div class="wpstream_player_wrapper wpstream_player_shortcode"><div class="wpstream_player_container">';
371 echo $this->main->playback_presentation->render( $product_id, 'auto' );
372 echo '</div></div>';
373 } else if( get_post_type($product_id) == 'wpstream_product_vod' ){
374 // Free VOD product is watchable without login.
375 echo '<div class="wpstream_player_wrapper wpstream_player_shortcode"><div class="wpstream_player_container">';
376 echo $this->main->playback_presentation->render( $product_id, 'auto' );
377 echo '</div></div>';
378 }
379 }
380 }
381
382
383
384 /**
385 * Strip a leading http:// or https:// scheme from a URL.
386 *
387 * @param string $url URL to normalise.
388 * @return string URL without its leading scheme, or unchanged if none matched.
389 * @author cretu
390 */
391 function remove_http($url) { return $this->main->playback_presentation->remove_http( $url ); }
392
393 /**
394 * Derive the "live connect" stats URI from an event-status payload.
395 *
396 * @deprecated Kept because separately-installed themes may call it; the
397 * derivation now lives in Wpstream_Channel_State.
398 * @param array $event_status Status data from the cloud API.
399 * @return string The stats URL, a host derived from the playback URL, or ''.
400 */
401 function wpstream_get_live_connect_uri($event_status) {
402 return Wpstream_Channel_State::derive_stats_url( $event_status );
403 }
404
405 /**
406 * Resolve the effective event settings for a product: per-event options when
407 * enabled, otherwise the site-wide global channel options.
408 *
409 * @param int $product_id Product/channel id.
410 * @return mixed The per-event options array, or the global options.
411 * @author cretu
412 */
413 function wpstream_return_event_settings($product_id){ return $this->main->playback_presentation->wpstream_return_event_settings( $product_id ); }
414
415 /**
416 * Deprecated misspelled alias of wpstream_return_event_settings().
417 *
418 * Kept because the method is public and may be called by external code.
419 *
420 * @deprecated 4.13.4 Use wpstream_return_event_settings() instead.
421 */
422 function wpestream_return_event_settings($product_id){ return $this->wpstream_return_event_settings( $product_id ); }
423
424 /**
425 * Render the live event player for a channel.
426 *
427 * Fetches channel status from the cloud API (cached 45s in a transient),
428 * then renders either the legacy Video.js markup or, when the channel has an
429 * embedUrl, the newer iframe-based player with a minted embed key.
430 *
431 * edited in 4.0
432 *
433 * @param int $channel_id Channel/post id.
434 * @param string $poster_show Pass 'no' to suppress the poster image.
435 * @return void Echoes the player markup.
436 * @author cretu
437 */
438 function wpstream_live_event_player($channel_id,$poster_show=''){ $this->main->playback_presentation->wpstream_live_event_player( $channel_id, $poster_show ); }
439
440
441 /*
442 * Ask the cloud API for a VOD's HLS manifest URL (and DRM keys/embed data).
443 *
444 * Persists a 5-minute transient of the HLS URL and stores decryption keys /
445 * embed metadata as post meta. Note: the transient read is immediately
446 * overwritten with false below, so the API is currently queried every call.
447 *
448 * @param string $video_name Remote video name/identifier.
449 * @param int $product_id Product/post to store the resulting meta on.
450 * @return string HLS URL on success, '' on failure.
451 */
452 public function wpstream_request_video_on_demand_hls_player($video_name,$product_id){ return $this->main->playback_presentation->wpstream_request_video_on_demand_hls_player( $video_name, $product_id ); }
453
454 /**
455 * Resolve the CSS class for the configured Video.js theme/skin.
456 *
457 * Enqueues the theme stylesheet as a side effect. Streamify users get no theme.
458 *
459 * @param int|null $channel_id Channel/product id (used for the streamify check).
460 * @return string A 'vjs-theme-*' class, or '' when no theme applies.
461 */
462 function wpstream_get_player_theme( $channel_id = null ) { return $this->main->playback_presentation->wpstream_get_player_theme( $channel_id ); }
463
464 /**
465 * Enqueue the bundled Video.js theme stylesheet for the selected skin.
466 *
467 * @param string $player_theme Theme slug (skips the built-in 'default').
468 * @return void
469 */
470 function wpstream_enqueue_player_theme_style( $player_theme ) { $this->main->playback_presentation->wpstream_enqueue_player_theme_style( $player_theme ); }
471
472 /**
473 * Resolve the VOD source URL and type for a product.
474 *
475 * Encrypted VODs (and WooCommerce products) get an HLS manifest fetched
476 * from the cloud API; unencrypted VODs use the stored external/local URL.
477 *
478 * @param int $product_id Product/post id.
479 * @return array Keys: video_path_final, wpstream_data_setup, video_type,
480 * free_video_type, post_type.
481 * @author cretu
482 */
483 public function wpstream_video_on_demand_player_uri_request($product_id){ return $this->main->playback_presentation->resolve_vod_source( $product_id ); }
484
485
486
487 /**
488 * VODPlayer url
489 *
490 * @author cretu
491 */
492
493 public function wpstream_video_on_demand_player($product_id){ $this->main->playback_presentation->wpstream_video_on_demand_player( $product_id ); }
494
495
496
497 /**
498 * Render a VOD player that shows only the trailer (used by the theme).
499 *
500 * No main video source is set; playback is limited to the trailer plus
501 * its play/mute/unmute controls.
502 *
503 * @param int $product_id Product/post id.
504 * @return void Echoes the trailer-only player markup.
505 * @author cretu
506 */
507 public function wpstream_video_on_demand_player_only_trailer($product_id){ $this->main->playback_presentation->wpstream_video_on_demand_player_only_trailer( $product_id ); }
508
509 /**
510 * Decide whether the current user may watch the given product.
511 *
512 * @deprecated Kept as a compatibility shim for separately-installed themes
513 * that call it; the rule itself lives in wpstream_user_entitled_for_product()
514 * (includes/Helpers/wpstream-entitlement.php). New code calls the helper.
515 *
516 * @param int $product_id Product id.
517 * @return bool True when the user is entitled to watch.
518 * @since 3.12
519 */
520 public function wpstream_check_if_player_can_dsplay($product_id){
521 // Delegate to the single entitlement rule.
522 return wpstream_user_entitled_for_product( $product_id );
523 }
524
525
526
527 /**
528 * Theme variant of the entitlement check.
529 *
530 * @deprecated Kept as a compatibility shim for separately-installed themes
531 * that call it; the rule itself lives in wpstream_user_entitled_for_product()
532 * (includes/Helpers/wpstream-entitlement.php). New code calls the helper.
533 *
534 * @param int $product_id Product/post id.
535 * @return bool True when the player may be displayed.
536 * @since 3.12
537 */
538 public function wpstream_check_if_player_can_dsplay_theme($product_id){
539 // Delegate to the single entitlement rule.
540 return wpstream_user_entitled_for_product( $product_id );
541 }
542
543
544 /**
545 * Check the global-subscription entitlement for a product (Netflix mode).
546 *
547 * @deprecated Logic moved to wpstream_user_has_qualifying_global_subscription()
548 * (includes/Helpers/wpstream-entitlement.php); kept as a compatibility shim
549 * for external callers.
550 *
551 * @param int $product_id Product id.
552 * @return bool True when the user has a qualifying active subscription.
553 * @since 3.12
554 */
555 public function wpstream_in_plugin_check_global_subscription_model($product_id) {
556 // Delegate to the moved Netflix-mode leg of the entitlement rule.
557 return wpstream_user_has_qualifying_global_subscription( $product_id );
558 }
559
560
561
562
563
564
565
566
567
568 /**
569 *
570 *
571 *
572 *
573 * check if the user bought the product and display the player - TO REDo
574 *
575 * @since 3.0.1
576 * returns html of the player
577 *
578 *
579 *
580 *
581 *
582 */
583 public function wpstream_user_logged_in_product_already_bought($from_sh_id='') {
584 // Work against the current WooCommerce product on the page.
585 global $product;
586 if ( ! $product instanceof WC_Product ) {
587 return;
588 }
589 $product_id = $product->get_id();
590 $current_user = wp_get_current_user();
591 // The theme may own the player placement and switch the injection off.
592 if ( ! $this->auto_insert_enabled( intval( $product_id ), 'woocommerce_before_single_product' ) ) {
593 return;
594 }
595 // Stable type slug is '' when the product has no term (never a crash).
596 $product_type_slug = wpstream_get_product_type_slug($product_id);
597
598 // A "simple subscription" (neither live nor VOD) has no player here.
599 $is_simple_subscription = get_post_meta($product_id, '_subscript_live_event');
600 if ( $product_type_slug == 'subscription' && ! empty( $is_simple_subscription ) && $is_simple_subscription[0] == 'none' ) {
601 return;
602 }
603
604 if ( is_user_logged_in() ) {
605 if( wpstream_user_entitled_for_product($product_id) ){
606 // Entitled: the presentation module classifies live vs VOD
607 // (product term or subscription flag) and renders.
608 echo '<div class="wpstream_player_wrapper "><div class="wpstream_player_container">';
609 echo $this->main->playback_presentation->render( $product_id, 'auto' );
610 echo '</div></div>';
611 } else {
612 // Not entitled: show the appropriate "no buy" message.
613 if( $product_type_slug=='subscription' ){
614 // For subscriptions, only prompt when there is no active subscription.
615 if( !wcs_user_has_subscription( $current_user->ID, $product_id ,'active') ) {
616 $this->wpstream_display_no_buy_message('nobuy',$product_id);
617 }
618
619 }else{
620 $this->wpstream_display_no_buy_message('nobuy',$product_id);
621 }
622
623
624 }
625
626
627 }else{
628 // Logged out: prompt the visitor to log in.
629 $this->wpstream_display_no_buy_message('nolog',$product_id);
630 }
631 }
632
633
634
635
636
637 /**
638 * display no buy Message
639 * Render the appropriate access notice ("must log in" / "not purchased" /
640 * "not subscribed" / "subscription active") for a product.
641 *
642 * @param string $log Which case to show: 'sub_active', 'nolog', or other (nobuy).
643 * @param int $product_id Product/post id.
644 * @return void Echoes the notice markup.
645 * @since 3.12.2
646 */
647 public function wpstream_display_no_buy_message($log,$product_id) {
648 // Special case: subscription already active -> confirmation notice and return early.
649 if($log=='sub_active'){
650 $message = (string) get_option( 'wpstream_subscription_active', 'Your Subscription is Active.' );
651 echo $this->not_entitled_notice( 'sub_active', $product_id, $message );
652 return;
653
654 }else if($log=='nolog'){
655 // Visitor is logged out.
656 $reason = 'nolog';
657 $message = (string) get_option( 'wpstream_product_not_login', 'You must be logged in to watch this video.' );
658 }else{
659 // Default: logged in but has not purchased.
660 $reason = 'nobuy';
661 $message = (string) get_option( 'wpstream_product_not_bought', 'You did not yet purchase this item.' );
662 }
663 // In subscription mode, swap in the "not subscribed" wording.
664 $subscription_model = intval( get_option('wpstream_global_sub','')) ;
665 if($subscription_model==1){
666 $message = (string) get_option( 'wpstream_product_not_subscribe', 'You did not yet subscribe to this item.' );
667 }
668
669
670 if( get_post_type($product_id) == 'product' && $subscription_model==0 ){
671 // PPV mode: only show the notice for live/VOD/subscription product types.
672 $product = wc_get_product($product_id);
673 $product_type_slug = wpstream_get_product_type_slug($product_id);
674 $product_type = $product->get_type();
675 $is_subscription_live_event = esc_html(get_post_meta($product_id,'_subscript_live_event',true));
676
677
678
679 if( $product_type_slug=='video_on_demand' || $product_type_slug=='live_stream' || $product_type=='subscription'){
680 echo $this->not_entitled_notice( $reason, $product_id, $message );
681 }
682
683 }else if( get_post_type($product_id) == 'product' && $subscription_model==1 ){
684 // Subscription mode: show the notice for any non-simple product.
685 // '' (no term) counts as non-simple, matching the pre-guard intent.
686 if( wpstream_get_product_type_slug($product_id)!=='simple'){
687 echo $this->not_entitled_notice( $reason, $product_id, $message );
688 }
689 }
690 }
691
692 /**
693 * Get the video player logo URL.
694 *
695 * @param int $product_id Product ID.
696 * @return mixed|string Logo URL, the WpStream symbol for streamify users, or ''.
697 */
698 public function wpstream_get_video_player_logo( $product_id ) { return $this->main->playback_presentation->wpstream_get_video_player_logo( $product_id ); }
699
700 /*
701 * Check whether a channel/product is on a basic (streamify) subscription.
702 *
703 * @param int $channel_id Channel ID
704 * @return bool True when the 'basicStreaming' meta equals '1'.
705 */
706 public function wpstream_is_streamify_user( $channel_id ) { return $this->main->playback_presentation->wpstream_is_streamify_user( $channel_id ); }
707 }
708