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