Ticket #6772: 6772.09.patch
File 6772.09.patch, 62.1 KB (added by , 8 years ago) |
---|
-
Gruntfile.js
5 5 BUILD_DIR = 'build/', 6 6 7 7 BP_CSS = [ 8 '**/*.css' 8 '**/*.css', 9 '**/css-*.php' 9 10 ], 10 11 11 12 // CSS exclusions, for excluding files from certain tasks, e.g. rtlcss 12 13 BP_EXCLUDED_CSS = [ 13 '!**/*-rtl.css' 14 '!**/*-rtl.css', 15 '!**/*-rtl.php' 14 16 ], 15 17 16 18 BP_JS = [ … … 105 107 cwd: SOURCE_DIR, 106 108 dest: SOURCE_DIR, 107 109 extDot: 'last', 108 ext: '-rtl.css', 109 src: BP_CSS.concat( BP_EXCLUDED_CSS, BP_EXCLUDED_MISC ) 110 src: BP_CSS.concat( BP_EXCLUDED_CSS, BP_EXCLUDED_MISC ), 111 rename: function ( dest, src ) { 112 if ( src.endsWith( '.php' ) ) { 113 return dest + src.replace( '.php', '-rtl.php' ); 114 } else { 115 return dest + src.replace( '.css', '-rtl.css' ); 116 } 117 } 110 118 } 111 119 }, 112 120 checktextdomain: { -
src/bp-activity/bp-activity-classes.php
13 13 require dirname( __FILE__ ) . '/classes/class-bp-activity-activity.php'; 14 14 require dirname( __FILE__ ) . '/classes/class-bp-activity-feed.php'; 15 15 require dirname( __FILE__ ) . '/classes/class-bp-activity-query.php'; 16 17 // Embeds - only applicable for WP 4.5+ 18 if ( bp_get_major_wp_version() >= 4.5 && bp_is_active( 'activity', 'embeds' ) ) { 19 require dirname( __FILE__ ) . '/classes/class-bp-activity-oembed-component.php'; 20 } 21 No newline at end of file -
new file src/bp-activity/bp-activity-embeds.php
new file mode 100644
- + 1 <?php 2 /** 3 * Functions related to embedding single activity items externally. 4 * 5 * Relies on WordPress 4.5. 6 * 7 * @since 2.6.0 8 * 9 * @package BuddyPress 10 * @subpackage ActivityEmbeds 11 */ 12 13 // Exit if accessed directly. 14 defined( 'ABSPATH' ) || exit; 15 16 /** 17 * Loads our activity oEmbed component. 18 * 19 * @since 2.6.0 20 */ 21 function bp_activity_setup_oembed() { 22 if ( bp_get_major_wp_version() >= 4.5 && bp_is_active( 'activity', 'embeds' ) ) { 23 buddypress()->activity->oembed = new BP_Activity_oEmbed_Component; 24 } 25 26 add_filter( 'bp_activity_get_embed_excerpt', 'wptexturize' ); 27 add_filter( 'bp_activity_get_embed_excerpt', 'convert_chars' ); 28 add_filter( 'bp_activity_get_embed_excerpt', 'make_clickable', 9 ); 29 add_filter( 'bp_activity_get_embed_excerpt', 'bp_activity_embed_excerpt_onclick_location_filter' ); 30 add_filter( 'bp_activity_get_embed_excerpt', 'bp_activity_at_name_filter' ); 31 add_filter( 'bp_activity_get_embed_excerpt', 'convert_smilies', 20 ); 32 add_filter( 'bp_activity_get_embed_excerpt', 'wpautop', 30 ); 33 } 34 add_action( 'bp_loaded', 'bp_activity_setup_oembed' ); 35 36 /** 37 * Catch links in embed excerpt so top.location.href can be added. 38 * 39 * Due to <iframe sandbox="allow-top-navigation">, links in embeds can only be 40 * clicked if invoked with top.location.href via JS. 41 * 42 * @since 2.6.0 43 * 44 * @param string $text Embed excerpt 45 * @return string 46 */ 47 function bp_activity_embed_excerpt_onclick_location_filter( $text ) { 48 return preg_replace_callback( '/<a href=\"([^\"]*)\"/iU', 'bp_activity_embed_excerpt_onclick_location_filter_callback', $text ); 49 } 50 /** 51 * Add onclick="top.location.href" to a link. 52 * 53 * @since 2.6.0 54 * 55 * @param array $matches Items matched by bp_activity_embed_excerpt_onclick_location_filter(). 56 * @return string 57 */ 58 function bp_activity_embed_excerpt_onclick_location_filter_callback( $matches ) { 59 return sprintf( '<a href="%1$s" onclick="top.location.href=\'%1$s\'"', $matches[1] ); 60 } 61 62 /** 63 * Add inline styles for BP activity embeds. 64 * 65 * This is subject to change or be removed entirely for a different system. 66 * Potentially for BP_Legacy::locate_asset_in_stack(). 67 * 68 * @since 2.6.0 69 * @access private 70 */ 71 function _bp_activity_embed_add_inline_styles() { 72 if ( false === bp_is_single_activity() ) { 73 return; 74 } 75 76 ob_start(); 77 if ( is_rtl() ) { 78 bp_get_asset_template_part( 'embeds/css-activity', 'rtl' ); 79 } else { 80 bp_get_asset_template_part( 'embeds/css-activity' ); 81 } 82 $css = ob_get_clean(); 83 84 // Rudimentary CSS protection. 85 $css = wp_kses( $css, array( "\'", '\"' ) ); 86 87 printf( '<style type="text/css">%s</style>', $css ); 88 } 89 add_action( 'embed_head', '_bp_activity_embed_add_inline_styles', 20 ); 90 91 /** 92 * Query for the activity item on the activity embed template. 93 * 94 * Basically a wrapper for {@link bp_has_activities()}, but allows us to 95 * use the activity loop without requerying for it again. 96 * 97 * @since 2.6.0 98 * 99 * @param int $activity_id The activity ID. 100 * @return bool 101 */ 102 function bp_activity_embed_has_activity( $activity_id = 0 ) { 103 global $activities_template; 104 105 if ( empty( $activity_id ) ) { 106 return false; 107 } 108 109 if ( ! empty( $activities_template->activities ) ) { 110 $activity = (array) $activities_template->activities; 111 $activity = reset( $activity ); 112 113 // No need to requery if we already got the embed activity 114 if ( (int) $activity_id === (int) $activity->id ) { 115 return $activities_template->has_activities(); 116 } 117 } 118 119 return bp_has_activities( array( 120 'display_comments' => 'threaded', 121 'show_hidden' => true, 122 'include' => (int) $activity_id, 123 ) ); 124 } 125 126 /** 127 * Outputs excerpt for an activity embed item. 128 * 129 * @since 2.6.0 130 */ 131 function bp_activity_embed_excerpt( $content = '' ) { 132 echo bp_activity_get_embed_excerpt( $content = '' ); 133 } 134 135 /** 136 * Generates excerpt for an activity embed item. 137 * 138 * @since 2.6.0 139 * 140 * @param string $content The content to generate an excerpt for. 141 * @return string 142 */ 143 function bp_activity_get_embed_excerpt( $content = '' ) { 144 if ( empty( $content ) && ! empty( $GLOBALS['activities_template']->in_the_loop ) ) { 145 $content = $GLOBALS['activities_template']->activity->content; 146 } 147 148 /** 149 * bp_activity_truncate_entry() includes the 'Read More' link, which is why 150 * we're using this instead of bp_create_excerpt(). 151 */ 152 $content = html_entity_decode( $content ); 153 $content = bp_activity_truncate_entry( $content, array( 154 'html' => false, 155 'filter_shortcodes' => true, 156 'strip_tags' => true, 157 'force_truncate' => true 158 ) ); 159 160 /** 161 * Filter the activity embed excerpt. 162 * 163 * @since 2.6.0 164 * 165 * @var string $content Embed Excerpt. 166 * @var string $unmodified_content Unmodified activity content. 167 */ 168 return apply_filters( 'bp_activity_get_embed_excerpt', $content, $GLOBALS['activities_template']->activity->content ); 169 } 170 171 /** 172 * Outputs the first embedded item in the activity oEmbed template. 173 * 174 * @since 2.6.0 175 */ 176 function bp_activity_embed_media() { 177 // Bail if oEmbed request explicitly hides media. 178 if ( isset( $_REQUEST['hide_media'] ) && true == wp_validate_boolean( $_REQUEST['hide_media'] ) ) { 179 /** 180 * Do something after media is rendered for an activity oEmbed item. 181 * 182 * @since 2.6.0 183 */ 184 do_action( 'bp_activity_embed_after_media' ); 185 186 return; 187 } 188 189 /** 190 * Should we display media in the oEmbed template? 191 * 192 * @since 2.6.0 193 * 194 * @param bool $retval Defaults to true. 195 */ 196 $allow_media = apply_filters( 'bp_activity_embed_display_media', true ); 197 198 // Find oEmbeds from only WP registered providers. 199 bp_remove_all_filters( 'oembed_providers' ); 200 $media = bp_core_extract_media_from_content( $GLOBALS['activities_template']->activity->content, 'embeds' ); 201 bp_restore_all_filters( 'oembed_providers' ); 202 203 // oEmbeds have precedence over inline video / audio. 204 if ( isset( $media['embeds'] ) && true === $allow_media ) { 205 // Autoembed first URL. 206 $oembed_defaults = wp_embed_defaults(); 207 $oembed_args = array( 208 'width' => $oembed_defaults['width'], 209 'height' => $oembed_defaults['height'], 210 'discover' => true 211 ); 212 $url = $media['embeds'][0]['url']; 213 $cachekey = '_oembed_response_' . md5( $url . serialize( $oembed_args ) ); 214 215 // Try to fetch oEmbed response from meta. 216 $oembed = bp_activity_get_meta( bp_get_activity_id(), $cachekey ); 217 218 // No cache, so fetch full oEmbed response now! 219 if ( '' === $oembed ) { 220 $o = _wp_oembed_get_object(); 221 $oembed = $o->fetch( $o->get_provider( $url, $oembed_args ), $url, $oembed_args ); 222 223 // Cache oEmbed response. 224 bp_activity_update_meta( bp_get_activity_id(), $cachekey, $oembed ); 225 } 226 227 $content = ''; 228 229 /** 230 * Filters the default embed display max width. 231 * 232 * This is used if the oEmbed response does not return a thumbnail width. 233 * 234 * @since 2.6.0 235 * 236 * @param int $width. 237 */ 238 $width = (int) apply_filters( 'bp_activity_embed_display_media_width', 550 ); 239 240 // Set thumbnail. 241 if ( 'photo' === $oembed->type ) { 242 $thumbnail = $oembed->url; 243 } elseif ( isset( $oembed->thumbnail_url ) ) { 244 $thumbnail = $oembed->thumbnail_url; 245 246 /* Non-oEmbed standard attributes */ 247 // Mixcloud 248 } elseif ( isset( $oembed->image ) ) { 249 $thumbnail = $oembed->image; 250 // ReverbNation 251 } elseif ( isset( $oembed->{'thumbnail-url'} ) ) { 252 $thumbnail = $oembed->{'thumbnail-url'}; 253 } 254 255 // Display thumb and related oEmbed meta. 256 if ( true === isset ( $thumbnail ) ) { 257 $play_icon = $caption = ''; 258 259 // Add play icon for non-photos. 260 if ( 'photo' !== $oembed->type ) { 261 /** 262 * ion-play icon from Ionicons. 263 * 264 * @link http://ionicons.com/ 265 * @license MIT 266 */ 267 $play_icon = <<<EOD 268 <svg id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M405.2,232.9L126.8,67.2c-3.4-2-6.9-3.2-10.9-3.2c-10.9,0-19.8,9-19.8,20H96v344h0.1c0,11,8.9,20,19.8,20 c4.1,0,7.5-1.4,11.2-3.4l278.1-165.5c6.6-5.5,10.8-13.8,10.8-23.1C416,246.7,411.8,238.5,405.2,232.9z"/></svg> 269 EOD; 270 271 $play_icon = sprintf( '<a rel="nofollow" class="play-btn" href="%1$s">%2$s</a>', esc_url( $url ), $play_icon ); 272 } 273 274 // Thumb width 275 $width = isset( $oembed->thumbnail_width ) && 'photo' !== $oembed->type && (int) $oembed->thumbnail_width < 550 ? (int) $oembed->thumbnail_width : $width; 276 277 // Set up thumb. 278 $content = sprintf( '<div class="thumb" style="max-width:%1$spx">%2$s<a href="%3$s" rel="nofollow" onclick="top.location.href=\'%3$s\'"><img src="%4$s" /></a></div>', $width, $play_icon, esc_url( $url ), esc_url( $thumbnail ) ); 279 280 // Show title. 281 if ( isset( $oembed->title ) ) { 282 $caption .= sprintf( '<p class="caption-title"><strong>%s</strong></p>', apply_filters( 'single_post_title', $oembed->title ) ); 283 } 284 285 // Show description (non-oEmbed standard) 286 if ( isset( $oembed->description ) ) { 287 $caption .= sprintf( '<div class="caption-description">%s</div>', apply_filters( 'bp_get_activity_action', $oembed->description ) ); 288 } 289 290 // Show author info. 291 if ( isset( $oembed->provider_name ) && isset( $oembed->author_name ) ) { 292 /* translators: By [oEmbed author] on [oEmbed provider]. eg. By BuddyPress on YouTube. */ 293 $anchor_text = sprintf( __( 'By %1$s on %2$s', 'buddypress' ), $oembed->author_name, $oembed->provider_name ); 294 295 } elseif ( isset( $oembed->provider_name ) ) { 296 $anchor_text = sprintf( __( 'View on %s', 'buddypress' ), $oembed->provider_name ); 297 } 298 299 if ( true === isset( $anchor_text ) ) { 300 $caption .= sprintf( '<a rel="nofollow" href="%1$s" onclick="top.location.href=\'%1$s\'">%2$s</a>', esc_url( $url ), apply_filters( 'the_title', $anchor_text ) ); 301 } 302 303 // Set up caption. 304 if ( '' !== $caption ) { 305 $css_class = isset( $oembed->provider_name ) ? sprintf( ' provider-%s', sanitize_html_class( strtolower( $oembed->provider_name ) ) ) : ''; 306 $caption = sprintf( '<div class="caption%1$s">%2$s</div>', $css_class, $caption ); 307 308 $content .= $caption; 309 } 310 } 311 312 // Print rich content. 313 if ( '' !== $content ) { 314 printf( '<div class="bp-activity-embed-display-media" style="max-width:%spx">%s</div>', $width, $content ); 315 } 316 317 // Video / audio. 318 } elseif ( true === $allow_media ) { 319 // Call BP_Embed if it hasn't already loaded. 320 bp_embed_init(); 321 322 // Run shortcode and embed routine. 323 $content = buddypress()->embed->run_shortcode( $GLOBALS['activities_template']->activity->content ); 324 $content = buddypress()->embed->autoembed( $content ); 325 326 // Try to find inline video / audio. 327 $media = bp_core_extract_media_from_content( $content, 96 ); 328 329 // Video takes precedence. HTML5-only. 330 if ( isset( $media['videos'] ) && 'shortcodes' === $media['videos'][0]['source'] ) { 331 printf( '<video controls preload="metadata"><source src="%1$s"><p>%2$s</p></video>', 332 esc_url( $media['videos'][0]['url'] ), 333 esc_html__( 'Your browser does not support HTML5 video', 'buddypress' ) 334 ); 335 336 // No video? Try audio. HTML5-only. 337 } elseif ( isset( $media['audio'] ) && 'shortcodes' === $media['audio'][0]['source'] ) { 338 printf( '<audio controls preload="metadata"><source src="%1$s"><p>%2$s</p></audio>', 339 esc_url( $media['audio'][0]['url'] ), 340 esc_html__( 'Your browser does not support HTML5 audio', 'buddypress' ) 341 ); 342 } 343 344 } 345 346 /** This hook is documented in /bp-activity/bp-activity-embeds.php */ 347 do_action( 'bp_activity_embed_after_media' ); 348 } -
src/bp-activity/bp-activity-filters.php
395 395 * This method can only be used inside the Activity loop. 396 396 * 397 397 * @since 1.5.0 398 * 399 * @uses bp_is_single_activity() 400 * @uses apply_filters() To call the 'bp_activity_excerpt_append_text' hook. 401 * @uses apply_filters() To call the 'bp_activity_excerpt_length' hook. 402 * @uses bp_create_excerpt() 403 * @uses bp_get_activity_id() 404 * @uses bp_get_activity_thread_permalink() 405 * @uses apply_filters() To call the 'bp_activity_truncate_entry' hook. 398 * @since 2.6.0 Added $args parameter. 406 399 * 407 400 * @param string $text The original activity entry text. 408 401 * @return string $excerpt The truncated text. 409 402 */ 410 function bp_activity_truncate_entry( $text ) {403 function bp_activity_truncate_entry( $text, $args = array() ) { 411 404 global $activities_template; 412 405 413 406 /** … … 423 416 ); 424 417 425 418 // The full text of the activity update should always show on the single activity screen. 426 if ( ! $maybe_truncate_text || bp_is_single_activity() ) {419 if ( empty( $args['force_truncate'] ) && ( ! $maybe_truncate_text || bp_is_single_activity() ) ) { 427 420 return $text; 428 421 } 429 422 … … 445 438 */ 446 439 $excerpt_length = apply_filters( 'bp_activity_excerpt_length', 358 ); 447 440 441 $args = wp_parse_args( $args, array( 'ending' => __( '…', 'buddypress' ) ) ); 442 448 443 // Run the text through the excerpt function. If it's too short, the original text will be returned. 449 $excerpt = bp_create_excerpt( $text, $excerpt_length, array( 'ending' => __( '…', 'buddypress' ) ));444 $excerpt = bp_create_excerpt( $text, $excerpt_length, $args ); 450 445 451 446 /* 452 447 * If the text returned by bp_create_excerpt() is different from the original text (ie it's 453 448 * been truncated), add the "Read More" link. Note that bp_create_excerpt() is stripping 454 449 * shortcodes, so we have strip them from the $text before the comparison. 455 450 */ 456 if ( $excerpt != strip_shortcodes( $text) ) {451 if ( strlen( $excerpt ) > strlen( strip_shortcodes( $text ) ) ) { 457 452 $id = !empty( $activities_template->activity->current_comment->id ) ? 'acomment-read-more-' . $activities_template->activity->current_comment->id : 'activity-read-more-' . bp_get_activity_id(); 458 453 459 454 $excerpt = sprintf( '%1$s<span class="activity-read-more" id="%2$s"><a href="%3$s" rel="nofollow">%4$s</a></span>', $excerpt, $id, bp_get_activity_thread_permalink(), $append_text ); -
src/bp-activity/bp-activity-functions.php
3338 3338 } 3339 3339 3340 3340 // Generate a text excerpt for this activity item (and remove any oEmbeds URLs). 3341 $summary = strip_shortcodes( html_entity_decode( strip_tags( $content ) ) ); 3342 $summary = bp_create_excerpt( preg_replace( '#^\s*(https?://[^\s"]+)\s*$#im', '', $summary ) ); 3341 $summary = bp_create_excerpt( html_entity_decode( $content ), 225, array( 3342 'html' => false, 3343 'filter_shortcodes' => true, 3344 'strip_tags' => true, 3345 'remove_links' => true 3346 ) ); 3343 3347 3344 3348 if ( $use_media_type === 'embeds' ) { 3345 3349 $summary .= PHP_EOL . PHP_EOL . $extracted_media['url']; … … 3500 3504 */ 3501 3505 function bp_activity_embed() { 3502 3506 add_filter( 'embed_post_id', 'bp_get_activity_id' ); 3507 add_filter( 'oembed_dataparse', 'bp_activity_oembed_dataparse', 10, 2 ); 3503 3508 add_filter( 'bp_embed_get_cache', 'bp_embed_activity_cache', 10, 3 ); 3504 3509 add_action( 'bp_embed_update_cache', 'bp_embed_activity_save_cache', 10, 3 ); 3505 3510 } 3506 3511 add_action( 'activity_loop_start', 'bp_activity_embed' ); 3507 3512 3508 3513 /** 3514 * Cache full oEmbed response from oEmbed. 3515 * 3516 * @since 2.6.0 3517 * 3518 * @param string $retval Current oEmbed result. 3519 * @param object $data Full oEmbed response. 3520 * @param string $url URL used for the oEmbed request. 3521 * @return string 3522 */ 3523 function bp_activity_oembed_dataparse( $retval, $data ) { 3524 buddypress()->activity->oembed_response = $data; 3525 3526 return $retval; 3527 } 3528 3529 /** 3509 3530 * Set up activity oEmbed cache while recursing through activity comments. 3510 3531 * 3511 3532 * While crawling through an activity comment tree … … 3605 3626 */ 3606 3627 function bp_embed_activity_save_cache( $cache, $cachekey, $id ) { 3607 3628 bp_activity_update_meta( $id, $cachekey, $cache ); 3629 3630 // Cache full oEmbed response. 3631 if ( true === isset( buddypress()->activity->oembed_response ) ) { 3632 $cachekey = str_replace( '_oembed', '_oembed_response', $cachekey ); 3633 bp_activity_update_meta( $id, $cachekey, buddypress()->activity->oembed_response ); 3634 } 3608 3635 } 3609 3636 3610 3637 /** -
src/bp-activity/classes/class-bp-activity-component.php
32 32 array( 33 33 'adminbar_myaccount_order' => 10, 34 34 'search_query_arg' => 'activity_search', 35 'features' => array( 'embeds' ) 35 36 ) 36 37 ); 37 38 } … … 72 73 $includes[] = 'akismet'; 73 74 } 74 75 76 // Embeds - only applicable for WP 4.5+ 77 if ( bp_get_major_wp_version() >= 4.5 && bp_is_active( $this->id, 'embeds' ) ) { 78 $includes[] = 'embeds'; 79 } 80 75 81 if ( is_admin() ) { 76 82 $includes[] = 'admin'; 77 83 } -
new file src/bp-activity/classes/class-bp-activity-oembed-component.php
new file mode 100644
- + 1 <?php 2 /** 3 * BuddyPress Activity Classes. 4 * 5 * @package BuddyPress 6 * @subpackage Embeds 7 */ 8 9 // Exit if accessed directly. 10 defined( 'ABSPATH' ) || exit; 11 12 require_once( buddypress()->plugin_dir . '/bp-core/classes/class-bp-oembed-component.php' ); 13 14 /** 15 * oEmbed handler to respond and render single activity items. 16 * 17 * @since 2.6.0 18 */ 19 class BP_Activity_oEmbed_Component extends BP_oEmbed_Component { 20 /** 21 * Custom oEmbed slug endpoint. 22 * 23 * @since 2.6.0 24 * 25 * @var string 26 */ 27 public $slug_endpoint = 'activity'; 28 29 /** 30 * Custom hooks. 31 * 32 * @since 2.6.0 33 */ 34 protected function custom_hooks() { 35 add_action( 'oembed_dataparse', array( $this, 'use_custom_iframe_sandbox_attribute' ), 20, 3 ); 36 add_action( 'embed_content_meta', array( $this, 'embed_comments_button' ), 5 ); 37 add_action( 'get_template_part_assets/embeds/header', array( $this, 'on_activity_header' ), 10, 2 ); 38 39 add_filter( 'bp_activity_embed_html', array( $this, 'modify_iframe' ) ); 40 } 41 42 /** 43 * Add custom endpoint arguments. 44 * 45 * Currently, includes 'hide_media'. 46 * 47 * @since 2.6.0 48 * 49 * @return array 50 */ 51 protected function set_route_args() { 52 return array( 53 'hide_media' => array( 54 'default' => false, 55 'sanitize_callback' => 'wp_validate_boolean' 56 ) 57 ); 58 } 59 60 /** 61 * Output our custom embed template part. 62 * 63 * @since 2.6.0 64 */ 65 protected function content() { 66 bp_get_asset_template_part( 'embeds/activity' ); 67 } 68 69 /** 70 * Check if we're on our single activity page. 71 * 72 * @since 2.6.0 73 * 74 * @return bool 75 */ 76 protected function is_page() { 77 return bp_is_single_activity(); 78 } 79 80 /** 81 * Validates the URL to determine if the activity item is valid. 82 * 83 * @since 2.6.0 84 * 85 * @param string $url The URL to check. 86 * @return int|bool Activity ID on success; boolean false on failure. 87 */ 88 protected function validate_url_to_item_id( $url ) { 89 if ( bp_core_enable_root_profiles() ) { 90 $domain = bp_get_root_domain(); 91 } else { 92 $domain = bp_get_members_directory_permalink(); 93 } 94 95 // Check the URL to see if this is a single activity URL. 96 if ( 0 !== strpos( $url, $domain ) ) { 97 return false; 98 } 99 100 // Check for activity slug. 101 if ( false === strpos( $url, '/' . bp_get_activity_slug() . '/' ) ) { 102 return false; 103 } 104 105 // Do more checks. 106 $url = trim( untrailingslashit( $url ) ); 107 108 // Grab the activity ID. 109 $activity_id = (int) substr( 110 $url, 111 strrpos( $url, '/' ) + 1 112 ); 113 114 if ( ! empty( $activity_id ) ) { 115 // Check if activity item still exists. 116 $activity = new BP_Activity_Activity( $activity_id ); 117 118 // Okay, we're good to go! 119 if ( ! empty( $activity->component ) && 0 === (int) $activity->is_spam ) { 120 return $activity_id; 121 } 122 } 123 124 return false; 125 } 126 127 /** 128 * Sets the oEmbed response data for our activity item. 129 * 130 * @since 2.6.0 131 * 132 * @param int $item_id The activity ID. 133 * @return array 134 */ 135 protected function set_oembed_response_data( $item_id ) { 136 $activity = new BP_Activity_Activity( $item_id ); 137 138 return array( 139 'user_id' => $activity->user_id, 140 'content' => $activity->content, 141 'title' => __( 'Activity', 'buddypress' ), 142 'author_url' => bp_core_get_user_domain( $activity->user_id ) 143 ); 144 } 145 146 /** 147 * Sets a custom <blockquote> for our oEmbed fallback HTML. 148 * 149 * @since 2.6.0 150 * 151 * @param int $item_id The activity ID. 152 * @return string 153 */ 154 protected function set_fallback_html( $item_id ) { 155 $activity = new BP_Activity_Activity( $item_id ); 156 $mentionname = bp_activity_do_mentions() ? ' (@' . bp_activity_get_user_mentionname( $activity->user_id ) . ')' : ''; 157 $date = date_i18n( get_option( 'date_format' ), strtotime( $activity->date_recorded ) ); 158 159 // Make sure we can use some activity functions that depend on the loop. 160 $GLOBALS['activities_template'] = new stdClass; 161 $GLOBALS['activities_template']->activity = $activity; 162 163 // 'wp-embedded-content' CSS class is necessary due to how the embed JS works. 164 $blockquote = sprintf( '<blockquote class="wp-embedded-content bp-activity-item">%1$s%2$s %3$s</blockquote>', 165 bp_activity_get_embed_excerpt( $activity->content ), 166 '- ' . bp_core_get_user_displayname( $activity->user_id ) . $mentionname, 167 '<a href="' . esc_url( bp_activity_get_permalink( $item_id ) ) . '">' . $date . '</a>' 168 ); 169 170 // Clean up. 171 unset( $GLOBALS['activities_template'] ); 172 173 /** 174 * Filters the fallback HTML used when embedding a BP activity item. 175 * 176 * @since 2.6.0 177 * 178 * @param string $blockquote Current fallback HTML 179 * @param BP_Activity_Activity $activity Activity object 180 */ 181 return apply_filters( 'bp_activity_embed_fallback_html', $blockquote, $activity ); 182 } 183 184 /** 185 * Sets a custom <iframe> title for our oEmbed item. 186 * 187 * @since 2.6.0 188 * 189 * @param int $item_id The activity ID 190 * @return string 191 */ 192 protected function set_iframe_title( $item_id ) { 193 return __( 'Embedded Activity Item', 'buddypress' ); 194 } 195 196 /** 197 * Use our custom <iframe> sandbox attribute in our oEmbed response. 198 * 199 * WordPress sets the <iframe> sandbox attribute to 'allow-scripts' regardless 200 * of whatever the oEmbed response is in {@link wp_filter_oembed_result()}. We 201 * need to add back our custom sandbox value so links will work. 202 * 203 * @since 2.6.0 204 * 205 * @see BP_Activity_Component::modify_iframe() where our custom sandbox value is set. 206 * 207 * @param string $result The oEmbed HTML result. 208 * @param object $data A data object result from an oEmbed provider. 209 * @param string $url The URL of the content to be embedded. 210 * @return string 211 */ 212 public function use_custom_iframe_sandbox_attribute( $result, $data, $url ) { 213 // Make sure we are on our activity embed URL. If not, bail. 214 if ( false === $this->validate_url_to_item_id( $url ) ) { 215 return $result; 216 } 217 218 // Get unfiltered sandbox attribute from our own oEmbed response. 219 $sandbox_pos = strpos( $data->html, 'sandbox=' ) + 9; 220 $sandbox = substr( $data->html, $sandbox_pos, strpos( $data->html, '"', $sandbox_pos ) - $sandbox_pos ); 221 222 // Replace only if our sandbox attribute contains 'allow-top-navigation'. 223 if ( false !== strpos( $sandbox, 'allow-top-navigation' ) ) { 224 $result = str_replace( ' sandbox="allow-scripts"', " sandbox=\"{$sandbox}\"", $result ); 225 226 // Also remove 'security' attribute; this is only used for IE < 10. 227 $result = str_replace( 'security="restricted"', "", $result ); 228 } 229 230 return $result; 231 } 232 233 /** 234 * Modify various IFRAME-related items if embeds are allowed. 235 * 236 * HTML modified: 237 * - Add sandbox="allow-top-navigation" attribute. This allows links to work 238 * within the iframe sandbox attribute. 239 * 240 * JS modified: 241 * - Remove IFRAME height restriction of 1000px. Fixes long embed items being 242 * truncated. 243 * 244 * @since 2.6.0 245 * 246 * @param string $retval Current embed HTML. 247 * @return string 248 */ 249 public function modify_iframe( $retval ) { 250 // Add 'allow-top-navigation' to allow links to be clicked. 251 $retval = str_replace( 'sandbox="', 'sandbox="allow-top-navigation ', $retval ); 252 253 // See /wp-includes/js/wp-embed.js. 254 if ( SCRIPT_DEBUG ) { 255 // Removes WP's hardcoded IFRAME height restriction. 256 $retval = str_replace( 'height = 1000;', 'height = height;', $retval ); 257 258 // This is for the WP build minified version. 259 } else { 260 $retval = str_replace( 'g=1e3', 'g=g', $retval ); 261 } 262 263 return $retval; 264 } 265 266 /** 267 * Do stuff when our oEmbed activity header template part is loading. 268 * 269 * Currently, removes wpautop() from the bp_activity_action() function. 270 * 271 * @since 2.6.0 272 * 273 * @param string $slug Template part slug requested. 274 * @param string $name Template part name requested. 275 */ 276 public function on_activity_header( $slug, $name ) { 277 if ( false === $this->is_page() || 'activity' !== $name ) { 278 return; 279 } 280 281 remove_filter( 'bp_get_activity_action', 'wpautop' ); 282 } 283 284 /** 285 * Prints the markup for the activity embed comments button. 286 * 287 * Basically a copy of {@link print_embed_comments_button()}, but modified for 288 * the BP activity component. 289 * 290 * @since 2.6.0 291 */ 292 public function embed_comments_button() { 293 if ( ! did_action( 'bp_embed_content' ) || ! bp_is_single_activity() ) { 294 return; 295 } 296 297 // Make sure our custom permalink shows up in the 'WordPress Embed' block. 298 add_filter( 'the_permalink', array( $this, 'filter_embed_url' ) ); 299 300 // Only show comment bubble if we have some activity comments. 301 $count = bp_activity_get_comment_count(); 302 if ( empty( $count ) ) { 303 return; 304 } 305 ?> 306 307 <div class="wp-embed-comments"> 308 <a href="<?php bp_activity_thread_permalink(); ?>"> 309 <span class="dashicons dashicons-admin-comments"></span> 310 <?php 311 printf( 312 _n( 313 '%s <span class="screen-reader-text">Comment</span>', 314 '%s <span class="screen-reader-text">Comments</span>', 315 $count, 316 'buddypress' 317 ), 318 number_format_i18n( $count ) 319 ); 320 ?> 321 </a> 322 </div> 323 324 <?php 325 } 326 } -
src/bp-core/bp-core-actions.php
40 40 add_action( 'setup_theme', 'bp_setup_theme', 10 ); 41 41 add_action( 'after_setup_theme', 'bp_after_setup_theme', 100 ); // After WP themes. 42 42 add_action( 'wp_enqueue_scripts', 'bp_enqueue_scripts', 10 ); 43 add_action( 'enqueue_embed_scripts', 'bp_enqueue_embed_scripts', 10 ); 43 44 add_action( 'admin_bar_menu', 'bp_setup_admin_bar', 20 ); // After WP core. 44 45 add_action( 'template_redirect', 'bp_template_redirect', 10 ); 45 46 add_action( 'widgets_init', 'bp_widgets_init', 10 ); -
src/bp-core/bp-core-dependency.php
477 477 } 478 478 479 479 /** 480 * Fires the 'bp_enqueue_embed_scripts' action in the <head> for BP oEmbeds. 481 * 482 * @since 2.6.0 483 */ 484 function bp_enqueue_embed_scripts() { 485 if ( ! is_buddypress() ) { 486 return; 487 } 488 489 /** 490 * Enqueue CSS and JS files for BuddyPress embeds. 491 * 492 * @since 2.6.0 493 */ 494 do_action ( 'bp_enqueue_embed_scripts' ); 495 } 496 497 /** 480 498 * Fire the 'bp_add_rewrite_tag' action, where BP adds its custom rewrite tags. 481 499 * 482 500 * @since 1.8.0 -
src/bp-core/bp-core-functions.php
1689 1689 return apply_filters( 'bp_use_embed_in_private_messages', !defined( 'BP_EMBED_DISABLE_PRIVATE_MESSAGES' ) || !BP_EMBED_DISABLE_PRIVATE_MESSAGES ); 1690 1690 } 1691 1691 1692 /** 1693 * Extracts media metadata from a given content. 1694 * 1695 * @since 2.6.0 1696 * 1697 * @param string $content The content to check. 1698 * @param string|int $type The type to check. Can also use a bitmask. See the class constants in the 1699 * BP_Media_Extractor class for more info. 1700 * @return array|bool If media exists, will return array of media metadata. Else, boolean false. 1701 */ 1702 function bp_core_extract_media_from_content( $content = '', $type = 'all' ) { 1703 if ( is_string( $type ) ) { 1704 $class = new ReflectionClass( 'BP_Media_Extractor' ); 1705 $bitmask = $class->getConstant( strtoupper( $type ) ); 1706 } else { 1707 $bitmask = (int) $type; 1708 } 1709 1710 // Type isn't valid, so bail. 1711 if ( empty( $bitmask ) ) { 1712 return false; 1713 } 1714 1715 $x = new BP_Media_Extractor; 1716 $media = $x->extract( $content, $bitmask ); 1717 1718 unset( $media['has'] ); 1719 $retval = array_filter( $media ); 1720 1721 return ! empty( $retval ) ? $retval : false; 1722 } 1723 1692 1724 /** Admin *********************************************************************/ 1693 1725 1694 1726 /** -
src/bp-core/bp-core-template-loader.php
66 66 } 67 67 68 68 /** 69 * Get an asset template part. 70 * 71 * Basically the same as {@link bp_get_template_part()}, but with 'assets/' 72 * prepended to the slug. 73 * 74 * @since 2.6.0 75 * 76 * @see bp_get_template_part() for full documentation. 77 */ 78 function bp_get_asset_template_part( $slug, $name = null ) { 79 return bp_get_template_part( "assets/{$slug}", $name ); 80 } 81 82 /** 69 83 * Retrieve the name of the highest priority template file that exists. 70 84 * 71 85 * Searches in the STYLESHEETPATH before TEMPLATEPATH so that themes which -
src/bp-core/bp-core-template.php
784 784 * This function is borrowed from CakePHP v2.0, under the MIT license. See 785 785 * http://book.cakephp.org/view/1469/Text#truncate-1625 786 786 * 787 * @since 2.6.0 Added 'strip_tags' and 'remove_links' as $options args. 788 * 787 789 * ### Options: 788 790 * 789 791 * - `ending` Will be used as Ending and appended to the trimmed string. … … 806 808 * excerpt length. Default: true. 807 809 * @type bool $filter_shortcodes If true, shortcodes will be stripped. 808 810 * Default: true. 811 * @type bool $strip_tags If true, HTML tags will be stripped. Default: false. 812 * Only applicable if $html is set to false. 813 * @type bool $remove_links If true, URLs will be stripped. Default: false. 814 * Only applicable if $html is set to false. 809 815 * } 810 816 * @return string Trimmed string. 811 817 */ … … 818 824 'ending' => __( ' […]', 'buddypress' ), 819 825 'exact' => false, 820 826 'html' => true, 821 'filter_shortcodes' => $filter_shortcodes_default 827 'filter_shortcodes' => $filter_shortcodes_default, 828 'strip_tags' => false, 829 'remove_links' => false, 822 830 ), 'create_excerpt' ); 823 831 824 832 // Save the original text, to be passed along to the filter. … … 904 912 } 905 913 } 906 914 } else { 915 // Strip HTML tags if necessary. 916 if ( ! empty( $r['strip_tags'] ) ) { 917 $text = strip_tags( $text ); 918 } 919 920 // Remove links if necessary. 921 if ( ! empty( $r['remove_links'] ) ) { 922 $text = preg_replace( '#^\s*(https?://[^\s"]+)\s*$#im', '', $text ); 923 } 924 907 925 if ( mb_strlen( $text ) <= $length ) { 908 return $text; 926 /** 927 * Filters the final generated excerpt. 928 * 929 * @since 1.1.0 930 * 931 * @param string $truncate Generated excerpt. 932 * @param string $original_text Original text provided. 933 * @param int $length Length of returned string, including ellipsis. 934 * @param array $options Array of HTML attributes and options. 935 */ 936 return apply_filters( 'bp_create_excerpt', $text, $original_text, $length, $options ); 909 937 } else { 910 938 $truncate = mb_substr( $text, 0, $length - mb_strlen( $ending ) ); 911 939 } -
src/bp-core/bp-core-theme-compatibility.php
547 547 } 548 548 549 549 /** 550 * Create a dummy WP_Post object. 551 * 552 * @since 2.6.0 553 * 554 * @param array $args Array of optional arguments. Arguments parallel the properties 555 * of {@link WP_Post}; see that class for more details. 556 * @return WP_Post 557 */ 558 function bp_theme_compat_create_dummy_post( $args = array() ) { 559 $args = wp_parse_args( $args, array( 560 'ID' => -9999, 561 'post_status' => 'public', 562 'post_author' => 0, 563 'post_parent' => 0, 564 'post_type' => 'page', 565 'post_date' => 0, 566 'post_date_gmt' => 0, 567 'post_modified' => 0, 568 'post_modified_gmt' => 0, 569 'post_content' => '', 570 'post_title' => '', 571 'post_excerpt' => '', 572 'post_content_filtered' => '', 573 'post_mime_type' => '', 574 'post_password' => '', 575 'post_name' => '', 576 'guid' => '', 577 'menu_order' => 0, 578 'pinged' => '', 579 'to_ping' => '', 580 'ping_status' => '', 581 'comment_status' => 'closed', 582 'comment_count' => 0, 583 'filter' => 'raw', 584 585 'is_404' => false, 586 'is_page' => false, 587 'is_single' => false, 588 'is_archive' => false, 589 'is_tax' => false, 590 ) ); 591 592 // Create the dummy post. 593 $post = new WP_Post( (object) $args ); 594 595 return $post; 596 } 597 598 /** 550 599 * Populate various WordPress globals with dummy data to prevent errors. 551 600 * 552 601 * This dummy data is necessary because theme compatibility essentially fakes … … 567 616 568 617 // Switch defaults if post is set. 569 618 if ( isset( $wp_query->post ) ) { 570 $ dummy= wp_parse_args( $args, array(619 $args = wp_parse_args( $args, array( 571 620 'ID' => $wp_query->post->ID, 572 621 'post_status' => $wp_query->post->post_status, 573 622 'post_author' => $wp_query->post->post_author, … … 592 641 'comment_status' => $wp_query->post->comment_status, 593 642 'comment_count' => $wp_query->post->comment_count, 594 643 'filter' => $wp_query->post->filter, 595 596 'is_404' => false,597 'is_page' => false,598 'is_single' => false,599 'is_archive' => false,600 'is_tax' => false,601 ) );602 } else {603 $dummy = wp_parse_args( $args, array(604 'ID' => -9999,605 'post_status' => 'public',606 'post_author' => 0,607 'post_parent' => 0,608 'post_type' => 'page',609 'post_date' => 0,610 'post_date_gmt' => 0,611 'post_modified' => 0,612 'post_modified_gmt' => 0,613 'post_content' => '',614 'post_title' => '',615 'post_excerpt' => '',616 'post_content_filtered' => '',617 'post_mime_type' => '',618 'post_password' => '',619 'post_name' => '',620 'guid' => '',621 'menu_order' => 0,622 'pinged' => '',623 'to_ping' => '',624 'ping_status' => '',625 'comment_status' => 'closed',626 'comment_count' => 0,627 'filter' => 'raw',628 629 'is_404' => false,630 'is_page' => false,631 'is_single' => false,632 'is_archive' => false,633 'is_tax' => false,634 644 ) ); 635 645 } 636 646 637 647 // Bail if dummy post is empty. 638 if ( empty( $ dummy) ) {648 if ( empty( $args ) ) { 639 649 return; 640 650 } 641 651 642 652 // Set the $post global. 643 $post = new WP_Post( (object) $dummy);653 $post = bp_theme_compat_create_dummy_post( $args ); 644 654 645 655 // Copy the new post global into the main $wp_query. 646 656 $wp_query->post = $post; … … 648 658 649 659 // Prevent comments form from appearing. 650 660 $wp_query->post_count = 1; 651 $wp_query->is_404 = $dummy['is_404']; 652 $wp_query->is_page = $dummy['is_page']; 653 $wp_query->is_single = $dummy['is_single']; 654 $wp_query->is_archive = $dummy['is_archive']; 655 $wp_query->is_tax = $dummy['is_tax']; 656 657 // Clean up the dummy post. 658 unset( $dummy ); 661 $wp_query->is_404 = $post->is_404; 662 $wp_query->is_page = $post->is_page; 663 $wp_query->is_single = $post->is_single; 664 $wp_query->is_archive = $post->is_archive; 665 $wp_query->is_tax = $post->is_tax; 659 666 660 667 /** 661 668 * Force the header back to 200 status if not a deliberate 404 … … 678 685 * 679 686 * @since 1.7.0 680 687 * 681 * @uses bp_is_single_user() To check if page is single user.682 * @uses bp_get_single_user_template() To get user template.683 * @uses bp_is_single_user_edit() To check if page is single user edit.684 * @uses bp_get_single_user_edit_template() To get user edit template.685 * @uses bp_is_single_view() To check if page is single view.686 * @uses bp_get_single_view_template() To get view template.687 * @uses bp_is_forum_edit() To check if page is forum edit.688 * @uses bp_get_forum_edit_template() To get forum edit template.689 * @uses bp_is_topic_merge() To check if page is topic merge.690 * @uses bp_get_topic_merge_template() To get topic merge template.691 * @uses bp_is_topic_split() To check if page is topic split.692 * @uses bp_get_topic_split_template() To get topic split template.693 * @uses bp_is_topic_edit() To check if page is topic edit.694 * @uses bp_get_topic_edit_template() To get topic edit template.695 * @uses bp_is_reply_edit() To check if page is reply edit.696 * @uses bp_get_reply_edit_template() To get reply edit template.697 * @uses bp_set_theme_compat_template() To set the global theme compat template.698 *699 688 * @param string $template Template name. 700 689 * @return string $template Template name. 701 690 */ 702 691 function bp_template_include_theme_compat( $template = '' ) { 692 // If embed template, bail. 693 if ( true === function_exists( 'is_embed' ) && is_embed() ) { 694 return $template; 695 } 703 696 704 697 // If the current theme doesn't need theme compat, bail at this point. 705 698 if ( ! bp_use_theme_compat_with_current_theme() ) { -
src/bp-core/classes/class-bp-admin.php
836 836 <a href="https://bbpress.org">bbPress</a>, 837 837 <a href="https://github.com/ichord/Caret.js">Caret.js</a>, 838 838 <a href="http://tedgoas.github.io/Cerberus/">Cerberus</a>, 839 <a href="http://ionicons.com/">Ionicons</a>, 839 840 <a href="https://github.com/carhartl/jquery-cookie">jquery.cookie</a>, 840 841 <a href="https://www.mediawiki.org/wiki/MediaWiki">MediaWiki</a>, 841 842 <a href="https://wordpress.org">WordPress</a>. -
new file src/bp-core/classes/class-bp-oembed-component.php
new file mode 100644
- + 1 <?php 2 /** 3 * Core component classes. 4 * 5 * @package BuddyPress 6 * @subpackage Core 7 */ 8 9 // Exit if accessed directly. 10 defined( 'ABSPATH' ) || exit; 11 12 /** 13 * API for responding and returning a custom oEmbed request. 14 * 15 * @since 2.6.0 16 */ 17 abstract class BP_oEmbed_Component { 18 19 /** START PROPERTIES ****************************************************/ 20 21 /** 22 * (required) The slug endpoint. 23 * 24 * Should be your component id. 25 * 26 * @since 2.6.0 27 * 28 * @var string 29 */ 30 public $slug_endpoint = ''; 31 32 /** END PROPERTIES ******************************************************/ 33 34 /** 35 * Constructor. 36 */ 37 final public function __construct() { 38 $this->setup_properties(); 39 40 // Some rudimentary logic checking. 41 if ( empty( $this->slug_endpoint ) ) { 42 $class = get_class( $this ); 43 throw new LogicException( $class . ' class must define $slug_endpoint property' ); 44 } 45 46 $this->setup_hooks(); 47 $this->custom_hooks(); 48 } 49 50 /** REQUIRED METHODS ****************************************************/ 51 52 /** 53 * Add content for your oEmbed response here. 54 * 55 * @since 2.6.0 56 */ 57 abstract protected function content(); 58 59 /** 60 * Add a check for when you are on the page you want to oEmbed. 61 * 62 * You'll want to return a boolean here. eg. bp_is_single_activity(). 63 * 64 * @since 2.6.0 65 * 66 * @return bool 67 */ 68 abstract protected function is_page(); 69 70 /** 71 * Validate the URL to see if it matches your item ID. 72 * 73 * @since 2.6.0 74 * 75 * @return int Your item ID 76 */ 77 abstract protected function validate_url_to_item_id( $url ); 78 79 /** 80 * Set the oEmbed response data. 81 * 82 * @since 2.6.0 83 * 84 * @param int $item_id Your item ID to do checks against. 85 * @return array Should contain 'user_id', 'content', 'title', 'author_url' as array keys. 86 * 'author_url' is optional; the rest are required. 87 */ 88 abstract protected function set_oembed_response_data( $item_id ); 89 90 /** 91 * Sets the fallback HTML for the oEmbed response. 92 * 93 * In a WordPress oEmbed item, the fallback HTML is a <blockquote>. This is 94 * usually hidden after the <iframe> is loaded. 95 * 96 * @since 2.6.0 97 * 98 * @param int $item_id Your item ID to do checks against. 99 * @return string Fallback HTML you want to output. 100 */ 101 abstract protected function set_fallback_html( $item_id ); 102 103 /** OPTIONAL METHODS ****************************************************/ 104 105 /** 106 * If your oEmbed endpoint requires additional arguments, set them here. 107 * 108 * @see register_rest_route() View the $args parameter for more info. 109 * 110 * @since 2.6.0 111 * 112 * @return array 113 */ 114 protected function set_route_args() { 115 return array(); 116 } 117 118 /** 119 * Set the iframe title. 120 * 121 * If not set, this will fallback to WP's 'Embedded WordPress Post'. 122 * 123 * @since 2.6.0 124 * 125 * @param int $item_id The item ID to do checks for. 126 * @return string 127 */ 128 protected function set_iframe_title( $item_id ) {} 129 130 /** 131 * Do what you need to do here to initialize any custom hooks. 132 * 133 * @since 2.6.0 134 */ 135 protected function custom_hooks() {} 136 137 /** 138 * Set permalink for oEmbed link discovery. 139 * 140 * This method will be called on the page we want to oEmbed. In most cases, 141 * you will not need to override this method. However, if you need to, do 142 * override in your extended class. 143 * 144 * @since 2.6.0 145 */ 146 protected function set_permalink() { 147 $url = bp_get_requested_url(); 148 149 // Remove querystring from bp_get_requested_url() 150 if ( false !== strpos( bp_get_requested_url(), '?' ) ) { 151 $url = substr( bp_get_requested_url(), 0, strpos( bp_get_requested_url(), '?' ) ); 152 } 153 154 return $url; 155 } 156 157 /** HELPERS *************************************************************/ 158 159 /** 160 * Get the item ID when filtering the oEmbed HTML. 161 * 162 * Should only be used during the 'embed_html' hook. 163 * 164 * @since 2.6.0 165 */ 166 protected function get_item_id() { 167 return $this->is_page() ? $this->validate_url_to_item_id( $this->set_permalink() ) : buddypress()->{$this->slug_endpoint}->embedid_in_progress; 168 } 169 170 /** SET UP **************************************************************/ 171 172 /** 173 * Set up properties. 174 * 175 * @since 2.6.0 176 */ 177 protected function setup_properties() { 178 $this->slug_endpoint = sanitize_title( $this->slug_endpoint ); 179 } 180 181 /** 182 * Hooks! We do the dirty work here, so you don't have to! :) 183 * 184 * More hooks are available in the setup_template_parts() method. 185 * 186 * @since 2.6.0 187 */ 188 protected function setup_hooks() { 189 add_action( 'rest_api_init', array( $this, 'register_route' ) ); 190 add_action( 'bp_embed_content', array( $this, 'inject_content' ) ); 191 192 add_filter( 'embed_template', array( $this, 'setup_template_parts' ) ); 193 add_filter( 'post_embed_url', array( $this, 'filter_embed_url' ) ); 194 add_filter( 'embed_html', array( $this, 'filter_embed_html' ) ); 195 add_filter( 'oembed_discovery_links', array( $this, 'add_oembed_discovery_links' ) ); 196 add_filter( 'rest_pre_serve_request', array( $this, 'oembed_xml_request' ), 20, 4 ); 197 } 198 199 /** HOOKS ***************************************************************/ 200 201 /** 202 * Register the oEmbed REST API route. 203 * 204 * @since 2.6.0 205 */ 206 public function register_route() { 207 /** This filter is documented in wp-includes/class-wp-oembed-controller.php */ 208 $maxwidth = apply_filters( 'oembed_default_width', 600 ); 209 210 // Required arguments. 211 $args = array( 212 'url' => array( 213 'required' => true, 214 'sanitize_callback' => 'esc_url_raw', 215 ), 216 'format' => array( 217 'default' => 'json', 218 'sanitize_callback' => 'wp_oembed_ensure_format', 219 ), 220 'maxwidth' => array( 221 'default' => $maxwidth, 222 'sanitize_callback' => 'absint', 223 ) 224 ); 225 226 // Merge custom arguments here. 227 $args = $args + (array) $this->set_route_args(); 228 229 register_rest_route( 'oembed/1.0', "/embed/{$this->slug_endpoint}", array( 230 array( 231 'methods' => WP_REST_Server::READABLE, 232 'callback' => array( $this, 'get_item' ), 233 'args' => $args 234 ), 235 ) ); 236 } 237 238 /** 239 * Set up custom embed template parts for BuddyPress use. 240 * 241 * @since 2.6.0 242 * 243 * @param string $template File path to current embed template. 244 * @return string 245 */ 246 public function setup_template_parts( $template ) { 247 // Determine if we're on our BP page. 248 if ( ! $this->is_page() || is_404() ) { 249 return $template; 250 } 251 252 // Set up some BP-specific embed template overrides. 253 add_action( 'get_template_part_embed', array( $this, 'content_buffer_start' ), -999, 2 ); 254 add_action( 'get_footer', array( $this, 'content_buffer_end' ), -999 ); 255 256 // Return the original WP embed template. 257 return $template; 258 } 259 260 /** 261 * Start object buffer. 262 * 263 * We're going to override WP's get_template_part( 'embed, 'content' ) call 264 * and inject our own template for BuddyPress use. 265 * 266 * @since 2.6.0 267 */ 268 public function content_buffer_start( $slug, $name ) { 269 if ( 'embed' !== $slug || 'content' !== $name ) { 270 return; 271 } 272 273 // Start the buffer to wipe out get_template_part( 'embed, 'content' ). 274 ob_start(); 275 } 276 277 /** 278 * End object buffer. 279 * 280 * We're going to override WP's get_template_part( 'embed, 'content' ) call 281 * and inject our own template for BuddyPress use. 282 * 283 * @since 2.6.0 284 */ 285 public function content_buffer_end( $name ) { 286 if ( 'embed' !== $name || is_404() ) { 287 return; 288 } 289 290 // Wipe out get_template_part( 'embed, 'content' ). 291 ob_end_clean(); 292 293 // Start our custom BuddyPress embed template! 294 echo '<div '; 295 post_class( 'wp-embed' ); 296 echo '>'; 297 298 // Template part for our embed header. 299 bp_get_asset_template_part( 'embeds/header', bp_current_component() ); 300 301 /** 302 * Inject BuddyPress embed content on this hook. 303 * 304 * You shouldn't really need to use this if you extend the 305 * {@link BP_oEmbed_Component} class. 306 * 307 * @since 2.6.0 308 */ 309 do_action( 'bp_embed_content' ); 310 311 // Template part for our embed footer. 312 bp_get_asset_template_part( 'embeds/footer', bp_current_component() ); 313 314 echo '</div>'; 315 } 316 317 /** 318 * Adds oEmbed discovery links on single activity pages. 319 * 320 * @since 2.6.0 321 * 322 * @param string $retval Current discovery links. 323 * @return string 324 */ 325 public function add_oembed_discovery_links( $retval ) { 326 if ( ! $this->is_page() ) { 327 return $retval; 328 } 329 330 $permalink = $this->set_permalink(); 331 if ( empty( $permalink ) ) { 332 return $retval; 333 } 334 335 add_filter( 'rest_url' , array( $this, 'filter_rest_url' ) ); 336 337 $retval = '<link rel="alternate" type="application/json+oembed" href="' . esc_url( get_oembed_endpoint_url( $permalink ) ) . '" />' . "\n"; 338 339 if ( class_exists( 'SimpleXMLElement' ) ) { 340 $retval .= '<link rel="alternate" type="text/xml+oembed" href="' . esc_url( get_oembed_endpoint_url( $permalink, 'xml' ) ) . '" />' . "\n"; 341 } 342 343 remove_filter( 'rest_url' , array( $this, 'filter_rest_url' ) ); 344 345 return $retval; 346 } 347 348 /** 349 * Callback for the API endpoint. 350 * 351 * Returns the JSON object for the item. 352 * 353 * @since 2.6.0 354 * 355 * @param WP_REST_Request $request Full data about the request. 356 * @return WP_Error|array oEmbed response data or WP_Error on failure. 357 */ 358 public function get_item( $request ) { 359 $url = $request['url']; 360 361 $data = false; 362 363 $item_id = (int) $this->validate_url_to_item_id( $url ); 364 365 if ( ! empty( $item_id ) ) { 366 $item = $this->set_oembed_response_data( $item_id ); 367 368 // Create dummy post to piggyback off of get_oembed_response_data() 369 $post = bp_theme_compat_create_dummy_post( array( 370 'post_author' => $item['user_id'], 371 'post_title' => $item['title'], 372 'post_content' => $item['content'], 373 374 // This passes the get_oembed_response_data() check. 375 'post_status' => 'publish' 376 ) ); 377 378 // Add markers to tell that we're embedding a single activity. 379 // This is needed for various oEmbed response data filtering. 380 if ( empty( buddypress()->{$this->slug_endpoint} ) ) { 381 buddypress()->{$this->slug_endpoint} = new stdClass; 382 } 383 buddypress()->{$this->slug_endpoint}->embedurl_in_progress = $url; 384 buddypress()->{$this->slug_endpoint}->embedid_in_progress = $item_id; 385 386 // Save custom route args as well. 387 $custom_args = array_keys( (array) $this->set_route_args() ); 388 if ( ! empty( $custom_args ) ) { 389 buddypress()->{$this->slug_endpoint}->embedargs_in_progress = array(); 390 391 foreach( $custom_args as $arg ) { 392 if ( isset( $request[ $arg ] ) ) { 393 buddypress()->{$this->slug_endpoint}->embedargs_in_progress[ $arg ] = $request[ $arg ]; 394 } 395 } 396 } 397 398 // Use WP's oEmbed response data function. 399 $data = get_oembed_response_data( $post, $request['maxwidth'] ); 400 401 // Set custom 'author_url' if we have one. 402 if ( ! empty( $item['author_url'] ) ) { 403 $data['author_url'] = $item['author_url']; 404 } 405 } 406 407 if ( ! $data ) { 408 return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) ); 409 } 410 411 return $data; 412 } 413 414 /** 415 * If oEmbed request wants XML, return XML instead of JSON. 416 * 417 * Basically a copy of {@link _oembed_rest_pre_serve_request()}. Unfortunate 418 * that we have to duplicate this just for a URL check. 419 * 420 * @since 2.6.0 421 * 422 * @param bool $served Whether the request has already been served. 423 * @param WP_HTTP_ResponseInterface $result Result to send to the client. Usually a WP_REST_Response. 424 * @param WP_REST_Request $request Request used to generate the response. 425 * @param WP_REST_Server $server Server instance. 426 * @return bool 427 */ 428 public function oembed_xml_request( $served, $result, $request, $server ) { 429 $params = $request->get_params(); 430 431 if ( ! isset( $params['format'] ) || 'xml' !== $params['format'] ) { 432 return $served; 433 } 434 435 // Validate URL against our oEmbed endpoint. If not valid, bail. 436 // This is our mod to _oembed_rest_pre_serve_request(). 437 $query_params = $request->get_query_params(); 438 if ( false === $this->validate_url_to_item_id( $query_params['url'] ) ) { 439 return $served; 440 } 441 442 // Embed links inside the request. 443 $data = $server->response_to_data( $result, false ); 444 445 if ( ! class_exists( 'SimpleXMLElement' ) ) { 446 status_header( 501 ); 447 die( get_status_header_desc( 501 ) ); 448 } 449 450 $result = _oembed_create_xml( $data ); 451 452 // Bail if there's no XML. 453 if ( ! $result ) { 454 status_header( 501 ); 455 return get_status_header_desc( 501 ); 456 } 457 458 if ( ! headers_sent() ) { 459 $server->send_header( 'Content-Type', 'text/xml; charset=' . get_option( 'blog_charset' ) ); 460 } 461 462 echo $result; 463 464 return true; 465 } 466 467 /** 468 * Pass our BuddyPress activity permalink for embedding. 469 * 470 * @since 2.6.0 471 * 472 * @see bp_activity_embed_rest_route_callback() 473 * 474 * @param string $retval Current embed URL 475 * @return string 476 */ 477 public function filter_embed_url( $retval ) { 478 if ( false === isset( buddypress()->{$this->slug_endpoint}->embedurl_in_progress ) && ! $this->is_page() ) { 479 return $retval; 480 } 481 482 $url = $this->is_page() ? $this->set_permalink() : buddypress()->{$this->slug_endpoint}->embedurl_in_progress; 483 $url = trailingslashit( $url ); 484 485 // This is for the 'WordPress Embed' block 486 // @see bp_activity_embed_comments_button() 487 if ( 'the_permalink' !== current_filter() ) { 488 $url = add_query_arg( 'embed', 'true', trailingslashit( $url ) ); 489 490 // Add custom route args to iframe. 491 if ( ! empty( buddypress()->{$this->slug_endpoint}->embedargs_in_progress ) ) { 492 foreach( buddypress()->{$this->slug_endpoint}->embedargs_in_progress as $key => $value ) { 493 $url = add_query_arg( $key, $value, $url ); 494 } 495 } 496 } 497 498 return $url; 499 } 500 501 /** 502 * Filters the embed HTML for our BP oEmbed endpoint. 503 * 504 * @since 2.6.0 505 * 506 * @param string $retval Current embed HTML 507 * @return string 508 */ 509 public function filter_embed_html( $retval ) { 510 if ( false === isset( buddypress()->{$this->slug_endpoint}->embedurl_in_progress ) && ! $this->is_page() ) { 511 return $retval; 512 } 513 514 $url = $this->set_permalink(); 515 516 $item_id = $this->is_page() ? $this->validate_url_to_item_id( $url ) : buddypress()->{$this->slug_endpoint}->embedid_in_progress; 517 518 // Change 'Embedded WordPress Post' to custom title. 519 $custom_title = $this->set_iframe_title( $item_id ); 520 if ( ! empty( $custom_title ) ) { 521 $title_pos = strpos( $retval, 'title=' ) + 7; 522 $title_end_pos = strpos( $retval, '"', $title_pos ); 523 524 $retval = substr_replace( $retval, esc_attr( $custom_title ), $title_pos, $title_end_pos - $title_pos ); 525 } 526 527 // Add 'max-width' CSS attribute to IFRAME. 528 // This will make our oEmbeds responsive. 529 if ( false === strpos( $retval, 'style="max-width' ) ) { 530 $retval = str_replace( '<iframe', '<iframe style="max-width:100%"', $retval ); 531 } 532 533 // Remove default <blockquote> 534 $retval = substr( $retval, strpos( $retval, '</blockquote>' ) + 13 ); 535 536 // Set up new fallback HTML 537 // @todo Maybe use KSES? 538 $fallback_html = $this->set_fallback_html( $item_id ); 539 540 /** 541 * Dynamic filter to return BP oEmbed HTML. 542 * 543 * @since 2.6.0 544 * 545 * @var string $retval 546 */ 547 return apply_filters( "bp_{$this->slug_endpoint}_embed_html", $fallback_html . $retval ); 548 } 549 550 /** 551 * Append our custom slug endpoint to oEmbed endpoint URL. 552 * 553 * Meant to be used as a filter on 'rest_url' before any call to 554 * {@link get_oembed_endpoint_url()} is used. 555 * 556 * @since 2.6.0 557 * 558 * @see add_oembed_discovery_links() 559 * 560 * @param string $retval Current oEmbed endpoint URL 561 * @return string 562 */ 563 function filter_rest_url( $retval = '' ) { 564 return $retval . "/{$this->slug_endpoint}"; 565 } 566 567 /** 568 * Inject content into the embed template. 569 * 570 * @since 2.6.0 571 */ 572 public function inject_content() { 573 if ( ! $this->is_page() ) { 574 return; 575 } 576 577 $this->content(); 578 } 579 } 580 No newline at end of file -
new file src/bp-templates/bp-legacy/buddypress/assets/embeds/activity.php
new file mode 100644
- + 1 2 <?php if ( bp_activity_embed_has_activity( bp_current_action() ) ) : ?> 3 4 <?php while ( bp_activities() ) : bp_the_activity(); ?> 5 <div class="bp-embed-excerpt"><?php bp_activity_embed_excerpt(); ?></div> 6 7 <?php bp_activity_embed_media(); ?> 8 9 <?php endwhile; ?> 10 11 <?php endif; ?> 12 -
new file src/bp-templates/bp-legacy/buddypress/assets/embeds/css-activity.php
new file mode 100644
- + 1 #bp-embed-header:after { 2 clear: both; 3 content: ""; 4 display: table; 5 margin-bottom: 1em; 6 } 7 8 .bp-embed-avatar { 9 float: left; 10 margin: 0 .75em 0 0; 11 } 12 13 p.bp-embed-activity-action { 14 font-size: 15px; 15 margin-bottom: 0; 16 } 17 18 p.bp-embed-activity-action a:first-child { 19 color: #32373c; 20 font-weight: bold; 21 } 22 23 p.bp-embed-activity-action img.avatar { 24 padding: 0 4px 0 3px; 25 vertical-align: text-bottom; 26 } 27 28 .bp-embed-excerpt { 29 margin-bottom: 1em; 30 } 31 32 .bp-embed-excerpt a { 33 color: #21759b; 34 display: inline-block; 35 overflow: hidden; 36 text-overflow: ellipsis; 37 vertical-align: top; 38 white-space: nowrap; 39 max-width: 250px; 40 } 41 42 .activity-read-more { 43 margin-left: .5em; 44 } 45 46 .activity-read-more a { 47 color: #b4b9be; 48 } 49 50 .wp-embed-footer { 51 margin-top: 20px; 52 } 53 54 span.bp-embed-timestamp { 55 font-size: .9em; 56 } 57 58 video { 59 width: 100%; 60 height: auto; 61 } 62 63 .bp-activity-embed-display-media { 64 border: 1px solid #ccc; 65 border-radius: 6px; 66 } 67 68 .bp-activity-embed-display-media, 69 .bp-activity-embed-display-media .thumb, 70 .bp-activity-embed-display-media .thumb img { 71 width: 100%; 72 } 73 74 .bp-activity-embed-display-media .thumb { 75 position: relative; 76 } 77 78 .bp-activity-embed-display-media .caption { 79 padding: .2em .5em .5em .5em; 80 } 81 82 a.play-btn { 83 background: rgba(0, 0, 0, 0.75); 84 border-radius: 50%; 85 height: 50px; 86 left: 50%; 87 margin: 0; 88 padding: 1em; 89 position: absolute; 90 text-indent: 0.25em; 91 top: 50%; 92 transform: translateY(-50%) translateX(-50%); 93 -webkit-transform: translateY(-50%) translateX(-50%); 94 transition: all 0.2s ease-out; 95 width: 50px; 96 } 97 98 a.play-btn:hover { 99 background: rgba(0, 0, 0, 0.95); 100 transform: translateY(-50%) translateX(-50%) scale(1.05); 101 -webkit-transform: translateY(-50%) translateX(-50%) scale(1.05); 102 transition: all 0.2s ease-out; 103 } 104 105 .bp-activity-embed-display-media .thumb svg { 106 fill: #fff; 107 overflow: hidden; 108 } 109 110 .bp-activity-embed-display-media .caption-description { 111 font-size: 90%; 112 margin: .4em 0; 113 } 114 No newline at end of file -
new file src/bp-templates/bp-legacy/buddypress/assets/embeds/footer.php
new file mode 100644
- + 1 <div class="wp-embed-footer"> 2 <?php the_embed_site_title() ?> 3 4 <div class="wp-embed-meta"> 5 <?php 6 /** This action is documented in wp-includes/theme-compat/embed-content.php */ 7 do_action( 'embed_content_meta'); ?> 8 </div> 9 </div> 10 No newline at end of file -
new file src/bp-templates/bp-legacy/buddypress/assets/embeds/header-activity.php
new file mode 100644
- + 1 2 <div id="bp-embed-header"> 3 <div class="bp-embed-avatar"> 4 <a href="<?php bp_displayed_user_link(); ?>"> 5 <?php bp_displayed_user_avatar( 'type=thumb&width=45&height=45' ); ?> 6 </a> 7 </div> 8 9 <?php if ( bp_activity_embed_has_activity( bp_current_action() ) ) : ?> 10 11 <?php while ( bp_activities() ) : bp_the_activity(); ?> 12 <p class="bp-embed-activity-action"> 13 <?php bp_activity_action( array( 'no_timestamp' => true ) ); ?> 14 </p> 15 <?php endwhile; ?> 16 17 <?php endif; ?> 18 19 <p class="bp-embed-header-meta"> 20 <?php if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() ) : ?> 21 <span class="bp-embed-mentionname">@<?php bp_displayed_user_mentionname(); ?> · </span> 22 <?php endif; ?> 23 24 <span class="bp-embed-timestamp"><a href="<?php bp_activity_thread_permalink(); ?>"><?php echo date_i18n( get_option( 'time_format' ) . ' - ' . get_option( 'date_format' ), strtotime( bp_get_activity_date_recorded() ) ); ?></a></span> 25 </p> 26 </div> -
new file src/bp-templates/bp-legacy/buddypress/assets/embeds/header.php
new file mode 100644
- + 1 2 <div id="bp-embed-header"> 3 <div class="bp-embed-avatar"> 4 <a href="<?php bp_displayed_user_link(); ?>"> 5 <?php bp_displayed_user_avatar( 'type=thumb&width=36&height=36' ); ?> 6 </a> 7 </div> 8 9 <p class="wp-embed-heading"> 10 <a href="<?php bp_displayed_user_link(); ?>"> 11 <?php bp_displayed_user_fullname(); ?> 12 </a> 13 </p> 14 15 <?php if ( bp_is_active( 'activity' ) && bp_activity_do_mentions() ) : ?> 16 <p class="bp-embed-mentionname">@<?php bp_displayed_user_mentionname(); ?></p> 17 <?php endif; ?> 18 </div>