OSSN MENTION USERS COMPONENT
This component enables a user to mention other users in posts and comments by using the syntax @Full Name or conditionally @username and that will send the mentioned user a notification telling them that they were mentioned in a post or comment with a link to that item.
This can be used in conjunction with the Display Username component as of v1.2 if you want to display usernames and mention @username instead of display Full Name and mention @Full Name. It will dynamically switch based whether or not the Display Username component is installed and turned on. You can find more info on it here: https://www.opensource-socialnetwork.org/component/view/3065/display-username
When mentioning usernames using the Display Username component the exact casing must be used to match the user.
When mentioning user full names the casing DOES matter as that can help find uniqueness.
Mentions are not limited to the friends of the user, the entire user base can be mentioned
Multiple mentions can be done in a post or comment.
CHANGE LOG
-------------------1.9.1------------------
Added ability to parse off trailing '(apostrophe) and 's for use in plurals and ownership
Added recommended method and hooks from Z-Man
-------------------1.9--------------------
Added 'ossn:notifications:mention:post:created' to en locale
Added 'ossn:notifications:mention:comment:created' to en locale
Renamed init method from basic name to prevent potential collision with other components
POSSIBLE ENHANCEMENTS
No UI based on prototype having significant cons
?
Yep I totally understand what you are saying. I will get that modified and post up a new version once I have it going. I forgot to ask. For the user delete action should I just delete notifications with my types or should I clean up anything remaining in notifications?
Yes, this will of course reduce the memory consumption of each user record, but on the other hand you still HAVE to loop until the end in the worst case until a matching record has been found. Or even not. :)
That's why i would basically stay with my logic for now, but change it that way to remove all this capitalisation stuff and fetch all matching records based on the first word only instead. This will already reduce the result drastically, and we just need to add some logic afterwards to verify whether the next word is still part of the first_name or already the lastname.
Here is a working sample of what I was talking about. This is going down the route as if they have the display username component on but off is basically the same with a different select with the first and last name concatenated.
function com_notificationHandler($callback, $type, $params, $message) {
$messageArr = str_split($message, 1);
$notifications = new OssnNotifications;
$db = new OssnDatabase;
$notifiedUsersArr = array();
// notify each user and add them to an array that we check to make sure we havent already sent a notification to that user in the case of @bryce alfred @bryce alfred
foreach ($messageArr as $messagekey => $messagevalue) {
if ($messagevalue == "@") {
// check to see if the component from https://www.opensource-socialnetwork.org/component/view/3065/display-username is enabled.
// if it is then we search the mentioned user by user name
if (com_is_active('DisplayUsername')) {
$query['params'] = array(
"guid",
"username"
);
$query['from'] = 'ossn_users';
$users = $db->select($query, true);
foreach ($users as $key => $user) {
$name = $user->username;
$len = strlen($name);
// we want to exact match the user names since they can be the characters but different casing
if (substr($message, $messagekey + 1, $len) == $name) {
// only add the user to the array if they are not in it already
if (!in_array($user, $notifiedUsersArr)) {
// if we have a poster guid then this is a wall post else its a comment so the payload changes
if (strlen($params['poster_guid']) > 0) {
$notifications->add('mention:post:created', $params['poster_guid'], $params['object_guid'], $params['object_guid'], $user->guid);
}
else {
$notifications->add('mention:comment:created', $params['owner_guid'], $params['subject_guid'], $params['id'], $user->guid);
}
array_push($notifiedUsersArr, $user);
}
}
}
}
}
}
}
BTW, Im not asking you to solve it for me, just trying to brainstorm for any ideas. So Im new to php so Im curious on how to tell if possible whether your implementation using a while loop and a foreach with the db query would be more performant or less or whatever than a hybrid solution of my original with a fetch of user full names or conditionally usernames/guids using the db query from the start. Curious on your thoughts as someone who knows php well. Also I could use a suggestion on a modification of your code if that is the better way to go to implement the condional use of the DisplayUsernames component which can also add in the complexity of lowercase starting word and only being a single word.
So Z-Man, I just looked at one of my test users on my dev installation and I noticed that I am able to create users with lower case first and last names which will break this. So for my user named bryce alfred people would see it as that in the UI and probably type @bryce alfred which wouldnt pass through the check to make sure it is beginning with an uppercase letter. Im trying to think of a solution that works with your implementation but I cant think of anything. Im not sure what to do about this.
Wow Z-Man, thank you for all the work man, I really appreciate the help. Im going to implement and put these changes through some of my tests I have been doing. I will keep you posted once done.
Here's my attempt of cutting the message into pieces and find out first and lastname ...
Still not thorougly tested, but works at first sight ... and prevents fetching the complete user list ...
complete it with your notification record creating part ...
function com_notificationHandler($callback, $type, $params, $message) {
while (($start = strpos($message, '@')) !== false) { // find substring starting after '@'
$substr = substr($message, $start + 1);
error_log($substr);
$substr_words = explode(" ", $substr); // convert to array of separate words
error_log(ossn_dump($substr_words));
$fullname = array();
if(count($substr_words) >= 2) {
foreach($substr_words as $word) {
if (mb_substr($word, 0, 1) != '@' && mb_substr($word, 0, 1) == mb_strtoupper(mb_substr($word, 0, 1))) {
if(preg_match('/[\p{P}\p{N}]$/u', $word)) { // remove punctuation from name in order ...
$fullname[] = mb_substr($word, 0, mb_strlen($word) - 1);
break; // ... to handle cases like "Yesterday we met @Bryce Alvord. Late at night we got drunk with @Monica Beerman"
}
else {
$fullname[] = $word; // add to array if word starts with capital letter
}
}
else {
break; // stop looping if first lowercase has been detected
}
}
if(($fullname_count = count($fullname)) >= 2) {
// now that we have found at least 2 words starting with a capital letter and following each other
// we assume the first one is part of the firstname and the last one part of the lastname
$firstname_part = $fullname[0];
$lastname_part = $fullname[$fullname_count - 1];
// build your own query -> see OssnDatabase->select() ...
$db = new OssnDatabase;
$query['from'] = 'ossn_users';
$query['wheres'] = array(
"first_name LIKE '{$firstname_part}%'",
"AND last_name LIKE '%{$lastname_part}'"
);
$result = $db->select($query);
if($result) {
// create notification record
}
}
}
$message = $substr; // try to find another mention in remaining part of original message string
}
}
First of all I found a bug: wall post description is json encoded, you can't retrieve the value by simply using str_val
...
function com_wall_created($callback, $type, $params) {
$new_wall_post_id = $params['object_guid'];
$wall_post = ossn_get_object($new_wall_post_id);
$description = json_decode($wall_post->description, true);
$post = $description['post'];
com_notificationHandler($callback, $type, $params, $post);
}
I forgot to add that I can easily implement using the user search method for usernames when using the Display Username component because I know the first word after an @ is exactly my search term, nothing more, nothing less. I really think the ideal use case for this until I get around to writing a UI to select a username based on the name and profile picture is to use the Display Username component because it guarantees uniqueness of usernames so no one will ever accidentally get a notification for another user by the same name.
I have the deletes handled now but regarding the suggestion to use the user search method I see a major problem which is that I don't know how many characters after the @ makes up a user full name. For example, a name could be Mike Rogers consisting of two names which most likely will be the case the majority of the time but I can't hard code for 2 words because obviously that will fail on multi word first names and or multi word last names. With the list of users before hand I can easily do what I am and look for the exact number of characters that all happen to match. Really what I need here is not to use the OssnUser object and somehow query the DB just for the names so the object isn't near the size of the OssnUser object once populated with users. Thoughts?