1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8class SocialLib 9{ 10 private $relationlib; 11 private $networkType; 12 13 function __construct() 14 { 15 global $prefs; 16 $this->relationlib = TikiLib::lib('relation'); 17 $this->networkType = $prefs['social_network_type'] ?: 'follow'; 18 } 19 20 function listFriends($user) 21 { 22 return $this->getRelations('follow', $user); 23 } 24 25 function listFollowers($user) 26 { 27 return $this->getRelations('follow.invert', $user); 28 } 29 30 function listIncomingRequests($user) 31 { 32 return $this->getRelations('request.invert', $user); 33 } 34 35 function listOutgoingRequests($user) 36 { 37 return $this->getRelations('request', $user); 38 } 39 40 function addFriend($user, $newFriend) 41 { 42 if ($user == $newFriend) { 43 return false; 44 } 45 46 $userlib = TikiLib::lib('user'); 47 if (! $userlib->user_exists($user) || ! $userlib->user_exists($newFriend)) { 48 return false; 49 } 50 51 $tx = TikiDb::get()->begin(); 52 53 $hash = $this->createHash($user, $newFriend); 54 55 if ($this->networkType == 'follow') { 56 $this->addRelation('follow', $user, $newFriend); 57 TikiLib::events()->trigger( 58 'tiki.user.follow.add', 59 [ 60 'type' => 'user', 61 'object' => $user, 62 'user' => $user, 63 'follow_id' => $newFriend, 64 'aggregate' => $hash, 65 ] 66 ); 67 TikiLib::events()->trigger( 68 'tiki.user.follow.incoming', 69 [ 70 'type' => 'user', 71 'object' => $newFriend, 72 'user' => $newFriend, 73 'follow_id' => $user, 74 'aggregate' => $hash, 75 ] 76 ); 77 } elseif ($this->networkType == 'follow_approval' || $this->networkType == 'friend') { 78 $request = $this->getRelation('request.invert', $user, $newFriend); 79 $follow = $this->getRelation('follow.invert', $user, $newFriend); 80 81 if ($request || $follow) { 82 // If there was a pending request by the other side (or pre-approved), remove the request 83 // and approve both directions. 84 85 // Re-add or empty-delete are not an issue 86 $this->relationlib->remove_relation($request); 87 $this->addRelation('follow', $user, $newFriend); 88 $this->addRelation('follow.invert', $user, $newFriend); 89 90 $event = ($this->networkType == 'friend') ? 'tiki.user.friend.add' : 'tiki.user.follow.add'; 91 TikiLib::events()->trigger( 92 $event, 93 [ 94 'type' => 'user', 95 'object' => $user, 96 'user' => $user, 97 'follow_id' => $newFriend, 98 'aggregate' => $hash, 99 ] 100 ); 101 TikiLib::events()->trigger( 102 $event, 103 [ 104 'type' => 'user', 105 'object' => $newFriend, 106 'user' => $newFriend, 107 'follow_id' => $user, 108 'aggregate' => $hash, 109 ] 110 ); 111 } else { 112 // New request 113 $this->addRelation('request', $user, $newFriend); 114 } 115 } 116 117 $tx->commit(); 118 119 require_once('lib/search/refresh-functions.php'); 120 refresh_index('user', $user); 121 refresh_index('user', $newFriend); 122 123 return true; 124 } 125 126 function approveFriend($user, $newFriend) 127 { 128 if ($this->networkType != 'follow_approval') { 129 return false; 130 } 131 132 $request = $this->getRelation('request.invert', $user, $newFriend); 133 134 if ($request) { 135 $tx = TikiDb::get()->begin(); 136 137 // If there was a pending request by the other side, remove the request 138 // and add them as follower 139 $this->relationlib->remove_relation($request); 140 $this->addRelation('follow.invert', $user, $newFriend); 141 142 TikiLib::events()->trigger( 143 'tiki.user.follow.add', 144 [ 145 'type' => 'user', 146 'object' => $newFriend, 147 'user' => $newFriend, 148 'follow_id' => $user, 149 ] 150 ); 151 TikiLib::events()->trigger( 152 'tiki.user.follow.incoming', 153 [ 154 'type' => 'user', 155 'object' => $user, 156 'user' => $user, 157 'follow_id' => $newFriend, 158 ] 159 ); 160 161 $tx->commit(); 162 163 require_once('lib/search/refresh-functions.php'); 164 refresh_index('user', $user); 165 refresh_index('user', $newFriend); 166 167 return true; 168 } 169 170 return false; 171 } 172 173 function removeFriend($user, $oldFriend) 174 { 175 $follow = $this->getRelation('follow', $user, $oldFriend); 176 $followInvert = $this->getRelation('follow.invert', $user, $oldFriend); 177 $request = $this->getRelation('request', $user, $oldFriend); 178 $requestInvert = $this->getRelation('request.invert', $user, $oldFriend); 179 180 if ($follow) { 181 $this->relationlib->remove_relation($follow); 182 183 if ($this->networkType == 'friend') { 184 // Friendship breakups are bidirectional, not follow ones 185 $this->relationlib->remove_relation($followInvert); 186 } 187 require_once('lib/search/refresh-functions.php'); 188 refresh_index('user', $user); 189 refresh_index('user', $oldFriend); 190 return true; 191 } elseif ($request || $requestInvert) { 192 $this->relationlib->remove_relation($request); 193 $this->relationlib->remove_relation($requestInvert); 194 require_once('lib/search/refresh-functions.php'); 195 refresh_index('user', $user); 196 refresh_index('user', $oldFriend); 197 return true; 198 } else { 199 return false; 200 } 201 } 202 203 private function getRelations($type, $from) 204 { 205 $relations = $this->relationlib->get_relations_from('user', $from, 'tiki.friend.' . $type); 206 207 return array_map( 208 function ($relation) { 209 return [ 210 'user' => $relation['itemId'], 211 ]; 212 }, 213 $relations 214 ); 215 } 216 217 private function addRelation($type, $from, $to) 218 { 219 return $this->relationlib->add_relation('tiki.friend.' . $type, 'user', $from, 'user', $to); 220 } 221 222 private function getRelation($type, $from, $to) 223 { 224 return $this->relationlib->get_relation_id('tiki.friend.' . $type, 'user', $from, 'user', $to); 225 } 226 227 private function createHash($a, $b) 228 { 229 // Hashing needs constant user ordering, so sort 230 if ($a > $b) { 231 $t = $b; 232 $b = $a; 233 $a = $t; 234 } 235 236 return sha1("friendrelation/$a/$b"); 237 } 238 239 function addLike($user, $type, $id) 240 { 241 $like = $this->getLike($user, $type, $id); 242 243 if (! $like) { 244 $this->relationlib->add_relation('tiki.social.like', 'user', $user, $type, $id); 245 TikiLib::events()->trigger( 246 'tiki.social.like.add', 247 [ 248 'type' => $type, 249 'object' => $id, 250 'user' => $user, 251 ] 252 ); 253 return true; 254 } 255 256 return false; 257 } 258 259 function removeLike($user, $type, $id) 260 { 261 $like = $this->getLike($user, $type, $id); 262 263 if ($like) { 264 $this->relationlib->remove_relation($like); 265 TikiLib::events()->trigger( 266 'tiki.social.like.remove', 267 [ 268 'type' => $type, 269 'object' => $id, 270 'user' => $user, 271 ] 272 ); 273 return true; 274 } 275 276 return false; 277 } 278 279 function getLikes($type, $id) 280 { 281 $relations = $this->relationlib->get_relations_to($type, $id, 'tiki.social.like'); 282 283 return array_map( 284 function ($relation) { 285 return $relation['itemId']; 286 }, 287 $relations 288 ); 289 } 290 291 private function getLike($user, $type, $id) 292 { 293 return $this->relationlib->get_relation_id('tiki.social.like', 'user', $user, $type, $id); 294 } 295} 296