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