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