PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 2.1.1
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v2.1.1
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 / Shortcode.php
embedpress / EmbedPress Last commit date
AMP 8 years ago Ends 8 years ago Plugins 8 years ago Providers 8 years ago AutoLoader.php 8 years ago Core.php 8 years ago Disabler.php 8 years ago Loader.php 8 years ago Shortcode.php 8 years ago index.html 9 years ago
Shortcode.php
586 lines
1 <?php
2 namespace EmbedPress;
3
4 use \EmbedPress\Core;
5 use \Embera\Embera;
6 use \Embera\Formatter;
7
8 (defined('ABSPATH') && defined('EMBEDPRESS_IS_LOADED')) or die("No direct script access allowed.");
9
10 /**
11 * Entity responsible to handle the plugin's shortcode events and behaviors.
12 *
13 * @package EmbedPress
14 * @author EmbedPress <help@embedpress.com>
15 * @copyright Copyright (C) 2018 EmbedPress. All rights reserved.
16 * @license GPLv2 or later
17 * @since 1.0.0
18 */
19 class Shortcode
20 {
21 /**
22 * The WP_oEmbed class instance.
23 *
24 * @since 1.0.0
25 * @access private
26 * @static
27 *
28 * @var string $oEmbedInstance
29 */
30 private static $oEmbedInstance = null;
31
32 /**
33 * Register the plugin's shortcode into WordPress.
34 *
35 * @since 1.0.0
36 * @static
37 *
38 * @return void
39 */
40 public static function register()
41 {
42 // Register the new shortcode for embeds.
43 add_shortcode(EMBEDPRESS_SHORTCODE, array('\\EmbedPress\\Shortcode', 'do_shortcode'), 1);
44 }
45
46 /**
47 * Method that converts the plugin shortcoded-string into its complex content.
48 *
49 * @since 1.0.0
50 * @static
51 *
52 * @param array $attributes Array of attributes
53 * @param string $subject The given string
54 * @return string
55 */
56 public static function do_shortcode($attributes = array(), $subject = null)
57 {
58 $embed = self::parseContent($subject, true, $attributes);
59
60 return is_object($embed) ? $embed->embed : $embed;
61 }
62
63 /**
64 * Replace a given content with its embeded HTML code.
65 *
66 * @since 1.0.0
67 * @static
68 *
69 * @param string The raw content that will be replaced.
70 * @param boolean Optional. If true, new lines at the end of the embeded code are stripped.
71 * @return string
72 */
73 public static function parseContent($subject, $stripNewLine = false, $customAttributes = array())
74 {
75 if (!empty($subject)) {
76 if (empty($customAttributes)) {
77 $customAttributes = self::parseContentAttributesFromString($subject);
78 }
79
80 $content = preg_replace('/(\['. EMBEDPRESS_SHORTCODE .'(?:\]|.+?\])|\[\/'. EMBEDPRESS_SHORTCODE .'\])/i', "", $subject);
81
82 // Converts any special HTML entities back to characters.
83 $content = htmlspecialchars_decode($content);
84
85 // Check if the WP_oEmbed class is loaded
86 if (!self::$oEmbedInstance) {
87 require_once ABSPATH .'wp-includes/class-oembed.php';
88
89 self::$oEmbedInstance = _wp_oembed_get_object();
90 }
91
92 $emberaInstanceSettings = array(
93 'params' => array()
94 );
95
96 $content_uid = md5( $content );
97
98 $attributes = self::parseContentAttributes($customAttributes, $content_uid);
99 if (isset($attributes['width']) || isset($attributes['height'])) {
100 if (isset($attributes['width'])) {
101 $emberaInstanceSettings['params']['width'] = $attributes['width'];
102 unset($attributes['width']);
103 }
104
105 if (isset($attributes['height'])) {
106 $emberaInstanceSettings['params']['height'] = $attributes['height'];
107 unset($attributes['height']);
108 }
109 }
110
111 // Identify what service provider the shortcode's link belongs to
112 $serviceProvider = self::$oEmbedInstance->get_provider($content);
113
114
115 // Check if OEmbed was unable to detect the url service provider.
116 if (empty($serviceProvider)) {
117 // Attempt to do the same using Embera.
118 $emberaInstance = new Embera($emberaInstanceSettings);
119 // Add support to the user's custom service providers
120 $additionalServiceProviders = Core::getAdditionalServiceProviders();
121 if (!empty($additionalServiceProviders)) {
122 foreach ($additionalServiceProviders as $serviceProviderClassName => $serviceProviderUrls) {
123 self::addServiceProvider($serviceProviderClassName, $serviceProviderUrls, $emberaInstance);
124 }
125
126 unset($serviceProviderUrls, $serviceProviderClassName);
127 }
128
129 // Attempt to fetch more info about the url-embed.
130 $urlData = $emberaInstance->getUrlInfo($content);
131 } else {
132 // Attempt to fetch more info about the url-embed.
133 $urlData = self::$oEmbedInstance->fetch($serviceProvider, $content, $attributes);
134 }
135
136 // Sanitize the data
137 $urlData = self::sanitizeUrlData($urlData);
138
139 // Stores the original content
140 if (is_object($urlData)) {
141 $urlData->originalContent = $content;
142 }
143
144 $eventResults = apply_filters('embedpress:onBeforeEmbed', $urlData);
145 if (empty($eventResults)) {
146 // EmbedPress seems unable to embed the url.
147 return $subject;
148 }
149
150 // Transform all shortcode attributes into html form. I.e.: {foo: "joe"} -> foo="joe"
151 $attributesHtml = array();
152 foreach ($attributes as $attrName => $attrValue) {
153 $attributesHtml[] = $attrName .'="'. $attrValue .'"';
154 }
155
156 // Define the EmbedPress html template where the generated embed will be injected in
157 $embedTemplate = '<div '. implode(' ', $attributesHtml) .'>{html}</div>';
158
159 // Check if $content is a google shortened url and tries to extract from it which Google service it refers to.
160 if (preg_match('/http[s]?:\/\/goo\.gl\/(?:([a-z]+)\/)?[a-z0-9]+\/?$/i', $content, $matches)) {
161 // Fetch all headers from the short-url so we can know how to handle its original content depending on the service.
162 $headers = get_headers($content);
163
164 $supportedServicesHeadersPatterns = array(
165 'maps' => '/^Location:\s+(http[s]?:\/\/.+)$/i'
166 );
167
168 $service = isset($matches[1]) ? strtolower($matches[1]) : null;
169 // No specific service was found in the url.
170 if (empty($service)) {
171 // Let's try to guess which service the original url belongs to.
172 foreach ($headers as $header) {
173 // Check if the short-url reffers to a Google Maps url.
174 if (preg_match($supportedServicesHeadersPatterns['maps'], $header, $matches)) {
175 // Replace the shortened url with its original url.
176 $content = $matches[1];
177 break;
178 }
179 }
180 unset($header);
181 } else {
182 // Check if the Google service is supported atm.
183 if (isset($supportedServicesHeadersPatterns[$service])) {
184 // Tries to extract the url based on its headers.
185 $originalUrl = self::extractContentFromHeaderAsArray($supportedServicesHeadersPatterns[$service], $headers);
186 // Replace the shortened url with its original url if the specific header was found.
187 if (!empty($originalUrl)) {
188 $content = $originalUrl;
189 }
190 unset($originalUrl);
191 }
192 }
193 unset($service, $supportedServicesHeadersPatterns, $headers, $matches);
194 }
195
196 // Facebook is a special case. WordPress will try to embed them using OEmbed, but they always end up embedding the profile page, regardless
197 // if the url was pointing to a photo, a post, etc. So, since Embera can embed only facebook-media/posts, we'll use it only for that.
198 if (isset($urlData->provider_name) && in_array($urlData->provider_name, array('Facebook'))) {
199 // Check if this is a Facebook profile url.
200 if (preg_match('/facebook\.com\/(?:[^\/]+?)\/?$/', $content, $match)) {
201 // Try to embed the url using WP's OSEmbed.
202 $parsedContent = self::$oEmbedInstance->get_html($content, $attributes);
203 } else {
204 // Try to embed the url using EmbedPress' Embera.
205 $parsedContent = false;
206 }
207 } else {
208 // Try to embed the url using WP's OSEmbed.
209 $parsedContent = self::$oEmbedInstance->get_html($content, $attributes);
210 }
211
212 if (!$parsedContent) {
213 if (!isset($emberaInstance)) {
214 // If the embed couldn't be generated, we'll try to use Embera's API
215 $emberaInstance = new Embera($emberaInstanceSettings);
216 // Add support to the user's custom service providers
217 $additionalServiceProviders = Core::getAdditionalServiceProviders();
218 if (!empty($additionalServiceProviders)) {
219 foreach ($additionalServiceProviders as $serviceProviderClassName => $serviceProviderUrls) {
220 self::addServiceProvider($serviceProviderClassName, $serviceProviderUrls, $emberaInstance);
221 }
222
223 unset($serviceProviderUrls, $serviceProviderClassName);
224 }
225 }
226
227 // Register the html template
228 $emberaFormaterInstance = new Formatter($emberaInstance, true);
229 $emberaFormaterInstance->setTemplate($embedTemplate);
230
231 // Try to generate the embed using Embera API
232 $parsedContent = $emberaFormaterInstance->transform($content);
233
234 unset($emberaFormaterInstance, $additionalServiceProviders, $emberaInstance);
235 } else {
236 // Inject the generated code inside the html template
237 $parsedContent = str_replace('{html}', $parsedContent, $embedTemplate);
238
239 // Replace all single quotes to double quotes. I.e: foo='joe' -> foo="joe"
240 $parsedContent = str_replace("'", '"', $parsedContent);
241
242 // Replace the flag `{provider_alias}` which is used by Embera with the "ose-<serviceProviderAlias>". I.e: YouTube -> "ose-youtube"
243 $parsedContent = preg_replace('/((?:ose-)?\{provider_alias\})/i', "ose-". strtolower($urlData->provider_name), $parsedContent);
244 }
245
246 if (isset($urlData->provider_name) || (is_array($urlData) && isset($urlData[$content]['provider_name']))) {
247 // NFB seems to always return their embed code with all HTML entities into their applicable characters string.
248 if ((isset($urlData->provider_name) && strtoupper($urlData->provider_name) === "NATIONAL FILM BOARD OF CANADA") || (is_array($urlData) && isset($urlData[$content]['provider_name']) && strtoupper($urlData[$content]['provider_name']) === "NATIONAL FILM BOARD OF CANADA")) {
249 $parsedContent = html_entity_decode($parsedContent);
250 } else if ((isset($urlData->provider_name) && strtoupper($urlData->provider_name) === "FACEBOOK") || (is_array($urlData) && isset($urlData[$content]['provider_name']) && strtoupper($urlData[$content]['provider_name']) === "FACEBOOK")) {
251 $plgSettings = Core::getSettings();
252
253 // Check if the user wants to force a certain language into Facebook embeds.
254 $locale = isset($plgSettings->fbLanguage) && !empty($plgSettings->fbLanguage) ? $plgSettings->fbLanguage : false;
255 if (!!$locale) {
256 // Replace the automatically detected language by Facebook's API with the language chosen by the user.
257 $parsedContent = preg_replace('/\/[a-z]{2}\_[a-z]{2}\/sdk\.js/i', "/{$locale}/sdk.js", $parsedContent);
258 }
259
260 // Make sure `adapt_container_width` parameter is set to false. Setting to true, as it is by default, might cause Facebook to render embeds inside editors (in admin) with only 180px wide.
261 if (is_admin()) {
262 $parsedContent = preg_replace('~data\-adapt\-container\-width=\"(?:true|1)\"~i', 'data-adapt-container-width="0"', $parsedContent);
263 }
264
265 unset($locale, $plgSettings);
266 }
267 }
268
269 unset($embedTemplate, $serviceProvider);
270
271 // This assure that the iframe has the same dimensions the user wants to
272 if (isset($emberaInstanceSettings['params']['width']) || isset($emberaInstanceSettings['params']['height'])) {
273 if (isset($emberaInstanceSettings['params']['width']) && isset($emberaInstanceSettings['params']['height'])) {
274 $customWidth = (int)$emberaInstanceSettings['params']['width'];
275 $customHeight = (int)$emberaInstanceSettings['params']['height'];
276 } else {
277 if (preg_match('~width="(\d+)"|width\s+:\s+(\d+)~i', $parsedContent, $matches)) {
278 $iframeWidth = (int)$matches[1];
279 }
280
281 if (preg_match('~height="(\d+)"|height\s+:\s+(\d+)~i', $parsedContent, $matches)) {
282 $iframeHeight = (int)$matches[1];
283 }
284
285 if (isset($iframeWidth) && isset($iframeHeight) && $iframeWidth > 0 && $iframeHeight > 0) {
286 $iframeRatio = ceil($iframeWidth / $iframeHeight);
287
288 if (isset($emberaInstanceSettings['params']['width'])) {
289 $customWidth = (int)$emberaInstanceSettings['params']['width'];
290 $customHeight = ceil($customWidth / $iframeRatio);
291 } else {
292 $customHeight = (int)$emberaInstanceSettings['params']['height'];
293 $customWidth = $iframeRatio * $customHeight;
294 }
295 }
296 }
297
298 if (isset($customWidth) && isset($customHeight)) {
299 if (preg_match('~width="(\d+)"~i', $parsedContent)) {
300 $parsedContent = preg_replace('~width="(\d+)"~i', 'width="'. $customWidth .'"', $parsedContent);
301 }
302
303 if (preg_match('~height="(\d+)"~i', $parsedContent)) {
304 $parsedContent = preg_replace('~height="(\d+)"~i', 'height="'. $customHeight .'"', $parsedContent);
305 }
306
307 if (preg_match('~width\s+:\s+(\d+)~i', $parsedContent)) {
308 $parsedContent = preg_replace('~width\s+:\s+(\d+)~i', 'width: '. $customWidth, $parseContent);
309 }
310
311 if (preg_match('~height\s+:\s+(\d+)~i', $parsedContent)) {
312 $parsedContent = preg_replace('~height\s+:\s+(\d+)~i', 'height: '. $customHeight, $parseContent);
313 }
314 }
315 }
316
317 if ($stripNewLine) {
318 $parsedContent = preg_replace('/\n/', '', $parsedContent);
319 }
320
321 $parsedContent = apply_filters('pp_embed_parsed_content', $parsedContent, $urlData, $attributes);
322
323 if (!empty($parsedContent)) {
324 $embed = (object)array_merge((array)$urlData, array(
325 'attributes' => (object)$attributes,
326 'embed' => $parsedContent,
327 'url' => $content
328 ));
329
330 $embed = apply_filters('embedpress:onAfterEmbed', $embed);
331
332 return $embed;
333 }
334 }
335
336 return $subject;
337 }
338
339 /**
340 * Method that adds support to a given new service provider (SP).
341 *
342 * @since 1.0.0
343 * @static
344 *
345 * @param string $className The new SP class name.
346 * @param string $reference The new SP reference name.
347 * @param \Embera\Embera $emberaInstance The embera's instance where the SP will be registered in.
348 * @return boolean
349 */
350 public static function addServiceProvider($className, $reference, &$emberaInstance)
351 {
352 if (empty($className) || empty($reference)) {
353 return false;
354 }
355
356 if (is_string($reference)) {
357 $emberaInstance->addProvider($reference, EMBEDPRESS_NAMESPACE ."\\Providers\\{$className}");
358 } else if (is_array($reference)) {
359 foreach ($reference as $serviceProviderUrl) {
360 self::addServiceProvider($className, $serviceProviderUrl, $emberaInstance);
361 }
362 } else {
363 return false;
364 }
365 }
366
367 /**
368 * Method that retrieves all custom parameters from a shortcoded string.
369 *
370 * @since 1.0.0
371 * @static
372 *
373 * @param string $subject The given shortcoded string.
374 * @return array
375 */
376 public static function parseContentAttributesFromString($subject)
377 {
378 $customAttributes = array();
379 if (preg_match('/\[embed\s*(.*?)\]/i', stripslashes($subject), $m)) {
380 if (preg_match_all('/(\!?\w+-?\w*)(?:="(.+?)")?/i', stripslashes($m[1]), $matches)) {
381 $attributes = $matches[1];
382 $attrValues = $matches[2];
383
384 foreach ($attributes as $attrIndex => $attrName) {
385 $customAttributes[$attrName] = $attrValues[$attrIndex];
386 }
387 }
388 }
389
390 return $customAttributes;
391 }
392
393 /**
394 * Method that parses and adds the "data-" prefix to the given custom shortcode attributes.
395 *
396 * @since 1.0.0
397 * @access private
398 * @static
399 *
400 * @param array $customAttributes The array containing the embed attributes.
401 * @param string $content_uid An optional string specifying a unique ID for the embed
402 * @return array
403 */
404 private static function parseContentAttributes(array $customAttributes, $content_uid = null)
405 {
406 $attributes = array(
407 'class' => array("embedpress-wrapper")
408 );
409
410 $embedShouldBeResponsive = true;
411 $embedShouldHaveCustomDimensions = false;
412 if (!empty($customAttributes)) {
413 if (isset($customAttributes['class'])) {
414 if (!empty($customAttributes['class'])) {
415 $customAttributes['class'] = explode(' ', $customAttributes['class']);
416
417 $attributes['class'] = array_merge($attributes['class'], $customAttributes['class']);
418 }
419
420 unset($customAttributes['class']);
421 }
422
423 if (isset($customAttributes['width'])) {
424 if (!empty($customAttributes['width'])) {
425 $attributes['width'] = (int)$customAttributes['width'];
426 $embedShouldHaveCustomDimensions = true;
427 }
428 }
429
430 if (isset($customAttributes['height'])) {
431 if (!empty($customAttributes['height'])) {
432 $attributes['height'] = (int)$customAttributes['height'];
433 $embedShouldHaveCustomDimensions = true;
434 }
435 }
436
437 if (!empty($customAttributes)) {
438 $attrNameDefaultPrefix = "data-";
439 foreach ($customAttributes as $attrName => $attrValue) {
440 if (is_numeric($attrName)) {
441 $attrName = $attrValue;
442 $attrValue = "";
443 }
444
445 $attrName = str_replace($attrNameDefaultPrefix, "", $attrName);
446
447 if (!strlen($attrValue)) {
448 if ($attrName[0] === "!") {
449 $attrValue = "false";
450 $attrName = substr($attrName, 1);
451 } else {
452 $attrValue = "true";
453 }
454 }
455
456 $attributes[$attrNameDefaultPrefix . $attrName] = $attrValue;
457 }
458 }
459
460 // Check if there's any "responsive" parameter
461 $responsiveAttributes = array("responsive", "data-responsive");
462 foreach ($responsiveAttributes as $responsiveAttr) {
463 if (isset($attributes[$responsiveAttr])) {
464 if (!strlen($attributes[$responsiveAttr])) { // If the parameter is passed but have no value, it will be true by default
465 $embedShouldBeResponsive = true;
466 } else {
467 $embedShouldBeResponsive = !self::valueIsFalse($attributes[$responsiveAttr]);
468 }
469
470 break;
471 }
472 }
473 unset($responsiveAttr, $responsiveAttributes);
474 }
475
476 $attributes['class'][] = 'ose-{provider_alias}';
477
478 if (! empty($content_uid)) {
479 $attributes['class'][] = 'ose-uid-' . $content_uid;
480 }
481
482 if ($embedShouldBeResponsive && !$embedShouldHaveCustomDimensions) {
483 $attributes['class'][] = 'responsive';
484 } else {
485 $attributes['data-responsive'] = "false";
486 }
487
488 $attributes['class'] = implode(' ', array_unique(array_filter($attributes['class'])));
489
490 return $attributes;
491 }
492
493 /**
494 * Method that checks if a given value is/can be identified as (bool)false.
495 *
496 * @since 1.0.0
497 * @static
498 *
499 * @param mixed $subject The value to be checked.
500 * @return boolean
501 */
502 public static function valueIsFalse($subject)
503 {
504 $subject = strtolower(trim((string)$subject));
505 switch ($subject) {
506 case "0":
507 case "false":
508 case "off":
509 case "no":
510 case "n":
511 case "nil":
512 case "null":
513 return true;
514 default:
515 return false;
516 }
517 }
518
519 /**
520 * Return the value from a header which is in an array resulted from a get_headers() call.
521 * If the header cannot be found, this method will return null instead.
522 *
523 * @since 1.1.0
524 * @access private
525 * @static
526 *
527 * @param string $headerPattern Regex pattern the header and its value must match.
528 * @param array $headersList A list of headers resulted from a get_headers() call.
529 *
530 * @return mixed
531 */
532 private static function extractContentFromHeaderAsArray($headerPattern, $headersList)
533 {
534 $headerValue = null;
535
536 foreach ($headersList as $header) {
537 if (preg_match($headerPattern, $header, $matches)) {
538 $headerValue = $matches[1];
539 break;
540 }
541 }
542
543 return $headerValue;
544 }
545
546 /**
547 * Sanitize the object returned by the embed source. Sometimes we need to convert
548 * attributes from "dash" separated to "underline" separated to be able to access
549 * those attributes from the object, without having to convert it to an array.
550 *
551 * @since 1.6.1
552 * @access private
553 * @static
554 *
555 * @param object $data
556 *
557 * @return object
558 */
559 private static function sanitizeUrlData($data)
560 {
561 if (is_object($data)) {
562 $attributes = get_object_vars($data);
563
564 foreach ($attributes as $key => $value) {
565 if (substr_count($key, '-')) {
566 unset($data->$key);
567
568 $key = str_replace('-', '_', $key);
569 $data->$key = $value;
570 }
571 }
572 } elseif (is_array($data)) {
573 foreach ($data as $key => $value) {
574 if (substr_count($key, '-')) {
575 unset($data[$key]);
576
577 $key = str_replace('-', '_', $key);
578 $data[$key] = $value;
579 }
580 }
581 }
582
583 return $data;
584 }
585 }
586