public function ajax_vote() { $this->verify_request(); if (!is_user_logged_in()) { wp_send_json_error(['message' => esc_html__('Sign in to vote.', 'cine-movie-engine')], 401); } $playlist_id = isset($_POST['playlist_id']) ? absint($_POST['playlist_id']) : 0; $playlist = get_post($playlist_id); if (!$playlist || $playlist->post_type !== self::POST_TYPE || $playlist->post_status !== 'publish' || get_post_meta($playlist_id, '_me_cp_visibility', true) !== 'public') { wp_send_json_error(['message' => esc_html__('This playlist cannot be voted on.', 'cine-movie-engine')], 404); } $vote_key = '_me_cp_voted_' . $playlist_id; if (!add_user_meta(get_current_user_id(), $vote_key, '1', true)) { wp_send_json_error(['message' => esc_html__('You have already voted for this playlist.', 'cine-movie-engine')], 409); } $votes = absint(get_post_meta($playlist_id, '_me_cp_vote_count', true)) + 1; update_post_meta($playlist_id, '_me_cp_vote_count', $votes); wp_send_json_success(['votes' => $votes]); } public function ajax_move_title() { $this->verify_request(); if (!is_user_logged_in()) { wp_send_json_error(['message' => esc_html__('Sign in to manage this playlist.', 'cine-movie-engine')], 401); } $playlist_id = isset($_POST['playlist_id']) ? absint($_POST['playlist_id']) : 0; $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; $direction = isset($_POST['direction']) ? sanitize_key(wp_unslash($_POST['direction'])) : ''; $playlist = get_post($playlist_id); if (!$playlist || $playlist->post_type !== self::POST_TYPE || $playlist->post_status !== 'publish' || (int) $playlist->post_author !== get_current_user_id()) { wp_send_json_error(['message' => esc_html__('You cannot manage this playlist.', 'cine-movie-engine')], 403); } if (!$post_id || !in_array($direction, ['up', 'down'], true)) { wp_send_json_error(['message' => esc_html__('Invalid playlist item.', 'cine-movie-engine')], 400); } $items = $this->get_valid_items($playlist_id); $index = array_search($post_id, $items, true); $target_index = $direction === 'up' ? $index - 1 : $index + 1; if ($index === false || !isset($items[$target_index])) { wp_send_json_error(['message' => esc_html__('This title cannot be moved further.', 'cine-movie-engine')], 400); } $moving_item = $items[$index]; $items[$index] = $items[$target_index]; $items[$target_index] = $moving_item; update_post_meta($playlist_id, '_me_cp_items', $items); $source_user_id = absint(get_post_meta($playlist_id, '_me_cp_source_user_id', true)); $source_playlist_id = get_post_meta($playlist_id, '_me_cp_source_playlist_id', true); if ($source_user_id === get_current_user_id() && $source_playlist_id !== '') { $source_playlists = \\MovieEngine\\Frontend\\Users\\UserLibrary::get_playlists($source_user_id); if (isset($source_playlists[$source_playlist_id])) { $source_playlists[$source_playlist_id]['items'] = $items; update_user_meta($source_user_id, '_movie_engine_playlists', $source_playlists); } } wp_send_json_success(['items' => $items]); } } private function render_listing($atts) { self::migrate_personal_playlists_batch(); $sort = isset($_GET['playlist_sort']) ? sanitize_key(wp_unslash($_GET['playlist_sort'])) : 'trending'; $sort = $sort === 'new' ? 'new' : 'trending'; $orderby = $sort === 'new' ? 'date' : 'meta_value_num'; $query_args = [ 'post_type' => self::POST_TYPE, 'post_status' => 'publish', 'posts_per_page' => max(1, min(48, absint($atts['limit']))), 'orderby' => $orderby, 'order' => 'DESC', 'meta_query' => [['key' => '_me_cp_visibility', 'value' => 'public']], ]; if ($sort !== 'new') { $query_args['meta_key'] = '_me_cp_vote_count'; } $query = new \WP_Query($query_args); echo '

' . esc_html__('Made by movie fans', 'cine-movie-engine') . '

' . esc_html($atts['title']) . '

'; echo ''; if ($query->have_posts()) { echo '
'; $rank = 0; while ($query->have_posts()) { $query->the_post(); $rank++; $this->render_card(get_the_ID(), $rank); } echo '
'; wp_reset_postdata(); } else { echo '

' . esc_html__('No public playlists yet. Publish one from My Playlists in your dashboard.', 'cine-movie-engine') . '

'; } } private function render_card($playlist_id, $rank) { $cover = get_post_meta($playlist_id, '_me_cp_cover', true); $items = $this->get_valid_items($playlist_id); $votes = absint(get_post_meta($playlist_id, '_me_cp_vote_count', true)); $views = absint(get_post_meta($playlist_id, '_me_cp_view_count', true)); $url = add_query_arg('community_playlist', $playlist_id, remove_query_arg('community_playlist')); $cover_posters = []; if (!$cover) { foreach (array_slice($items, 0, 4) as $item_id) { $poster = \MovieEngine\Utils\Helpers::get_poster_url($item_id, 'medium'); if ($poster) { $cover_posters[] = $poster; } } } echo '
#' . esc_html($rank) . ''; if ($cover) { echo '' . esc_attr(get_the_title($playlist_id)) . ''; } elseif ($cover_posters) { echo ''; } else { echo '' . esc_html__('Playlist', 'cine-movie-engine') . ''; } echo '' . esc_html(sprintf(_n('%s title', '%s titles', count($items), 'cine-movie-engine'), number_format_i18n(count($items)))) . ''; echo '

' . esc_html(get_post_meta($playlist_id, '_me_cp_category', true) ?: __('Community pick', 'cine-movie-engine')) . '

'; echo '

' . esc_html(get_the_title($playlist_id)) . '

'; echo '

' . esc_html(get_the_author_meta('display_name', (int) get_post_field('post_author', $playlist_id))) . '

'; echo '
'; } public static function get_personal_playlist_post_id($user_id, $playlist_id) { $matches = get_posts([ 'post_type' => self::POST_TYPE, 'post_status' => ['publish', 'draft', 'private', 'pending'], 'numberposts' => 1, 'fields' => 'ids', 'meta_query' => [ 'relation' => 'AND', ['key' => '_me_cp_source_user_id', 'value' => absint($user_id)], ['key' => '_me_cp_source_playlist_id', 'value' => sanitize_text_field($playlist_id)], ], ]); return $matches ? absint($matches[0]) : 0; } private static function find_legacy_personal_playlist_post_id($user_id, $playlist_name) { $matches = get_posts([ 'post_type' => self::POST_TYPE, 'post_status' => 'publish', 'author' => absint($user_id), 'title' => sanitize_text_field($playlist_name), 'numberposts' => 1, 'fields' => 'ids', 'meta_query' => [ 'relation' => 'AND', ['key' => '_me_cp_visibility', 'value' => 'public'], ['key' => '_me_cp_source_playlist_id', 'compare' => 'NOT EXISTS'], ], ]); return $matches ? absint($matches[0]) : 0; } public static function sync_personal_playlist($user_id, $playlist_id, $playlist = null) { $community_id = self::get_personal_playlist_post_id($user_id, $playlist_id); if (!is_array($playlist)) { if (!$community_id) { return 0; } wp_trash_post($community_id); return $community_id; } $items = array_values(array_filter(array_map('absint', (array) ($playlist['items'] ?? [])), function ($item_id) { return in_array(get_post_type($item_id), ['movie_engine_movie', 'movie_engine_series'], true) && get_post_status($item_id) === 'publish'; })); if (!$community_id) { $community_id = self::find_legacy_personal_playlist_post_id($user_id, $playlist['name'] ?? ''); } if (!$community_id && !$items) { return 0; } $description = sanitize_textarea_field($playlist['description'] ?? ''); if ($description === '' && $items) { $description = sprintf( /* translators: %d: number of playlist titles */ _n('A curated collection of %d title.', 'A curated collection of %d titles.', count($items), 'cine-movie-engine'), count($items) ); } if (!$community_id) { $community_id = wp_insert_post([ 'post_type' => self::POST_TYPE, 'post_status' => 'publish', 'post_title' => sanitize_text_field($playlist['name'] ?? ''), 'post_author' => absint($user_id), ], true); if (is_wp_error($community_id)) { return 0; } update_post_meta($community_id, '_me_cp_vote_count', 0); update_post_meta($community_id, '_me_cp_category', __('Community playlist', 'cine-movie-engine')); update_post_meta($community_id, '_me_cp_language', ''); update_post_meta($community_id, '_me_cp_cover', ''); } update_post_meta($community_id, '_me_cp_source_user_id', absint($user_id)); update_post_meta($community_id, '_me_cp_source_playlist_id', sanitize_text_field($playlist_id)); wp_update_post([ 'ID' => $community_id, 'post_title' => sanitize_text_field($playlist['name'] ?? ''), 'post_content' => $description, 'post_status' => $items ? 'publish' : 'draft', 'post_author' => absint($user_id), ]); update_post_meta($community_id, '_me_cp_items', $items); update_post_meta($community_id, '_me_cp_visibility', $items ? 'public' : 'private'); return $community_id; } public static function migrate_personal_playlists_batch() { if (get_option('me_community_personal_playlists_migrated')) { return; } $batch_size = 100; $offset = absint(get_option('me_community_personal_playlists_offset', 0)); $user_ids = get_users([ 'fields' => 'ID', 'number' => $batch_size, 'offset' => $offset, 'meta_key' => '_movie_engine_playlists', ]); foreach ($user_ids as $user_id) { $playlists = \MovieEngine\Frontend\Users\UserLibrary::get_playlists($user_id); foreach ($playlists as $playlist_id => $playlist) { if (!empty($playlist['items'])) { self::sync_personal_playlist($user_id, $playlist_id, $playlist); } } } if (count($user_ids) < $batch_size) { update_option('me_community_personal_playlists_migrated', 1, false); delete_option('me_community_personal_playlists_offset'); } else { update_option('me_community_personal_playlists_offset', $offset + $batch_size, false); } } private function render_detail($playlist_id) { $playlist = get_post($playlist_id); if (!$playlist || $playlist->post_type !== self::POST_TYPE || $playlist->post_status !== 'publish') { echo '

' . esc_html__('Playlist not found.', 'cine-movie-engine') . '

'; return; } $visibility = get_post_meta($playlist_id, '_me_cp_visibility', true) ?: 'public'; if ($visibility === 'private' && (int) $playlist->post_author !== get_current_user_id() && !current_user_can('edit_post', $playlist_id)) { echo '

' . esc_html__('This playlist is private.', 'cine-movie-engine') . '

'; return; } $items = $this->get_valid_items($playlist_id); $votes = absint(get_post_meta($playlist_id, '_me_cp_vote_count', true)); $views = absint(get_post_meta($playlist_id, '_me_cp_view_count', true)) + 1; update_post_meta($playlist_id, '_me_cp_view_count', $views); $back_url = remove_query_arg('community_playlist'); $can_manage = is_user_logged_in() && (int) $playlist->post_author === get_current_user_id(); $cover = get_post_meta($playlist_id, '_me_cp_cover', true); $hero_posters = []; if (!$cover) { foreach (array_slice($items, 0, 5) as $item_id) { $poster = \MovieEngine\Utils\Helpers::get_poster_url($item_id, 'large'); if ($poster) { $hero_posters[] = $poster; } } } $author_id = absint($playlist->post_author); $author_avatar = get_user_meta($author_id, 'movie_engine_avatar_url', true); if (!$author_avatar) { $author_avatar = get_avatar_url($author_id, ['size' => 72]); } $social_whatsapp = preg_replace('/\D/', '', (string) get_user_meta($author_id, 'movie_engine_social_whatsapp', true)); $social_instagram = get_user_meta($author_id, 'movie_engine_social_instagram', true); $social_facebook = get_user_meta($author_id, 'movie_engine_social_facebook', true); $created_date = get_the_date(get_option('date_format'), $playlist_id); echo '← ' . esc_html__('All playlists', 'cine-movie-engine') . ''; echo '
'; echo '
'; echo '

' . esc_html(get_post_meta($playlist_id, '_me_cp_category', true) ?: __('Community playlist', 'cine-movie-engine')) . '

'; echo '

' . esc_html($playlist->post_title) . '

'; if ($playlist->post_content !== '') { echo '

' . esc_html(wp_strip_all_tags($playlist->post_content)) . '

'; } echo '
' . esc_html(get_the_author_meta('display_name', $author_id)) . '
'; if ($social_whatsapp || $social_instagram || $social_facebook) { echo ''; } echo '
'; if ($visibility === 'public') { echo ' ' . esc_html(number_format_i18n($votes)) . ''; echo ' ' . esc_html(number_format_i18n($views)) . ''; } echo '
'; if ($visibility === 'public') { $this->render_vote_button($playlist_id, $votes); } echo '
'; echo '

' . esc_html__('Movies & TV series', 'cine-movie-engine') . '

'; if (!$items) { echo '

' . esc_html__('This playlist has no available titles.', 'cine-movie-engine') . '

'; } else { foreach ($items as $index => $item_id) { $poster = \MovieEngine\Utils\Helpers::get_poster_url($item_id, 'medium'); echo '
' . esc_html((string) ($index + 1)) . ''; echo ''; if ($poster) { echo ''; } else { echo ''; } echo '

' . esc_html(get_the_title($item_id)) . '

' . esc_html(get_post_type($item_id) === 'movie_engine_series' ? __('TV Series', 'cine-movie-engine') : __('Movie', 'cine-movie-engine')) . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Helper escapes its output. echo self::render_title_extras($item_id) . '
'; echo '
'; if ($can_manage) { echo '
'; echo ''; echo ''; echo '
'; } echo '' . esc_html__('View title', 'cine-movie-engine') . '
'; } } echo '
'; } private function render_vote_button($playlist_id, $votes) { $has_voted = is_user_logged_in() && get_user_meta(get_current_user_id(), '_me_cp_voted_' . $playlist_id, true); $label = $has_voted ? __('Voted', 'cine-movie-engine') : __('Vote', 'cine-movie-engine'); if (!is_user_logged_in()) { echo ' ' . esc_html__('Sign in to vote', 'cine-movie-engine') . ''; return; } echo ''; } private function get_valid_items($playlist_id) { $items = get_post_meta($playlist_id, '_me_cp_items', true); if (!is_array($items)) { return []; } return array_values(array_filter(array_map('absint', $items), function ($item_id) { return in_array(get_post_type($item_id), ['movie_engine_movie', 'movie_engine_series'], true) && get_post_status($item_id) === 'publish'; })); } public static function render_title_extras($post_id) { $details = []; $year = get_post_meta($post_id, '_movie_engine_year', true); $rating = get_post_meta($post_id, '_movie_engine_rating', true); $language = get_post_meta($post_id, '_movie_engine_primary_language', true); $content_rating = get_post_meta($post_id, '_movie_engine_mpaa', true); $genres = get_the_terms($post_id, 'movie_engine_genre'); if ($year) { $details[] = esc_html($year); } if (is_numeric($rating)) { $details[] = ' ' . esc_html(number_format((float) $rating, 1)); } if ($content_rating) { $details[] = esc_html($content_rating); } if ($language) { $details[] = esc_html($language); } if (is_array($genres) && !is_wp_error($genres)) { $genre_names = array_map(function ($term) { return $term->name; }, array_slice($genres, 0, 2)); if ($genre_names) { $details[] = esc_html(implode(', ', $genre_names)); } } $summary = get_post_field('post_excerpt', $post_id); if (!$summary) { $summary = get_post_meta($post_id, '_movie_engine_tagline', true); } if (!$summary) { $summary = get_post_field('post_content', $post_id); } $summary = $summary ? wp_trim_words(wp_strip_all_tags($summary), 22) : ''; $platform_names = [ 'jiohotstar' => 'JioHotstar', 'manoramamax' => 'ManoramaMax', 'netflix' => 'Netflix', 'prime_video' => 'Prime Video', 'saina_play' => 'Saina Play', 'sonyliv' => 'SonyLIV', 'sunnxt' => 'SunNXT', 'zee5' => 'ZEE5', ]; $provider_links = get_post_meta($post_id, '_movie_engine_ott_links', true); $html = '
'; if ($details) { $html .= '

' . implode('', $details) . '

'; } if ($summary) { $html .= '

' . esc_html($summary) . '

'; } $valid_links = []; foreach ((array) $provider_links as $provider_link) { $platform = sanitize_key($provider_link['platform'] ?? ''); $url = esc_url($provider_link['url'] ?? ''); if (isset($platform_names[$platform]) && $url) { $valid_links[] = [ 'name' => $platform_names[$platform], 'url' => $url, 'availability' => ($provider_link['availability'] ?? 'rent') === 'free' ? 'free' : 'rent', ]; } } if ($valid_links) { $html .= ''; } else { $html .= '

' . esc_html__('Streaming links not listed', 'cine-movie-engine') . '

'; } return $html . '
'; } public function ajax_vote() { $this->verify_request(); if (!is_user_logged_in()) { wp_send_json_error(['message' => esc_html__('Sign in to vote.', 'cine-movie-engine')], 401); } $playlist_id = isset($_POST['playlist_id']) ? absint($_POST['playlist_id']) : 0; $playlist = get_post($playlist_id); if (!$playlist || $playlist->post_type !== self::POST_TYPE || $playlist->post_status !== 'publish' || get_post_meta($playlist_id, '_me_cp_visibility', true) !== 'public') { wp_send_json_error(['message' => esc_html__('This playlist cannot be voted on.', 'cine-movie-engine')], 404); } $vote_key = '_me_cp_voted_' . $playlist_id; if (!add_user_meta(get_current_user_id(), $vote_key, '1', true)) { wp_send_json_error(['message' => esc_html__('You have already voted for this playlist.', 'cine-movie-engine')], 409); } $votes = absint(get_post_meta($playlist_id, '_me_cp_vote_count', true)) + 1; update_post_meta($playlist_id, '_me_cp_vote_count', $votes); wp_send_json_success(['votes' => $votes]); } public function ajax_move_title() { $this->verify_request(); if (!is_user_logged_in()) { wp_send_json_error(['message' => esc_html__('Sign in to manage this playlist.', 'cine-movie-engine')], 401); } $playlist_id = isset($_POST['playlist_id']) ? absint($_POST['playlist_id']) : 0; $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; $direction = isset($_POST['direction']) ? sanitize_key(wp_unslash($_POST['direction'])) : ''; $playlist = get_post($playlist_id); if (!$playlist || $playlist->post_type !== self::POST_TYPE || $playlist->post_status !== 'publish' || (int) $playlist->post_author !== get_current_user_id()) { wp_send_json_error(['message' => esc_html__('You cannot manage this playlist.', 'cine-movie-engine')], 403); } if (!$post_id || !in_array($direction, ['up', 'down'], true)) { wp_send_json_error(['message' => esc_html__('Invalid playlist item.', 'cine-movie-engine')], 400); } $items = $this->get_valid_items($playlist_id); $index = array_search($post_id, $items, true); $target_index = $direction === 'up' ? $index - 1 : $index + 1; if ($index === false || !isset($items[$target_index])) { wp_send_json_error(['message' => esc_html__('This title cannot be moved further.', 'cine-movie-engine')], 400); } $moving_item = $items[$index]; $items[$index] = $items[$target_index]; $items[$target_index] = $moving_item; update_post_meta($playlist_id, '_me_cp_items', $items); $source_user_id = absint(get_post_meta($playlist_id, '_me_cp_source_user_id', true)); $source_playlist_id = get_post_meta($playlist_id, '_me_cp_source_playlist_id', true); if ($source_user_id === get_current_user_id() && $source_playlist_id !== '') { $source_playlists = \MovieEngine\Frontend\Users\UserLibrary::get_playlists($source_user_id); if (isset($source_playlists[$source_playlist_id])) { $source_playlists[$source_playlist_id]['items'] = $items; update_user_meta($source_user_id, '_movie_engine_playlists', $source_playlists); } } wp_send_json_success(['items' => $items]); } private function verify_request() { $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : ''; if (!wp_verify_nonce($nonce, self::NONCE_ACTION)) { wp_send_json_error(['message' => esc_html__('Your session has expired. Refresh the page and try again.', 'cine-movie-engine')], 403); } } }