Skip to:
Content

BuddyPress.org


Ignore:
Timestamp:
11/27/2014 05:13:29 PM (7 years ago)
Author:
imath
Message:

Post Type Activity Tracking feature

So far the tracking feature was requiring the Blogs component to be active. In 2.2 we are centralizing the majority of the tracking code into the Activity component. A new set of functions and hooks has been created to catch public post types supporting the feature 'buddypress-activity' and to automatically generate an activity when a new item is publicly published.

It's now possible to add this support to any public post type using one unique line of code, eg: add_post_type_support( 'page', 'buddypress-activity' ). In this case BuddyPress will use generic activity attributes.
Each activity attribute of the supported post type can be customized using a specific function (eg: set custom strings to describe the post type activity action).

When registering a post type in WordPress it's also possible to set the 'buddypress-activity' feature using the support parameter of the second argument of the register_post_type() function. Custom activity action strings can be defined within the labels parameter and activity attributes can be set using the new parameter 'bp_activity'.

When the Blogs component is active, the 'post' post type is automatically supporting the 'buddypress-activity' feature. The conditional logic (eg: blog_public option set to 1 ...) that occurs before a new activity is posted and the comments tracking remain unchanged.

props boonebgorges, DJPaul

Fixes #5669

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/bp-activity/bp-activity-functions.php

    r9155 r9194  
    317317 * @since BuddyPress (1.1.0)
    318318 *
    319  * @param string $component_id The unique string ID of the component.
    320  * @param string $type The action type.
    321  * @param string $description The action description.
    322  * @param callable $format_callback Callback for formatting the action string.
    323  * @param string $label String to describe this action in the activity stream
    324  *        filter dropdown.
    325  * @param array $context Activity stream contexts where the filter should appear.
    326  *        'activity', 'member', 'member_groups', 'group'
     319 * @param  string   $component_id    The unique string ID of the component.
     320 * @param  string   $type            The action type.
     321 * @param  string   $description    The action description.
     322 * @param  callable $format_callback Callback for formatting the action string.
     323 * @param  string   $label           String to describe this action in the activity stream filter dropdown.
     324 * @param  array    $context         Optional. Activity stream contexts where the filter should appear.
     325 *                                   Values: 'activity', 'member', 'member_groups', 'group'
     326 * @param  int      $position        Optional. The position of the action when listed in dropdowns.
    327327 * @return bool False if any param is empty, otherwise true.
    328328 */
    329 function bp_activity_set_action( $component_id, $type, $description, $format_callback = false, $label = false, $context = array() ) {
     329function bp_activity_set_action( $component_id, $type, $description, $format_callback = false, $label = false, $context = array(), $position = 0 ) {
    330330    $bp = buddypress();
    331331
     
    369369        'label'           => $label,
    370370        'context'         => $context,
     371        'position'        => $position,
    371372    ), $component_id, $type, $description, $format_callback, $label, $context );
    372373
    373374    return true;
     375}
     376
     377/**
     378 * Set tracking arguments for a given post type.
     379 *
     380 * @since BuddyPress (2.2.0)
     381 *
     382 * @global $wp_post_types
     383 *
     384 * @param string $post_type The name of the post type, as registered with WordPress. Eg 'post' or 'page'.
     385 * @param array  $args {
     386 *     An associative array of tracking parameters. All items are optional.
     387 *
     388 *     @type string   $bp_activity_admin_filter String to use in the Dashboard > Activity dropdown.
     389 *     @type string   $bp_activity_front_filter String to use in frontend dropdown.
     390 *     @type string   $bp_activity_new_post     String format to use for generating the activity action. Should be a
     391 *                                              translatable string where %1$s is replaced by a user link and %2$s is
     392 *                                              the URL of the newly created post.
     393 *     @type string   $bp_activity_new_post_ms  String format to use for generating the activity action on Multisite.
     394 *                                              Should be a translatable string where %1$s is replaced by a user link,
     395 *                                              %2$s is the URL of the newly created post, and %3$s is a link to
     396 *                                              the site.
     397 *     @type string   $component_id             ID of the BuddyPress component to associate the activity item.
     398 *     @type string   $action_id                Value for the 'type' param of the new activity item.
     399 *     @type callable $format_callback          Callback for formatting the activity action string.
     400 *                                              Default: 'bp_activity_format_activity_action_custom_post_type_post'.
     401 *     @type array    $contexts                 The directory contexts in which the filter will show.
     402 *                                              Default: array( 'activity' ),
     403 *     @type array    $position                 Position of the item in filter dropdowns.
     404 *     @type string   $singular                 Singular, translatable name of the post type item. If no value is
     405 *                                              provided, it's pulled from the 'singular_name' of the post type.
     406 *     @type bool     $activity_comment         Whether to allow comments on the activity items. Defaults to true if
     407 *                                              the post type does not natively support comments, otherwise false.
     408 * }
     409 */
     410function bp_activity_set_post_type_tracking_args( $post_type = '', $args = array() ) {
     411    global $wp_post_types;
     412    $bp = buddypress();
     413
     414    if ( empty( $wp_post_types[ $post_type ] ) || ! post_type_supports( $post_type, 'buddypress-activity' ) || ! is_array( $args ) ) {
     415        return false;
     416    }
     417
     418    // Labels are loaded into the post type object.
     419    foreach ( array( 'bp_activity_admin_filter', 'bp_activity_front_filter', 'bp_activity_new_post', 'bp_activity_new_post_ms' ) as $label_type ) {
     420        if ( ! empty( $args[ $label_type ] ) ) {
     421            $wp_post_types[ $post_type ]->labels->{$label_type} = $args[ $label_type ];
     422            unset( $args[ $post_type ] );
     423        }
     424    }
     425
     426    // If there are any additional args, put them in the bp_activity attribute of the post type.
     427    if ( ! empty( $args ) ) {
     428        $wp_post_types[ $post_type ]->bp_activity = $args;
     429    }
     430}
     431
     432/**
     433 * Get tracking arguments for a specific post type.
     434 *
     435 * @since BuddyPress (2.2.0)
     436 *
     437 * @param  string $post_type Name of the post type
     438 * @return object The tracking arguments of the post type.
     439 */
     440function bp_activity_get_post_type_tracking_args( $post_type ) {
     441    if ( ! post_type_supports( $post_type, 'buddypress-activity' ) ) {
     442        return false;
     443    }
     444
     445    $post_type_object = get_post_type_object( $post_type );
     446
     447    $post_type_activity = array(
     448        'component_id'      => buddypress()->activity->id,
     449        'action_id'         => 'new_' . $post_type,
     450        'format_callback'   => 'bp_activity_format_activity_action_custom_post_type_post',
     451        'front_filter'      => $post_type_object->labels->name,
     452        'contexts'          => array( 'activity' ),
     453        'position'          => 0,
     454        'singular'          => strtolower( $post_type_object->labels->singular_name ),
     455        'activity_comment' => ! post_type_supports( $post_type, 'comments' ),
     456    );
     457
     458    if ( ! empty( $post_type_object->bp_activity ) ) {
     459        $post_type_activity = bp_parse_args( (array) $post_type_object->bp_activity, $post_type_activity, $post_type . '_tracking_args' );
     460    }
     461
     462    $post_type_activity = (object) $post_type_activity;
     463
     464    // Try to get the admin filter from the post type labels.
     465    if ( ! empty( $post_type_object->labels->bp_activity_admin_filter ) ) {
     466        $post_type_activity->admin_filter = $post_type_object->labels->bp_activity_admin_filter;
     467
     468    // Fall back to a generic name.
     469    } else {
     470        $post_type_activity->admin_filter = _x( 'New item published', 'Post Type generic activity post admin filter', 'buddypress' );
     471    }
     472
     473    // Check for the front filter in the post type labels.
     474    if ( ! empty( $post_type_object->labels->bp_activity_front_filter ) ) {
     475        $post_type_activity->front_filter = $post_type_object->labels->bp_activity_front_filter;
     476    }
     477
     478    // Try to get the action for new post type action on non-multisite installations.
     479    if ( ! empty( $post_type_object->labels->bp_activity_new_post ) ) {
     480        $post_type_activity->new_post_type_action = $post_type_object->labels->bp_activity_new_post;
     481    }
     482
     483    // Try to get the action for new post type action on multisite installations.
     484    if ( ! empty( $post_type_object->labels->bp_activity_new_post_ms ) ) {
     485        $post_type_activity->new_post_type_action_ms = $post_type_object->labels->bp_activity_new_post_ms;
     486    }
     487
     488    return apply_filters( 'bp_activity_get_post_type_tracking_args', $post_type_activity, $post_type );
     489}
     490
     491/**
     492 * Get tracking arguments for all post types.
     493 *
     494 * @since BuddyPress (2.2.0)
     495 *
     496 * @return array List of post types with their tracking arguments.
     497 */
     498function bp_activity_get_post_types_tracking_args() {
     499    // Fetch all public post types
     500    $post_types = get_post_types( array( 'public' => true ), 'names' );
     501
     502    $post_types_tracking_args = array();
     503
     504    foreach ( $post_types as $post_type ) {
     505        $track_post_type = bp_activity_get_post_type_tracking_args( $post_type );
     506
     507        if ( ! empty( $track_post_type ) ) {
     508            $post_types_tracking_args[ $track_post_type->action_id ] = $track_post_type;
     509        }
     510
     511    }
     512
     513    return apply_filters( 'bp_activity_get_post_types_tracking_args', $post_types_tracking_args );
     514}
     515
     516/**
     517 * Get all components' activity actions, sorted by their position attribute.
     518 *
     519 * @since BuddyPress (2.2.0)
     520 *
     521 * @return object actions ordered by their position
     522 */
     523function bp_activity_get_actions() {
     524    $bp = buddypress();
     525
     526    $post_types = bp_activity_get_post_types_tracking_args();
     527
     528    // Create the actions for the post types, if they haven't already been created.
     529    if ( ! empty( $post_types ) ) {
     530        foreach ( $post_types as $post_type ) {
     531            if ( isset( $bp->activity->actions->{$post_type->component_id}->{$post_type->action_id} ) ) {
     532                continue;
     533            }
     534
     535            bp_activity_set_action(
     536                $post_type->component_id,
     537                $post_type->action_id,
     538                $post_type->admin_filter,
     539                $post_type->format_callback,
     540                $post_type->front_filter,
     541                $post_type->contexts,
     542                $post_type->position
     543            );
     544        }
     545    }
     546
     547    // Sort the actions by their position within each component.
     548    foreach ( $bp->activity->actions as $component => $actions ) {
     549        $actions = (array) $actions;
     550        $temp = bp_sort_by_key( $actions, 'position', 'num' );
     551
     552        // Restore keys.
     553        $bp->activity->actions->{$component} = new stdClass;
     554        foreach ( $temp as $key_ordered ) {
     555            $bp->activity->actions->{$component}->{$key_ordered['key']} = $key_ordered;
     556        }
     557    }
     558
     559    return $bp->activity->actions;
    374560}
    375561
     
    392578    }
    393579
    394     $bp     = buddypress();
    395     $retval = isset( $bp->activity->actions->{$component_id}->{$key} )
    396         ? $bp->activity->actions->{$component_id}->{$key}
    397         : false;
     580    $bp      = buddypress();
     581    $actions = bp_activity_get_actions();
     582
     583    $retval = false;
     584    if ( isset( $actions->{$component_id}->{$key} ) ) {
     585        $retval = $actions->{$component_id}->{$key};
     586    }
    398587
    399588    /**
     
    402591     * @since BuddyPress (1.1.0)
    403592     *
    404      * @param string|bool $retval The action key.
     593     * @param string|bool $retval       The action key.
    405594     * @param string      $component_id The unique string ID of the component.
    406      * @param string      $key The action key.
     595     * @param string      $key          The action key.
    407596     */
    408597    return apply_filters( 'bp_activity_get_action', $retval, $component_id, $key );
     
    420609
    421610    // Walk through the registered actions, and build an array of actions/values.
    422     foreach ( buddypress()->activity->actions as $action ) {
     611    foreach ( bp_activity_get_actions() as $action ) {
    423612        $action = array_values( (array) $action );
    424613
     
    11291318     */
    11301319    return apply_filters( 'bp_activity_comment_action', $action, $activity );
     1320}
     1321
     1322/**
     1323 * Format activity action strings for custom post types.
     1324 *
     1325 * @since BuddyPress (2.2.0)
     1326 *
     1327 * @param string $action   Static activity action.
     1328 * @param object $activity Activity data object.
     1329 * @return string
     1330 */
     1331function bp_activity_format_activity_action_custom_post_type_post( $action, $activity ) {
     1332    $bp = buddypress();
     1333
     1334    // Fetch all the tracked post types once.
     1335    if ( empty( $bp->activity->track ) ) {
     1336        $bp->activity->track = bp_activity_get_post_types_tracking_args();
     1337    }
     1338
     1339    if ( empty( $activity->type ) || empty( $bp->activity->track[ $activity->type ] ) ) {
     1340        return $action;
     1341    }
     1342
     1343    $user_link = bp_core_get_userlink( $activity->user_id );
     1344    $blog_url  = get_home_url( $activity->item_id );
     1345
     1346    if ( empty( $activity->post_url ) ) {
     1347        $post_url = add_query_arg( 'p', $activity->secondary_item_id, trailingslashit( $blog_url ) );
     1348    } else {
     1349        $post_url = $activity->post_url;
     1350    }
     1351
     1352    if ( is_multisite() ) {
     1353        $blog_link = '<a href="' . $blog_url . '">' . get_blog_option( $activity->item_id, 'blogname' ) . '</a>';
     1354
     1355        if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_action_ms ) ) {
     1356            $action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_action_ms, $user_link, $post_url, $blog_link );
     1357        } else {
     1358            $action = sprintf( _x( '%1$s wrote a new <a href="%2$s">item</a>, on the site %3$s', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_url, $blog_link );
     1359        }
     1360    } else {
     1361        if ( ! empty( $bp->activity->track[ $activity->type ]->new_post_type_action ) ) {
     1362            $action = sprintf( $bp->activity->track[ $activity->type ]->new_post_type_action, $user_link, $post_url );
     1363        } else {
     1364            $action = sprintf( _x( '%1$s wrote a new <a href="%2$s">item</a>', 'Activity Custom Post Type post action', 'buddypress' ), $user_link, $post_url );
     1365        }
     1366    }
     1367
     1368    /**
     1369     * Filters the formatted custom post type activity post action string.
     1370     *
     1371     * @since BuddyPress (2.2.0)
     1372     *
     1373     * @param string               $action Activity action string value.
     1374     * @param BP_Activity_Activity $activity Activity item object.
     1375     */
     1376    return apply_filters( 'bp_activity_custom_post_type_post_action', $action, $activity );
    11311377}
    11321378
     
    15141760
    15151761/**
     1762 * Create an activity item for a newly published post type post.
     1763 *
     1764 * @since BuddyPress (2.2.0)
     1765 *
     1766 * @param  int      $post_id ID of the new post.
     1767 * @param  WP_Post  $post    Post object.
     1768 * @param  int      $user_id ID of the post author.
     1769 * @return int|bool The ID of the activity on success. False on error.
     1770 */
     1771function bp_activity_post_type_publish( $post_id = 0, $post = null, $user_id = 0 ) {
     1772    $bp = buddypress();
     1773
     1774    if ( ! is_a( $post, 'WP_Post' ) ) {
     1775        return;
     1776    }
     1777
     1778    // Get the post type tracking args.
     1779    $activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
     1780
     1781    if ( 'publish' != $post->post_status || ! empty( $post->post_password ) || empty( $activity_post_object->action_id ) ) {
     1782        return;
     1783    }
     1784
     1785    if ( empty( $post_id ) ) {
     1786        $post_id = $post->ID;
     1787    }
     1788
     1789    $blog_id = get_current_blog_id();
     1790
     1791    if ( empty( $user_id ) ) {
     1792        $user_id = (int) $post->post_author;
     1793    }
     1794
     1795    // Bail if an activity item already exists for this post.
     1796    $existing = bp_activity_get( array(
     1797        'filter' => array(
     1798            'action'       => $activity_post_object->action_id,
     1799            'primary_id'   => $blog_id,
     1800            'secondary_id' => $post_id,
     1801        )
     1802    ) );
     1803
     1804    if ( ! empty( $existing['activities'] ) ) {
     1805        return;
     1806    }
     1807
     1808    // Let components/plugins bail before the activity is posted.
     1809    if ( false === apply_filters( "bp_activity_{$post->post_type}_pre_publish", true, $blog_id, $post_id, $user_id ) ) {
     1810        return;
     1811    }
     1812
     1813    // Record this in activity streams.
     1814    $blog_url = get_home_url( $blog_id );
     1815    $post_url = add_query_arg(
     1816        'p',
     1817        $post_id,
     1818        trailingslashit( $blog_url )
     1819    );
     1820
     1821    // Backward compatibility filters for the 'blogs' component.
     1822    if ( 'blogs' == $activity_post_object->component_id )  {
     1823        $activity_content      = apply_filters( 'bp_blogs_activity_new_post_content', $post->post_content, $post, $post_url, $post->post_type );
     1824        $activity_primary_link = apply_filters( 'bp_blogs_activity_new_post_primary_link', $post_url, $post_id, $post->post_type );
     1825    } else {
     1826        $activity_content      = $post->post_content;
     1827        $activity_primary_link = $post_url;
     1828    }
     1829
     1830    $activity_args = array(
     1831        'user_id'           => $user_id,
     1832        'content'           => $activity_content,
     1833        'primary_link'      => $activity_primary_link,
     1834        'component'         => $activity_post_object->component_id,
     1835        'type'              => $activity_post_object->action_id,
     1836        'item_id'           => $blog_id,
     1837        'secondary_item_id' => $post_id,
     1838        'recorded_time'     => $post->post_date_gmt,
     1839    );
     1840
     1841    // Remove large images and replace them with just one image thumbnail.
     1842    if ( ! empty( $activity_args['content'] ) ) {
     1843        $activity_args['content'] = bp_activity_thumbnail_content_images( $activity_args['content'], $activity_args['primary_link'], $activity_args );
     1844    }
     1845
     1846    if ( ! empty( $activity_args['content'] ) ) {
     1847        // Create the excerpt.
     1848        $activity_excerpt = bp_create_excerpt( $activity_args['content'] );
     1849
     1850        // Backward compatibility filter for blog posts.
     1851        if ( 'blogs' == $activity_post_object->component_id )  {
     1852            $activity_args['content'] = apply_filters( 'bp_blogs_record_activity_content', $activity_excerpt, $activity_args['content'], $activity_args, $post->post_type );
     1853        } else {
     1854            $activity_args['content'] = $activity_excerpt;
     1855        }
     1856    }
     1857
     1858    // Set up the action by using the format functions.
     1859    $action_args = array_merge( $activity_args, array(
     1860        'post_title' => $post->post_title,
     1861        'post_url'   => $post_url,
     1862    ) );
     1863
     1864    $activity_args['action'] = call_user_func_array( $activity_post_object->format_callback, array( '', (object) $action_args ) );
     1865
     1866    // Make sure the action is set.
     1867    if ( empty( $activity_args['action'] ) ) {
     1868        return;
     1869    } else {
     1870        // Backward compatibility filter for the blogs component.
     1871        if ( 'blogs' == $activity_post_object->component_id )  {
     1872            $activity_args['action'] = apply_filters( 'bp_blogs_record_activity_action', $activity_args['action'] );
     1873        }
     1874    }
     1875
     1876    $activity_id = bp_activity_add( $activity_args );
     1877
     1878    do_action( 'bp_activity_post_type_published', $activity_id, $post, $activity_args );
     1879
     1880    return $activity_id;
     1881}
     1882
     1883/**
     1884 * Update the activity item for a custom post type entry.
     1885 *
     1886 * @since BuddyPress (2.2.0)
     1887 *
     1888 * @param  WP_Post $post Post item.
     1889 * @return bool    True on success, false on failure.
     1890 */
     1891function bp_activity_post_type_update( $post = null ) {
     1892    $bp = buddypress();
     1893
     1894    if ( ! is_a( $post, 'WP_Post' ) ) {
     1895        return;
     1896    }
     1897
     1898    // Get the post type tracking args.
     1899    $activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
     1900
     1901    if ( empty( $activity_post_object->action_id ) ) {
     1902        return;
     1903    }
     1904
     1905    $activity_id = bp_activity_get_activity_id( array(
     1906        'component'         => $activity_post_object->component_id,
     1907        'item_id'           => get_current_blog_id(),
     1908        'secondary_item_id' => $post->ID,
     1909        'type'              => $activity_post_object->action_id,
     1910    ) );
     1911
     1912    // Activity ID doesn't exist, so stop!
     1913    if ( empty( $activity_id ) ) {
     1914        return;
     1915    }
     1916
     1917    // Delete the activity if the post was updated with a password.
     1918    if ( ! empty( $post->post_password ) ) {
     1919        bp_activity_delete( array( 'id' => $activity_id ) );
     1920    }
     1921
     1922    // Update the activity entry.
     1923    $activity = new BP_Activity_Activity( $activity_id );
     1924
     1925    if ( ! empty( $post->post_content ) ) {
     1926        // Make sure to update the thumbnail image.
     1927        $post_content = bp_activity_thumbnail_content_images( $post->post_content, $activity->primary_link, (array) $activity );
     1928
     1929        // Generate an excerpt.
     1930        $activity_excerpt = bp_create_excerpt( $post_content );
     1931
     1932        // Backward compatibility filter for the blogs component.
     1933        if ( 'blogs' == $activity_post_object->component_id ) {
     1934            $activity->content = apply_filters( 'bp_blogs_record_activity_content', $activity_excerpt, $post_content, (array) $activity, $post->post_type );
     1935        } else {
     1936            $activity->content = $activity_excerpt;
     1937        }
     1938    }
     1939
     1940    // Save the updated activity.
     1941    $updated = $activity->save();
     1942
     1943    do_action( 'bp_activity_post_type_updated', $post, $activity );
     1944
     1945    return $updated;
     1946}
     1947
     1948/**
     1949 * Unpublish an activity for the custom post type.
     1950 *
     1951 * @since BuddyPress (2.2.0)
     1952 *
     1953 * @param  int     $post_id ID of the post being unpublished.
     1954 * @param  WP_Post $post    Post object.
     1955 * @return bool    True on success, false on failure.
     1956 */
     1957function bp_activity_post_type_unpublish( $post_id = 0, $post = null ) {
     1958    $bp = buddypress();
     1959
     1960    if ( ! is_a( $post, 'WP_Post' ) ) {
     1961        return;
     1962    }
     1963
     1964    // Get the post type tracking args
     1965    $activity_post_object = bp_activity_get_post_type_tracking_args( $post->post_type );
     1966
     1967    if ( empty( $activity_post_object->action_id ) ) {
     1968        return;
     1969    }
     1970
     1971    if ( empty( $post_id ) ) {
     1972        $post_id = $post->ID;
     1973    }
     1974
     1975    $delete_activity_args = array(
     1976        'item_id'           => get_current_blog_id(),
     1977        'secondary_item_id' => $post_id,
     1978        'component'         => $activity_post_object->component_id,
     1979        'type'              => $activity_post_object->action_id,
     1980        'user_id'           => false,
     1981    );
     1982
     1983    $deleted = bp_activity_delete_by_item_id( $delete_activity_args );
     1984
     1985    do_action( 'bp_activity_post_type_unpublished', $delete_activity_args, $post, $deleted );
     1986
     1987    return $deleted;
     1988}
     1989
     1990/**
    15161991 * Add an activity comment.
    15171992 *
     
    19892464 */
    19902465function bp_activity_get_permalink( $activity_id, $activity_obj = false ) {
     2466    $bp = buddypress();
    19912467
    19922468    if ( empty( $activity_obj ) ) {
     
    19982474    }
    19992475
    2000     if ( 'new_blog_post' == $activity_obj->type || 'new_blog_comment' == $activity_obj->type || 'new_forum_topic' == $activity_obj->type || 'new_forum_post' == $activity_obj->type ) {
     2476    $use_primary_links = array(
     2477        'new_blog_post',
     2478        'new_blog_comment',
     2479        'new_forum_topic',
     2480        'new_forum_post',
     2481    );
     2482
     2483    if ( ! empty( $bp->activity->track ) ) {
     2484        $use_primary_links = array_merge( $use_primary_links, array_keys( $bp->activity->track ) );
     2485    }
     2486
     2487    if ( false !== array_search( $activity_obj->type, $use_primary_links ) ) {
    20012488        $link = $activity_obj->primary_link;
    20022489    } else {
Note: See TracChangeset for help on using the changeset viewer.