This version is a great step forward and I'm really looking forward to using it. However, there appears to be a pretty severe issue in the code that forces any view to only display activity from the current user. It ignores any arguments that you add. As far as I can tell, the culprit appears to be here:

/**
 * Implementation of hook_db_rewrite_sql().
 */
function activity_db_rewrite_sql($query, $primary_table, $primary_field, $args) {
  if ($primary_field == 'amid') {
    $return['join'] = "INNER JOIN {activity_access} aa ON " . $primary_table . "." . $primary_field . " = aa.amid";
    $return['where'] = _activity_access_sql("aa", $GLOBALS['user']);
    $return['distinct'] = 1;
    return $return;
  }

Since this is in hook_db_rewrite_sql, it always adds the WHERE clause for the current user, meaning that Views arguments aren't going to do much of anything.

Comments

sirkitree’s picture

Actually the where is for the explicit purpose of verifying that the current user has been granted access to the activity.

Here is the full SQL statement that is generated:

SELECT  DISTINCT(activity_messages.amid) AS amid,
   activity_messages.everyone_message AS activity_messages_everyone_message,
   activity_messages.author_message AS activity_messages_author_message,
   activity_messages.uid AS activity_messages_uid
 FROM {activity_messages} activity_messages 
     INNER JOIN {activity_access} aa ON activity_messages.amid = aa.amid WHERE ((aa.module = 'activity' AND aa.value IN (1)))

So when an activity is recorded, an entry is also recorded into the {activity_access} table for everyone who has access to this activity. Each person who has access will then be added to the WHERE statement's IN qualifier.

In order to expand upon what user are recorded as having access to this activity, we have the hook_activity_access_grants(). There is an example in the DEVELOPER.txt of how Flag Friend will implement such a hook.

    /**
     * Implementation of hook_activity_access_grants().
     */
    function flag_friend_activity_access_grants($account) {
      $friends = flag_friend_get_friends($account->uid);
      $realm_ids = array();
      if (!empty($friends)) {
        foreach ($friends as $friend) {
          $realm_ids[] = $friend;
        }
      }
      return $realm_ids;
    }

What this does is check to see if the $account has any friends and give them access and add them to the WHERE statement's IN qualifier. So if user 1 is friend with user 3, 6, and 9, the statement would look like this instead:

SELECT  DISTINCT(activity_messages.amid) AS amid,
   ...
 FROM {activity_messages} activity_messages 
     INNER JOIN {activity_access} aa ON activity_messages.amid = aa.amid WHERE ((aa.module = 'activity' AND aa.value IN (1,3,6,9)))

In order to record these ids whom have access in the first place, hook_activity_grants() is used.

Scott Reynolds’s picture

To piggy back on this, sirkitree is almost right :-D. The final query actually looks just a little different.

SELECT  DISTINCT(activity_messages.amid) AS amid,
   ...
FROM {activity_messages} activity_messages
     INNER JOIN {activity_access} aa ON activity_messages.amid = aa.amid WHERE ((aa.module = 'activity' AND aa.value IN (1)) OR (aa.module='flag_friend' AND aa.value IN (3,6,9))

So the activity module comes out of the box with an access control called 'activity' and all its values are the uid of the "Actor". Flag_friend provides an access control as well that says any activity from whom the current user is friends with hence the multiple values.

Something that I haven't brought up yet is whether or not we store and use the 'activity' access control. Seems redundant and useless but I wrote it orginally as an example. Probably needs to go so that the final query looks like

SELECT  DISTINCT(activity_messages.amid) AS amid,
   ...
FROM {activity_messages} activity_messages
     INNER JOIN {activity_access} aa ON activity_messages.amid = aa.amid WHERE (activity_messages.uid = 1) OR (aa.module='flag_friend' AND aa.value IN (3,6,9))

That way we don't store such a simple access implementation.

sirkitree’s picture

Component: Code » Documentation
Category: bug » support
Priority: Critical » Normal
sirkitree’s picture

Title: Views build on Activity only show activity from current user » hook_activity_grants() & hook_activity_access_grants()

Scott, maybe you can help explain hook_activity_grants() as well?

mrothroc’s picture

Thanks for the suggestion, but I still can't see how this works. Following your suggestion I created a new module that implements hook_activity_access_grants() but the behavior (and query) aren't as I would expect from what you've described above. I'm using User Relationships, so I created the following function:

function my_module_activity_access_grants($account) {

  $friends = (array) module_invoke_all('socnet_get_related_users', $account->uid);
  $friends = array_unique($friends);
  
  return $friends;
}

My function is being called as expected, but the resulting SQL is:

SELECT DISTINCT (
activity_messages.amid
) AS amid, activity_messages.everyone_message AS activity_messages_everyone_message, activity_messages.author_message AS activity_messages_author_message, activity_messages.uid AS activity_messages_uid
FROM activity_messages activity_messages
INNER JOIN activity_access aa ON activity_messages.amid = aa.amid
WHERE (
(
aa.module =  'activity'
AND aa.value
IN ( 1 )
)
OR (
aa.module =  'my_module'
AND aa.value
IN ( 1932 )
)
)

(This was run as user 1 who is friends with user 1932.)

Still, no output. I see that it's looking for access entries associated with my module. Does this mean that I also have to add an entry to that table as well? What happens if I want to provide access to historical activity messages when someone is added as a friend? Do I have to query all of the prior messages for that user and then add a corresponding entry in AA? If so, I'm not sure how well this will scale over the long run.

Scott Reynolds’s picture

What happens if I want to provide access to historical activity messages when someone is added as a friend? Do I have to query all of the prior messages for that user and then add a corresponding entry in AA?

No you don't have to query for prior messages. It works just like node_access system works. Which means it is just as hard to explain :-D but I will try.

Does this mean that I also have to add an entry to that table as well?

sortof. Really hard to explain without a concrete example. So I will do flag_friend, since I use that all the time

Lets start first with a copy and paste from the DEVELOPER.txt

hook_activity_grants($activity)
  Provides a means to record what should have access to any particular message.

  Parameters:
    $activity - an object that holds a full activity record from the database.

  Return value:
    A list of ids to be stored into the access_table with the above properties.
  
  Example:
    For instance, flag_friend returns a one element array of the creator of the 
    activity. 

    /**
     * Implementation of hook_activity_grants().
     */
    function flag_friend_activity_grants($activity) {
      return array(
        $activity->uid, // the module_id that will be used
      );
    }

This hook adds to the access table, module = 'flag_friend', value = 'THE ACTOR'. This will be used later in the following hook. This will be used later in the query. This is what you are missing in your example. The activity module has no record of 'my_module' providing access control on this particular activity.

I think of this hook as saying "Activity module, I am providing access control for this activity. Use the following ids as the value column"

hook_activity_access_grants($account)
  Proivde a means for other modules to determine who can have access to any
  given activity message.

  Parameters:
    $account - the account of the message that we're determining access for.

  Return value:
    The Ids for the module that the user will have access too.

  Example:
    /**
     * Implementation of hook_activity_access_grants().
     */
    function flag_friend_activity_access_grants($acccount) {
      $friends = flag_friend_get_friends($account->uid);
      $realm_ids = array();
      if (!empty($friends)) {
        foreach ($friends as $friend) {
          $realm_ids[] = $friend;
        }
      }
      return $realm_ids;
    }

This says to activity, "Allow access to any activity message that is created by a friend of $account". This is what adds the (module = 'flag_friend' AND value IN (explode($realm_ids)).

What this means is that if Sirkitree creates a node, but were not friends, I don't have access to it. But we become friends later, and then I have access to it. And all that takes for storage is one row in the activity_access table:

amid = ACTIVITY_MESSAGE_ID, module = 'flag_friend', value = UID_OF_SIRKITREE

And if we no longer are friends, then I will not have access to those. All with one row in a table.

It models exactly node_access system and you must implement both hooks. If you module implements hook_activity_access_grants AFTER the activity is recorded you won't have access to it as the activity module, at the time of the activity's creation, didn't know about you.

The node access system provides a way to rebuild access control. This too needs to be explored. So that we can resync access control and so that people arn't so confused.

Sry, hope this all makes sense. I promise it works and is more efficient then your thinking.

mrothroc’s picture

It took some time but I think I'm starting to understand the mechanism. (Right now, I have working code, but without complete understanding it still seems like it's powered by a little bit of magic.)

Thanks for the explanation!

vermario’s picture

Hi!

this issue seems related to what I am currently trying to achieve (with no luck): getting all the activity that a user and his friends (through user relationship api) have generated.

What is the current way to do this? Mrothroc, would you post the code which got this to work? Which kind of view would one build to accomplish this?
Any info would be great, i'm currently very stuck with this in my project. Thank you! :)

Scott Reynolds’s picture

vermario, that is way off topic. And a cursorily search of user relationships issue queue would have found you this.

#445698: Default view for activity2

vermario’s picture

Ouch! I read that issue many times, but I thought it was only to get "User A is now friend with user B". It turns out it should do what I need... I'll try again. Sorry for the inconvenience.

sirkitree’s picture

Status: Active » Closed (fixed)

This looks to be fixed to me.