Index: src/bp-activity/bp-activity-embeds.php new file mode 100644 =================================================================== --- /dev/null +++ src/bp-activity/bp-activity-embeds.php @@ -0,0 +1,359 @@ + WP_REST_Server::READABLE, + 'callback' => 'bp_activity_embed_rest_route_callback', + 'args' => array( + 'url' => array( + 'required' => true, + 'sanitize_callback' => 'esc_url_raw', + ), + 'format' => array( + 'default' => 'json', + 'sanitize_callback' => 'wp_oembed_ensure_format', + ), + 'maxwidth' => array( + 'default' => $maxwidth, + 'sanitize_callback' => 'absint', + ), + ), + ), + ) ); + +} +add_action( 'rest_api_init', 'bp_activity_embed_register_rest_route' ); + +/** + * Callback for the API endpoint. + * + * Returns the JSON object for the post. + * + * @since 2.5.0 + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Error|array oEmbed response data or WP_Error on failure. + */ +function bp_activity_embed_rest_route_callback( $request ) { + $url = $request['url']; + + $invalid = $data = false; + + if ( bp_core_enable_root_profiles() ) { + $domain = bp_get_root_domain(); + } else { + $domain = bp_get_members_directory_permalink(); + } + + // Check the URL to see if this is a single activity URL. + if ( 0 !== strpos( $url, $domain ) ) { + $invalid = true; + } + + // Check for activity slug. + if ( false === strpos( $url, '/' . bp_get_activity_slug() . '/' ) ) { + $invalid = true; + } + + // Do more checks. + if ( false === $invalid ) { + $url = trim( untrailingslashit( $url ) ); + + // Grab the activity ID. + $activity_id = (int) substr( + $url, + strrpos( $url, '/' ) + 1 + ); + + if ( ! empty( $activity_id ) ) { + // Check if activity item still exists. + $activity = new BP_Activity_Activity( $activity_id ); + + // Okay, we're good to go! + if ( ! empty( $activity->component ) ) { + // Create dummy post to piggyback off of get_oembed_response_data() + $post = bp_theme_compat_create_dummy_post( array( + 'post_author' => $activity->user_id, + 'post_title' => __( 'Activity', 'buddypress' ), + 'post_content' => $activity->content, + + // This passes the get_oembed_response_data() check. + 'post_status' => 'publish' + ) ); + + // Add markers to tell that we're embedding a single activity. + // This needed for various oEmbed response data filtering. + buddypress()->activity->embedurl_in_progress = $url; + buddypress()->activity->embedid_in_progress = $activity_id; + + // Use WP's oEmbed response data function. + $data = get_oembed_response_data( $post, $request['maxwidth'] ); + + // Make one change here to reference our BP user profile + $data['author_url'] = bp_core_get_user_domain( $activity->user_id ); + } + } + } + + if ( ! $data ) { + return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) ); + } + + return $data; +} + +/** + * Register oEmbed provider on BuddyPress pages. + * + * This allows single activity items to be embedded. WP's TinyMCE doesn't + * require this somehow, which is why we only do this on BuddyPress pages. + * + * @since 2.5.0 + * + * @todo Fix up issues with javascript not firing to hide the fallback
+ */ +function bp_activity_embed_add_oembed_provider() { + // Only register our provider on BuddyPress pages. + if ( false === is_buddypress() ) { + return; + } + + if ( bp_core_enable_root_profiles() ) { + $domain = bp_get_root_domain(); + } else { + $domain = bp_get_members_directory_permalink(); + } + + add_filter( 'rest_url' , 'bp_activity_embed_filter_rest_url' ); + wp_oembed_add_provider( $domain . '*/activity/*', get_oembed_endpoint_url() ); + remove_filter( 'rest_url' , 'bp_activity_embed_filter_rest_url' ); +} +add_action( 'bp_init', 'bp_activity_embed_add_oembed_provider' ); + +/** + * Use our custom embed template for activity items. + * + * @since 2.5.0 + * + * @param string $retval Current embed template + * @return string + */ +function bp_activity_embed_filter_template( $retval ) { + if ( ! bp_is_single_activity() ) { + return $retval; + } + + // Embed template hierarchy! + return bp_locate_template( array( + 'embeds/template-single-activity.php', + 'embeds/template.php' + ) ); +} +add_filter( 'embed_template', 'bp_activity_embed_filter_template' ); + +/** + * Inject activity content into the embed template. + * + * @since 2.5.0 + */ +function bp_activity_embed_inject_content() { + if ( ! bp_is_single_activity() ) { + return; + } + + bp_get_template_part( 'embeds/activity' ); +} +add_action( 'embed_content', 'bp_activity_embed_inject_content' ); + +/** + * Adds oEmbed discovery links on single activity pages. + * + * @since 2.5.0 + * + * @param string $retval Current discovery links. + * @return string + */ +function bp_activity_embed_oembed_discovery_links( $retval ) { + if ( ! bp_is_single_activity() ) { + return $retval; + } + + $permalink = bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_current_action() . '/'; + + add_filter( 'rest_url' , 'bp_activity_embed_filter_rest_url' ); + + $retval = '' . "\n"; + + if ( class_exists( 'SimpleXMLElement' ) ) { + $retval .= '' . "\n"; + } + + remove_filter( 'rest_url' , 'bp_activity_embed_filter_rest_url' ); + + return $retval; +} +add_filter( 'oembed_discovery_links', 'bp_activity_embed_oembed_discovery_links' ); + +/** + * Pass our BuddyPress activity permalink for embedding. + * + * @since 2.5.0 + * + * @see bp_activity_embed_rest_route_callback() + * + * @param string $retval Current embed URL + * @return string + */ +function bp_activity_embed_filter_post_embed_url( $retval ) { + if ( false === isset( buddypress()->activity->embedurl_in_progress ) && ! bp_is_single_activity() ) { + return $retval; + } + + $url = bp_is_single_activity() ? bp_displayed_user_domain() . bp_get_activity_slug() . '/' . bp_current_action() . '/' : buddypress()->activity->embedurl_in_progress; + $url = trailingslashit( $url ); + + // This is for the 'WordPress Embed' block + // @see bp_activity_embed_comments_button() + if ( 'the_permalink' !== current_filter() ) { + $url = add_query_arg( 'embed', 'true', trailingslashit( $url ) ); + } + + return $url; +} +add_filter( 'post_embed_url', 'bp_activity_embed_filter_post_embed_url' ); + +/** + * Filters the embed HTML for the default oEmbed fallback HTML. + * + * @since 2.5.0 + * + * @param string $retval Current embed HTML + * @return string + */ +function bp_activity_embed_filter_html( $retval ) { + if ( false === isset( buddypress()->activity->embedurl_in_progress ) && ! bp_is_single_activity() ) { + return $retval; + } + + // Change 'Embedded WordPress Post' to 'Embedded Activity Item' + $retval = str_replace( __( 'Embedded WordPress Post' ), __( 'Embedded Activity Item', 'buddypress' ), $retval ); + + // Remove default+ $retval = substr( $retval, strpos( $retval, '' ) + 13 ); + + // Set up new+ $activity_id = bp_is_single_activity() ? bp_current_action() : buddypress()->activity->embedid_in_progress; + $activity = new BP_Activity_Activity( $activity_id ); + $mentionname = bp_activity_do_mentions() ? ' (@' . bp_activity_get_user_mentionname( $activity->user_id ) . ')' : ''; + $date = date_i18n( get_option( 'date_format' ), strtotime( $activity->date_recorded ) ); + + // 'wp-embedded-content' CSS class is necessary due to how the embed JS works. + $blockquote = sprintf( '%1$s%2$s %3$s', + apply_filters( 'bp_get_activity_content_body', $activity->content ), + '- ' . bp_core_get_user_displayname( $activity->user_id ) . $mentionname, + '' . $date . '' + ); + + /** + * Filters the fallback HTML used when embedding a BP activity item. + * + * @since 2.5.0 + * + * @param string $blockquote Current fallback HTML + * @param BP_Activity_Activity $activity Activity object + */ + $blockquote = apply_filters( 'bp_activity_embed_fallback_html', $blockquote, $activity ); + + // Add our custom+ return $blockquote . $retval; +} +add_filter( 'embed_html', 'bp_activity_embed_filter_html' ); + +/** + * Prints the markup for the activity embed comments button. + * + * @since 2.5.0 + */ +function bp_activity_embed_comments_button() { + if ( ! bp_is_single_activity() ) { + return; + } + + // Make sure our custom permalink shows up in the 'WordPress Embed' block. + add_filter( 'the_permalink', 'bp_activity_embed_filter_post_embed_url' ); + + // Only show comment bubble if we have some activity comments. + $count = bp_activity_get_comment_count(); + if ( empty( $count ) ) { + return; + } +?> + + + + + + + + + + Index: src/bp-templates/bp-legacy/buddypress/embeds/css.php new file mode 100644 =================================================================== --- /dev/null +++ src/bp-templates/bp-legacy/buddypress/embeds/css.php @@ -0,0 +1,26 @@ + \ No newline at end of file Index: src/bp-templates/bp-legacy/buddypress/embeds/footer.php new file mode 100644 =================================================================== --- /dev/null +++ src/bp-templates/bp-legacy/buddypress/embeds/footer.php @@ -0,0 +1,33 @@ + \ No newline at end of file Index: src/bp-templates/bp-legacy/buddypress/embeds/header.php new file mode 100644 =================================================================== --- /dev/null +++ src/bp-templates/bp-legacy/buddypress/embeds/header.php @@ -0,0 +1,18 @@ + ++ + 10, 'search_query_arg' => 'activity_search', + 'features' => array( 'embeds' ) ) ); } @@ -67,6 +68,12 @@ $includes[] = 'akismet'; } + // Embeds - only applicable for WP 4.4+ + if ( bp_get_major_wp_version() >= 4.4 && bp_is_active( $this->id, 'embeds' ) ) { + $includes[] = 'embeds'; + } + + // Admin-specific if ( is_admin() ) { $includes[] = 'admin'; } Index: src/bp-core/bp-core-theme-compatibility.php =================================================================== --- src/bp-core/bp-core-theme-compatibility.php +++ src/bp-core/bp-core-theme-compatibility.php @@ -650,6 +650,55 @@ } /** + * Create a dummy WP_Post object. + * + * @since 2.5.0 + * + * @param array $args Array of optional arguments. Arguments parallel the properties + * of {@link WP_Post}; see that class for more details. + * @return WP_Post + */ +function bp_theme_compat_create_dummy_post( $args = array() ) { + $dummy = wp_parse_args( $args, array( + 'ID' => -9999, + 'post_status' => 'public', + 'post_author' => 0, + 'post_parent' => 0, + 'post_type' => 'page', + 'post_date' => 0, + 'post_date_gmt' => 0, + 'post_modified' => 0, + 'post_modified_gmt' => 0, + 'post_content' => '', + 'post_title' => '', + 'post_excerpt' => '', + 'post_content_filtered' => '', + 'post_mime_type' => '', + 'post_password' => '', + 'post_name' => '', + 'guid' => '', + 'menu_order' => 0, + 'pinged' => '', + 'to_ping' => '', + 'ping_status' => '', + 'comment_status' => 'closed', + 'comment_count' => 0, + 'filter' => 'raw', + + 'is_404' => false, + 'is_page' => false, + 'is_single' => false, + 'is_archive' => false, + 'is_tax' => false, + ) ); + + // Create the dummy post. + $post = new WP_Post( (object) $dummy ); + + return $post; +} + +/** * Populate various WordPress globals with dummy data to prevent errors. * * This dummy data is necessary because theme compatibility essentially fakes @@ -670,7 +719,7 @@ // Switch defaults if post is set. if ( isset( $wp_query->post ) ) { - $dummy = wp_parse_args( $args, array( + $args = wp_parse_args( $args, array( 'ID' => $wp_query->post->ID, 'post_status' => $wp_query->post->post_status, 'post_author' => $wp_query->post->post_author, @@ -695,55 +744,16 @@ 'comment_status' => $wp_query->post->comment_status, 'comment_count' => $wp_query->post->comment_count, 'filter' => $wp_query->post->filter, - - 'is_404' => false, - 'is_page' => false, - 'is_single' => false, - 'is_archive' => false, - 'is_tax' => false, - ) ); - } else { - $dummy = wp_parse_args( $args, array( - 'ID' => -9999, - 'post_status' => 'public', - 'post_author' => 0, - 'post_parent' => 0, - 'post_type' => 'page', - 'post_date' => 0, - 'post_date_gmt' => 0, - 'post_modified' => 0, - 'post_modified_gmt' => 0, - 'post_content' => '', - 'post_title' => '', - 'post_excerpt' => '', - 'post_content_filtered' => '', - 'post_mime_type' => '', - 'post_password' => '', - 'post_name' => '', - 'guid' => '', - 'menu_order' => 0, - 'pinged' => '', - 'to_ping' => '', - 'ping_status' => '', - 'comment_status' => 'closed', - 'comment_count' => 0, - 'filter' => 'raw', - - 'is_404' => false, - 'is_page' => false, - 'is_single' => false, - 'is_archive' => false, - 'is_tax' => false, ) ); } // Bail if dummy post is empty. - if ( empty( $dummy ) ) { + if ( empty( $args ) ) { return; } // Set the $post global. - $post = new WP_Post( (object) $dummy ); + $post = bp_theme_compat_create_dummy_post( $args ); // Copy the new post global into the main $wp_query. $wp_query->post = $post; @@ -751,14 +761,11 @@ // Prevent comments form from appearing. $wp_query->post_count = 1; - $wp_query->is_404 = $dummy['is_404']; - $wp_query->is_page = $dummy['is_page']; - $wp_query->is_single = $dummy['is_single']; - $wp_query->is_archive = $dummy['is_archive']; - $wp_query->is_tax = $dummy['is_tax']; - - // Clean up the dummy post. - unset( $dummy ); + $wp_query->is_404 = $post->is_404; + $wp_query->is_page = $post->is_page; + $wp_query->is_single = $post->is_single; + $wp_query->is_archive = $post->is_archive; + $wp_query->is_tax = $post->is_tax; /** * Force the header back to 200 status if not a deliberate 404 Index: src/bp-templates/bp-legacy/buddypress/embeds/activity.php new file mode 100644 =================================================================== --- /dev/null +++ src/bp-templates/bp-legacy/buddypress/embeds/activity.php @@ -0,0 +1,15 @@ + + + + + + ++Index: src/bp-templates/bp-legacy/buddypress/embeds/template.php new file mode 100644 =================================================================== --- /dev/null +++ src/bp-templates/bp-legacy/buddypress/embeds/template.php @@ -0,0 +1,54 @@ + + + class="no-js"> + + + + + + + + ++ + + + +> + > + + ++ + + + \ No newline at end of file