feature: block for "people you might know"

zilla - March 26, 2008 - 23:43
Project:User Relationships
Version:6.x-1.x-dev
Component:Code
Category:feature request
Priority:normal
Assigned:Unassigned
Status:patch (code needs review)
Description

okay, this is the trend du jour - linkedin has been doing it for a while, facebook just started doing it...basic idea:

User A has friends E, D, H
User B has friend C, F
User E has friends A, B, H, M

User A looks at his friends and sees B E and D, looks at User E and sees friends in common (H and B) but when viewing that same profile (or content created by any 'friend' he also sees a BLOCK that displays 2nd degree connections through "all user A friends" (E, D, H) as "people you might know" (in this case it would include B, M, C, F)

it's simply running all users against %user's friend list, removing existing friends and displaying only those connected by one degree from a current friend (not already on %user friend list)

it's linear social networking, not really dynamic, even though it may feel dynamic.

#1

zilla - March 26, 2008 - 23:45

sorry, that very last line of 3rd paragraph: user A sees a block saying 'people you might know: user B and User M (not C or F, those are one more node out)

#2

sprsquish - April 21, 2008 - 00:20
Version:6.x-1.0-beta6» 6.x-1.x-dev
Status:active» active (needs more info)

Moving this to the more general -dev version.

I think more discussion will need to go in to this feature. For instance, how will relationship types weigh on how users know each other. I'm working on a site that uses UR for two differing relational ideas: friends and subscribers. Just cause someone is a subscriber doesn't mean they know each other in the way that I think you're talking about. So how do we provide the option to either forgo or at least devalue that relationship?

#3

zilla - April 21, 2008 - 01:43

interesting point. perhaps a top level stratification - when a user defines a "relationship type" it is either "intimate" or "removed" - in this case, a group is created like "family" or "friends" and a second level option is to note that the group type is "intimate" - user creates another group called "reviewers" (for a text for example) or "subscibers" (for a periodical, etc) and indicates "removed" (perhaps the wrong term for what i mean, but just not 'intimate' - at which point these other relationships (distal and on the connected nodes, etc) are either pegged to "all" or "intimate only" (by admin)

#4

sprsquish - April 21, 2008 - 14:54

As I think about this, I wonder if the Facebook approach might be more effective.

Users are given a list of people they may know because X number of their friends know them.

Alice: Bob, Cliff, Daniel
Bob: Alice, Frank, Gabe, Cliff
Cliff: Alice, Frank, Daniel
Daniel: Harold, Alice, Frank

Alice is friends with Bob, Cliff, and Daniel and all three of them are friends with Frank so it's likely Alice also knows Frank. Bob knows Alice and Cliff who both know Daniel, but the admin has required 3 connections before considering a match so the system wont display Daniel as a possible connection to Bob.

Relationship types can then be considered a weight. So if a set of connections are all of the same relationship type the number of needed connections would be lower than if they were all of a different type.

Alice:
Friends: Bob, Cliff
Coworkers: Daniel

Bob:
Friends: Alice, Cliff
Coworkers: Gabe, Frank

Cliff:
Friends: Alice, Bob
Coworkers: Daniel

Daniel:
Friends: Harold
Coworkers: Alice, Cliff, Frank

Here Daniel might know Bob through Alice and Cliff but because they're her Coworkers and Bob is a Friend of Alice and Cliff there's a lower likelihood of that connection than if Daniel was a Friend of Alice and Cliff.

We'd give the admin the ability to tune those weights. They could decided that 4 connections of the same type should be considered a possible match while 10 connections of differing types might be a match.

#5

zilla - April 22, 2008 - 02:00

good point - using a simple 1 degree with 2 or more points of intersection is easy and flexible - and giving admin controls might or might not be good - unfortunately most people don't really get into social network analysis and so may tend toward lower numbers in the hopes of 'growing a network' at the sacrifice of quality or integrity in implied relationships...but perhaps a simple ruleset exactly like what you describe: if user A has two or more friends (adjustable by admin, but no lower than 2 or 3) and those 2 friends have at least 1 (or more) friends in common - but not on user A list - then suggest to them...

#6

ajayg - April 22, 2008 - 15:20

I think the purpose of this block is mainly for discoverability and not really deciding the strength of relationship a user need to establish with new users. That has to be determined by each user. e..g Alice just need to discover Bob has some relationship with Frank. What relationship Alice establishes with Frank is her decision and not based on the "type" of relationship between Bob and Frank. It might influence but is not the deciding factor.

So I think the weight etc is little overkill, it is nice to have but not necessary. All the block is saying " you might know" so if you find someone you already know, it is easier to add them to your network with whatver "level" of friendship you want to establish.

#7

zilla - April 22, 2008 - 19:29

i hear you - and i suppose the overarching theme here is that aside from popsugar, fastcompany a handful of other sites, most users won't be dealing with hundreds of thousands or millions of users ;)

#8

sprsquish - April 22, 2008 - 20:24

You may be right. I'm probably getting ahead of myself here.

I do think we need a way for admins to dissociate specific types from the "you may know" list. Going back to my previous example, when "subscriber" and "friend" are both types "subscribers" should not be included in the list.

@ajayg The example above wasn't meant to limit possible friends to a specific type when they decide to actually become friends. It was only meant to say that groups of relationships under the same type should hold more weight than relationships crossing over types.

So... anyone know anything about social graphs and how to represent them in an RDBMS?

#9

ajayg - April 22, 2008 - 20:49

I agree certain types of relationships should be disassociated with this block. One prime example would be "one way" relations where you are "fan" of someone doesn't make you a real friend of whoever you are admiring. Just felt weight etc is little overkills.
One way would be just give a list of roles (types) this will search on and admin can choose certain roles that would be used to
create the query, ignoring rest.

More Trees & Hierarchies in SQL
http://www.sqlteam.com/article/more-trees-hierarchies-in-sql

Specially look at the lineage concept at the end. These ideas depend on data manipulation at the time of storage because if you don't do at storage, compleletely doing a lookup is not scalable for larger data sets.

I think this will be great addition to UR because
1. It is done at the time of insertion of records so no issue with scalability
2. Provides a shortest path
3. Will allow to easily implement the above block.

#10

sprsquish - April 22, 2008 - 22:03

So my only issue with that site is that it's talking about Trees and Hierarchies. Those'll work fine for familial or organizational relationships, but friends are more of a web. Any tips for representing that?

#11

ajayg - April 23, 2008 - 01:53

Think of this as multiple hierarchies. Each user has his/her own tree starting from him/her and he/she is part of someone elses tree. At some level say beyond 4 or 5 you become part of a big web. But In real life relationship beyond 4 th level are seldome useful for meaninful reference. I am thinking of model used by a very popular social networking site linkedin.com. They used to have 5 levels and reduced to 4 levels.

So you already have implmented a single root user to whom everybody is connected. So any user can have a lineage
/root/level1/level2/level3/level4 I haven't done the math but I suspect this itself can represent a billion users. So for all users in your tree (below your level) you go and find just how many people they are connected.

If the lineage is represented as a string as mentioned in the algorithm, One simple search shortcut would be (and I haven't thought through all implications) search for records which match say /root/level1/level2/*/level4/*
* is wild card. First * is level you are at etc

I must say I haven't completely thought through yet just writing as I am thinking openly.

#12

sprsquish - April 23, 2008 - 07:47

I'm opposed to misrepresenting data. In this case the data isn't a hierarchy unless it is. It's really a graph and I think should be thought of and manipulated as such.

I found some interesting ideas here: http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html#nodes_...

The algorithms will need to be modified so that an update wont trigger an entire rewrite of the routes table. It's very late and my brain is very fried so I may need to go over all this info again tomorrow.

#13

IceCreamYou - June 19, 2008 - 18:34

I've been trying to do this without a module, just by creating a custom block; I think it can be done without creating extra tables.

[EDIT: I removed the rest of this post to make this page clearer, because there was a lot of (wrong) code.]

#14

IceCreamYou - June 19, 2008 - 05:30

Alright, that code was a little messy and none of it worked. Here's the first part, working, which displays a list of users with whom the current user and the user whose profile the current user is viewing have in common. I hard-coded the type of relationship in here: "Friends," and rtid = 2. I also hard-coded the website URL because I don't know enough about how to get it dynamically.

I'm working on the rest of it.

<?php
global $user;
$cur_user = $user->uid;
$view_user = arg(1); //assuming this block only shows up on user pages

echo "<strong>Friends in common:</strong>";
$view_user_rel = db_query("
SELECT u.name, u.uid FROM
  (SELECT u.name, u.uid FROM {users} AS u
  LEFT JOIN {user_relationships} AS ur ON (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
  WHERE (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
    AND (ur.requester_id = $cur_user OR ur.requestee_id = $cur_user)
    AND (ur.approved = 1)
    AND (ur.rtid = 2)
  GROUP BY (u.name))
AS u
LEFT JOIN {user_relationships} AS ur ON (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
WHERE (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
  AND (ur.requester_id = $view_user OR ur.requestee_id = $view_user)
  AND (ur.approved = 1)
  AND (ur.rtid = 2)
GROUP BY (u.name)
ORDER BY RAND()
"
);
echo
"<ul>";
$i = 1;
while (
$value = db_fetch_array($view_user_rel)) {
  if (
$value['uid'] != $cur_user && $value['uid'] != $view_user ) {
    echo
"<li><a href=\"http://www.example.com/user/" . $value['uid'] . "\">" . $value['name'] . "</a></li>";
   
$i++;
  }
  if (
$i > 5 ) { break; }
}
echo
"</ul>";
?>

#15

IceCreamYou - June 19, 2008 - 18:37

Okay, here's what we've all been waiting for. It displays a list of users the current user might relate to, Facebook style. There are a few hard-coded values in here--check the code comments to change them.

This is intended to go in a block, but as the final comment says I recommend putting it in a block and adding a "[More]" link to a page.

Enjoy!

<?php
global $user;
$cur_user = $user->uid;
$user_rel = db_query("
SELECT u.name, u.uid, u.picture
FROM {users} AS u
LEFT JOIN {user_relationships} AS ur ON (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
WHERE (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
  AND (ur.requester_id = $cur_user OR ur.requestee_id = $cur_user)
  AND (ur.approved = 1)
  AND (ur.rtid = 2)
GROUP BY (u.name)
ORDER BY COUNT(ur.requester_id OR ur.requestee_id) DESC
"
);  //rtid is the relationship ID, in my case it represents "Friends"

if ( db_fetch_array($user_rel) ) { //skip the rest if the user has no friends

$user_rel_com = array();
mysql_data_seek($user_rel, 0); //I don't know if this is necessary...
//assigns the actual values into an array
while($value = db_fetch_array($user_rel)) {
 
$user_rel_com[] = $value;
}


echo
"<h2>Users you might know:</h2>"; //needs to be themed, can be removed if you're putting this in a block

foreach($user_rel_com as $row){ //writes the sub-query for $user_rel_rel
 
$req_req_id_list .= " OR ur.requester_id = " . $row['uid'] . " OR ur.requestee_id = " . $row['uid'];
}
$len = strlen($req_req_id_list);
$req_req_id_list = substr($req_req_id_list, 4, $len); //trims the first " OR "

//gets all of the current user's friends' friends and orders by how many times they show up in the list
$user_rel_rel = db_query("
SELECT u.name, u.uid, u.picture
FROM {users} AS u
LEFT JOIN {user_relationships} AS ur ON (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
WHERE (u.uid = ur.requester_id OR u.uid = ur.requestee_id)
  AND ($req_req_id_list)
  AND (ur.approved = 1)
  AND (ur.rtid = 2)
GROUP BY (u.name)
ORDER BY COUNT(ur.requester_id OR ur.requestee_id) DESC
"
);

$user_rel_rel_com = array();
mysql_data_seek($user_rel_rel, 0);
//assigns the actual values into an array
while($value = db_fetch_array($user_rel_rel)) {
 
$user_rel_rel_com[] = $value;
}

//array_diff() wasn't working so I wrote my own; this just removes users who the current user already has a relationship with
$remaining = array();
foreach(
$user_rel_rel_com as $value){
  if ( !
array_search($value,$user_rel_com) ) {
   
$remaining[] = $value;
  }
}

$i = 0;
echo
"<ul>";
foreach (
$remaining as $value) {
  if (
$i < 5 ) { //change '5' to whatever you want; it limits the number of users shown
   
if ( $value['uid'] != $cur_user ) {
      if ( !
$value['picture'] ) { $value['picture'] = "/files/avatar_selection/BUp B 85.png"; } //change to whatever your default avatar is
     
echo "<li><a href=\"http://www.babelup.com/user/" . $value['uid'] . "\"><img src=\"http://www.babelup.com/" . $value['picture'] . "\" height = \"20px\" width = \"20px\" /> " . check_plain($value['name']) . "</a></li>"; //change according to your URL
   
}
  }
 
$i++;
}
echo
"</ul>";
//I recommend using this code in a block, and adding a [More] link here that goes to a page with all recommended users on it

}
?>

#16

IceCreamYou - June 19, 2008 - 18:45

One more thing: I wrote this on D5, and haven't tested it on D6. It only works on MySQL (should be okay on 4 and 5) because of mysql_data_seek() but I'm not entirely convinced that that needs to be in there at all.

I'm not aware of any changes from D5 to D6 that would affect this, except maybe something in the UR tables that I wouldn't know about because I'm not using D6.

If the maintainers of UR don't want to put this into the module itself, I can look into contributing it as a separate module.

#17

IceCreamYou - June 19, 2008 - 18:46
Status:active (needs more info)» patch (code needs review)

#18

ajayg - July 16, 2008 - 04:16
Status:patch (code needs review)» patch (code needs work)

I am getting following warning. I substituted ur.rtid with appropriate number.

warning: mysql_data_seek(): supplied argument is not a valid MySQL result resource in C:\xampp\htdocs\drupal-57\includes\common.inc(1352) : eval()'d code on line 19.
warning: mysql_data_seek(): supplied argument is not a valid MySQL result resource in C:\xampp\htdocs\drupal-57\includes\common.inc(1352) : eval()'d code on line 48.

#19

IceCreamYou - July 16, 2008 - 16:08

Try removing the mysql_data_seek() lines (on line 19 and 48, as the error you got notes) and see if that helps. I had never heard of any code in Drupal needing to use mysql_data_seek() before, but I was looking through some PHP documentation and it mentioned that it was necessary. It worked for me, so I left it in, but it's likely it's not needed at all.

#20

IceCreamYou - July 16, 2008 - 16:10
Status:patch (code needs work)» patch (code needs review)

#21

ajayg - July 17, 2008 - 05:29

I removed the two mysql_data_seek() lines and it is working fine now as expected. I noticed a strange thing though. On user profile page it showed correct list of people I might know (friend of friends). But the same block shows a different list which were just direct friend on the other pages. So it is kind of odd to see direct friends as "people you might know". SO to work correctly the block needs to be disabled from pages other than profile pages.

#22

IceCreamYou - July 17, 2008 - 13:49

That should never be the case with the "friends of friends" block. The code works completely independently of anything in the URL and doesn't depend on anything being on the page. I can't duplicate the experience you're having; the code even works fine for me if I put it in a page instead of a block.

The "mutual friends" block, however, should ONLY show up on profile pages that are not the user's own. To accomplish that, use this PHP in the Block Visibility field:

<?php
global $user;
return
$user->uid && arg(0) == 'user' && is_numeric(arg(1)) && arg(1) != $user->uid && !arg(2);
?>

For the websites I'm using this code, I have the "friends of friends" block show up on the user's own profile, and the "mutual friends" block show up on other people's profiles.

#23

IceCreamYou - August 17, 2008 - 20:03

I'm attaching some code I started for a submodule that adds this capability (both the "users you might know" and the "mutual friends" blocks) but I don't have time to finish it. There's a note at the very beginning of the file that should help anyone who wants to work on this.

Remember that when you download the file, you need to change the extension from .module.txt back to .module.

AttachmentSize
ur_blocks.module13.42 KB
 
 

Drupal is a registered trademark of Dries Buytaert.