PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more / 1.7.5
EmbedPress – PDF Embedder, Embed PDF viewer, YouTube Videos, 3D FlipBook, Social feeds & more v1.7.5
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
Ends 9 years ago Plugins 9 years ago Providers 9 years ago AutoLoader.php 9 years ago Core.php 9 years ago Disabler.php 9 years ago Loader.php 9 years ago Shortcode.php 9 years ago Updater.php 9 years ago index.html 9 years ago
Shortcode.php
577 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 PressShack <help@pressshack.com>
15 * @copyright Copyright (C) 2017 PressShack. 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 // Check if OEmbed was unable to detect the url service provider.
114 if (empty($serviceProvider)) {
115 // Attempt to do the same using Embera.
116 $emberaInstance = new Embera($emberaInstanceSettings);
117 // Add support to the user's custom service providers
118 $additionalServiceProviders = Core::getAdditionalServiceProviders();
119 if (!empty($additionalServiceProviders)) {
120 foreach ($additionalServiceProviders as $serviceProviderClassName => $serviceProviderUrls) {
121 self::addServiceProvider($serviceProviderClassName, $serviceProviderUrls, $emberaInstance);
122 }
123
124 unset($serviceProviderUrls, $serviceProviderClassName);
125 }
126
127 // Attempt to fetch more info about the url-embed.
128 $urlData = $emberaInstance->getUrlInfo($content);
129 } else {
130 // Attempt to fetch more info about the url-embed.
131 $urlData = self::$oEmbedInstance->fetch($serviceProvider, $content, $attributes);
132 }
133
134 // Sanitize the data
135 $urlData = self::sanitizeUrlData($urlData);
136
137 $eventResults = apply_filters('embedpress:onBeforeEmbed', $urlData);
138 if (empty($eventResults)) {
139 // EmbedPress seems unable to embed the url.
140 return $subject;
141 }
142
143 // Transform all shortcode attributes into html form. I.e.: {foo: "joe"} -> foo="joe"
144 $attributesHtml = array();
145 foreach ($attributes as $attrName => $attrValue) {
146 $attributesHtml[] = $attrName .'="'. $attrValue .'"';
147 }
148
149 // Define the EmbedPress html template where the generated embed will be injected in
150 $embedTemplate = '<div '. implode(' ', $attributesHtml) .'>{html}</div>';
151
152 // Check if $content is a google shortened url and tries to extract from it which Google service it refers to.
153 if (preg_match('/http[s]?:\/\/goo\.gl\/(?:([a-z]+)\/)?[a-z0-9]+\/?$/i', $content, $matches)) {
154 // Fetch all headers from the short-url so we can know how to handle its original content depending on the service.
155 $headers = get_headers($content);
156
157 $supportedServicesHeadersPatterns = array(
158 'maps' => '/^Location:\s+(http[s]?:\/\/.+)$/i'
159 );
160
161 $service = isset($matches[1]) ? strtolower($matches[1]) : null;
162 // No specific service was found in the url.
163 if (empty($service)) {
164 // Let's try to guess which service the original url belongs to.
165 foreach ($headers as $header) {
166 // Check if the short-url reffers to a Google Maps url.
167 if (preg_match($supportedServicesHeadersPatterns['maps'], $header, $matches)) {
168 // Replace the shortened url with its original url.
169 $content = $matches[1];
170 break;
171 }
172 }
173 unset($header);
174 } else {
175 // Check if the Google service is supported atm.
176 if (isset($supportedServicesHeadersPatterns[$service])) {
177 // Tries to extract the url based on its headers.
178 $originalUrl = self::extractContentFromHeaderAsArray($supportedServicesHeadersPatterns[$service], $headers);
179 // Replace the shortened url with its original url if the specific header was found.
180 if (!empty($originalUrl)) {
181 $content = $originalUrl;
182 }
183 unset($originalUrl);
184 }
185 }
186 unset($service, $supportedServicesHeadersPatterns, $headers, $matches);
187 }
188
189 // Facebook is a special case. WordPress will try to embed them using OEmbed, but they always end up embedding the profile page, regardless
190 // 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.
191 if (isset($urlData->provider_name) && in_array($urlData->provider_name, array('Facebook'))) {
192 // Check if this is a Facebook profile url.
193 if (preg_match('/facebook\.com\/(?:[^\/]+?)\/?$/', $content, $match)) {
194 // Try to embed the url using WP's OSEmbed.
195 $parsedContent = self::$oEmbedInstance->get_html($content, $attributes);
196 } else {
197 // Try to embed the url using EmbedPress' Embera.
198 $parsedContent = false;
199 }
200 } else {
201 // Try to embed the url using WP's OSEmbed.
202 $parsedContent = self::$oEmbedInstance->get_html($content, $attributes);
203 }
204
205 if (!$parsedContent) {
206 if (!isset($emberaInstance)) {
207 // If the embed couldn't be generated, we'll try to use Embera's API
208 $emberaInstance = new Embera($emberaInstanceSettings);
209 // Add support to the user's custom service providers
210 $additionalServiceProviders = Core::getAdditionalServiceProviders();
211 if (!empty($additionalServiceProviders)) {
212 foreach ($additionalServiceProviders as $serviceProviderClassName => $serviceProviderUrls) {
213 self::addServiceProvider($serviceProviderClassName, $serviceProviderUrls, $emberaInstance);
214 }
215
216 unset($serviceProviderUrls, $serviceProviderClassName);
217 }
218 }
219
220 // Register the html template
221 $emberaFormaterInstance = new Formatter($emberaInstance, true);
222 $emberaFormaterInstance->setTemplate($embedTemplate);
223
224 // Try to generate the embed using Embera API
225 $parsedContent = $emberaFormaterInstance->transform($content);
226
227 unset($emberaFormaterInstance, $additionalServiceProviders, $emberaInstance);
228 } else {
229 // Inject the generated code inside the html template
230 $parsedContent = str_replace('{html}', $parsedContent, $embedTemplate);
231
232 // Replace all single quotes to double quotes. I.e: foo='joe' -> foo="joe"
233 $parsedContent = str_replace("'", '"', $parsedContent);
234
235 // Replace the flag `{provider_alias}` which is used by Embera with the "ose-<serviceProviderAlias>". I.e: YouTube -> "ose-youtube"
236 $parsedContent = preg_replace('/((?:ose-)?\{provider_alias\})/i', "ose-". strtolower($urlData->provider_name), $parsedContent);
237 }
238
239 if (isset($urlData->provider_name) || (is_array($urlData) && isset($urlData[$content]['provider_name']))) {
240 // NFB seems to always return their embed code with all HTML entities into their applicable characters string.
241 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")) {
242 $parsedContent = html_entity_decode($parsedContent);
243 } 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")) {
244 $plgSettings = Core::getSettings();
245
246 // Check if the user wants to force a certain language into Facebook embeds.
247 $locale = isset($plgSettings->fbLanguage) && !empty($plgSettings->fbLanguage) ? $plgSettings->fbLanguage : false;
248 if (!!$locale) {
249 // Replace the automatically detected language by Facebook's API with the language chosen by the user.
250 $parsedContent = preg_replace('/\/[a-z]{2}\_[a-z]{2}\/sdk\.js/i', "/{$locale}/sdk.js", $parsedContent);
251 }
252
253 // 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.
254 if (is_admin()) {
255 $parsedContent = preg_replace('~data\-adapt\-container\-width=\"(?:true|1)\"~i', 'data-adapt-container-width="0"', $parsedContent);
256 }
257
258 unset($locale, $plgSettings);
259 }
260 }
261
262 unset($embedTemplate, $serviceProvider);
263
264 // This assure that the iframe has the same dimensions the user wants to
265 if (isset($emberaInstanceSettings['params']['width']) || isset($emberaInstanceSettings['params']['height'])) {
266 if (isset($emberaInstanceSettings['params']['width']) && isset($emberaInstanceSettings['params']['height'])) {
267 $customWidth = (int)$emberaInstanceSettings['params']['width'];
268 $customHeight = (int)$emberaInstanceSettings['params']['height'];
269 } else {
270 if (preg_match('~width="(\d+)"|width\s+:\s+(\d+)~i', $parsedContent, $matches)) {
271 $iframeWidth = (int)$matches[1];
272 }
273
274 if (preg_match('~height="(\d+)"|height\s+:\s+(\d+)~i', $parsedContent, $matches)) {
275 $iframeHeight = (int)$matches[1];
276 }
277
278 if (isset($iframeWidth) && isset($iframeHeight) && $iframeWidth > 0 && $iframeHeight > 0) {
279 $iframeRatio = ceil($iframeWidth / $iframeHeight);
280
281 if (isset($emberaInstanceSettings['params']['width'])) {
282 $customWidth = (int)$emberaInstanceSettings['params']['width'];
283 $customHeight = ceil($customWidth / $iframeRatio);
284 } else {
285 $customHeight = (int)$emberaInstanceSettings['params']['height'];
286 $customWidth = $iframeRatio * $customHeight;
287 }
288 }
289 }
290
291 if (isset($customWidth) && isset($customHeight)) {
292 if (preg_match('~width="(\d+)"~i', $parsedContent)) {
293 $parsedContent = preg_replace('~width="(\d+)"~i', 'width="'. $customWidth .'"', $parsedContent);
294 }
295
296 if (preg_match('~height="(\d+)"~i', $parsedContent)) {
297 $parsedContent = preg_replace('~height="(\d+)"~i', 'height="'. $customHeight .'"', $parsedContent);
298 }
299
300 if (preg_match('~width\s+:\s+(\d+)~i', $parsedContent)) {
301 $parsedContent = preg_replace('~width\s+:\s+(\d+)~i', 'width: '. $customWidth, $parseContent);
302 }
303
304 if (preg_match('~height\s+:\s+(\d+)~i', $parsedContent)) {
305 $parsedContent = preg_replace('~height\s+:\s+(\d+)~i', 'height: '. $customHeight, $parseContent);
306 }
307 }
308 }
309
310 if ($stripNewLine) {
311 $parsedContent = preg_replace('/\n/', '', $parsedContent);
312 }
313
314 if (!empty($parsedContent)) {
315 $embed = (object)array_merge((array)$urlData, array(
316 'attributes' => (object)$attributes,
317 'embed' => $parsedContent,
318 'url' => $content
319 ));
320
321 $embed = apply_filters('embedpress:onAfterEmbed', $embed);
322
323 return $embed;
324 }
325 }
326
327 return $subject;
328 }
329
330 /**
331 * Method that adds support to a given new service provider (SP).
332 *
333 * @since 1.0.0
334 * @static
335 *
336 * @param string $className The new SP class name.
337 * @param string $reference The new SP reference name.
338 * @param \Embera\Embera $emberaInstance The embera's instance where the SP will be registered in.
339 * @return boolean
340 */
341 public static function addServiceProvider($className, $reference, &$emberaInstance)
342 {
343 if (empty($className) || empty($reference)) {
344 return false;
345 }
346
347 if (is_string($reference)) {
348 $emberaInstance->addProvider($reference, EMBEDPRESS_NAMESPACE ."\\Providers\\{$className}");
349 } else if (is_array($reference)) {
350 foreach ($reference as $serviceProviderUrl) {
351 self::addServiceProvider($className, $serviceProviderUrl, $emberaInstance);
352 }
353 } else {
354 return false;
355 }
356 }
357
358 /**
359 * Method that retrieves all custom parameters from a shortcoded string.
360 *
361 * @since 1.0.0
362 * @static
363 *
364 * @param string $subject The given shortcoded string.
365 * @return array
366 */
367 public static function parseContentAttributesFromString($subject)
368 {
369 $customAttributes = array();
370 if (preg_match('/\[embed\s*(.*?)\]/i', stripslashes($subject), $m)) {
371 if (preg_match_all('/(\!?\w+-?\w*)(?:="(.+?)")?/i', stripslashes($m[1]), $matches)) {
372 $attributes = $matches[1];
373 $attrValues = $matches[2];
374
375 foreach ($attributes as $attrIndex => $attrName) {
376 $customAttributes[$attrName] = $attrValues[$attrIndex];
377 }
378 }
379 }
380
381 return $customAttributes;
382 }
383
384 /**
385 * Method that parses and adds the "data-" prefix to the given custom shortcode attributes.
386 *
387 * @since 1.0.0
388 * @access private
389 * @static
390 *
391 * @param array $customAttributes The array containing the embed attributes.
392 * @param string $content_uid An optional string specifying a unique ID for the embed
393 * @return array
394 */
395 private static function parseContentAttributes(array $customAttributes, $content_uid = null)
396 {
397 $attributes = array(
398 'class' => array("embedpress-wrapper")
399 );
400
401 $embedShouldBeResponsive = true;
402 $embedShouldHaveCustomDimensions = false;
403 if (!empty($customAttributes)) {
404 if (isset($customAttributes['class'])) {
405 if (!empty($customAttributes['class'])) {
406 $customAttributes['class'] = explode(' ', $customAttributes['class']);
407
408 $attributes['class'] = array_merge($attributes['class'], $customAttributes['class']);
409 }
410
411 unset($customAttributes['class']);
412 }
413
414 if (isset($customAttributes['width'])) {
415 if (!empty($customAttributes['width'])) {
416 $attributes['width'] = (int)$customAttributes['width'];
417 $embedShouldHaveCustomDimensions = true;
418 }
419 }
420
421 if (isset($customAttributes['height'])) {
422 if (!empty($customAttributes['height'])) {
423 $attributes['height'] = (int)$customAttributes['height'];
424 $embedShouldHaveCustomDimensions = true;
425 }
426 }
427
428 if (!empty($customAttributes)) {
429 $attrNameDefaultPrefix = "data-";
430 foreach ($customAttributes as $attrName => $attrValue) {
431 if (is_numeric($attrName)) {
432 $attrName = $attrValue;
433 $attrValue = "";
434 }
435
436 $attrName = str_replace($attrNameDefaultPrefix, "", $attrName);
437
438 if (!strlen($attrValue)) {
439 if ($attrName[0] === "!") {
440 $attrValue = "false";
441 $attrName = substr($attrName, 1);
442 } else {
443 $attrValue = "true";
444 }
445 }
446
447 $attributes[$attrNameDefaultPrefix . $attrName] = $attrValue;
448 }
449 }
450
451 // Check if there's any "responsive" parameter
452 $responsiveAttributes = array("responsive", "data-responsive");
453 foreach ($responsiveAttributes as $responsiveAttr) {
454 if (isset($attributes[$responsiveAttr])) {
455 if (!strlen($attributes[$responsiveAttr])) { // If the parameter is passed but have no value, it will be true by default
456 $embedShouldBeResponsive = true;
457 } else {
458 $embedShouldBeResponsive = !self::valueIsFalse($attributes[$responsiveAttr]);
459 }
460
461 break;
462 }
463 }
464 unset($responsiveAttr, $responsiveAttributes);
465 }
466
467 $attributes['class'][] = 'ose-{provider_alias}';
468
469 if (! empty($content_uid)) {
470 $attributes['class'][] = 'ose-uid-' . $content_uid;
471 }
472
473 if ($embedShouldBeResponsive && !$embedShouldHaveCustomDimensions) {
474 $attributes['class'][] = 'responsive';
475 } else {
476 $attributes['data-responsive'] = "false";
477 }
478
479 $attributes['class'] = implode(' ', array_unique(array_filter($attributes['class'])));
480
481 return $attributes;
482 }
483
484 /**
485 * Method that checks if a given value is/can be identified as (bool)false.
486 *
487 * @since 1.0.0
488 * @static
489 *
490 * @param mixed $subject The value to be checked.
491 * @return boolean
492 */
493 public static function valueIsFalse($subject)
494 {
495 $subject = strtolower(trim((string)$subject));
496 switch ($subject) {
497 case "0":
498 case "false":
499 case "off":
500 case "no":
501 case "n":
502 case "nil":
503 case "null":
504 return true;
505 default:
506 return false;
507 }
508 }
509
510 /**
511 * Return the value from a header which is in an array resulted from a get_headers() call.
512 * If the header cannot be found, this method will return null instead.
513 *
514 * @since 1.1.0
515 * @access private
516 * @static
517 *
518 * @param string $headerPattern Regex pattern the header and its value must match.
519 * @param array $headersList A list of headers resulted from a get_headers() call.
520 *
521 * @return mixed
522 */
523 private static function extractContentFromHeaderAsArray($headerPattern, $headersList)
524 {
525 $headerValue = null;
526
527 foreach ($headersList as $header) {
528 if (preg_match($headerPattern, $header, $matches)) {
529 $headerValue = $matches[1];
530 break;
531 }
532 }
533
534 return $headerValue;
535 }
536
537 /**
538 * Sanitize the object returned by the embed source. Sometimes we need to convert
539 * attributes from "dash" separated to "underline" separated to be able to access
540 * those attributes from the object, without having to convert it to an array.
541 *
542 * @since 1.6.1
543 * @access private
544 * @static
545 *
546 * @param object $data
547 *
548 * @return object
549 */
550 private static function sanitizeUrlData($data)
551 {
552 if (is_object($data)) {
553 $attributes = get_object_vars($data);
554
555 foreach ($attributes as $key => $value) {
556 if (substr_count($key, '-')) {
557 unset($data->$key);
558
559 $key = str_replace('-', '_', $key);
560 $data->$key = $value;
561 }
562 }
563 } elseif (is_array($data)) {
564 foreach ($data 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 }
573
574 return $data;
575 }
576 }
577