Skip to:
Content

BuddyPress.org

Changeset 7952


Ignore:
Timestamp:
02/21/2014 02:49:33 PM (11 years ago)
Author:
boonebgorges
Message:

Automatically check for newly created items when viewing the Activity Stream.

Leveraging WP's Heartbeat API, this new functionality performs periodic checks
to see whether new activity items matching the current filter have been posted
since the page was originally loaded. If new items are found, a Load Newest
link is inserted into the top of the stream, which will insert the new items.

A filter is included, bp_activity_heartbeat_pulse, that allows site owners to
set a specific interval for these checks. The default value is 15 seconds, or
whatever your global Heartbeat interval is.

To disable the feature, there is a new setting "Automatically check for new
activity items when viewing the activity stream".

Fixes #5328

Props imath, boonebgorges

Location:
trunk
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/bp-activity/bp-activity-filters.php

    r7890 r7952  
    397397    return apply_filters( 'bp_activity_truncate_entry', $excerpt, $text, $append_text );
    398398}
     399
     400/**
     401 * Include extra javascript dependencies for activity component.
     402 *
     403 * @since BuddyPress (2.0.0)
     404 *
     405 * @uses bp_activity_do_heartbeat() to check if heartbeat is required.
     406 *
     407 * @param array $js_handles The original dependencies.
     408 * @return array $js_handles The new dependencies.
     409 */
     410function bp_activity_get_js_dependencies( $js_handles = array() ) {
     411    if ( bp_activity_do_heartbeat() ) {
     412        $js_handles[] = 'heartbeat';
     413    }
     414
     415    return $js_handles;
     416}
     417add_filter( 'bp_core_get_js_dependencies', 'bp_activity_get_js_dependencies', 10, 1 );
     418
     419/**
     420 * Add a just-posted classes to the most recent activity item.
     421 *
     422 * We use these classes to avoid pagination issues when items are loaded
     423 * dynamically into the activity stream.
     424 *
     425 * @since BuddyPress (2.0.0)
     426 *
     427 * @param string $classes
     428 * @return string $classes
     429 */
     430function bp_activity_newest_class( $classes = '' ) {
     431    $bp = buddypress();
     432
     433    if ( ! empty( $bp->activity->new_update_id ) && $bp->activity->new_update_id == bp_get_activity_id() ) {
     434        $classes .= ' new-update';
     435    }
     436
     437    $classes .= ' just-posted';
     438    return $classes;
     439}
     440
     441/**
     442 * Use WordPress Heartbeat API to check for latest activity update.
     443 *
     444 * @since BuddyPress (2.0.0)
     445 *
     446 * @uses bp_activity_get_last_updated() to get the recorded date of the last activity
     447
     448 * @param array $response
     449 * @param array $data
     450 * @return array $response
     451 */
     452function bp_activity_heartbeat_last_recorded( $response = array(), $data = array() ) {
     453    $bp = buddypress();
     454
     455    if ( empty( $data['bp_activity_last_id'] ) ) {
     456        return $response;
     457    }
     458
     459    // Use the querystring argument stored in the cookie (to preserve
     460    // filters), but force the offset to get only new items
     461    $activity_latest_args = bp_parse_args(
     462        bp_ajax_querystring( 'activity' ),
     463        array( 'offset' => absint( $data['bp_activity_last_id'] ) + 1 ),
     464        'activity_latest_args'
     465    );
     466
     467    $newest_activities = array();
     468    $last_activity_id  = 0;
     469
     470    // Temporarly add a just-posted class for new activity items
     471    add_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
     472
     473    ob_start();
     474    if ( bp_has_activities( $activity_latest_args ) ) {
     475        while ( bp_activities() ) {
     476            bp_the_activity();
     477
     478            if ( $last_activity_id < bp_get_activity_id() ) {
     479                $last_activity_id = bp_get_activity_id();
     480            }
     481
     482            bp_get_template_part( 'activity/entry' );
     483        }
     484    }
     485
     486    $newest_activities['activities'] = ob_get_contents();
     487    $newest_activities['last_id']    = $last_activity_id;
     488    ob_end_clean();
     489
     490    // Remove the temporary filter
     491    remove_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
     492
     493    if ( ! empty( $newest_activities['last_id'] ) ) {
     494        $response['bp_activity_newest_activities'] = $newest_activities;
     495    }
     496
     497    return $response;
     498}
     499add_filter( 'heartbeat_received', 'bp_activity_heartbeat_last_recorded', 10, 2 );
     500add_filter( 'heartbeat_nopriv_received', 'bp_activity_heartbeat_last_recorded', 10, 2 );
     501
     502/**
     503 * Set the strings for WP HeartBeat API where needed.
     504 *
     505 * @since BuddyPress (2.0.0)
     506 *
     507 * @param array $strings Localized strings.
     508 * @return array $strings
     509 */
     510function bp_activity_heartbeat_strings( $strings = array() ) {
     511
     512    if ( ! bp_activity_do_heartbeat() ) {
     513        return $strings;
     514    }
     515
     516    $global_pulse = 0;
     517
     518    // Check whether the global heartbeat settings already exist.
     519    $heartbeat_settings = apply_filters( 'heartbeat_settings', array() );
     520    if ( ! empty( $heartbeat_settings['interval'] ) ) {
     521        // 'Fast' is 5
     522        $global_pulse = is_numeric( $heartbeat_settings['interval'] ) ? absint( $heartbeat_settings['interval'] ) : 5;
     523    }
     524
     525    // Filter here to specify a BP-specific pulse frequency
     526    $bp_activity_pulse = apply_filters( 'bp_activity_heartbeat_pulse', 15 );
     527
     528    /**
     529     * Use the global pulse value unless:
     530     * a. the BP-specific value has been specifically filtered, or
     531     * b. it doesn't exist (ie, BP will be the only one using the heartbeat,
     532     *    so we're responsible for enabling it)
     533     */
     534    if ( has_filter( 'bp_activity_heartbeat_pulse' ) || empty( $global_pulse ) ) {
     535        $pulse = $bp_activity_pulse;
     536    } else {
     537        $pulse = $global_pulse;
     538    }
     539
     540    $strings = array_merge( $strings, array(
     541        'newest' => __( 'Load Newest', 'buddypress' ),
     542        'pulse'  => absint( $pulse ),
     543    ) );
     544
     545    return $strings;
     546}
     547add_filter( 'bp_core_get_js_strings', 'bp_activity_heartbeat_strings', 10, 1 );
  • trunk/bp-activity/bp-activity-functions.php

    r7906 r7952  
    18501850    bp_activity_update_meta( $id, $cachekey, $cache );
    18511851}
     1852
     1853/**
     1854 * Should we use Heartbeat to refresh activities?
     1855 *
     1856 * @since BuddyPress (2.0.0)
     1857 *
     1858 * @uses bp_is_activity_heartbeat_active() to check if heatbeat setting is on.
     1859 * @uses bp_is_activity_directory() to check if the current page is the activity
     1860 *       directory.
     1861 * @uses bp_is_active() to check if the group component is active.
     1862 * @uses bp_is_group_activity() to check if on a single group, the current page
     1863 *       is the group activities.
     1864 * @uses bp_is_group_home() to check if the current page is a single group home
     1865 *       page.
     1866 *
     1867 * @return bool True if activity heartbeat is enabled, otherwise false.
     1868 */
     1869function bp_activity_do_heartbeat() {
     1870    $retval = false;
     1871
     1872    if ( ! bp_is_activity_heartbeat_active() ) {
     1873        return $retval;
     1874    }
     1875
     1876    if ( bp_is_activity_directory() ) {
     1877        $retval = true;
     1878    }
     1879
     1880    if ( bp_is_active( 'groups') ) {
     1881        // If no custom front, then activities are loaded in group's home
     1882        $has_custom_front = bp_locate_template( array( 'groups/single/front.php' ), false, true );
     1883
     1884        if ( bp_is_group_activity() || ( ! $has_custom_front && bp_is_group_home() ) ) {
     1885            $retval = true;
     1886        }
     1887    }
     1888
     1889    return $retval;
     1890}
  • trunk/bp-activity/bp-activity-template.php

    r7951 r7952  
    777777    global $activities_template;
    778778
    779     $remaining_pages = floor( ( $activities_template->total_activity_count - 1 ) / ( $activities_template->pag_num * $activities_template->pag_page ) );
     779    $remaining_pages = 0;
     780
     781    if ( ! empty( $activities_template->pag_page ) ) {
     782        $remaining_pages = floor( ( $activities_template->total_activity_count - 1 ) / ( $activities_template->pag_num * $activities_template->pag_page ) );
     783    }
     784
    780785    $has_more_items  = (int) $remaining_pages ? true : false;
    781786
  • trunk/bp-core/admin/bp-core-settings.php

    r6456 r7952  
    100100    <input id="bp-disable-blogforum-comments" name="bp-disable-blogforum-comments" type="checkbox" value="1" <?php checked( !bp_disable_blogforum_comments( false ) ); ?> />
    101101    <label for="bp-disable-blogforum-comments"><?php _e( 'Allow activity stream commenting on blog and forum posts', 'buddypress' ); ?></label>
     102
     103<?php
     104}
     105
     106/**
     107 * Allow Heartbeat to refresh activity stream.
     108 *
     109 * @since BuddyPress (2.0.0)
     110 */
     111function bp_admin_setting_callback_heartbeat() {
     112?>
     113
     114    <input id="_bp_enable_heartbeat_refresh" name="_bp_enable_heartbeat_refresh" type="checkbox" value="1" <?php checked( bp_is_activity_heartbeat_active( true ) ); ?> />
     115    <label for="_bp_enable_heartbeat_refresh"><?php _e( 'Automatically check for new items while viewing the activity stream', 'buddypress' ); ?></label>
    102116
    103117<?php
  • trunk/bp-core/bp-core-admin.php

    r7754 r7952  
    330330            add_settings_field( 'bp-disable-blogforum-comments', __( 'Blog &amp; Forum Comments', 'buddypress' ), 'bp_admin_setting_callback_blogforum_comments', 'buddypress', 'bp_activity' );
    331331            register_setting( 'buddypress', 'bp-disable-blogforum-comments', 'bp_admin_sanitize_callback_blogforum_comments' );
     332
     333            // Activity Heartbeat refresh
     334            add_settings_field( '_bp_enable_heartbeat_refresh', __( 'Activity auto-refresh', 'buddypress' ), 'bp_admin_setting_callback_heartbeat', 'buddypress', 'bp_activity' );
     335            register_setting( 'buddypress', '_bp_enable_heartbeat_refresh', 'intval' );
    332336
    333337            // Allow activity akismet
  • trunk/bp-core/bp-core-functions.php

    r7860 r7952  
    18531853    return $nav_item_url;
    18541854}
     1855
     1856/**
     1857 * Get the javascript dependencies for buddypress.js.
     1858 *
     1859 * @since BuddyPress (2.0.0)
     1860 *
     1861 * @uses apply_filters() to allow other component to load extra dependencies
     1862 *
     1863 * @return array The javascript dependencies.
     1864 */
     1865function bp_core_get_js_dependencies() {
     1866    return apply_filters( 'bp_core_get_js_dependencies', array( 'jquery' ) );
     1867}
  • trunk/bp-core/bp-core-options.php

    r7904 r7952  
    7676        // Users from all sites can post
    7777        '_bp_enable_akismet'              => true,
     78
     79        /** Activity HeartBeat ************************************************/
     80
     81        // HeartBeat is on to refresh activities
     82        '_bp_enable_heartbeat_refresh'    => true,
    7883
    7984        /** BuddyBar **********************************************************/
     
    587592
    588593/**
     594 * Check whether Activity Heartbeat refresh is enabled.
     595 *
     596 * @since BuddyPress (2.0.0)
     597 *
     598 * @uses bp_get_option() To get the Heartbeat option.
     599 *
     600 * @param bool $default Optional. Fallback value if not found in the database.
     601 *        Default: true.
     602 * @return bool True if Heartbeat refresh is enabled, otherwise false.
     603 */
     604function bp_is_activity_heartbeat_active( $default = true ) {
     605    return (bool) apply_filters( 'bp_is_activity_heartbeat_active', (bool) bp_get_option( '_bp_enable_heartbeat_refresh', $default ) );
     606}
     607
     608/**
    589609 * Get the current theme package ID.
    590610 *
  • trunk/bp-templates/bp-legacy/buddypress-functions.php

    r7949 r7952  
    226226        // without it
    227227        if ( isset( $asset['location'], $asset['handle'] ) ) {
    228             wp_enqueue_script( $asset['handle'], $asset['location'], array( 'jquery' ), $this->version );
     228            wp_enqueue_script( $asset['handle'], $asset['location'], bp_core_get_js_dependencies(), $this->version );
    229229        }
    230230
    231231        // Add words that we need to use in JS to the end of the page
    232232        // so they can be translated and still used.
    233         $params = array(
     233        $params = apply_filters( 'bp_core_get_js_strings', array(
    234234            'accepted'            => __( 'Accepted', 'buddypress' ),
    235235            'close'               => __( 'Close', 'buddypress' ),
     
    245245            'unsaved_changes'     => __( 'Your profile has unsaved changes. If you leave the page, the changes will be lost.', 'buddypress' ),
    246246            'view'                => __( 'View', 'buddypress' ),
    247         );
     247        ) );
    248248        wp_localize_script( $asset['handle'], 'BP_DTheme', $params );
    249249
     
    510510        $just_posted = wp_parse_id_list( $_POST['exclude_just_posted'] );
    511511        $qs[] = 'exclude=' . implode( ',', $just_posted );
     512    }
     513
     514    // to get newest activities
     515    if ( ! empty( $_POST['offset'] ) ) {
     516        $qs[] = 'offset=' . intval( $_POST['offset'] );
    512517    }
    513518
     
    650655 */
    651656function bp_legacy_theme_post_update() {
     657    $bp = buddypress();
     658
    652659    // Bail if not a POST action
    653660    if ( 'POST' !== strtoupper( $_SERVER['REQUEST_METHOD'] ) )
     
    678685        exit( '-1<div id="message" class="error"><p>' . __( 'There was a problem posting your update, please try again.', 'buddypress' ) . '</p></div>' );
    679686
    680     if ( bp_has_activities ( 'include=' . $activity_id ) ) {
     687    if ( ! empty( $_POST['offset'] ) && $last_id = absint( $_POST['offset'] ) ) {
     688        $activity_args = array( 'offset' => $last_id );
     689        $bp->activity->new_update_id = $activity_id;
     690        add_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
     691    } else {
     692        $activity_args = array( 'include' => $activity_id );
     693    }
     694
     695    if ( bp_has_activities ( $activity_args ) ) {
    681696        while ( bp_activities() ) {
    682697            bp_the_activity();
    683698            bp_get_template_part( 'activity/entry' );
    684699        }
     700    }
     701
     702    if ( ! empty( $last_id ) ) {
     703        remove_filter( 'bp_get_activity_css_class', 'bp_activity_newest_class', 10, 1 );
    685704    }
    686705
  • trunk/bp-templates/bp-legacy/css/buddypress.css

    r7949 r7952  
    291291    white-space: nowrap;
    292292}
    293 #buddypress .activity-list li.load-more {
     293#buddypress .activity-list li.load-more,
     294#buddypress .activity-list li.load-newest {
    294295    background: #f0f0f0;
    295296    font-size: 110%;
     
    298299    text-align: center;
    299300}
    300 #buddypress .activity-list li.load-more a {
     301#buddypress .activity-list li.load-more a,
     302#buddypress .activity-list li.load-newest a {
    301303    color: #4D4D4D;
    302304}
  • trunk/bp-templates/bp-legacy/js/buddypress.js

    r7949 r7952  
    44// Global variable to prevent multiple AJAX requests
    55var bp_ajax_request = null;
     6
     7// Global variables to temporarly store newest activities
     8var newest_activities = '';
     9var activity_last_id  = 0;
    610
    711jq(document).ready( function() {
     
    5357        if ( $whats_new_form.hasClass("submitted") ) {
    5458            $whats_new_form.removeClass("submitted");   
     59        }
     60
     61        // Return to the 'All Members' tab and 'Everything' filter,
     62        // to avoid inconsistencies with the heartbeat integration
     63        var $activity_all = jq( '#activity-all' );
     64        if ( $activity_all.length  ) {
     65            if ( ! $activity_all.hasClass( 'selected' ) ) {
     66                // reset to everyting
     67                jq( '#activity-filter-select select' ).val( '-1' );
     68                $activity_all.children( 'a' ).trigger( "click" );
     69            } else if ( '-1' != jq( '#activity-filter-select select' ).val() ) {
     70                jq( '#activity-filter-select select' ).val( '-1' );
     71                jq( '#activity-filter-select select' ).trigger( 'change' );
     72            }
    5573        }
    5674    });
     
    7492    /* New posts */
    7593    jq("#aw-whats-new-submit").on( 'click', function() {
     94        var last_displayed_id = 0;
    7695        var button = jq(this);
    7796        var form = button.closest("form#whats-new-form");
     
    92111        var item_id = jq("#whats-new-post-in").val();
    93112        var content = jq("#whats-new").val();
     113        var firstrow = jq( '#buddypress ul.activity-list li' ).first();
     114
     115        if ( firstrow.hasClass( 'load-newest' ) ) {
     116            last_displayed_id = firstrow.next().prop( 'id' ) ? firstrow.next().prop( 'id' ).replace( 'activity-','' ) : 0;
     117        } else {
     118            last_displayed_id = firstrow.prop( 'id' ) ? firstrow.prop( 'id' ).replace( 'activity-','' ) : 0;
     119        }
    94120
    95121        /* Set object for non-profile posts */
     
    105131            'object': object,
    106132            'item_id': item_id,
     133            'offset': last_displayed_id,
    107134            '_bp_as_nonce': jq('#_bp_as_nonce').val() || ''
    108135        },
     
    126153                }
    127154
     155                if ( firstrow.hasClass( 'load-newest' ) )
     156                    firstrow.remove();
     157
    128158                jq("#activity-stream").prepend(response);
    129                 jq("#activity-stream li:first").addClass('new-update just-posted');
     159
     160                if ( ! last_displayed_id )
     161                    jq("#activity-stream li:first").addClass('new-update just-posted');
    130162
    131163                if ( 0 != jq("#latest-update").length ) {
     
    150182                jq("li.new-update").removeClass( 'new-update' );
    151183                jq("#whats-new").val('');
     184
     185                // reset vars to get newest activities
     186                newest_activities = '';
     187                activity_last_id  = 0;
    152188            }
    153189
     
    288324                } else {
    289325                    li.slideUp(300);
     326
     327                    // reset vars to get newest activities
     328                    if ( activity_last_id == id ) {
     329                        newest_activities = '';
     330                        activity_last_id  = 0;
     331                    }
    290332                }
    291333            });
     
    312354                } else {
    313355                    li.slideUp( 300 );
     356                    // reset vars to get newest activities
     357                    if ( activity_last_id == id ) {
     358                        newest_activities = '';
     359                        activity_last_id  = 0;
     360                    }
    314361                }
    315362            });
     
    353400
    354401            return false;
     402        }
     403
     404        /* Load newest updates at the top of the list */
     405        if ( target.parent().hasClass('load-newest') ) {
     406
     407            event.preventDefault();
     408
     409            target.parent().hide();
     410            jq( '#buddypress ul.activity-list' ).prepend( newest_activities );
     411
     412            // reset the newest activities now they're displayed
     413            newest_activities = '';
    355414        }
    356415    });
     
    13791438    if( jq('body').hasClass('no-js') )
    13801439        jq('body').attr('class', jq('body').attr('class').replace( /no-js/,'js' ) );
    1381        
     1440
     1441    /** Activity HeartBeat ************************************************/
     1442
     1443    // Set the interval and the namespace event
     1444    if ( typeof wp != 'undefined' && typeof wp.heartbeat != 'undefined' && typeof BP_DTheme.pulse != 'undefined' ) {
     1445
     1446        wp.heartbeat.interval( Number( BP_DTheme.pulse ) );
     1447
     1448        jq.fn.extend({
     1449            'heartbeat-send': function() {
     1450            return this.bind( 'heartbeat-send.buddypress' );
     1451            },
     1452        });
     1453
     1454    }
     1455
     1456    // Set the last id to request after
     1457    jq( document ).on( 'heartbeat-send.buddypress', function( e, data ) {
     1458
     1459        // First row is default latest activity id
     1460        if ( jq( '#buddypress ul.activity-list li' ).first().prop( 'id' ) ) {
     1461            firstrow = jq( '#buddypress ul.activity-list li' ).first().prop( 'id' ).replace( 'activity-','' );
     1462        } else {
     1463            firstrow = 0;
     1464        }
     1465
     1466        if ( 0 == activity_last_id || Number( firstrow ) > activity_last_id )
     1467            activity_last_id = Number( firstrow );
     1468
     1469        data['bp_activity_last_id'] = activity_last_id;
     1470    });
     1471
     1472    // Increment newest_activities and activity_last_id if data has been returned
     1473    jq( document ).on( 'heartbeat-tick', function( e, data ) {
     1474
     1475        // Only proceed if we have newest activities
     1476        if ( ! data['bp_activity_newest_activities'] ) {
     1477            return;
     1478        }
     1479
     1480        newest_activities = data['bp_activity_newest_activities']['activities'] + newest_activities;
     1481        activity_last_id  = Number( data['bp_activity_newest_activities']['last_id'] );
     1482
     1483        if ( jq( '#buddypress ul.activity-list li' ).first().hasClass( 'load-newest' ) )
     1484            return;
     1485
     1486        jq( '#buddypress ul.activity-list' ).prepend( '<li class="load-newest"><a href="#newest">' + BP_DTheme.newest + '</a></li>' );
     1487    });
    13821488});
    13831489
Note: See TracChangeset for help on using the changeset viewer.