Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 130 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
PlayerInline | |
0.00% |
0 / 130 |
|
0.00% |
0 / 13 |
2352 | |
0.00% |
0 / 1 |
init | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
registerShortcodes | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
playerShortcode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
autoPrependPlayer | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
playerHtml | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
56 | |||
hasCustomPlayer | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
jsPlayerHtml | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
ampPlayerHtml | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
isPlayerEnabled | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
useAmpPlayer | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
usePlayerJsSdk | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
90 | |||
jsPlayerParams | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
56 | |||
addPluginSettingsToSdkParams | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Beyondwords\Wordpress\Core\Player; |
6 | |
7 | use Beyondwords\Wordpress\Component\Post\PostMetaUtils; |
8 | use Beyondwords\Wordpress\Component\Settings\Fields\PlayerUI\PlayerUI; |
9 | use Beyondwords\Wordpress\Core\Environment; |
10 | use Beyondwords\Wordpress\Core\CoreUtils; |
11 | use Symfony\Component\DomCrawler\Crawler; |
12 | |
13 | /** |
14 | * The BeyondWords Player. |
15 | * |
16 | * This is an alternate Player class using the inline script method that |
17 | * is recommended in the player docs. |
18 | * |
19 | * @link https://github.com/beyondwords-io/player/blob/main/doc/getting-started.md. |
20 | **/ |
21 | class PlayerInline |
22 | { |
23 | /** |
24 | * Init. |
25 | */ |
26 | public function init() |
27 | { |
28 | // Actions |
29 | add_action('init', array($this, 'registerShortcodes')); |
30 | |
31 | // Filters |
32 | add_filter('the_content', array($this, 'autoPrependPlayer'), 1000000); |
33 | add_filter('newsstand_the_content', array($this, 'autoPrependPlayer')); |
34 | } |
35 | |
36 | /** |
37 | * Register shortcodes. |
38 | * |
39 | * @since 4.2.0 |
40 | */ |
41 | public function registerShortcodes() |
42 | { |
43 | add_shortcode('beyondwords_player', array($this, 'playerShortcode')); |
44 | } |
45 | |
46 | /** |
47 | * HTML output for the BeyondWords player shortcode. |
48 | * |
49 | * @since 4.2.0 |
50 | * |
51 | * @param array $atts Shortcode attributes. |
52 | * |
53 | * @return string |
54 | */ |
55 | public function playerShortcode() |
56 | { |
57 | return $this->playerHtml(); |
58 | } |
59 | |
60 | /** |
61 | * Auto-prepends the BeyondWords player to WordPress content. |
62 | * |
63 | * @since 3.0.0 |
64 | * @since 4.2.0 Renamed from addPlayerToContent to autoPrependPlayer. |
65 | * @since 4.2.0 Perform hasCustomPlayer() check here. |
66 | * @since 4.6.1 Only auto-prepend player for frontend is_singular screens. |
67 | * |
68 | * @param string $content WordPress content. |
69 | * |
70 | * @return string |
71 | */ |
72 | public function autoPrependPlayer($content) |
73 | { |
74 | if (! is_singular()) { |
75 | return $content; |
76 | } |
77 | |
78 | if ($this->hasCustomPlayer($content)) { |
79 | return $content; |
80 | } |
81 | |
82 | return $this->playerHtml() . $content; |
83 | } |
84 | |
85 | /** |
86 | * Player HTML. |
87 | * |
88 | * Displays JS SDK variant of the BeyondWords audio player, for both |
89 | * AMP and non-AMP content. |
90 | * |
91 | * @param WP_Post $post WordPress Post. |
92 | * |
93 | * @since 3.0.0 |
94 | * @since 3.1.0 Added _doing_it_wrong deprecation warnings |
95 | * |
96 | * @return string |
97 | */ |
98 | public function playerHtml($post = false) |
99 | { |
100 | if (! ($post instanceof \WP_Post)) { |
101 | $post = get_post($post); |
102 | } |
103 | |
104 | if (! $post) { |
105 | return ''; |
106 | } |
107 | |
108 | if (! $this->isPlayerEnabled($post)) { |
109 | return ''; |
110 | } |
111 | |
112 | $projectId = PostMetaUtils::getProjectId($post->ID); |
113 | |
114 | if (! $projectId) { |
115 | return ''; |
116 | } |
117 | |
118 | $contentId = PostMetaUtils::getContentId($post->ID); |
119 | |
120 | if (! $contentId) { |
121 | return ''; |
122 | } |
123 | |
124 | // AMP or JS Player? |
125 | if ($this->useAmpPlayer()) { |
126 | $html = $this->ampPlayerHtml($post->ID, $projectId, $contentId); |
127 | } else { |
128 | $html = $this->jsPlayerHtml($post->ID, $projectId, $contentId); |
129 | } |
130 | |
131 | /** |
132 | * Filters the HTML of the BeyondWords Player. |
133 | * |
134 | * @since 4.0.0 |
135 | * @since 4.3.0 Applied to both AMP and no-AMP content. |
136 | * |
137 | * @param string $html The HTML for the JS audio player. The audio player JavaScript may |
138 | * fail to locate the target element if you remove or replace the |
139 | * default contents of this parameter. |
140 | * @param int $postId WordPress post ID. |
141 | * @param int $projectId BeyondWords project ID. |
142 | * @param int $contentId BeyondWords content ID. |
143 | */ |
144 | $html = apply_filters('beyondwords_player_html', $html, $post->ID, $projectId, $contentId); |
145 | |
146 | return $html; |
147 | } |
148 | |
149 | /** |
150 | * Has custom player? |
151 | * |
152 | * Checks the post content to see whether a custom player has been added. |
153 | * |
154 | * @since 3.2.0 |
155 | * @since 4.2.0 Pass $content as a parameter, check for [beyondwords_player] shortcode |
156 | * @since 4.2.4 Check $content is a string |
157 | * |
158 | * @param string $content WordPress content. |
159 | * |
160 | * @return boolean |
161 | */ |
162 | public function hasCustomPlayer($content) |
163 | { |
164 | if (! is_string($content)) { |
165 | return false; |
166 | } |
167 | |
168 | if (strpos($content, '[beyondwords_player]') !== false) { |
169 | return true; |
170 | } |
171 | |
172 | $crawler = new Crawler($content); |
173 | |
174 | return count($crawler->filterXPath('//div[@data-beyondwords-player="true"]')) > 0; |
175 | } |
176 | |
177 | /** |
178 | * JS Player HTML. |
179 | * |
180 | * Displays the HTML required for the JS player. |
181 | * |
182 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) |
183 | * |
184 | * @param int $postId WordPress Post ID. |
185 | * @param int $projectId BeyondWords Project ID. |
186 | * @param int $contentId BeyondWords Content ID. |
187 | * |
188 | * @since 3.0.0 |
189 | * @since 3.1.0 Added speechkit_js_player_html filter |
190 | * @since 4.2.0 Remove hasCustomPlayer() check from here. |
191 | * @since 5.2.0 Replace div[data-beyondwords-player] with script[onload] |
192 | * @since 5.3.0 Use new jsPlayerParams() object return. |
193 | * |
194 | * @return string |
195 | */ |
196 | public function jsPlayerHtml($postId, $projectId, $contentId) |
197 | { |
198 | if (! $this->usePlayerJsSdk()) { |
199 | return ''; |
200 | } |
201 | |
202 | $post = get_post($postId); |
203 | $params = $this->jsPlayerParams($post); |
204 | |
205 | $playerUI = get_option('beyondwords_player_ui', PlayerUI::ENABLED); |
206 | |
207 | $params->projectId = $projectId; |
208 | $params->contentId = $contentId; |
209 | |
210 | $jsonParams = wp_json_encode($params, JSON_UNESCAPED_SLASHES); |
211 | |
212 | // Headless instantiates a player without a target |
213 | if ($playerUI !== PlayerUI::HEADLESS) { |
214 | $jsonParams = sprintf('{...%s, target:this}', $jsonParams); |
215 | } |
216 | |
217 | $onload = sprintf('new BeyondWords.Player(%s);', $jsonParams); |
218 | |
219 | /** |
220 | * Filters the onload attribute of the BeyondWords Player script. |
221 | * |
222 | * Note that to support multiple players on one page, the |
223 | * default script uses `document.querySelectorAll() to target all |
224 | * instances of `div[data-beyondwords-player]` in the HTML source. |
225 | * If this approach is removed then multiple occurrences of the |
226 | * BeyondWords player in one page may not work as expected. |
227 | * |
228 | * @link https://github.com/beyondwords-io/player/blob/main/doc/getting-started.md#how-to-configure-it |
229 | * |
230 | * @since 4.0.0 |
231 | * |
232 | * @param string $script The string value of the onload script. |
233 | * @param array $params The SDK params for the current post, including |
234 | * `projectId` and `contentId`. |
235 | */ |
236 | $onload = apply_filters('beyondwords_player_script_onload', $onload, $params); |
237 | |
238 | $html = sprintf( |
239 | // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript |
240 | '<script async defer src="%s" onload=\'%s\'></script>', |
241 | Environment::getJsSdkUrl(), |
242 | $onload |
243 | ); |
244 | |
245 | return $html; |
246 | } |
247 | |
248 | /** |
249 | * AMP Player HTML. |
250 | * |
251 | * Displays the HTML required for the AMP player. |
252 | * |
253 | * @param int $postId WordPress Post ID. |
254 | * @param int $projectId BeyondWords Project ID. |
255 | * @param int $contentId BeyondWords Content ID. |
256 | * |
257 | * @since 3.0.0 |
258 | * @since 3.1.0 Added speechkit_amp_player_html filter |
259 | * |
260 | * @return string |
261 | */ |
262 | public function ampPlayerHtml($postId, $projectId, $contentId) |
263 | { |
264 | $src = sprintf(Environment::getAmpPlayerUrl(), $projectId, $contentId); |
265 | |
266 | // Turn on output buffering |
267 | ob_start(); |
268 | |
269 | ?> |
270 | <amp-iframe |
271 | frameborder="0" |
272 | height="43" |
273 | layout="responsive" |
274 | sandbox="allow-scripts allow-same-origin allow-popups" |
275 | scrolling="no" |
276 | src="<?php echo esc_url($src); ?>" |
277 | width="295" |
278 | > |
279 | <amp-img |
280 | height="150" |
281 | layout="responsive" |
282 | placeholder |
283 | src="<?php echo esc_url(Environment::getAmpImgUrl()); ?>" |
284 | width="643" |
285 | ></amp-img> |
286 | </amp-iframe> |
287 | <?php |
288 | |
289 | $html = ob_get_clean(); |
290 | |
291 | /** |
292 | * Filters the HTML of the BeyondWords AMP audio player. |
293 | * |
294 | * This filter is scheduled to be removed in v5.0. |
295 | * |
296 | * @since 3.3.3 |
297 | * |
298 | * @deprecated 4.3.0 beyondwords_player_html is now applied to AMP and non-AMP content. |
299 | * @see Beyondwords\Wordpress\Core\Player\Player::playerHtml() |
300 | * |
301 | * @param string $html The HTML for the AMP audio player. |
302 | * @param int $post_id WordPress Post ID. |
303 | * @param int $project_id BeyondWords Project ID. |
304 | * @param int $contentId BeyondWords Content ID. |
305 | */ |
306 | $html = apply_filters('beyondwords_amp_player_html', $html, $postId, $projectId, $contentId); |
307 | |
308 | return $html; |
309 | } |
310 | |
311 | /** |
312 | * Should we show the BeyondWords audio player? |
313 | * |
314 | * We DO NOT want to show the player if: |
315 | * 1. BeyondWords has been disabled in our plugin settings. |
316 | * 2. The current post type has not been selected in our plugin settings. |
317 | * 3. The current post has specifically been disabled from processing. |
318 | * |
319 | * The return value of this can be overriden with the WordPress |
320 | * "beyondwords_post_player_enabled" filter. |
321 | * |
322 | * @param int|WP_Post (Optional) Post ID or WP_Post object. Default is global $post. |
323 | * |
324 | * @since 3.0.0 |
325 | * @since 3.3.4 Accept int|WP_Post as method parameter. |
326 | * @since 4.0.0 Check beyondwords_player_ui custom field. |
327 | * @since 5.0.0 Remove beyondwords_post_player_enabled filter. |
328 | * |
329 | * @return bool |
330 | **/ |
331 | public function isPlayerEnabled($post = null) |
332 | { |
333 | $post = get_post($post); |
334 | |
335 | if (! ($post instanceof \WP_Post)) { |
336 | return false; |
337 | } |
338 | |
339 | // Assume we can show the player |
340 | $enabled = true; |
341 | |
342 | // Has 'Display Player' been unchecked? |
343 | if (PostMetaUtils::getDisabled($post->ID)) { |
344 | $enabled = false; |
345 | } |
346 | |
347 | // Is the player ui enabled in plugin settings? |
348 | if ($enabled) { |
349 | $enabled = get_option('beyondwords_player_ui', PlayerUI::ENABLED) === PlayerUI::ENABLED; |
350 | } |
351 | |
352 | return $enabled; |
353 | } |
354 | |
355 | /** |
356 | * Use the AMP player? |
357 | * |
358 | * There are multiple AMP plugins for WordPress, so multiple checks are performed. |
359 | * |
360 | * @since 3.0.7 |
361 | * |
362 | * @return bool |
363 | */ |
364 | public function useAmpPlayer() |
365 | { |
366 | // https://amp-wp.org/reference/function/amp_is_request/ |
367 | if (function_exists('amp_is_request')) { |
368 | return \amp_is_request(); |
369 | } |
370 | |
371 | // https://ampforwp.com/tutorials/article/detect-amp-page-function/ |
372 | if (function_exists('ampforwp_is_amp_endpoint')) { |
373 | return \ampforwp_is_amp_endpoint(); |
374 | } |
375 | |
376 | // https://amp-wp.org/reference/function/is_amp_endpoint/ |
377 | if (function_exists('is_amp_endpoint')) { |
378 | return \is_amp_endpoint(); |
379 | } |
380 | |
381 | return false; |
382 | } |
383 | |
384 | /** |
385 | * Use Player JS SDK? |
386 | * |
387 | * @since 3.0.7 |
388 | * |
389 | * @return string |
390 | */ |
391 | public function usePlayerJsSdk() |
392 | { |
393 | // AMP requests don't use the Player JS SDK |
394 | if ($this->useAmpPlayer()) { |
395 | return false; |
396 | } |
397 | |
398 | // Both Gutenberg/Classic editors have their own player scripts |
399 | if (CoreUtils::isGutenbergPage() || CoreUtils::isEditScreen()) { |
400 | return false; |
401 | } |
402 | |
403 | // Disable audio player in Preview, because we have not sent updates to BeyondWords API yet |
404 | if (function_exists('is_preview') && is_preview()) { |
405 | return false; |
406 | } |
407 | |
408 | $post = get_post(); |
409 | |
410 | if (! $post) { |
411 | return false; |
412 | } |
413 | |
414 | $projectId = PostMetaUtils::getProjectId($post->ID); |
415 | $contentId = PostMetaUtils::getContentId($post->ID); |
416 | |
417 | if ($projectId && $contentId) { |
418 | return true; |
419 | } |
420 | |
421 | return false; |
422 | } |
423 | |
424 | /** |
425 | * JavaScript SDK parameters. |
426 | * |
427 | * @since 3.1.0 |
428 | * @since 4.0.0 Use new JS SDK params format. |
429 | * @since 5.3.0 Prioritise post-specific player settings, falling-back to the |
430 | * values of the "Player" tab in the plugin settings. |
431 | * @since 5.3.0 Support loadContentAs param and return an object. |
432 | * |
433 | * @param WP_Post $post WordPress Post. |
434 | * |
435 | * @return object |
436 | */ |
437 | public function jsPlayerParams($post) |
438 | { |
439 | if (!($post instanceof \WP_Post)) { |
440 | return []; |
441 | } |
442 | |
443 | $projectId = PostMetaUtils::getProjectId($post->ID); |
444 | $contentId = PostMetaUtils::getContentId($post->ID); |
445 | |
446 | $params = [ |
447 | 'projectId' => is_numeric($projectId) ? (int)$projectId : $projectId, |
448 | 'contentId' => is_numeric($contentId) ? (int)$contentId : $contentId, |
449 | ]; |
450 | |
451 | // Set initial SDK params from plugin settings |
452 | $params = $this->addPluginSettingsToSdkParams($params); |
453 | |
454 | // Player UI |
455 | $playerUI = get_option('beyondwords_player_ui', PlayerUI::ENABLED); |
456 | if ($playerUI === PlayerUI::HEADLESS) { |
457 | $params['showUserInterface'] = false; |
458 | } |
459 | |
460 | // Player Style |
461 | // @todo overwrite global styles with post settings |
462 | $playerStyle = PostMetaUtils::getPlayerStyle($post->ID); |
463 | if (!empty($playerStyle)) { |
464 | $params['playerStyle'] = $playerStyle; |
465 | } |
466 | |
467 | // Player content |
468 | $playerContent = get_post_meta($post->ID, 'beyondwords_player_content', true); |
469 | if (!empty($playerContent)) { |
470 | $params['loadContentAs'] = [ $playerContent ]; |
471 | } |
472 | |
473 | /** |
474 | * Filters the BeyondWords JavaScript SDK parameters. |
475 | * |
476 | * @since 4.0.0 |
477 | * |
478 | * @param array $params The default JS SDK params. |
479 | * @param int $postId The Post ID. |
480 | */ |
481 | $params = apply_filters('beyondwords_player_sdk_params', $params, $post->ID); |
482 | |
483 | // Cast assoc array to object |
484 | return (object)$params; |
485 | } |
486 | |
487 | /** |
488 | * Add plugin settings to SDK params. |
489 | * |
490 | * @since 5.0.0 |
491 | * |
492 | * @param array $params BeyondWords Player SDK params. |
493 | * |
494 | * @return array Modified SDK params. |
495 | */ |
496 | public function addPluginSettingsToSdkParams($params) |
497 | { |
498 | $mapping = [ |
499 | 'beyondwords_player_style' => 'playerStyle', |
500 | 'beyondwords_player_call_to_action' => 'callToAction', |
501 | 'beyondwords_player_highlight_sections' => 'highlightSections', |
502 | 'beyondwords_player_widget_style' => 'widgetStyle', |
503 | 'beyondwords_player_widget_position' => 'widgetPosition', |
504 | 'beyondwords_player_skip_button_style' => 'skipButtonStyle', |
505 | ]; |
506 | |
507 | foreach ($mapping as $wpOption => $sdkParam) { |
508 | $val = get_option($wpOption); |
509 | if (!empty($val)) { |
510 | $params[$sdkParam] = $val; |
511 | } |
512 | } |
513 | |
514 | // Special case for clickableSections |
515 | $val = get_option('beyondwords_player_clickable_sections'); |
516 | if (!empty($val)) { |
517 | $params['clickableSections'] = 'body'; |
518 | } |
519 | |
520 | return $params; |
521 | } |
522 | } |