clear_old_logs(); } // Run the cleanup handler whenever the scheduled 'wpstream_log_cleanup' event fires. add_action('wpstream_log_cleanup', 'wpstream_cleanup_logs_handler'); /** * The code that runs during plugin activation. * This action is documented in includes/class-wpstream-activator.php */ function activate_wpstream() { // Load the activator class on demand. require_once plugin_dir_path( __FILE__ ) . 'includes/class-wpstream-activator.php'; // Run the activation routine (creates DB tables, options, etc.). Wpstream_Activator::activate(); // Schedule the daily log-cleanup cron event if it isn't already scheduled. if( !wp_next_scheduled( 'wpstream_log_cleanup' ) ) { // Register a daily recurring event starting now. wp_schedule_event( time(), 'daily', 'wpstream_log_cleanup' ); } } /** * The code that runs during plugin deactivation. * This action is documented in includes/class-wpstream-deactivator.php */ function deactivate_wpstream() { // Load the deactivator class on demand. require_once plugin_dir_path( __FILE__ ) . 'includes/class-wpstream-deactivator.php'; // Run the deactivation routine. Wpstream_Deactivator::deactivate(); // Remove the scheduled daily log-cleanup cron event. wp_clear_scheduled_hook( 'wpstream_log_cleanup' ); } // Register the activation/deactivation callbacks with WordPress. register_activation_hook( __FILE__, 'activate_wpstream' ); register_deactivation_hook( __FILE__, 'deactivate_wpstream' ); /** * One-time cleanup of leftovers from the removed streamify HLS proxy. * * Older plugin versions shipped a /wpstreamify/ proxy that scheduled a * recurring cron event, registered a rewrite rule, and kept a cache directory * under uploads. Sites upgrading past its removal still carry all three, so * this clears them once and records a marker option so later loads skip it. * * @return void */ function wpstream_cleanup_streamify_leftovers() { // Already cleaned on this site — nothing to do. if ( get_option( 'wpstream_streamify_removed' ) ) { return; } // Remove the recurring proxy-cache cleanup event the old code scheduled. wp_clear_scheduled_hook( 'wpstreamify_cleanup_event' ); // Regenerate the saved rewrite rules so the stale /wpstreamify/ route // (whose query var no longer exists) drops out of them. flush_rewrite_rules(); // Delete the orphaned proxy cache directory and its segment files. $wpstream_streamify_cache_dir = WP_CONTENT_DIR . '/uploads/wpstreamify_cache/'; if ( is_dir( $wpstream_streamify_cache_dir ) ) { $wpstream_streamify_files = glob( $wpstream_streamify_cache_dir . '*' ); foreach ( is_array( $wpstream_streamify_files ) ? $wpstream_streamify_files : array() as $wpstream_streamify_file ) { if ( is_file( $wpstream_streamify_file ) ) { unlink( $wpstream_streamify_file ); } } rmdir( $wpstream_streamify_cache_dir ); } // Mark the cleanup done; autoloaded because it is checked on every load. update_option( 'wpstream_streamify_removed', 1 ); } // Run late on init so every remaining rewrite registration precedes the flush. add_action( 'init', 'wpstream_cleanup_streamify_leftovers', 99 ); /** * One-time rename of the VOD list Elementor widget key in stored page data. * * Older plugin versions registered the widget under 'class Wpstream_Media_List_Vod' * (a stray "class " prefix left in get_name()). The name is now the clean * 'Wpstream_Media_List_Vod', so any Elementor page saved with the old key would * render the widget as "missing". This rewrites the stored widgetType in every * _elementor_data row (pages and revisions alike), clears Elementor's generated * CSS so the wrapper class regenerates, and records a marker option so later * loads skip it. * * @return void */ function wpstream_fix_vod_widget_name() { // Already migrated on this site — nothing to do. if ( get_option( 'wpstream_vod_widget_name_fixed' ) ) { return; } global $wpdb; // Rewrite the exact quoted JSON pair so user-authored content can never match. $wpdb->query( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE( meta_value, '\"widgetType\":\"class Wpstream_Media_List_Vod\"', '\"widgetType\":\"Wpstream_Media_List_Vod\"' ) WHERE meta_key = '_elementor_data' AND meta_value LIKE '%class Wpstream_Media_List_Vod%'" ); // Elementor caches per-post CSS containing the old wrapper class; regenerate it. if ( class_exists( '\\Elementor\\Plugin' ) ) { \Elementor\Plugin::instance()->files_manager->clear_cache(); } // Mark the migration done; autoloaded because it is checked on every load. update_option( 'wpstream_vod_widget_name_fixed', 1 ); } // Run on init so Elementor (loaded on plugins_loaded) is available for the cache clear. add_action( 'init', 'wpstream_fix_vod_widget_name', 99 ); /** * Wrapper function for wpstream_dashboard_save_channel_data to be called from theme * This provides backward compatibility for the theme */ function wpstream_dashboard_save_channel_data_plugin() { // Reach the global plugin instance created at the bottom of this file. global $wpstream_plugin; // Only delegate if the plugin and its AJAX handler are available. if (isset($wpstream_plugin) && isset($wpstream_plugin->wpstream_ajax)) { // Forward the call to the real AJAX save-channel handler. $wpstream_plugin->wpstream_ajax->wpstream_dashboard_save_channel_data(); } } /** * The core plugin class that is used to define internationalization, * admin-specific hooks, and public-facing site hooks. */ // Load the core plugin class (defines i18n, admin, and public hooks). require plugin_dir_path( __FILE__ ) . 'includes/class-wpstream.php'; // Migrate legacy option, transient and user-meta names once per site. require plugin_dir_path( __FILE__ ) . 'includes/class-wpstream-storage-migration.php'; // Elementor requirement gate + widget registrar (autoloaded; checks run on plugins_loaded). new Wpstream_Elementor_Widgets(); // Load third-party integrations bootstrap. require plugin_dir_path( __FILE__ ) . 'integrations/integrations.php'; /** * Load the bundled hello-wpstream theme framework files. * * Hooked to after_setup_theme so the theme's helper functions, post types, * WooCommerce glue, widgets, and (when Elementor is active) Elementor pieces are * available. Missing files are logged rather than fatal. * * @return void */ function wpstream_load_theme_functionality() { // Absolute path to the bundled hello-wpstream framework directory. define ('WPSTREAM_FRAMEWORK_BASE', plugin_dir_path(__FILE__) . 'hello-wpstream' ); // Framework files to load, relative to WPSTREAM_FRAMEWORK_BASE. $core_files = array( '/framework/metaboxes.php', '/framework/query-functions.php', '/framework/wpstream-video-functions.php', '/framework/ajax-functions.php', '/framework/dashboard-functions.php', '/framework/wpstream-help-functions.php', '/framework/video-pages-functions.php', '/framework/comments-functions.php', '/framework/gallery-functions.php', '/framework/shortcodes-functions.php', '/framework/post-types/main.php', '/framework/woocommerce-functions.php', '/framework/ajax-upload.php', '/framework/email-functions.php', '/framework/widgets/class-wpstream-widget-manager.php', '/framework/classes/class-wpstream-login-register.php', '/framework/classes/class-wpstream_theme-social-login.php', ); // Load core files // Include each framework file, or log it if the file is missing. foreach ( $core_files as $file ) { // Build the absolute path to this framework file. $file_path = WPSTREAM_FRAMEWORK_BASE . $file; // Include it if present; otherwise record the missing file. if ( file_exists( $file_path ) ) { // File exists: load it once. require_once $file_path; } else { // File missing: log an error but keep loading the rest. error_log( 'Missing required file: ' . $file_path ); } } // Load Elementor integration if the plugin is active // ELEMENTOR_VERSION is only defined when Elementor is loaded. if ( defined( 'ELEMENTOR_VERSION' ) ) { // Additional framework files needed only for the Elementor integration. $elementor_files = array( '/elementor/functions/blog_functions.php', '/elementor/wpstream-elementor.php' ); // Include each Elementor file, or log it if missing. foreach ( $elementor_files as $file ) { // Build the absolute path to this Elementor file. $file_path = WPSTREAM_FRAMEWORK_BASE . $file; // Include it if present; otherwise record the missing file. if ( file_exists( $file_path ) ) { // File exists: load it once. require_once $file_path; } else { // File missing: log an error but keep loading the rest. error_log( 'Missing required file: ' . $file_path ); } } } } // Load the theme framework after the active theme is set up. add_action( 'after_setup_theme', 'wpstream_load_theme_functionality' ); /** * Load js for players * * @return void */ // Guard the definition so the theme can define its own version without collision. if ( ! function_exists( 'wpstream_enqueue_player_assets' ) ) { /** * Enqueue the full player stack (scripts + styles) at a player render site. * * Every handle is registered (not enqueued) on wp_enqueue_scripts, so nothing * loads on pages without a player; render sites call this the moment player * markup is emitted. Scripts print in the footer; the two stylesheets print * late as well, which is fine because the player element is built by JS. * videojs-logo / quality-selector / quality-levels arrive as dependencies * of wpstream-player. * * @return void */ function wpstream_enqueue_player_assets() { // Video.js core. wp_enqueue_script( 'video.min' ); // WpStream player wrapper script (drags logo/quality deps with it). wp_enqueue_script( 'wpstream-player' ); // Base Video.js CSS + the WpStream player skin. wp_enqueue_style( 'video-js.min' ); wp_enqueue_style( 'videojs-wpstream-player' ); } } if ( ! function_exists( 'wpstream_get_product_type_name' ) ) { /** * Safely read a product's first product_type term name. * * wp_get_post_terms() returns a WP_Error when the taxonomy is unavailable * and an empty array when the product carries no term; naive * $term_list[0]->name reads crash on both under PHP 8. Callers that need the * display label use this accessor; branching logic uses the slug accessor below. * * @param int $product_id Product/post id. * @return string The first term name, or '' when none can be read. */ function wpstream_get_product_type_name( $product_id ) { $term_list = wp_get_post_terms( intval( $product_id ), 'product_type' ); // WP_Error (missing taxonomy) or no assigned term: report "no type". if ( is_wp_error( $term_list ) || empty( $term_list[0]->name ) ) { return ''; } return $term_list[0]->name; } } if ( ! function_exists( 'wpstream_get_product_type_slug' ) ) { /** * Return the stable WooCommerce product-type identifier for a post. * * Product-type names are editable display labels, while the slug is the * identifier used by WooCommerce and WpStream branching logic. * * @param int $product_id Product/post id. * @return string The first term slug, or '' when none can be read. */ function wpstream_get_product_type_slug( $product_id ) { $term_list = wp_get_post_terms( intval( $product_id ), 'product_type' ); if ( is_wp_error( $term_list ) || empty( $term_list[0]->slug ) ) { return ''; } return $term_list[0]->slug; } } if ( ! function_exists( 'wpstream_load_player_js_on_demand' ) ) { /** * Enqueue player scripts only when unit cards are configured to use video. * * @return void */ function wpstream_load_player_js_on_demand() { // Read the customizer setting that toggles video previews on unit cards. $wpstream_unit_card_use_video = get_theme_mod('wpstream_unit_card_use_video'); // Only enqueue the player assets when that setting is enabled. if ($wpstream_unit_card_use_video) { // Full player stack (scripts + styles). wpstream_enqueue_player_assets(); } } } /** * Begins execution of the plugin. * * Since everything within the plugin is registered via hooks, * then kicking off the plugin from this point in the file does * not affect the page life cycle. * * @since 3.0.1 */ // Expose the plugin instance globally so themes/helpers can reach it. global $wpstream_plugin; // Build the core plugin object. $wpstream_plugin = new Wpstream(); // Register all of the plugin's hooks and start it running. $wpstream_plugin->run(); // After any core/plugin/theme update completes, run our upgrade cleanup. add_action( 'upgrader_process_complete', 'wpstream_my_upgrade_function',10, 2); /** * Clear cached API token data after this plugin is updated. * * @param WP_Upgrader $upgrader_object The upgrader instance (unused). * @param array $options Details about what was upgraded. * @return void */ function wpstream_my_upgrade_function( $upgrader_object, $options ) { // This plugin's basename, used to detect if it was among the updated plugins. $current_plugin_path_name = plugin_basename( __FILE__ ); // Only act on plugin update operations. if ($options['action'] == 'update' && $options['type'] == 'plugin' ) { // Guard against a missing/non-array options payload. if(is_array($options)): // Inspect every plugin that was updated in this batch. foreach($options['plugins'] as $each_plugin) { // Match against this plugin. if ($each_plugin==$current_plugin_path_name) { // Drop the cached API token transient. delete_transient('wpstream_token_api'); // Reset the stored token expiry timestamp. update_option('wpstream_token_expire',0); // Blank out the stored current token. update_option('wpstream_current_token',' '); } } endif; } } // Front-end SEO metadata (Open Graph, Twitter cards, structured data). The file // registers its own wp_head hook when loaded — see Wpstream_Seo::init(). require plugin_dir_path( __FILE__ ) . 'includes/class-wpstream-seo.php'; /* * * Check integrations * */ // Only wire the One Click Demo Import (OCDI) hooks when the hello-wpstream theme is active. if ( get_template() === 'hello-wpstream' ) { // Load the theme-import helper functions referenced by the filters below. require_once plugin_dir_path( __FILE__ ) . 'integrations/hello-wpstream/theme-import.php'; // Register the set of demo import files offered by OCDI. add_filter( 'pt-ocdi/import_files', 'wpstream_ocdi_import_files' ); // Run post-import setup (menus, pages, options) after a demo import. add_action( 'pt-ocdi/after_import', 'wpstream_ocdi_after_import_setup' ); // Customize the intro text shown on the OCDI import screen. add_filter( 'pt-ocdi/plugin_intro_text', 'wpstream_ocdi_plugin_intro_text' ); // Send telemetry when a demo content import is attempted. add_action( 'ocdi/before_content_import', 'wpstream_track_ocdi_import_attempt', 10, 1 ); } // Once all plugins are loaded, evaluate which integrations should run. add_action( 'plugins_loaded', 'wpstream_check_integrations' ); /* * * Redirect on plugin activation * */ // Queue activation intent, then consume it safely on the next admin request. require_once plugin_dir_path( __FILE__ ) . 'includes/class-wpstream-activation-redirect.php'; /* * * remove the style selectWoo for the theme * */ // The existence of wpstream_get_author_id signals the wpstream theme is active. if ( function_exists( 'wpstream_get_author_id' ) ) { // only if wpstream-theme is activated remove_filter( 'pre_user_description', 'wp_filter_kses' ); // Removes the filter that applies KSES filtering for user descriptions remove_filter( 'term_description', 'wp_kses_data' ); // Removes the filter that applies KSES data filtering for term descriptions // Define the Select2 dequeue callback only once (guard against redefinition). if ( ! function_exists( 'wsis_dequeue_stylesandscripts_select2' ) ) { // Run the dequeue late (priority 100) so it overrides earlier enqueues. add_action( 'wp_enqueue_scripts', 'wsis_dequeue_stylesandscripts_select2', 100 ); /** * Remove CSS and/or JS for Select2 used by WooCommerce */ function wsis_dequeue_stylesandscripts_select2() { // Only relevant when WooCommerce is present. if ( class_exists( 'woocommerce' ) ) { // Remove WooCommerce's selectWoo stylesheet. wp_dequeue_style( 'selectWoo' ); wp_deregister_style( 'selectWoo' ); // Remove WooCommerce's selectWoo script. wp_dequeue_script( 'selectWoo' ); wp_deregister_script( 'selectWoo' ); } } } } /** * Register broadcaster page endpoint */ function wpstream_register_broadcaster_endpoint() { // Register the query var that flags a broadcaster page request (0 or 1). add_rewrite_tag('%broadcaster_page%', '([0-1]{1})'); // Register the numeric channel-id query var. add_rewrite_tag('%channel_id%', '([0-9]+)'); // Current stored rewrite rules, used to detect if our rule already exists. $rewrite_rules = get_option('rewrite_rules'); // Pretty-URL pattern: /broadcaster-page/{id}/ $rule_pattern = 'broadcaster-page/([0-9]+)/?$'; // Internal target that maps the captured id onto our query vars. $rule_target = 'index.php?broadcaster_page=1&channel_id=$matches[1]'; // Add the rewrite rule at the top so it takes precedence. add_rewrite_rule($rule_pattern, $rule_target, 'top'); // Flush rules only when they are missing our pattern (avoids flushing every request). if ( !is_array($rewrite_rules) || !key_exists( $rule_pattern, $rewrite_rules ) ) { // Rebuild the rewrite rules so the new endpoint works immediately. flush_rewrite_rules(); } } // Register the broadcaster endpoint on every init. add_action('init', 'wpstream_register_broadcaster_endpoint'); /** * Load broadcaster template when the custom endpoint is accessed */ function wpstream_load_broadcaster_template($template) { // When the broadcaster_page query var is set, serve our custom template. if (get_query_var('broadcaster_page') == 1) { // Return the path to our broadcaster template return plugin_dir_path(__FILE__) . 'templates/broadcaster-template.php'; } // Otherwise fall back to the theme's normal template. return $template; } // Intercept template selection to swap in the broadcaster template when needed. add_filter('template_include', 'wpstream_load_broadcaster_template'); /** * Flush rewrite rules on plugin activation to ensure our endpoint works */ function wpstream_flush_rewrite_rules() { // Register the endpoint's rewrite tags/rules first. wpstream_register_broadcaster_endpoint(); // Then flush so the new rules are active immediately after activation. flush_rewrite_rules(); } // Ensure rewrite rules are flushed when the plugin is activated. register_activation_hook(__FILE__, 'wpstream_flush_rewrite_rules');