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 / hello-wpstream / framework / wpstream-help-functions.php

wpstream-help-functions.php in WpStream – Live Streaming, Video on Demand, Pay Per View 4.14.0, at hello-wpstream/framework/wpstream-help-functions.php

879 lines 32.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Theme helper / utility functions for the bundled hello-wpstream companion theme.
4 *
5 * @package Wpstream
6 * @subpackage Wpstream/hello-wpstream
7 */
8
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 if ( ! function_exists( 'wpstream_theme_featured_image' ) ) {
16 /**
17 * Display the featured image for a post or page with additional functionality.
18 *
19 * @param int $post_id The ID of the post or page.
20 * @param string $size Optional. The image size to retrieve. Default is 'full'.
21 * @param string $context Optional. The context in which the function is being used. Default is 'unit_card'.
22 * @param bool $link Optional. Link to post.
23 * @param bool $show_badge Optional. Show badge.
24 * @return string The HTML markup for the featured image.
25 */
26 function wpstream_theme_featured_image( $post_id, $size = 'full', $context = 'unit_card', $link = false, $show_badge = false ) {
27 // Start with the theme's fallback cover image in case the post has none.
28 $featured_image_src = wpstream_theme_default_card_no_image_url( $size );
29 // Attachment ID of the post's featured image (0 when none is set).
30 $featured_image_id = get_post_thumbnail_id( $post_id );
31 // Alt text stored on the featured image attachment (used for the <img alt>).
32 $featured_image_alt = get_post_meta( $featured_image_id, '_wp_attachment_image_alt', true );
33
34 // Check if a featured image is set for the current post or page.
35
36 // When a real thumbnail exists, override the fallback with its URL.
37 if ( has_post_thumbnail( $post_id ) ) {
38 $featured_image_src = get_the_post_thumbnail_url( $post_id, $size );
39 }
40
41 // Optional short preview clip attached to the post (empty string when absent).
42 $video_attachment_id = get_post_meta( $post_id, 'video_preview', true );
43
44 // Open the thumbnail wrapper that all card markup is built inside.
45 $return_string = '<div class="post-thumbnail wpstream_featured_image">';
46
47 // When linking is requested, wrap the whole card in an anchor to the post.
48 if ( $link ) {
49 $return_string .= '<a class="d-block w-100 h-100" href="' . esc_url( get_permalink( $post_id ) ) . '">';
50 }
51
52 // Card contexts that support an inline hover/preview video AND actually have a clip:
53 // render a muted <video> instead of a plain image.
54 if ( ('unit_card' === $context || 'video_preview' === $context ) && '' !== $video_attachment_id ) {
55 // Unique DOM id so multiple preview players on one page don't collide.
56 $video_id = 'wpstream_unit_video_preview_' . wp_rand( 0, 99999999 );
57
58 // Re-read the preview attachment ID (kept for clarity of the source meta).
59 $video_attachment_id = get_post_meta( $post_id, 'video_preview', true );
60
61 // Resolve the preview clip's file URL for the <source> element.
62 $video_src = wp_get_attachment_url( $video_attachment_id );
63 // Only the dedicated "video_preview" context shows a play overlay by default.
64 $play_button = $context === 'video_preview' ? '<div class="wpstream_video_unit_video_play">' . wpstream_theme_get_svg_icon('play_icon_white.svg') . '</div>' : "";
65
66 // Blog posts get a themed "blog hover" icon rather than the generic play icon.
67 if ($context === 'video_preview' && get_post_type($post_id) === 'post'){
68 $play_button = '<div class="wpstream_video_unit_video_play">' . wpstream_theme_get_svg_icon('bloghover.svg') . '</div>';
69 }
70
71
72
73 // Append the preview-video markup: play button, hover overlay and the muted
74 // <video> element whose poster is the featured image and source is the clip.
75 $return_string .= '<div class="wpstream_video_unit_video_wrapper wpstream_video_unit_video_wrapper_trigger"
76 data-video-id="'. esc_attr( $video_id ).'">'
77
78 . $play_button
79 .'<div class="wpstream_video_unit_overlay"></div>'
80 .'<video id="' . esc_attr( $video_id ) . '" class="wpstream_video_unit_video" preload="none" poster="' . esc_url( $featured_image_src ) . '" muted>
81 <source src="' . esc_url( $video_src ) . '" type="video/mp4">
82 Your browser does not support the video tag.
83 </video>
84
85 </div>';
86 } else {
87 // No preview clip: render a still image card instead.
88 // Blog posts and non-video products use the "blog hover" icon; video products
89 // (VOD/live) use the white play icon.
90 if( get_post_type($post_id) === 'post' || !wpstream_is_woo_video_product( $post_id ) ){
91 $play_button = '<div class="wpstream_video_unit_video_play">' . wpstream_theme_get_svg_icon('bloghover.svg') . '</div>';
92 }else{
93 $play_button = '<div class="wpstream_video_unit_video_play">' . wpstream_theme_get_svg_icon('play_icon_white.svg') . '</div>';
94 }
95
96
97 // Overlay + play button + the featured image (alt falls back to the post title).
98 $return_string .= '<div class="wpstream_video_unit_overlay"></div>'. $play_button .'<img src="' . esc_url( $featured_image_src ) . '" alt="' . esc_attr( ! empty( $featured_image_alt ) ? $featured_image_alt : get_the_title( $post_id ) ) . '" class="wpstream_featured_unit_cards" />';
99 }
100
101 // Close the wrapping anchor when the card was made clickable.
102 if ( $link ) {
103 $return_string .= '</a>';
104 }
105
106 // On unit cards (or when a badge is forced) show the view/read counter.
107 if ( 'unit_card' === $context || $show_badge ) {
108 // Cached view count stored as post meta.
109 $views = intval( get_post_meta( $post_id, 'post_view_count', true ) );
110 // Default label for blog-style content is "reads".
111 $views_label = __( 'reads', 'hello-wpstream' );
112 // Product / video post types use "views" instead.
113 if ( in_array(
114 get_post_type( $post_id ),
115 array(
116 'wpstream_product_vod',
117 'wpstream_product',
118 'product',
119 'wpstream_bundles'
120 ),
121 true
122 ) ) {
123 $views_label = __( 'views', 'hello-wpstream' );
124 }
125 // Append the counter badge.
126 $return_string .= '<div class="wpstream_featured_image_views">' . $views . ' ' . esc_html( $views_label ) . '</div>';
127 }
128
129 // For live-stream products / free live events, add a "LIVE" tag when the current
130 // user actually has this event live right now.
131 if ( ( get_post_type( $post_id ) === 'product' && has_term( 'live_stream', 'product_type', $post_id ) || get_post_type( $post_id ) === 'wpstream_product' ) ) {
132
133 // Map of the current user's live events keyed by post ID.
134 $live_events = wpestream_integrations_get_current_user_live_events();
135 if ( is_array( $live_events) && array_key_exists( $post_id, $live_events ) ) {
136 $return_string .= '<div class="wpstream_featured_image_live_tag">' . esc_html_x( 'LIVE', 'Card tag', 'hello-wpstream' ) . '</div>';
137 }
138 }
139
140 // Close the thumbnail wrapper and hand back the assembled markup.
141 $return_string .= '</div><!-- .post-thumbnail -->';
142
143 return $return_string;
144 }
145 }
146
147 if ( ! function_exists( 'wpstream_theme_default_card_no_image_url' ) ) {
148 /**
149 * Returns the default URL for card images if no image is available.
150 *
151 * @param string $size The size of the card image.
152 * @return string The URL of the default image.
153 */
154 function wpstream_theme_default_card_no_image_url( $size ) {
155 // Bundle cards use a larger default cover; everything else uses the standard one.
156 if ( 'wpstream_bundle_unit_cards_image' === $size ) {
157 $image_path = '/img/default-cover-big.png';
158 } else {
159 $image_path = '/img/default-cover.png';
160 }
161
162 // Define the default image URL.
163 $default_image_url = get_stylesheet_directory_uri() . $image_path;
164
165 // Return the default image URL.
166 return esc_url( $default_image_url );
167 }
168 }
169
170 if ( ! function_exists( 'wpstream_theme_get_svg_icon' ) ) {
171 /**
172 * Get the SVG content from a specified SVG file.
173 *
174 * @param string $icon_path The path to the SVG icon file.
175 * @return string|false The SVG content or false if the file doesn't exist.
176 */
177 function wpstream_theme_get_svg_icon( $icon_path ) {
178 // Resolve the icon name to an absolute path under the theme's icon folder.
179 $icon_path = get_template_directory() . '/img/icons/' . $icon_path;
180
181 // Check if the file exists.
182 if ( file_exists( $icon_path ) ) {
183 // Buffer the included SVG file so its raw markup can be returned as a string.
184 ob_start();
185 include $icon_path;
186
187 return ob_get_clean();
188 } else {
189 return false; // Return false if the icon file doesn't exist.
190 }
191 }
192 }
193
194 if ( ! function_exists( 'wpstream_get_count_like_post' ) ) :
195 /**
196 * Get the count of likes for a specific post.
197 *
198 * @param int $post_id The ID of the post.
199 * @return int The count of likes for the post.
200 */
201 function wpstream_get_count_like_post( $post_id ) {
202 // Read the accumulated like counter stored in post meta and cast it to an int.
203 return intval( get_post_meta( $post_id, 'wpstream_like_items', true ) );
204 }
205 endif;
206
207 if ( ! function_exists( 'wpstream_query_arguments_add_order_by' ) ) {
208 /**
209 * Generate query arguments for ordering posts.
210 *
211 * @param int $order The order value.
212 * @return array An array containing the query arguments and transient appendix.
213 */
214 function wpstream_query_arguments_add_order_by( $order ) {
215 // Sensible defaults: newest-first by post ID, no meta key involved.
216 $meta_directions = 'DESC';
217 $meta_order = '';
218 $order_by = 'ID';
219
220 // Translate the numeric $order code into WP_Query orderby/order (and a meta key
221 // for meta-based sorts such as views, price and likes).
222 switch ( $order ) {
223 case 1:
224 $meta_order = '';
225 $meta_directions = 'DESC';
226 $order_by = 'ID';
227 break;
228 case 2:
229 $meta_order = '';
230 $meta_directions = 'ASC';
231 $order_by = 'ID';
232 break;
233 case 3:
234 $meta_order = '';
235 $meta_directions = 'DESC';
236 $order_by = 'modified';
237 break;
238 case 4:
239 $meta_order = '';
240 $meta_directions = 'ASC';
241 $order_by = 'modified';
242 break;
243 case 5:
244 $meta_order = 'post_view_count';
245 $meta_directions = 'DESC';
246 $order_by = 'meta_value_num';
247 break;
248 case 6:
249 $meta_order = 'post_view_count';
250 $meta_directions = 'ASC';
251 $order_by = 'meta_value_num';
252 break;
253 case 7:
254 $meta_order = '_price';
255 $meta_directions = 'DESC';
256 $order_by = 'meta_value_num';
257 break;
258 case 8:
259 $meta_order = '_price';
260 $meta_directions = 'ASC';
261 $order_by = 'meta_value_num';
262 break;
263 case 11:
264 $meta_order = 'wpstream_like_items';
265 $meta_directions = 'DESC';
266 $order_by = 'meta_value_num';
267 break;
268 case 12:
269 $meta_order = 'wpstream_like_items';
270 $meta_directions = 'ASC';
271 $order_by = 'meta_value_num';
272 break;
273 case 99:
274 $meta_order = '';
275 $meta_directions = 'ASC';
276 $order_by = 'rand';
277 break;
278 }
279
280 // Build a cache-key suffix that uniquely reflects this sort choice.
281 $transient_appendix = '_' . $meta_order . '_' . $meta_directions;
282
283 // Order 0 is the user's manual ordering; mark its cache key distinctly.
284 if ( 0 === $order ) {
285 $transient_appendix .= '_myorder';
286 }
287
288 // Base query-args always carry the orderby column.
289 $order_array = array(
290 'orderby' => $order_by,
291 );
292
293 // Add the meta key only for meta-value based sorts.
294 if ( '' !== $meta_order ) {
295 $order_array['meta_key'] = $meta_order;//phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
296 }
297
298 // Add the sort direction when one is defined.
299 if ( '' !== $meta_directions ) {
300 $order_array['order'] = $meta_directions;
301 }
302
303 // Return both the WP_Query fragment and the cache-key suffix.
304 return array(
305 'order_array' => $order_array,
306 'transient_appendix' => $transient_appendix,
307 );
308 }
309 }
310
311 if ( ! function_exists( 'wpstream_post_type_options_select' ) ) {
312 /**
313 * Generate a select dropdown for post type options.
314 *
315 * @param array $attributes An array of attributes for the select dropdown.
316 * @param string $selected_option The selected option.
317 * @return string The HTML for the select dropdown.
318 */
319 function wpstream_post_type_options_select( $attributes, $selected_option = '' ) {
320 // Available post-type choices (label => translated text).
321 $options = wpstream_post_type_options();
322
323 // Static field identifiers/classes for the AJAX-triggering dropdown.
324 $name = 'post_type';
325 $class = 'wpstream_dropdown_select wpstream_dropdown_select_trigger_ajax wpstream_dropdown_select_post_type';
326 $id = 'post_type_dropdown';
327 // Visible label comes from the caller's attributes.
328 $label = $attributes['label_post_types'];
329 // Delegate the actual markup to the shared custom-dropdown builder.
330 $output = wpstream_create_custom_dropdown( $options, $name, $class, $id, $label, $selected_option );
331
332
333 return $output;
334 }
335 }
336
337 if ( ! function_exists( 'wpstream_post_type_options' ) ) {
338 /**
339 * Retrieve an array of available post type options.
340 *
341 * @return array An associative array of post type options.
342 */
343 function wpstream_post_type_options() {
344 // Map each selectable post type to its human-readable, translated label.
345 $options = array(
346 'product' => esc_html__( 'Products', 'hello-wpstream' ),
347 'wpstream_bundles' => esc_html__( 'Bundles', 'hello-wpstream' ),
348 'wpstream_product_vod' => esc_html__( 'Free Vod', 'hello-wpstream' ),
349 'wpstream_product' => esc_html__( 'Free Events', 'hello-wpstream' ),
350 );
351
352 return $options;
353 }
354 }
355
356 if ( ! function_exists( 'wpstream_is_woo_video_product' ) ) {
357 /**
358 * Check if the product is a WpStream video product.
359 *
360 * @param int $product_id The ID of the product.
361 * @return bool True if the product is a video product, false otherwise.
362 */
363 function wpstream_is_woo_video_product( $product_id ) {
364 // WooCommerce product types that WpStream considers "video" products.
365 $wpstream_woo_product_types = ['video_on_demand', 'live_stream', 'wpstream_bundle', 'subscription'];
366
367 // Without WooCommerce there are no such products.
368 if ( ! function_exists( 'wc_get_product' ) ) {
369 return false;
370 }
371 // Load the product object for the given ID.
372 $product = wc_get_product( $product_id );
373
374 // Guard against an invalid / non-existent product.
375 if ( ! $product ) {
376 return false;
377 }
378
379 // True only when the product's type is one of the recognised video types.
380 return in_array( $product->get_type(), $wpstream_woo_product_types, true );
381 }
382 }
383
384 /**
385 * Get the full path for a given card type.
386 *
387 * @param string $card_type The card type template name.
388 * @return string The full path to the card type template.
389 */
390 if ( ! function_exists( 'wpstream_get_card_type_path' ) ) {
391 function wpstream_get_card_type_path($card_type)
392 {
393 // Resolve a card template name to its absolute path inside the theme folder.
394 return WPSTREAM_PLUGIN_PATH . 'hello-wpstream/' . $card_type;
395 }
396 }
397
398 if ( ! function_exists( 'wpstream_get_all_items_list' ) ) {
399 /**
400 * Get all items list.
401 *
402 * @param int $limit The limit of posts to retrieve.
403 * @param string $post_type The post type to retrieve.
404 * @param array $ids The array of post IDs to retrieve.
405 * @return array Array containing titles and types of retrieved posts.
406 */
407 function wpstream_get_all_items_list( $limit = -1, $post_type = 'free', $ids = array() ) {
408
409 $options = array();
410
411 // "free" means the two free WpStream post types with no taxonomy filter;
412 // anything else targets WooCommerce products (live/VOD or type-less products).
413 if ( 'free' === $post_type ) {
414 $post_type = array( 'wpstream_product_vod', 'wpstream_product' );
415 $taxonomy_array = array();
416 } else {
417 $post_type = array( 'product' );
418
419 $taxonomy_array = array(
420 'relation' => 'OR',
421 array(
422 'taxonomy' => 'product_type',
423 'field' => 'slug',
424 'terms' => array( 'live_stream', 'video_on_demand' ),
425 ),
426
427 array(
428 'taxonomy' => 'product_type',
429 'operator' => 'NOT EXISTS',
430 ),
431
432 );
433
434 }
435
436 // Base query: chosen post type(s), row limit and any taxonomy constraint.
437 $args = array(
438 'post_type' => $post_type,
439 'posts_per_page' => $limit,
440 'tax_query' => $taxonomy_array, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
441 );
442
443 // When explicit IDs are supplied, restrict to them and preserve their order.
444 if ( ! empty( $ids ) ) {
445 $args['post__in'] = $ids;
446 $args['orderby'] = 'post__in';
447 }
448
449 // Run the query.
450 $custom_post_types = get_posts( $args );
451
452 $options = array();
453
454 // Reduce each post to a compact title/type entry keyed by post ID.
455 foreach ( $custom_post_types as $custom_post ) {
456
457 $options[ $custom_post->ID ] = array(
458 'title' => $custom_post->post_title,
459 'type' => $custom_post->post_type,
460 );
461 }
462
463 return $options;
464 }
465 }
466
467
468 if ( ! function_exists( 'wstream_sort_options_array' ) ) {
469 /**
470 * Sort options array
471 *
472 * Builds the label list for the listing "sort by" dropdown. Each key is the
473 * numeric order code consumed by wpstream_query_arguments_add_order_by().
474 *
475 * @return array Order-code => translated label.
476 */
477 function wstream_sort_options_array() {
478 // Order codes mapped to their human-readable, translated dropdown labels.
479 $listing_filter_array = array(
480 '1' => esc_html__( 'Newest first', 'hello-wpstream' ),
481 '2' => esc_html__( 'Oldest first', 'hello-wpstream' ),
482 '5' => esc_html__( 'Views High to Low', 'hello-wpstream' ),
483 '6' => esc_html__( 'Views Low to high', 'hello-wpstream' ),
484 '11' => esc_html__( 'Liked High to Low', 'hello-wpstream' ),
485 '12' => esc_html__( 'Liked Low to high', 'hello-wpstream' ),
486 '7' => esc_html__( 'Price High to Low', 'hello-wpstream' ),
487 '8' => esc_html__( 'Price Low to High', 'hello-wpstream' ),
488 '3' => esc_html__( 'Newest Edited', 'hello-wpstream' ),
489 '4' => esc_html__( 'Oldest Edited ', 'hello-wpstream' ),
490 '0' => esc_html__( 'Default', 'hello-wpstream' ),
491 );
492
493 return $listing_filter_array;
494 }
495 }
496
497 if ( ! function_exists( 'wpstream_theme_is_show_sidebar' ) ) {
498 /**
499 * Returns true if the sidebar should be display for post.
500 *
501 * @param $post_id
502 * @return bool
503 */
504 function wpstream_theme_is_show_sidebar( $post_id = null ) {
505 $is_show_sidebar=null;
506 // Prefer a per-post override stored via Meta Box, when that plugin is active.
507 if(function_exists('rwmb_meta')){
508 $is_show_sidebar = rwmb_meta( 'wpstream_theme_show_sidebar_on_post', array(), $post_id );
509 }
510 // If the per-post override is set, honour it ('1' => show).
511 if ( '' !== $is_show_sidebar ) {
512 return '1' === $is_show_sidebar;
513 } else {
514 // Otherwise fall back to a per-post-type default.
515 $post_type = get_post_type( $post_id );
516
517 switch ( $post_type ) {
518 case 'post':
519 // Blog posts use the customizer sidebar setting (default: on).
520 $wpstream_show_sidebar = get_theme_mod( 'wpstream_blog_post_sidebar', true );
521 break;
522 default:
523 // Other post types default to off, but allow a filter override.
524 $wpstream_show_sidebar = apply_filters( 'wpstream_show_sidebar_for_post_type', false, $post_type );
525 break;
526 }
527
528 return $wpstream_show_sidebar;
529 }
530 }
531 }
532
533 if ( ! function_exists( 'wpstream_theme_featured_image_simple' ) ) {
534 /**
535 * Display the featured image for a post or page in a simple format.
536 *
537 * @param int $post_id The ID of the post or page.
538 * @param string $size Optional. The image size to retrieve. Default is 'full'.
539 * @param string $context Optional. The context in which the function is being used. Default is 'unit_card'.
540 * @return string The HTML markup for the featured image.
541 */
542 function wpstream_theme_featured_image_simple( $post_id, $size = 'full', $context = 'unit_card' ) {
543
544 // Start from the default cover; replaced below when a real thumbnail exists.
545 $featured_image_src = wpstream_theme_default_card_no_image_url( $size );
546
547 // Check if a featured image is set for the current post or page.
548 if ( has_post_thumbnail( $post_id ) ) {
549 $featured_image_src = get_the_post_thumbnail_url( $post_id, $size );
550 }
551
552 // Open the thumbnail wrapper.
553 $return_string = '<div class="post-thumbnail wpstream_featured_image">';
554
555 // Simple variant: just the image, no preview video or play overlay.
556 $return_string .= '<img src="' . esc_url( $featured_image_src ) . '" class="wpstream_featured_unit_cards rounded mb-3" />';
557
558 // On unit cards, append the view counter.
559 if ( 'unit_card' === $context ) {
560 $views = intval( get_post_meta( $post_id, 'post_view_count', true ) );
561 $return_string .= '<div class="wpstream_featured_image_views">' . $views . ' ' . esc_html__( 'views', 'hello-wpstream' ) . '</div>';
562 }
563
564 // Close the wrapper and return the markup.
565 $return_string .= '</div><!-- .post-thumbnail -->';
566
567 return $return_string;
568 }
569 }
570
571 if ( ! function_exists( 'wpstream_generate_user_menu' ) ) {
572 /**
573 * Generate user menu
574 *
575 * Builds the "My Account" navigation list. Uses WooCommerce's account menu
576 * items when WooCommerce is active, otherwise a minimal built-in menu. Each
577 * entry is prefixed with its themed SVG icon.
578 *
579 * @return string HTML markup for the account menu links.
580 */
581 function wpstream_generate_user_menu() {
582 $return_string = '';
583
584 // Map each known account endpoint to its inline SVG icon markup.
585 $svg_icons = [
586 'dashboard' => wpstream_theme_get_svg_icon('dashboard.svg'),
587 'orders' => wpstream_theme_get_svg_icon('orders-icon.svg'),
588 'downloads' => wpstream_theme_get_svg_icon('downloads.svg'),
589 'subscriptions' => wpstream_theme_get_svg_icon('orders-subscription.svg'),
590 'edit-address' => wpstream_theme_get_svg_icon('location-icon.svg'),
591 'edit-account' => wpstream_theme_get_svg_icon('edit-account-icon.svg'),
592 'event-list' => wpstream_theme_get_svg_icon('purchased-events-icon.svg'),
593 'video-list' => wpstream_theme_get_svg_icon('purchased-video-icon.svg'),
594 'start-streaming' => wpstream_theme_get_svg_icon('go-to-live-icon.svg'),
595 'watch-later' => wpstream_theme_get_svg_icon('watch-later-icon.svg'),
596 'customer-logout' => wpstream_theme_get_svg_icon('logout_icon.svg'),
597 'logout' => wpstream_theme_get_svg_icon('logout_icon.svg'),
598 ];
599
600 // Buffer the markup so it can be returned as a string.
601 ob_start();
602
603 // Preferred path: WooCommerce provides the canonical account menu items.
604 if ( function_exists( 'wc_get_account_menu_items' ) ) {
605 $menu_items = wc_get_account_menu_items();
606
607 // Hide the "Go Live" entry for users without streaming permission.
608 if ( ! wpstream_check_if_user_can_stream()) {
609 unset($menu_items['start-streaming']);
610 }
611
612 // Render each endpoint as an account list-group link.
613 foreach ( $menu_items as $endpoint => $label ) :
614 $menu_link= wc_get_account_endpoint_url( $endpoint ) ;
615 // Subscriptions are served from the theme dashboard, not the Woo endpoint.
616 if($endpoint === 'subscriptions') {
617 $menu_link = home_url('/dashboard/subscriptions/');
618 }
619 ?>
620 <a href="<?php echo esc_url($menu_link); ?>" class="list-group-item list-group-item-action wpstream-account-item-<?php echo esc_attr( $endpoint ); ?>">
621 <?php echo isset($svg_icons[$endpoint]) ? trim($svg_icons[$endpoint]) : ''; ?>
622 <?php echo trim( esc_html( $label ));?>
623 </a>
624 <?php
625 endforeach;
626
627 } else {
628 // Fallback path (no WooCommerce): a minimal built-in account menu.
629 $menu_items = array(
630 'dashboard' => esc_html__( 'Dashboard', 'hello-wpstream' ),
631 'start-streaming' => esc_html__( 'Go Live', 'hello-wpstream' ),
632 'edit-account' => esc_html__( 'Edit Account', 'hello-wpstream' ),
633 'watch-later' => esc_html__( 'Watch Later', 'hello-wpstream' ),
634 'logout' => esc_html__( 'Logout', 'hello-wpstream' ),
635 );
636
637 // Same streaming-permission gate as the WooCommerce branch.
638 if ( ! wpstream_check_if_user_can_stream()) {
639 unset($menu_items['start-streaming']);
640 }
641
642 // Render each endpoint, resolving links via the non-Woo helper.
643 foreach ( $menu_items as $endpoint => $label ) :
644 $item_link = wpstream_non_woo_get_account_endpoint_url( $endpoint );
645 // Logout needs the real WordPress logout URL (with redirect home).
646 if ( 'logout' === $endpoint ) {
647 $item_link = wp_logout_url( home_url() );
648 }
649 ?>
650
651 <a href="<?php echo esc_url( $item_link ); ?>"
652 class="list-group-item list-group-item-action wpstream-account-item-<?php echo esc_attr( $endpoint ); ?>">
653 <?php echo trim($svg_icons[ $endpoint ]) ?? ''; ?>
654 <?php echo esc_html( $label ); ?>
655 </a>
656 <?php
657
658 endforeach;
659
660 }
661
662 // Capture the buffered menu markup and clean the buffer.
663 $return_string = ob_get_contents();
664
665 ob_end_clean();
666
667 return $return_string;
668 }
669 }
670
671 if ( ! function_exists( 'wpstream_dashboard_get_products_by_user' ) ) {
672 /**
673 * Retrieve products purchased by a specific user.
674 *
675 * Retrieves all products of a specific type (default: 'video_on_demand') purchased by a user.
676 *
677 * @param int $user_id The ID of the user.
678 * @param string $product_type The type of products to retrieve. Default is 'video_on_demand'.
679 * @param int $page Optional. The page number of the results. Default is 1.
680 * @param int $per_page Optional. The number of products to retrieve per page. Default is 10.
681 * @return array An array containing paginated products, total number of products, and maximum number of pages.
682 */
683 function wpstream_dashboard_get_products_by_user( $user_id, $product_type = 'video_on_demand', $page = 1, $per_page = 10 ) {
684 $all_products = array();
685
686 // Get all customer orders.
687 $customer_orders = wc_get_orders(
688 array(
689 'customer_id' => $user_id,
690 'status' => 'completed',
691 'limit' => -1
692 )
693 );
694
695 // Loop through each customer order.
696 foreach ( $customer_orders as $customer_order ) {
697 // Order line items and order-level metadata (id + created date).
698 $order_items = $customer_order->get_items();
699 $order = wc_get_order( $customer_order );
700 $order_id = $order->get_id();
701 $order_date = $order->get_date_created();
702 $formatted_date = wc_format_datetime( $order_date );
703
704 // Inspect each line item in the order.
705 foreach ( $order_items as $item ) {
706 $product = $item->get_product();
707
708 // Keep only products matching the requested type.
709 if ( $product && $product_type === $product->get_type() ) {
710 // Record what was paid plus the order context for this product.
711 $total_paid = $item->get_total();
712 $temp_array = array(
713 'price' => $total_paid,
714 'order_id' => $order_id,
715 'order_date' => $formatted_date,
716 'product' => $product,
717 );
718
719 // Key by product ID so duplicate purchases collapse to one entry.
720 $all_products[$product->get_id()] = $temp_array;
721 }
722 }
723 }
724
725 // per_page of -1 means "return everything" without pagination.
726 if(intval($per_page) === -1){
727 return $all_products;
728 }
729
730 // Calculate offset for pagination.
731 $offset = ( $page - 1 ) * $per_page;
732 // Slice out just the requested page of products.
733 $paginated_products = array_slice( $all_products, $offset, $per_page );
734
735 // Return the page plus total counts for building pagination controls.
736 return array(
737 'products' => $paginated_products,
738 'total' => count( $all_products ),
739 'max_num_pages' => ceil( count( $all_products ) / $per_page ),
740 );
741 }
742 }
743
744 if ( ! function_exists( 'wpstream_non_woo_get_account_endpoint_url' ) ) {
745 /**
746 * Retrieves the URL for a given account endpoint.
747 *
748 * @param string $endpoint Optional. Endpoint to append to the URL.
749 * @return string URL for the account endpoint.
750 */
751 function wpstream_non_woo_get_account_endpoint_url( $endpoint = '' ) {
752 // Base URL is the theme's dashboard page.
753 $url = wpstream_return_dashboard_page();
754
755 // Append the endpoint as a query arg when one is given.
756 if ( $endpoint ) {
757 $url = add_query_arg( 'endpoint', $endpoint, $url );
758 }
759
760 // Return a sanitised URL safe for storage/output.
761 return esc_url_raw( $url );
762 }
763 }
764
765 if ( ! function_exists( 'wpstream_theme_show_social_share_page' ) ) {
766 /**
767 * Display social share buttons for a given post.
768 *
769 * @param int $post_id The ID of the post.
770 * @param string $size Optional. The size of the post image. Default is 'full'.
771 * @param int $is_single Optional. Whether the post is single or not. Default is 1 (true).
772 * @return string The HTML markup for the social share buttons.
773 */
774 function wpstream_theme_show_social_share_page( $post_id, $size = 'full', $is_single = 1 ) {
775 // Thin wrapper: open a container, delegate to the share-buttons builder, close it.
776 $return_string = '<div class="wpstream-social-share-wrapper">';
777 $return_string .= wpstream_share_unit_desing( $post_id, $size, $is_single );
778 $return_string .= '</div>';
779
780 return $return_string;
781 }
782 }
783
784 if ( ! function_exists( 'wpstream_share_unit_desing' ) ) {
785 /**
786 * Generate social share buttons HTML for a post.
787 *
788 * @param int $post_id The ID of the post.
789 * @param string $size Optional. The size of the post image. Default is 'full'.
790 * @param int $is_single Optional. Whether the post is single or not. Default is 0 (false).
791 * @return string The HTML markup for the social share buttons.
792 */
793 function wpstream_share_unit_desing( $post_id, $size = 'full', $is_single = 0 ) {
794 // Gather the pieces every share URL needs: scheme, image, permalink and title.
795 $protocol = is_ssl() ? 'https' : 'http';
796 $pinterest = wp_get_attachment_image_src( get_post_thumbnail_id( $post_id ), $size );
797 $link = esc_url( get_permalink( $post_id ) );
798 $title = get_the_title( $post_id );
799 // Pre-encoded payloads for the Twitter/X intent and the mailto link.
800 $twitter_status = rawurlencode( $title . ' ' . $link );
801 $email_link = 'subject=' . rawurlencode( $title ) . '&body=' . rawurlencode( esc_url( $link ) );
802
803 // Buffer the button markup for return as a string.
804 ob_start();
805
806 // Text labels default to empty (icon-only) ...
807 $facebook_label = '';
808 $twitter_label = '';
809 $pinterest_label = '';
810 $whatsup_label = '';
811 $email_label = '';
812
813 // ... and are filled in only on single/detail pages where labels are wanted.
814 if ( intval( $is_single ) === 1 ) {
815 $facebook_label = esc_html__( 'Facebook', 'hello-wpstream' );
816 $twitter_label = esc_html__( 'Twitter', 'hello-wpstream' );
817 $pinterest_label = esc_html__( 'Pinterest', 'hello-wpstream' );
818 $whatsup_label = esc_html__( 'WhatsApp', 'hello-wpstream' );
819 $email_label = esc_html__( 'Email', 'hello-wpstream' );
820 }
821
822 // WhatsApp share text (title + permalink) and its wa.me deep link.
823 $text_whats = get_the_title( $post_id ) . ' ' . esc_url( get_permalink( $post_id ) );
824
825 $whatsup_link = 'https://wa.me/?text=' . ( $text_whats );
826
827 ?>
828
829 <!-- Social share buttons: each anchor opens the matching network's share dialog (or a mailto). Icons are inline SVG; labels only render on single pages. -->
830 <div class="share_unit">
831 <a href="<?php echo esc_url( $protocol . '://www.facebook.com/sharer.php?u=' . $link . '&amp;t=' . rawurlencode( get_the_title() ) ); ?>"
832 target="_blank" rel="noreferrer noopener nofollow" class="social_facebook d-flex align-items-center">
833
834 <?php echo wpstream_theme_get_svg_icon( 'facebook.svg' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
835
836 <?php echo esc_html( $facebook_label ); ?>
837
838 </a>
839 <a href="<?php echo esc_url( $protocol . '://twitter.com/intent/tweet?text=' . $twitter_status ); ?>"
840 class="social_twitter d-flex align-items-center" rel="noreferrer noopener nofollow" target="_blank">
841
842 <?php echo wpstream_theme_get_svg_icon( 'x_twitter.svg' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
843
844 <?php echo esc_html( $twitter_label ); ?>
845
846 </a>
847 <a href="<?php echo esc_url( $protocol . '://pinterest.com/pin/create/button/?url=' . $link . '&amp;media=' .
848 ( isset( $pinterest[0] ) ? esc_url( $pinterest[0] ) : '' ) .
849 '&amp;description=' . rawurlencode( get_the_title() ) ); ?>" target="_blank"
850 rel="noreferrer noopener nofollow" class="social_pinterest d-flex align-items-center">
851
852 <?php echo wpstream_theme_get_svg_icon( 'pinterest.svg' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
853
854 <?php echo esc_html( $pinterest_label ); ?>
855
856 </a>
857 <a href="<?php echo esc_url( $whatsup_link ); ?>" class="social_whatsapp d-flex align-items-center"
858 rel="noreferrer noopener nofollow" target="_blank">
859
860 <?php echo wpstream_theme_get_svg_icon( 'whatsapp.svg' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
861
862 <?php echo esc_html( $whatsup_label ); ?>
863
864 </a>
865 <a href="mailto:email@email.com?<?php echo esc_attr( trim( $email_link ) ); ?>" data-action="share email"
866 class="social_email d-flex align-items-center" rel="noreferrer noopener nofollow">
867
868 <?php echo wpstream_theme_get_svg_icon( 'email.svg' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
869
870 <?php echo esc_html( $email_label ); ?>
871
872 </a>
873 </div>
874 <?php
875
876 // Return the buffered share-buttons markup.
877 return ob_get_clean();
878 }
879 }