PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents / 4.6.0
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents v4.6.0
4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / EmbedPress / Providers / TemplateLayouts / YoutubeLayout.php
embedpress / EmbedPress / Providers / TemplateLayouts Last commit date
YoutubeLayout.php 1 month ago
YoutubeLayout.php
741 lines
1 <?php
2
3 namespace EmbedPress\Providers\TemplateLayouts;
4
5
6 use Embera\Provider\ProviderAdapter;
7 use Embera\Provider\ProviderInterface;
8 use Embera\Url;
9 use EmbedPress\Includes\Classes\Helper;
10
11
12 class YoutubeLayout
13 {
14
15
16
17 public static function create_channel_info_layout($channel_info, $channel_url)
18 {
19 $title = isset($channel_info['snippet']['title']) ? $channel_info['snippet']['title'] : null;
20 $handle = isset($channel_info['snippet']['customUrl']) ? $channel_info['snippet']['customUrl'] : null;
21 $subscriberCount = isset($channel_info['statistics']['subscriberCount']) ? $channel_info['statistics']['subscriberCount'] : null;
22 $videoCount = isset($channel_info['statistics']['videoCount']) ? $channel_info['statistics']['videoCount'] : null;
23 $thumbnailUrl = isset($channel_info['snippet']['thumbnails']['high']['url']) ? $channel_info['snippet']['thumbnails']['high']['url'] : null;
24
25 ob_start();
26 ?>
27 <div class="channel-header" data-channel-url="<?php echo esc_url($channel_url); ?>">
28 <img src="<?php echo esc_url($thumbnailUrl); ?>" alt="<?php echo esc_attr($title); ?>" class="profile-picture">
29 <div class="channel-info">
30 <div class="info-description">
31 <h1 class="channel-name"><?php echo esc_html($title); ?></h1>
32 <p class="channel-details"><?php echo esc_html($handle); ?> · <?php echo esc_html(Helper::format_number($subscriberCount)); ?> subscribers · <?php echo esc_html(Helper::format_number($videoCount)); ?> videos</p>
33 </div>
34 <a target="_blank" href="<?php echo esc_url('https://www.youtube.com/' . $handle); ?>" class="subscribe-button">
35 <svg width="16" height="20" viewBox="0 0 16 20" fill="none" xmlns="http://www.w3.org/2000/svg">
36 <path fill-rule="evenodd" clip-rule="evenodd" d="M9.467 1.653A1.6 1.6 0 0 1 8.4 3.093 6.026 6.026 0 0 1 14 9.067v4.586c0 1.067.053 1.92.96 2.4a.694.694 0 0 1-.267 1.28H8.88a1.333 1.333 0 1 1-1.76 0H1.36a.693.693 0 0 1-.32-1.28c.907-.48.96-1.333.96-2.4V9.067a6.027 6.027 0 0 1 5.6-5.974 1.493 1.493 0 1 1 1.867-1.44m-1.44 2.774a4.8 4.8 0 0 0-4.694 4.64v4.693c0 .587 0 1.493-.373 2.293h10.133c-.426-.8-.373-1.653-.373-2.293V9.067a4.8 4.8 0 0 0-1.405-3.289c-.874-.874-2.052-1.324-3.288-1.351" fill="#fff" /></svg>
37 <?php echo esc_html__('Subscribe', 'embedpress'); ?></a>
38 </div>
39 </div>
40 <?php
41
42 $channel_layout = ob_get_clean();
43
44 return $channel_layout;
45 }
46
47
48 public static function generate_youtube_video_description($video_data) {
49 $title = isset($video_data['snippet']['title']) ? $video_data['snippet']['title'] : null;
50 $description = isset($video_data['snippet']['description']) ? $video_data['snippet']['description'] : null;
51 $publishedAt = isset($video_data['snippet']['publishedAt']) ? date("M j, Y", strtotime($video_data['snippet']['publishedAt'])) : null;
52 $channelTitle = isset($video_data['snippet']['channelTitle']) ? $video_data['snippet']['channelTitle'] : null;
53 $viewCount = isset($video_data['statistics']['viewCount']) ? $video_data['statistics']['viewCount'] : null;
54 $likeCount = isset($video_data['statistics']['likeCount']) ? $video_data['statistics']['likeCount'] : null;
55 $commentCount = isset($video_data['statistics']['commentCount']) ? $video_data['statistics']['commentCount'] : null;
56 $videoId = isset($video_data['id']) ? $video_data['id'] : null;
57 $videoUrl = $videoId ? "https://www.youtube.com/watch?v={$videoId}" : null;
58
59
60 $title = esc_html($title);
61 $description = esc_html($description);
62 $viewCount = esc_html($viewCount);
63 $likeCount = esc_html($likeCount);
64 $commentCount = esc_html($commentCount);
65 $publishedAt = esc_html($publishedAt);
66
67 $html = "
68 <div class='youtube-video-description'>
69 <div class='youtube-video-header'>
70 <h1>{$title}</h1>
71 <div class='youtube-video-meta'>
72 <span>{$viewCount} views</span>
73 <span>{$publishedAt}</span>
74 </div>
75 </div>
76 <div class='youtube-video-body'>
77 <p>{$description}</p>
78 </div>
79 <div class='youtube-video-footer'>
80 <div class='youtube-video-stats'>
81 <span><svg width='20' height='17.539' viewBox='0 0 20 17.539' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M12.648.109a2.814 2.814 0 0 0-3.477 1.93l-.223.781a3.4 3.4 0 0 1-.762 1.367L6.182 6.39a.937.937 0 0 0 1.387 1.261l2.004-2.203a5.3 5.3 0 0 0 1.176-2.113l.223-.781a.937.937 0 1 1 1.805.515l-.223.781a7.2 7.2 0 0 1-1.039 2.168c-.203.285-.227.66-.066.973s.48.508.832.508H17.5a.627.627 0 0 1 .219 1.211.94.94 0 0 0-.375 1.5.624.624 0 0 1-.352 1.027.938.938 0 0 0-.562 1.504.625.625 0 0 1-.265.969.94.94 0 0 0-.563 1.125.627.627 0 0 1-.602.793h-3.809c-.492 0-.977-.145-1.387-.418l-2.41-1.605c-.43-.289-1.012-.172-1.301.262s-.172 1.012.262 1.301l2.41 1.605c.719.48 1.563.734 2.426.734H15a2.5 2.5 0 0 0 2.5-2.422 2.5 2.5 0 0 0 .887-2.461 2.49 2.49 0 0 0 .879-2.722A2.53 2.53 0 0 0 20 8.125a2.5 2.5 0 0 0-2.5-2.5h-3.605c.184-.406.34-.828.461-1.258l.223-.781a2.814 2.814 0 0 0-1.93-3.477M1.25 6.25A1.25 1.25 0 0 0 0 7.5v8.75c0 .691.559 1.25 1.25 1.25h2.5A1.25 1.25 0 0 0 5 16.25V7.5a1.25 1.25 0 0 0-1.25-1.25z' fill='#fff'/></svg> {$likeCount}</span>
82 <span><svg width='20' height='15.975' viewBox='0 0 20 15.975' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M2.783 9.644a1.5 1.5 0 0 0-.234-1.741c-.664-.699-1.02-1.538-1.02-2.412 0-1.981 1.991-3.994 4.992-3.994s4.992 2.012 4.992 3.994-1.991 3.994-4.992 3.994a6.3 6.3 0 0 1-1.179-.112 1.5 1.5 0 0 0-.958.131q-.192.098-.393.187c-.499.225-1.027.421-1.557.562a14 14 0 0 0 .346-.608zM.031 5.491c0 1.304.537 2.499 1.432 3.441l-.087.159C1.055 9.665.68 10.23.234 10.717a.751.751 0 0 0 .546 1.264c1.342 0 2.699-.415 3.828-.927q.227-.104.443-.212c.471.094.964.14 1.47.14 3.585 0 6.49-2.459 6.49-5.491S10.106 0 6.521 0 .031 2.459.031 5.491m13.479 9.485c.505 0 .995-.05 1.47-.14q.217.109.443.212c1.129.512 2.487.927 3.828.927a.746.746 0 0 0 .542-1.26c-.443-.487-.817-1.051-1.142-1.626l-.087-.159c.899-.945 1.435-2.14 1.435-3.445 0-2.945-2.743-5.351-6.184-5.485q.192.712.193 1.491v.019c2.721.209 4.493 2.106 4.493 3.975 0 .874-.356 1.713-1.02 2.409a1.5 1.5 0 0 0-.234 1.741l.1.184q.118.21.246.424a10 10 0 0 1-1.557-.562q-.201-.089-.393-.187a1.5 1.5 0 0 0-.958-.131q-.566.112-1.179.112c-1.925 0-3.432-.827-4.268-1.944a8.7 8.7 0 0 1-1.56.368c1.058 1.823 3.274 3.078 5.832 3.078' fill='#fff'/></svg> {$commentCount}</span>
83 </div>
84 </div>
85 </div>";
86
87 return $html;
88 }
89
90
91 public static function create_list_layout($jsonResult, $gallobj, $options, $data, $channelTitle, $channelThumb){
92
93 // $channelTitle = $data['get_channel_info']['snippet']['title'];
94 // $channelThumb = $data['get_channel_info']['snippet']['thumbnails']['default']['url'];
95
96 foreach ($jsonResult->items as $item) : ?>
97 <?php
98 $privacyStatus = isset($item->status->privacyStatus) ? $item->status->privacyStatus : null;
99 $thumbnail = Helper::get_thumbnail_url($item, $options['thumbnail'], $privacyStatus);
100 $vid = Helper::get_id($item);
101 $videoTitle = isset($item->snippet->title) ? $item->snippet->title : '';
102 $videoDescription = isset($item->snippet->description) ? $item->snippet->description : '';
103
104 $publishedAt = isset($item->snippet->publishedAt) ? $item->snippet->publishedAt : '';
105
106 if (empty($gallobj->first_vid)) {
107 $gallobj->first_vid = $vid;
108 }
109 if ($privacyStatus == 'private' && $options['hideprivate']) {
110 continue;
111 }
112 ?>
113 <div class="item" data-vid="<?php echo $vid; ?>">
114 <div class="thumb" style="background: <?php echo "url({$thumbnail}) no-repeat center"; ?>">
115 <div class="play-icon">
116 <img src="<?php echo esc_url(EMBEDPRESS_URL_ASSETS. 'images/youtube.svg'); ?>" alt="">
117 </div>
118 </div>
119 <div class="body youtube-body-content">
120 <div class="description-container">
121 <div class="thumbnail"><img src="<?php echo esc_url($channelThumb ); ?>"/></div>
122 <div class="details">
123 <div class="channel"><?php echo esc_html($channelTitle); ?></div>
124 <div class="title"><?php echo esc_html($videoTitle); ?></div>
125 <p class="description"><?php echo esc_html(Helper::trimTitle($videoDescription, 25)); ?></p>
126
127 <div class="views-time">
128 <span class="time"><?php echo esc_html(Helper::timeAgo($publishedAt)); ?></span>
129 </div>
130 </div>
131 </div>
132 </div>
133 </div>
134 <?php endforeach;
135 }
136 public static function create_gallery_layout($jsonResult, $gallobj, $options, $data, $channelTitle, $channelThumb){
137
138 // $channelTitle = $data['get_channel_info']['snippet']['title'];
139 // $channelThumb = $data['get_channel_info']['snippet']['thumbnails']['default']['url'];
140
141 foreach ($jsonResult->items as $item) : ?>
142 <?php
143 $privacyStatus = isset($item->status->privacyStatus) ? $item->status->privacyStatus : null;
144 $thumbnail = Helper::get_thumbnail_url($item, $options['thumbnail'], $privacyStatus);
145 $vid = Helper::get_id($item);
146 $videoTitle = isset($item->snippet->title) ? $item->snippet->title : '';
147 $publishedAt = isset($item->snippet->publishedAt) ? $item->snippet->publishedAt : '';
148
149 if (empty($gallobj->first_vid)) {
150 $gallobj->first_vid = $vid;
151 }
152 if ($privacyStatus == 'private' && $options['hideprivate']) {
153 continue;
154 }
155 ?>
156 <div class="item" data-vid="<?php echo $vid; ?>">
157 <div class="thumb" style="background: <?php echo "url({$thumbnail}) no-repeat center"; ?>">
158 <div class="play-icon">
159 <img src="<?php echo esc_url(EMBEDPRESS_URL_ASSETS. 'images/youtube.svg'); ?>" alt="">
160 </div>
161 </div>
162 <div class="body youtube-body-content">
163 <div class="description-container">
164 <div class="thumbnail"><img src="<?php echo esc_url($channelThumb ); ?>"/></div>
165 <div class="details">
166 <div class="channel"><?php echo esc_html($channelTitle); ?></div>
167 <div class="title"><?php echo esc_html(Helper::trimTitle($videoTitle, 6)); ?></div>
168
169 <div class="views-time">
170 <span class="time"><?php echo esc_html(Helper::timeAgo($publishedAt)); ?></span>
171 </div>
172 </div>
173 </div>
174 </div>
175 </div>
176 <?php endforeach;
177 }
178
179
180 /**
181 * Build the YouTube playlist "queue" shell (left player, right scrollable
182 * playlist list, header with title/channel/N-of-M position). This bypasses
183 * the standard channel/gallery shell — no .ep-youtube__content__block grid,
184 * no channel-header banner, no per-page pagination strip. Infinite scroll
185 * is wired client-side against the same playlistItems endpoint.
186 */
187 public static function create_queue_layout($jsonResult, $gallobj, $options, $data, $playlist_info, $first_vid, $next_page_token) {
188 $playlist_id = isset($options['playlistId']) ? $options['playlistId'] : '';
189 $title = !empty($playlist_info['title']) ? $playlist_info['title'] : __('Playlist', 'embedpress');
190 $channel_title = !empty($playlist_info['channel_title']) ? $playlist_info['channel_title'] : '';
191 $item_count = !empty($playlist_info['item_count']) ? (int) $playlist_info['item_count'] : 0;
192 $playlist_url = !empty($playlist_info['playlist_url']) ? $playlist_info['playlist_url'] : 'https://www.youtube.com/playlist?list=' . rawurlencode($playlist_id);
193 $start_vid = !empty($playlist_info['start_vid']) ? $playlist_info['start_vid'] : '';
194
195 // Deep-link target overrides the playlist's first item.
196 $opening_vid = $start_vid ?: $first_vid;
197
198 $first_src = $opening_vid
199 ? 'https://www.youtube.com/embed/' . $opening_vid . '?feature=oembed&enablejsapi=1'
200 : 'about:blank';
201
202 // User-configured height drives the whole queue wrapper (player + list),
203 // not just the iframe. Falls back to the CSS default (480px) when unset.
204 $wrap_height = !empty($options['maxheight']) ? (int) $options['maxheight'] : 0;
205 $wrap_style = $wrap_height ? '--epq-h:' . $wrap_height . 'px;' : '';
206
207 ob_start();
208 ?>
209 <div class="ep-yt-playlist" data-layout="queue"
210 data-playlist-id="<?php echo esc_attr($playlist_id); ?>"
211 data-pagesize="<?php echo intval($options['pagesize']); ?>"
212 data-next-page-token="<?php echo esc_attr($next_page_token); ?>"
213 data-total="<?php echo esc_attr($item_count); ?>"
214 <?php if ($wrap_style) : ?>style="<?php echo esc_attr($wrap_style); ?>"<?php endif; ?>>
215 <div class="ep-yt-queue">
216 <div class="ep-yt-queue__player">
217 <iframe class="ep-yt-queue__iframe"
218 src="<?php echo esc_url($first_src); ?>"
219 frameborder="0"
220 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
221 allowfullscreen
222 title="<?php echo esc_attr($title); ?>"></iframe>
223 </div>
224 <aside class="ep-yt-queue__list">
225 <header class="ep-yt-queue__header">
226 <a class="ep-yt-queue__title" href="<?php echo esc_url($playlist_url); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html($title); ?></a>
227 <div class="ep-yt-queue__meta">
228 <span class="ep-yt-queue__channel"><?php echo esc_html($channel_title); ?></span>
229 <span class="ep-yt-queue__sep">&middot;</span>
230 <span class="ep-yt-queue__position"><span class="ep-yt-queue__position-current">1</span> / <span class="ep-yt-queue__position-total"><?php echo esc_html($item_count); ?></span></span>
231 </div>
232 <div class="ep-yt-queue__controls">
233 <button type="button" class="ep-yt-queue__loop ep-yt-icon-btn" aria-pressed="false" aria-label="<?php echo esc_attr__('Loop', 'embedpress'); ?>" title="<?php echo esc_attr__('Loop', 'embedpress'); ?>">
234 <span class="ep-yt-icon ep-yt-icon--loop" aria-hidden="true"></span>
235 </button>
236 <button type="button" class="ep-yt-queue__shuffle ep-yt-icon-btn" aria-pressed="false" aria-label="<?php echo esc_attr__('Shuffle', 'embedpress'); ?>" title="<?php echo esc_attr__('Shuffle', 'embedpress'); ?>">
237 <span class="ep-yt-icon ep-yt-icon--shuffle" aria-hidden="true"></span>
238 </button>
239 </div>
240 </header>
241 <ol class="ep-yt-queue__items">
242 <?php echo self::render_queue_items($jsonResult, $options, 0, $opening_vid); ?>
243 </ol>
244 <div class="ep-yt-queue__loader" hidden>
245 <span class="ep-yt-queue__spinner" aria-hidden="true"></span>
246 <span class="ep-yt-queue__loader-text"><?php esc_html_e('Loading more…', 'embedpress'); ?></span>
247 </div>
248 </aside>
249 </div>
250 </div>
251 <?php
252 return ob_get_clean();
253 }
254
255 /**
256 * Render a YouTube playlist as a "Theatre" layout — large player on top,
257 * horizontal scrolling thumbnail strip below with prev/next controls.
258 * Wrapped in .ep-yt-playlist so it shares the same scoped CSS reset as
259 * the queue (no theme/plugin style leakage in either direction).
260 */
261 public static function create_theatre_layout($jsonResult, $gallobj, $options, $data, $playlist_info, $first_vid, $next_page_token) {
262 $playlist_id = isset($options['playlistId']) ? $options['playlistId'] : '';
263 $title = !empty($playlist_info['title']) ? $playlist_info['title'] : __('Playlist', 'embedpress');
264 $channel_title = !empty($playlist_info['channel_title']) ? $playlist_info['channel_title'] : '';
265 $item_count = !empty($playlist_info['item_count']) ? (int) $playlist_info['item_count'] : 0;
266 $playlist_url = !empty($playlist_info['playlist_url']) ? $playlist_info['playlist_url'] : 'https://www.youtube.com/playlist?list=' . rawurlencode($playlist_id);
267 $start_vid = !empty($playlist_info['start_vid']) ? $playlist_info['start_vid'] : '';
268 $opening_vid = $start_vid ?: $first_vid;
269 $first_src = $opening_vid
270 ? 'https://www.youtube.com/embed/' . $opening_vid . '?feature=oembed&enablejsapi=1'
271 : 'about:blank';
272
273 ob_start();
274 ?>
275 <div class="ep-yt-playlist" data-layout="theatre"
276 data-playlist-id="<?php echo esc_attr($playlist_id); ?>"
277 data-pagesize="<?php echo intval($options['pagesize']); ?>"
278 data-next-page-token="<?php echo esc_attr($next_page_token); ?>"
279 data-total="<?php echo esc_attr($item_count); ?>">
280 <div class="ep-yt-theatre">
281 <div class="ep-yt-theatre__player">
282 <iframe class="ep-yt-theatre__iframe"
283 src="<?php echo esc_url($first_src); ?>"
284 frameborder="0"
285 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
286 allowfullscreen
287 title="<?php echo esc_attr($title); ?>"></iframe>
288 </div>
289 <header class="ep-yt-theatre__meta">
290 <a class="ep-yt-theatre__title" href="<?php echo esc_url($playlist_url); ?>" target="_blank" rel="noopener noreferrer">
291 <span class="ep-yt-theatre__playicon" aria-hidden="true"></span>
292 <?php echo esc_html($title); ?>
293 </a>
294 <div class="ep-yt-theatre__sub">
295 <?php if ($channel_title) : ?>
296 <span class="ep-yt-theatre__channel"><?php echo esc_html($channel_title); ?></span>
297 <span class="ep-yt-theatre__sep">&middot;</span>
298 <?php endif; ?>
299 <span class="ep-yt-theatre__position"><span class="ep-yt-theatre__position-current">1</span> / <span class="ep-yt-theatre__position-total"><?php echo esc_html($item_count); ?></span></span>
300 <span class="ep-yt-theatre__sep">&middot;</span>
301 <div class="ep-yt-theatre__controls">
302 <button type="button" class="ep-yt-theatre__loop ep-yt-icon-btn" aria-pressed="false" aria-label="<?php echo esc_attr__('Loop', 'embedpress'); ?>" title="<?php echo esc_attr__('Loop', 'embedpress'); ?>">
303 <span class="ep-yt-icon ep-yt-icon--loop" aria-hidden="true"></span>
304 </button>
305 <button type="button" class="ep-yt-theatre__shuffle ep-yt-icon-btn" aria-pressed="false" aria-label="<?php echo esc_attr__('Shuffle', 'embedpress'); ?>" title="<?php echo esc_attr__('Shuffle', 'embedpress'); ?>">
306 <span class="ep-yt-icon ep-yt-icon--shuffle" aria-hidden="true"></span>
307 </button>
308 </div>
309 </div>
310 </header>
311 <div class="ep-yt-theatre__strip-wrap">
312 <button type="button" class="ep-yt-theatre__nav ep-yt-theatre__nav--prev ep-yt-icon-btn" aria-label="<?php echo esc_attr__('Scroll left', 'embedpress'); ?>">
313 <span class="ep-yt-icon ep-yt-icon--chev-left" aria-hidden="true"></span>
314 </button>
315 <ol class="ep-yt-theatre__strip">
316 <?php echo self::render_theatre_cards($jsonResult, $options, 0, $opening_vid); ?>
317 </ol>
318 <button type="button" class="ep-yt-theatre__nav ep-yt-theatre__nav--next ep-yt-icon-btn" aria-label="<?php echo esc_attr__('Scroll right', 'embedpress'); ?>">
319 <span class="ep-yt-icon ep-yt-icon--chev-right" aria-hidden="true"></span>
320 </button>
321 </div>
322 <div class="ep-yt-theatre__loader" hidden>
323 <span class="ep-yt-theatre__spinner" aria-hidden="true"></span>
324 <span class="ep-yt-theatre__loader-text"><?php esc_html_e('Loading more…', 'embedpress'); ?></span>
325 </div>
326 </div>
327 </div>
328 <?php
329 return ob_get_clean();
330 }
331
332 /**
333 * Render a batch of theatre cards. Same contract as render_queue_items
334 * but in the horizontal card shape — used for both initial render and
335 * the infinite-scroll REST endpoint.
336 */
337 public static function render_theatre_cards($jsonResult, $options, $offset = 0, $active_vid = '') {
338 if (empty($jsonResult->items) || !is_array($jsonResult->items)) {
339 return '';
340 }
341 ob_start();
342 $i = (int) $offset;
343 $auto_active = empty($active_vid) && $offset === 0;
344 foreach ($jsonResult->items as $item) {
345 $i++;
346 $privacyStatus = isset($item->status->privacyStatus) ? $item->status->privacyStatus : null;
347 if ($privacyStatus === 'private' && !empty($options['hideprivate'])) {
348 continue;
349 }
350 $vid = Helper::get_id($item);
351 $title = isset($item->snippet->title) ? $item->snippet->title : '';
352 $channel = isset($item->snippet->videoOwnerChannelTitle)
353 ? $item->snippet->videoOwnerChannelTitle
354 : (isset($item->snippet->channelTitle) ? $item->snippet->channelTitle : '');
355 $thumbnail = Helper::get_thumbnail_url($item, !empty($options['thumbnail']) ? $options['thumbnail'] : 'medium', $privacyStatus);
356 $active = '';
357 if ($active_vid && $vid === $active_vid) {
358 $active = ' is-active';
359 } elseif ($auto_active && $i === 1) {
360 $active = ' is-active';
361 }
362 ?>
363 <li class="ep-yt-theatre__card<?php echo $active; ?>" data-vid="<?php echo esc_attr($vid); ?>" data-index="<?php echo esc_attr($i); ?>">
364 <span class="ep-yt-theatre__card-thumb-wrap">
365 <img class="ep-yt-theatre__card-thumb" src="<?php echo esc_url($thumbnail); ?>" alt="" loading="lazy" />
366 <span class="ep-yt-theatre__card-num"><?php echo esc_html($i); ?></span>
367 </span>
368 <span class="ep-yt-theatre__card-title"><?php echo esc_html($title); ?></span>
369 <?php if ($channel) : ?>
370 <span class="ep-yt-theatre__card-channel"><?php echo esc_html($channel); ?></span>
371 <?php endif; ?>
372 </li>
373 <?php
374 }
375 return ob_get_clean();
376 }
377
378 /**
379 * Render a batch of queue items. Used both for the initial render and for
380 * infinite-scroll AJAX response payloads, so the markup contract is
381 * defined in exactly one place.
382 */
383 public static function render_queue_items($jsonResult, $options, $offset = 0, $active_vid = '') {
384 if (empty($jsonResult->items) || !is_array($jsonResult->items)) {
385 return '';
386 }
387 ob_start();
388 $i = (int) $offset;
389 // No explicit active vid → first rendered item is active.
390 $auto_active = empty($active_vid) && $offset === 0;
391 foreach ($jsonResult->items as $item) {
392 $i++;
393 $privacyStatus = isset($item->status->privacyStatus) ? $item->status->privacyStatus : null;
394 if ($privacyStatus === 'private' && !empty($options['hideprivate'])) {
395 continue;
396 }
397 $vid = Helper::get_id($item);
398 $title = isset($item->snippet->title) ? $item->snippet->title : '';
399 $channel = isset($item->snippet->videoOwnerChannelTitle)
400 ? $item->snippet->videoOwnerChannelTitle
401 : (isset($item->snippet->channelTitle) ? $item->snippet->channelTitle : '');
402 $thumbnail = Helper::get_thumbnail_url($item, !empty($options['thumbnail']) ? $options['thumbnail'] : 'medium', $privacyStatus);
403 $active = '';
404 if ($active_vid && $vid === $active_vid) {
405 $active = ' is-active';
406 } elseif ($auto_active && $i === 1) {
407 $active = ' is-active';
408 }
409 ?>
410 <li class="ep-yt-queue__item<?php echo $active; ?>" data-vid="<?php echo esc_attr($vid); ?>" data-index="<?php echo esc_attr($i); ?>" data-thumb="<?php echo esc_attr($thumbnail); ?>">
411 <span class="ep-yt-queue__item-index"><?php echo esc_html($i); ?></span>
412 <img class="ep-yt-queue__item-thumb" src="<?php echo esc_url($thumbnail); ?>" alt="" loading="lazy" />
413 <span class="ep-yt-queue__item-body">
414 <span class="ep-yt-queue__item-title"><?php echo esc_html($title); ?></span>
415 <?php if ($channel) : ?>
416 <span class="ep-yt-queue__item-channel"><?php echo esc_html($channel); ?></span>
417 <?php endif; ?>
418 </span>
419 </li>
420 <?php
421 }
422 return ob_get_clean();
423 }
424
425 public static function create_youtube_layout($options, $data, $layout, $url) {
426 $nextPageToken = '';
427 $prevPageToken = '';
428 $gallobj = new \stdClass();
429 $options = wp_parse_args($options, [
430 'playlistId' => '',
431 'pageToken' => '',
432 'pagesize' => $data['get_pagesize'] ? $data['get_pagesize'] : 6,
433 'currentpage' => '',
434 'columns' => 3,
435 'thumbnail' => 'medium',
436 'gallery' => true,
437 'autonext' => true,
438 'thumbplay' => true,
439 'apiKey' => $data['get_api_key'],
440 'hideprivate' => '',
441 ]);
442 $options['pagesize'] = $options['pagesize'] > 50 ? 50 : $options['pagesize'];
443 $options['pagesize'] = $options['pagesize'] < 1 ? 1 : $options['pagesize'];
444
445 if (empty($options['apiKey'])) {
446 $gallobj->html = $data['get_api_key_error_message'];
447 return $gallobj;
448 }
449
450 $apiEndpoint = 'https://www.googleapis.com/youtube/v3/playlistItems?part=snippet,status,contentDetails&playlistId=' . $options['playlistId']
451 . '&maxResults=' . $options['pagesize']
452 . '&key=' . $options['apiKey'];
453 if ($options['pageToken'] != null) {
454 $apiEndpoint .= '&pageToken=' . $options['pageToken'];
455 }
456
457 $transient_key = 'ep_embed_youtube_channel_' . md5($apiEndpoint);
458 $gallobj->transient_key = $transient_key;
459 $jsonResult = get_transient($transient_key);
460
461
462 if (empty($jsonResult)) {
463 $apiResult = wp_remote_get($apiEndpoint, array('timeout' => $data['curltimeout']));
464 if (is_wp_error($apiResult)) {
465 $gallobj->html = Helper::clean_api_error_html($apiResult->get_error_message(), true);
466 return $gallobj;
467 }
468 $jsonResult = json_decode($apiResult['body']);
469
470 if (empty($jsonResult->error)) {
471 set_transient($transient_key, $jsonResult, MINUTE_IN_SECONDS * 20);
472 }
473 else{
474 set_transient($transient_key, $jsonResult, 10);
475 }
476 }
477
478
479 if (isset($jsonResult->error)) {
480 if(!empty($jsonResult->error->errors[0]->reason) && $jsonResult->error->errors[0]->reason == 'playlistNotFound'){
481 $gallobj->html = Helper::clean_api_error_html(__('There is nothing on the playlist.', 'embedpress'));
482 return $gallobj;
483 }
484 if (isset($jsonResult->error->message)) {
485 $gallobj->html = Helper::clean_api_error_html($jsonResult->error->message);
486 return $gallobj;
487 }
488 $gallobj->html = Helper::clean_api_error_html(__('Sorry, there may be an issue with your YouTube API key.', 'embedpress'));
489 return $gallobj;
490 }
491
492
493
494 $resultsPerPage = $jsonResult->pageInfo->resultsPerPage;
495 $totalResults = $jsonResult->pageInfo->totalResults;
496 $totalPages = ceil($totalResults / $resultsPerPage);
497 if (isset($jsonResult->nextPageToken)) {
498 $nextPageToken = $jsonResult->nextPageToken;
499 }
500
501 if (isset($jsonResult->prevPageToken)) {
502 $prevPageToken = $jsonResult->prevPageToken;
503 }
504
505
506 if (!empty($jsonResult->items) && is_array($jsonResult->items)) :
507 if($options['gallery'] === "false"){
508 $gallobj->html = "";
509 if(count($jsonResult->items) === 1){
510 $gallobj->first_vid = Helper::get_id($jsonResult->items[0]);
511 }
512 return $gallobj;
513 }
514
515 // Playlist-only layouts: own shells, own JS wiring, no
516 // channel-header / grid wrapper / pagination strip. Each one is
517 // isolated under .ep-yt-playlist with its own scoped CSS reset
518 // so theme + other plugin styles can't leak in.
519 $playlist_layouts = ['queue', 'theatre', 'library', 'spotlight', 'cinema', 'magazine'];
520 if (in_array($layout, $playlist_layouts, true)) {
521 $gallobj->first_vid = Helper::get_id($jsonResult->items[0]);
522 $playlist_info = !empty($data['playlist_info']) ? $data['playlist_info'] : [];
523 if ($layout === 'theatre') {
524 $gallobj->html = self::create_theatre_layout($jsonResult, $gallobj, $options, $data, $playlist_info, $gallobj->first_vid, $nextPageToken);
525 } elseif ($layout === 'queue') {
526 $gallobj->html = self::create_queue_layout($jsonResult, $gallobj, $options, $data, $playlist_info, $gallobj->first_vid, $nextPageToken);
527 } else {
528 // Pro layouts (library, spotlight, cinema, magazine) — Pro
529 // plugin handlers register on these actions. Action prints
530 // the HTML into the ob buffer (mirrors channel grid/carousel
531 // pattern). Free falls back to queue earlier in the chain
532 // when Pro is inactive, so this only fires for Pro users.
533 ob_start();
534 do_action(
535 'embedpress/youtube_' . $layout . '_layout',
536 $jsonResult, $gallobj, $options, $data, $playlist_info, $gallobj->first_vid, $nextPageToken
537 );
538 $rendered = ob_get_clean();
539 // If no handler printed anything, fall back to queue so we
540 // never ship an empty embed in production.
541 $gallobj->html = !empty($rendered)
542 ? $rendered
543 : self::create_queue_layout($jsonResult, $gallobj, $options, $data, $playlist_info, $gallobj->first_vid, $nextPageToken);
544 }
545 return $gallobj;
546 }
547
548 // if(count($jsonResult->items) === 1 && empty($nextPageToken) && empty($prevPageToken)){
549 // $gallobj->first_vid = Helper::get_id($jsonResult->items[0]);
550 // $gallobj->html = "";
551 // return $gallobj;
552 // }
553
554 if (strpos($options['playlistId'], 'UU') === 0) {
555 // sort only channels
556 usort($jsonResult->items, array(Helper::class, 'compare_vid_date')); // sorts in place
557 }
558
559
560 ob_start();
561
562 ?>
563
564
565 <?php
566 $channel_info = $data['get_channel_info'];
567
568
569
570 $chanelTitle = isset($channel_info['snippet']['title']) ? $channel_info['snippet']['title'] : null;
571 $channelThumb = isset($channel_info['snippet']['thumbnails']['high']['url']) ? $channel_info['snippet']['thumbnails']['high']['url'] : null;
572
573
574 echo self::create_channel_info_layout($channel_info, $url);
575
576 $channel_id = '';
577 if(isset($channel_info['id'])) {
578 $channel_id = $channel_info['id'];
579 }
580
581 $carouselWrapperClass = '';
582 $carouselSelectorId = '';
583 if($layout == 'carousel') {
584 $carouselWrapperClass = 'youtube-carousel';
585 $carouselSelectorId = 'data-youtube-channel-carousel="carousel-'.esc_attr(md5($channel_id)).'"';
586 }
587
588 ?>
589
590 <div class="ep-youtube__content__block" <?php echo $carouselSelectorId; ?> data-unique-id="<?php echo esc_attr(md5($channel_id)); ?>">
591 <div class="youtube__content__body youtube-carousel-container">
592 <div class="content__wrap <?php echo esc_attr($carouselWrapperClass); ?>" >
593
594 <?php
595 if($layout === 'gallery'){
596 echo self::create_gallery_layout($jsonResult, $gallobj, $options, $data, $chanelTitle, $channelThumb);
597 }
598
599 else if($layout === 'list'){
600 echo self::create_list_layout($jsonResult, $gallobj, $options, $data, $chanelTitle, $channelThumb);
601 }
602 else if($layout === 'grid'){
603 do_action('embedpress/youtube_grid_layout', $jsonResult, $gallobj, $options, $data, $chanelTitle, $channelThumb);
604
605 }
606 else if($layout === 'carousel'){
607 do_action('embedpress/youtube_carousel_layout', $jsonResult, $gallobj, $options, $data, $chanelTitle, $channelThumb);
608 }
609 else{
610 echo self::create_gallery_layout($jsonResult, $gallobj, $options, $data, $chanelTitle, $channelThumb);
611
612 }
613 ?>
614 <div class="item" style="height: 0"></div>
615 </div>
616
617 <!-- Pagination and other content remains unchanged -->
618 <?php if ($totalPages > 1 && $layout !== 'carousel') : ?>
619 <div class="ep-youtube__content__pagination <?php echo (empty($prevPageToken) && empty($nextPageToken)) ? ' hide ' : ''; ?>">
620 <div
621 class="ep-prev" <?php echo empty($prevPageToken) ? ' style="display:none" ' : ''; ?>
622 data-playlistid="<?php echo esc_attr($options['playlistId']) ?>"
623 data-pagetoken="<?php echo esc_attr($prevPageToken) ?>"
624 data-pagesize="<?php echo intval($options['pagesize']) ?>"
625
626 >
627 <span><?php _e("Prev", "embedpress"); ?></span>
628 </div>
629 <div class="is_desktop_device ep-page-numbers <?php echo $totalPages > 1 ? '' : 'hide'; ?>">
630 <?php
631
632 $numOfPages = $totalPages;
633 $renderedEllipses = false;
634
635 $currentPage = !empty($options['currentpage'])?$options['currentpage'] : 1;
636
637 for($i = 1; $i<=$numOfPages; $i++)
638 {
639 //render pages 1 - 3
640 if($i < 4) {
641 //render link
642 $is_current = $i == (int)$currentPage? "active__current_page" : "";
643
644 echo wp_kses_post("<span class='page-number $is_current' data-page='$i'>$i</span>");
645
646 }
647
648 //render current page number
649 else if($i == (int)$currentPage) {
650 //render link
651 echo wp_kses_post('<span class="page-number active__current_page" data-page="'.$i.'">'.$i.'</span>');
652 //reset ellipses
653 $renderedEllipses = false;
654 }
655
656 //last page number
657 else if ($i >= $numOfPages - 1) {
658 //render link
659 echo wp_kses_post('<span class="page-number" data-page="'.$i.'">'.$i.'</span>');
660 }
661
662 //make sure you only do this once per ellipses group
663 else {
664 if (!$renderedEllipses){
665 print("...");
666 $renderedEllipses = true;
667 }
668 }
669 }
670 ?>
671
672 </div>
673
674 <div class="is_mobile_device ep-page-numbers <?php echo $totalPages > 1 ? '' : 'hide'; ?>">
675 <?php
676
677 $numOfPages = $totalPages;
678 $renderedEllipses = false;
679
680 $currentPage = !empty($options['currentpage'])?$options['currentpage'] : 1;
681
682 for($i = 1; $i<=$numOfPages; $i++)
683 {
684
685 //render current page number
686 if($i == (int)$currentPage) {
687 //render link
688 echo wp_kses_post('<span class="page-number-mobile" data-page="'.$i.'">'.$i.'</span>');
689 //reset ellipses
690 $renderedEllipses = false;
691 }
692
693 //last page number
694 else if ($i >= $numOfPages ) {
695 //render link
696 echo wp_kses_post('...<span class="page-number-mobile" data-page="'.$i.'">'.$i.'</span>');
697 }
698 }
699 ?>
700
701 </div>
702
703
704 <div
705 class="ep-next " <?php echo empty($nextPageToken) ? ' style="display:none" ' : ''; ?>
706 data-playlistid="<?php echo esc_attr($options['playlistId']) ?>"
707 data-pagetoken="<?php echo esc_attr($nextPageToken) ?>"
708 data-pagesize="<?php echo intval($options['pagesize']) ?>"
709 >
710 <span><?php _e("Next ", "embedpress"); ?> </span>
711 </div>
712 </div>
713 <?php endif; ?>
714
715 </div>
716
717
718 <?php if($layout === 'carousel'): ?>
719 <div class="carousel-controls">
720 <button class="preview" type="button" aria-label="<?php esc_attr_e('Previous', 'embedpress'); ?>"><span aria-hidden="true"></span></button>
721 <button class="next" type="button" aria-label="<?php esc_attr_e('Next', 'embedpress'); ?>"><span aria-hidden="true"></span></button>
722 <?php endif; ?>
723
724 </div>
725
726 <?php
727 $gallobj->html = ob_get_clean();
728 else:
729 $gallobj->html = Helper::clean_api_error_html(__("There is nothing on the playlist.", 'embedpress'));
730 endif;
731
732 return $gallobj;
733 }
734
735 }
736
737
738
739
740
741