Admin
2 weeks ago
Builder
2 weeks ago
Helpers
2 weeks ago
Integrations
2 weeks ago
CFF_Autolink.php
2 weeks ago
CFF_Blocks.php
2 weeks ago
CFF_Cache.php
2 weeks ago
CFF_Education.php
2 weeks ago
CFF_Elementor_Base.php
2 weeks ago
CFF_Elementor_Widget.php
2 weeks ago
CFF_Error_Reporter.php
2 weeks ago
CFF_FB_Settings.php
2 weeks ago
CFF_Feed_Elementor_Control.php
2 weeks ago
CFF_Feed_Locator.php
2 weeks ago
CFF_Feed_Pro.php
2 weeks ago
CFF_GDPR_Integrations.php
2 weeks ago
CFF_Group_Posts.php
2 weeks ago
CFF_HTTP_Request.php
2 weeks ago
CFF_Oembed.php
2 weeks ago
CFF_Parse.php
2 weeks ago
CFF_Resizer.php
2 weeks ago
CFF_Response.php
2 weeks ago
CFF_Shortcode.php
2 weeks ago
CFF_Shortcode_Display.php
2 weeks ago
CFF_SiteHealth.php
2 weeks ago
CFF_Utils.php
2 weeks ago
CFF_View.php
2 weeks ago
Custom_Facebook_Feed.php
2 weeks ago
Email_Notification.php
2 weeks ago
Platform_Data.php
2 weeks ago
SB_Facebook_Data_Encryption.php
2 weeks ago
SB_Facebook_Data_Manager.php
2 weeks ago
index.php
2 weeks ago
CFF_Oembed.php
517 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Class CFF_Oembed |
| 5 | * |
| 6 | * Replaces the native WordPress functionality for Facebook oembed |
| 7 | * to allow authenticated oembeds |
| 8 | * |
| 9 | * @since 2.16/3.16 |
| 10 | */ |
| 11 | |
| 12 | namespace CustomFacebookFeed; |
| 13 | |
| 14 | if (! defined('ABSPATH')) { |
| 15 | die('-1'); |
| 16 | } |
| 17 | |
| 18 | class CFF_Oembed |
| 19 | { |
| 20 | /** |
| 21 | * CFF_Oembed constructor. |
| 22 | * |
| 23 | * If an account has been connected, hooks are added |
| 24 | * to change how Facebook links are handled for oembeds |
| 25 | * |
| 26 | * @since 2.16/3.16 |
| 27 | */ |
| 28 | public function __construct() |
| 29 | { |
| 30 | if (CFF_Oembed::can_do_oembed()) { |
| 31 | if (CFF_Oembed::can_check_for_old_oembeds()) { |
| 32 | add_action('init', array('CustomFacebookFeed\CFF_Oembed', 'clear_checks')); |
| 33 | add_action('admin_init', array($this, 'cffOembedNotice')); |
| 34 | } |
| 35 | add_filter('oembed_providers', array( 'CustomFacebookFeed\CFF_Oembed', 'oembed_providers' ), 10, 1); |
| 36 | add_filter('oembed_fetch_url', array( 'CustomFacebookFeed\CFF_Oembed', 'oembed_set_fetch_url' ), 10, 3); |
| 37 | add_filter('oembed_result', array( 'CustomFacebookFeed\CFF_Oembed', 'oembed_result' ), 10, 3); |
| 38 | } |
| 39 | if (CFF_Oembed::should_extend_ttl()) { |
| 40 | add_filter('oembed_ttl', array( 'CustomFacebookFeed\CFF_Oembed', 'oembed_ttl' ), 10, 4); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | /** |
| 45 | * Check to make sure there is a saved access token to |
| 46 | * enable authenticated oembeds |
| 47 | * |
| 48 | * @return bool |
| 49 | * |
| 50 | * @since 2.16/3.16 |
| 51 | */ |
| 52 | public static function can_do_oembed() |
| 53 | { |
| 54 | $oembed_token_settings = get_option('cff_oembed_token', array()); |
| 55 | |
| 56 | if (isset($oembed_token_settings['disabled']) && $oembed_token_settings['disabled'] === true) { |
| 57 | return false; |
| 58 | } |
| 59 | |
| 60 | $access_token = CFF_Oembed::last_access_token(); |
| 61 | if (! $access_token) { |
| 62 | return false; |
| 63 | } |
| 64 | |
| 65 | return true; |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * The "time to live" for Instagram oEmbeds is extended if the access token expires. |
| 70 | * Even if new oEmbeds will not use the Instagram Feed system due to an expired token |
| 71 | * the time to live should continue to be extended. |
| 72 | * |
| 73 | * @return bool |
| 74 | * |
| 75 | * @since 2.16/3.16 |
| 76 | */ |
| 77 | public static function should_extend_ttl() |
| 78 | { |
| 79 | $oembed_token_settings = get_option('cff_oembed_token', array()); |
| 80 | |
| 81 | if (isset($oembed_token_settings['disabled']) && $oembed_token_settings['disabled']) { |
| 82 | return false; |
| 83 | } |
| 84 | |
| 85 | $will_expire = CFF_Oembed::oembed_access_token_will_expire(); |
| 86 | if ($will_expire) { |
| 87 | return true; |
| 88 | } |
| 89 | |
| 90 | return false; |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * Checking for old oembeds makes permanent changes to posts |
| 95 | * so we want the user to turn it off and on |
| 96 | * |
| 97 | * @return bool |
| 98 | * |
| 99 | * @since 2.16/3.16 |
| 100 | */ |
| 101 | public static function can_check_for_old_oembeds() |
| 102 | { |
| 103 | $cff_statuses = get_option('cff_statuses', array()); |
| 104 | if (isset($cff_statuses['oembed_api_change_notice'])) { |
| 105 | return false; |
| 106 | } |
| 107 | return true; |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Filters the WordPress list of oembed providers to |
| 112 | * change what url is used for remote requests for the |
| 113 | * oembed data |
| 114 | * |
| 115 | * @param array $providers |
| 116 | * |
| 117 | * @return mixed |
| 118 | * |
| 119 | * @since 2.16/3.16 |
| 120 | */ |
| 121 | public static function oembed_providers($providers) |
| 122 | { |
| 123 | $oembed_url = CFF_Oembed::oembed_url(); |
| 124 | if ($oembed_url) { |
| 125 | $post_embed_providers = CFF_Oembed::post_providers(); |
| 126 | foreach ($post_embed_providers as $post_provider) { |
| 127 | $providers[ $post_provider ] = array( $oembed_url . 'oembed_post', true ); |
| 128 | } |
| 129 | |
| 130 | $video_embed_providers = CFF_Oembed::video_providers(); |
| 131 | foreach ($video_embed_providers as $video_provider) { |
| 132 | $providers[ $video_provider ] = array( $oembed_url . 'oembed_video', true ); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | return $providers; |
| 137 | } |
| 138 | |
| 139 | /** |
| 140 | * Add the access token from a connected account to make an authenticated |
| 141 | * call to get oembed data from Facebook |
| 142 | * |
| 143 | * @param string $provider |
| 144 | * @param string $url |
| 145 | * @param array $args |
| 146 | * |
| 147 | * @return string |
| 148 | * |
| 149 | * @since 2.16/3.16 |
| 150 | */ |
| 151 | public static function oembed_set_fetch_url($provider, $url, $args) |
| 152 | { |
| 153 | $access_token = CFF_Oembed::last_access_token(); |
| 154 | if (! $access_token) { |
| 155 | return $provider; |
| 156 | } |
| 157 | |
| 158 | if ( |
| 159 | strpos($provider, 'oembed_post') !== false |
| 160 | || strpos($provider, 'oembed_video') !== false |
| 161 | ) { |
| 162 | if (strpos($url, '?') !== false) { |
| 163 | $provider = self::get_provider_from_url_with_query_vars($provider, $url); |
| 164 | } |
| 165 | $provider = add_query_arg('access_token', $access_token, $provider); |
| 166 | } |
| 167 | |
| 168 | return $provider; |
| 169 | } |
| 170 | |
| 171 | /** |
| 172 | * URLs with query variables are handled specially |
| 173 | * |
| 174 | * @param $provider |
| 175 | * @param $url |
| 176 | * |
| 177 | * @return array|mixed|string|string[] |
| 178 | */ |
| 179 | public static function get_provider_from_url_with_query_vars($provider, $url) |
| 180 | { |
| 181 | $exploded = explode('?', $url); |
| 182 | if (! empty($exploded[1])) { |
| 183 | if (strpos($url, '?v=') !== false) { |
| 184 | $exploded = explode('&', $url); |
| 185 | $provider = str_replace(urlencode('&' . $exploded[1]), '', $provider); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | return $provider; |
| 190 | } |
| 191 | |
| 192 | /** |
| 193 | * New oembeds are wrapped in a div for easy detection of older oembeds |
| 194 | * that will need to be updated |
| 195 | * |
| 196 | * @param string $html |
| 197 | * @param string $url |
| 198 | * @param array $args |
| 199 | * |
| 200 | * @return string |
| 201 | * |
| 202 | * @since 2.16/3.16 |
| 203 | */ |
| 204 | public static function oembed_result($html, $url, $args) |
| 205 | { |
| 206 | $post_embed_providers = CFF_Oembed::post_providers(); |
| 207 | foreach ($post_embed_providers as $post_provider) { |
| 208 | if (preg_match($post_provider, $url) === 1) { |
| 209 | if (strpos($html, 'class="fb-post"') !== false) { |
| 210 | $html = '<div class="cff-embed-wrap cff-post-embed-wrap">' . str_replace('class="fb-post"', 'class="fb-post cff-embed cff-post-embed"', $html) . '</div>'; |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | $video_embed_providers = CFF_Oembed::video_providers(); |
| 216 | foreach ($video_embed_providers as $video_provider) { |
| 217 | if (preg_match($video_provider, $url) === 1) { |
| 218 | if (strpos($html, 'class="fb-video"') !== false) { |
| 219 | $html = '<div class="cff-embed-wrap cff-video-embed-wrap">' . str_replace('class="fb-video"', 'class="fb-video cff-embed cff-video-embed"', $html) . '</div>'; |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | return $html; |
| 225 | } |
| 226 | |
| 227 | /** |
| 228 | * Extend the "time to live" for oEmbeds created with access tokens that expire |
| 229 | * |
| 230 | * @param $ttl |
| 231 | * @param $url |
| 232 | * @param $attr |
| 233 | * @param $post_ID |
| 234 | * |
| 235 | * @return float|int |
| 236 | * |
| 237 | * @since 2.16/3.16 |
| 238 | */ |
| 239 | public static function oembed_ttl($ttl, $url, $attr, $post_ID) |
| 240 | { |
| 241 | $providers = CFF_Oembed::post_providers(); |
| 242 | foreach ($providers as $provider) { |
| 243 | if (preg_match($provider, $url) === 1) { |
| 244 | $ttl = 30 * YEAR_IN_SECONDS; |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | $providers = CFF_Oembed::video_providers(); |
| 249 | foreach ($providers as $provider) { |
| 250 | if (preg_match($provider, $url) === 1) { |
| 251 | $ttl = 30 * YEAR_IN_SECONDS; |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | return $ttl; |
| 256 | } |
| 257 | |
| 258 | /** |
| 259 | * Only one api URL for FB |
| 260 | * |
| 261 | * @return bool|string |
| 262 | * |
| 263 | * @since 2.16/3.16 |
| 264 | */ |
| 265 | public static function oembed_url() |
| 266 | { |
| 267 | return 'https://graph.facebook.com/'; |
| 268 | } |
| 269 | |
| 270 | /** |
| 271 | * Any access token will work for oembeds so the access token |
| 272 | * saved in settings is used |
| 273 | * |
| 274 | * @return bool|string |
| 275 | * |
| 276 | * @since 2.16/3.16 |
| 277 | */ |
| 278 | public static function last_access_token() |
| 279 | { |
| 280 | $oembed_token_settings = get_option('cff_oembed_token', array()); |
| 281 | $will_expire = CFF_Oembed::oembed_access_token_will_expire(); |
| 282 | $encryption = new \CustomFacebookFeed\SB_Facebook_Data_Encryption(); |
| 283 | |
| 284 | if ( |
| 285 | ! empty($oembed_token_settings['access_token']) |
| 286 | && (! $will_expire || $will_expire > time()) |
| 287 | ) { |
| 288 | $oembed_token_settings['access_token'] = $encryption->maybe_decrypt($oembed_token_settings['access_token']); |
| 289 | return $oembed_token_settings['access_token']; |
| 290 | } else { |
| 291 | $settings_access_token = trim(get_option('cff_access_token')); |
| 292 | $settings_access_token = $encryption->maybe_decrypt($settings_access_token); |
| 293 | if (! empty($settings_access_token)) { |
| 294 | return $settings_access_token; |
| 295 | } |
| 296 | |
| 297 | if (class_exists('SB_Instagram_Oembed')) { |
| 298 | $sbi_oembed_token_settings = get_option('sbi_oembed_token', array()); |
| 299 | if (! empty($sbi_oembed_token_settings['access_token'])) { |
| 300 | $sbi_oembed_token_settings['access_token'] = $encryption->maybe_decrypt($sbi_oembed_token_settings['access_token']); |
| 301 | return $sbi_oembed_token_settings['access_token']; |
| 302 | } |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | return false; |
| 307 | } |
| 308 | |
| 309 | /** |
| 310 | * Access tokens created from FB accounts not connected to an |
| 311 | * FB page expire after 60 days. |
| 312 | * |
| 313 | * @return bool|int |
| 314 | */ |
| 315 | public static function oembed_access_token_will_expire() |
| 316 | { |
| 317 | $oembed_token_settings = get_option('cff_oembed_token', array()); |
| 318 | $will_expire = isset($oembed_token_settings['expiration_date']) && (int)$oembed_token_settings['expiration_date'] > 0 ? (int)$oembed_token_settings['expiration_date'] : false; |
| 319 | |
| 320 | return $will_expire; |
| 321 | } |
| 322 | |
| 323 | |
| 324 | /** |
| 325 | * Loop through post meta data and if it's an oembed and has content |
| 326 | * that looks like a Facebook oembed, delete it |
| 327 | * |
| 328 | * @param $post_ID |
| 329 | * |
| 330 | * @return int number of old oembed caches found |
| 331 | * |
| 332 | * @since 2.16/3.16 |
| 333 | */ |
| 334 | public static function delete_facebook_oembed_caches($post_ID) |
| 335 | { |
| 336 | $post_metas = get_post_meta($post_ID); |
| 337 | if (empty($post_metas)) { |
| 338 | return 0; |
| 339 | } |
| 340 | |
| 341 | $total_found = 0; |
| 342 | foreach ($post_metas as $post_meta_key => $post_meta_value) { |
| 343 | if ('_oembed_' === substr($post_meta_key, 0, 8)) { |
| 344 | if ( |
| 345 | strpos($post_meta_value[0], 'class="fb-post"') !== false |
| 346 | && strpos($post_meta_value[0], 'cff-embed-wrap') === false |
| 347 | ) { |
| 348 | $total_found++; |
| 349 | delete_post_meta($post_ID, $post_meta_key); |
| 350 | if ('_oembed_time_' !== substr($post_meta_key, 0, 13)) { |
| 351 | delete_post_meta($post_ID, str_replace('_oembed_', '_oembed_time_', $post_meta_key)); |
| 352 | } |
| 353 | } elseif ( |
| 354 | strpos($post_meta_value[0], 'class="fb-video"') !== false |
| 355 | && strpos($post_meta_value[0], 'cff-embed-wrap') === false |
| 356 | ) { |
| 357 | $total_found++; |
| 358 | delete_post_meta($post_ID, $post_meta_key); |
| 359 | if ('_oembed_time_' !== substr($post_meta_key, 0, 13)) { |
| 360 | delete_post_meta($post_ID, str_replace('_oembed_', '_oembed_time_', $post_meta_key)); |
| 361 | } |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | return $total_found; |
| 367 | } |
| 368 | |
| 369 | /** |
| 370 | * Current list of regex to identify FB URLs that could become oembeds using |
| 371 | * the 'oembed_post' endpoint. |
| 372 | * |
| 373 | * @return array |
| 374 | * |
| 375 | * @since 2.16/3.16 |
| 376 | */ |
| 377 | public static function post_providers() |
| 378 | { |
| 379 | $post_embed_providers = array( |
| 380 | '#https?://www\.facebook\.com/.*/posts/.*#i', |
| 381 | '#https?://www\.facebook\.com/.*/activity/.*#i', |
| 382 | '#https?://www\.facebook\.com/.*/photos/.*#i', |
| 383 | '#https?://www\.facebook\.com/photo(s/|\.php).*#i', |
| 384 | '#https?://www\.facebook\.com/permalink\.php.*#i', |
| 385 | '#https?://www\.facebook\.com/media/.*#i', |
| 386 | '#https?://www\.facebook\.com/questions/.*#i', |
| 387 | '#https?://www\.facebook\.com/notes/.*#i', |
| 388 | ); |
| 389 | |
| 390 | return $post_embed_providers; |
| 391 | } |
| 392 | |
| 393 | /** |
| 394 | * Current list of regex to identify FB URLs that could become oembeds using |
| 395 | * the 'oembed_video' endpoint. |
| 396 | * |
| 397 | * @return array |
| 398 | * |
| 399 | * @since 2.16/3.16 |
| 400 | */ |
| 401 | public static function video_providers() |
| 402 | { |
| 403 | $video_embed_providers = array( |
| 404 | '#https?://www\.facebook\.com/.*/videos/.*#i', |
| 405 | '#https?://www\.facebook\.com/video\.php.*#i', |
| 406 | '#https?://www\.facebook\.com/watch/.*#i', |
| 407 | '#https?://fb\.watch/.*#i' |
| 408 | ); |
| 409 | |
| 410 | return $video_embed_providers; |
| 411 | } |
| 412 | |
| 413 | /** |
| 414 | * Used for clearing the oembed update check flag for all posts |
| 415 | * |
| 416 | * @since 2.16/3.16 |
| 417 | */ |
| 418 | public static function clear_checks() |
| 419 | { |
| 420 | global $wpdb; |
| 421 | $table_name = esc_sql($wpdb->prefix . "postmeta"); |
| 422 | $result = $wpdb->query(" |
| 423 | DELETE |
| 424 | FROM $table_name |
| 425 | WHERE meta_key = '_cff_oembed_done_checking';"); |
| 426 | } |
| 427 | |
| 428 | /** |
| 429 | * Display oembed notice in the plugin's pages |
| 430 | * |
| 431 | * @since 6.3.7 |
| 432 | */ |
| 433 | public function cffOembedNotice() |
| 434 | { |
| 435 | $allowed_screens = array( |
| 436 | 'cff-feed-builder', |
| 437 | 'cff-settings', |
| 438 | 'cff-oembeds-manager', |
| 439 | 'cff-extensions-manager', |
| 440 | 'cff-about-us', |
| 441 | 'cff-support', |
| 442 | ); |
| 443 | $current_screen = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : ''; |
| 444 | $is_allowed = in_array($current_screen, $allowed_screens); |
| 445 | |
| 446 | // We will display the notice only on those allowed screens. |
| 447 | if (!$current_screen || ! $is_allowed) { |
| 448 | return; |
| 449 | } |
| 450 | |
| 451 | // Only display notice to admins. |
| 452 | $cap = current_user_can('manage_custom_facebook_feed_options') ? 'manage_custom_facebook_feed_options' : 'manage_options'; |
| 453 | $cap = apply_filters('cff_settings_pages_capability', $cap); |
| 454 | if (!current_user_can($cap)) { |
| 455 | return; |
| 456 | } |
| 457 | |
| 458 | $cff_statuses = get_option('cff_statuses', array()); |
| 459 | if (isset($cff_statuses['oembed_api_change_notice'])) { |
| 460 | return; |
| 461 | } |
| 462 | |
| 463 | global $cff_notices; |
| 464 | $title = __('Account reconnection needed for Facebook and Instagram oEmbeds', 'custom-facebook-feed'); |
| 465 | $message = '<p>' . __('Starting May of 2024, Facebook is making some changes to their API that will affect your oEmbeds. Make sure to connect to our oEmbed specific Smash Balloon Tools app to avoid disruption.', 'custom-facebook-feed') . '</p>'; |
| 466 | |
| 467 | $error_args = array( |
| 468 | 'class' => 'cff-admin-notices', |
| 469 | 'title' => array( |
| 470 | 'text' => $title, |
| 471 | 'class' => 'sb-notice-title', |
| 472 | 'tag' => 'h4', |
| 473 | ), |
| 474 | 'message' => $message, |
| 475 | 'buttons' => array( |
| 476 | array( |
| 477 | 'text' => __('Reconnect', 'custom-facebook-feed'), |
| 478 | 'class' => 'sb-btn sb-reconnect-oembed', |
| 479 | 'tag' => 'button', |
| 480 | ), |
| 481 | ), |
| 482 | 'buttons_wrap_start' => '<div class="buttons">', |
| 483 | 'buttons_wrap_end' => '</div>', |
| 484 | 'priority' => 1, |
| 485 | 'page' => array( |
| 486 | 'cff-feed-builder', |
| 487 | 'cff-settings', |
| 488 | 'cff-oembeds-manager', |
| 489 | 'cff-extensions-manager', |
| 490 | 'cff-about-us', |
| 491 | 'cff-support', |
| 492 | ), |
| 493 | 'icon' => array( |
| 494 | 'src' => CFF_PLUGIN_URL . 'admin/assets/img/cff-exclamation.svg', |
| 495 | 'wrap' => '<span class="sb-notice-icon sb-error-icon"><img {src}></span>', |
| 496 | ), |
| 497 | 'styles' => array( |
| 498 | 'display' => 'flex', |
| 499 | 'justify-content' => 'space-between', |
| 500 | 'gap' => '2rem', |
| 501 | ), |
| 502 | 'wrap_schema' => '<div {id} {class}>{icon}<div class="cff-notice-wrap" {styles}><div class="cff-notice-body">{title}{message}</div>{buttons}</div></div>', |
| 503 | ); |
| 504 | |
| 505 | $cff_notices->add_notice('oembed_api_change', 'information', $error_args); |
| 506 | $cff_statuses['oembed_api_change_notice'] = true; |
| 507 | update_option('cff_statuses', $cff_statuses); |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | /* |
| 512 | function cffOembedInit() { |
| 513 | return new CFF_Oembed(); |
| 514 | } |
| 515 | cffOembedInit(); |
| 516 | */ |
| 517 |