├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── config └── acquaintances.php ├── database └── migrations │ ├── create_acquaintances_friendship_table.php │ ├── create_acquaintances_friendships_groups_table.php │ └── create_acquaintances_interactions_table.php ├── phpunit.xml ├── src ├── AcquaintancesServiceProvider.php ├── Interaction.php ├── Models │ ├── FriendFriendshipGroups.php │ ├── Friendship.php │ └── InteractionRelation.php ├── Status.php └── Traits │ ├── CanBeFavorited.php │ ├── CanBeFollowed.php │ ├── CanBeLiked.php │ ├── CanBeRated.php │ ├── CanBeReported.php │ ├── CanBeSubscribed.php │ ├── CanBeViewed.php │ ├── CanBeVoted.php │ ├── CanFavorite.php │ ├── CanFollow.php │ ├── CanLike.php │ ├── CanRate.php │ ├── CanReport.php │ ├── CanSubscribe.php │ ├── CanView.php │ ├── CanVote.php │ └── Friendable.php └── tests ├── CreatesApplication.php ├── FriendshipsEventsTest.php ├── FriendshipsGroupsTest.php ├── FriendshipsTest.php ├── Stub_User.php ├── TestCase.php ├── config ├── app.php ├── database.php └── friendships.php └── helpers.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor/ 3 | composer.lock 4 | .php_cs.cache 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v3.7.2 2 | 3 | * fix: RELATION_REPORT instead of RELATION_FAVORITE in `CanBeReported` class. #117 4 | 5 | ## v3.7.1 6 | 7 | * fix: numberToReadable will return an int if the $shorthand is empty. 8 | 9 | ## v3.7.0 10 | 11 | * feat: new trait for reporting added by @jayenne. PR #116 12 | 13 | ## v3.6.2 14 | 15 | * feat: new functions added `followingCount()` and `followingCountReadable()` thanks to @jayenne <3. PR #115 16 | 17 | ## v3.6.1 18 | 19 | * use config variable `interactions_user_id_fk_column_name` PR #109 20 | 21 | ## v3.6.0 22 | 23 | * Bump Laravel support to V11. 24 | 25 | ## v3.5.9 26 | 27 | * feat: adding a way to get a list of blocked Friendships by current user by @mercExec 28 | * feat: adding a way to get a list of blocked Friendships by others by @mercExec 29 | 30 | ## v3.5.8 31 | 32 | * feat: adding get pending count function by @prawn185 33 | * fix: solve isRelationExists causing potential ambiguous errors when doing some complex DB queries. by @midblep 34 | 35 | ## v3.5.7 36 | 37 | * Laravel 10 Support - composer-wise not fully support, and no PHP 8.1 types or anything fancy here 38 | 39 | ## v3.5.6 40 | 41 | * fix: findFriendships with params `sender` & `recipient` were not working, fixed by @beratkara 42 | 43 | ## v3.5.5 44 | 45 | * feat: add cursor paginate support to `getFriends` and `getOrPaginate` 46 | 47 | ## v3.5.4 48 | 49 | * fix: make interaction relation_value type a double, better for rating values. 50 | * fix: rating a non-custom type (AKA `config('acquaintances.rating.defaults.type')`) just after a custom type rating, 51 | will leave to an odd behavior 52 | 53 | ## v3.5.3 54 | 55 | * feat: allow user to turn off migrations, thanks to @jaulz 56 | 57 | ## v3.5.2 58 | 59 | * fix: deprecated method studly_case() PR #53, thanks to @Forsakenrox 60 | 61 | ## v3.5.1 62 | 63 | * fix: Trait helper method morph key of 'ratingsTo'. 64 | 65 | ## v3.5.0 66 | 67 | * fix: rename collision method name 'ratings' to 'ratingsTo' 68 | 69 | ## v3.4.7 70 | 71 | * feat: change the naming of migration files to be prefixed with the current time. 72 | 73 | ## v3.4.6 74 | 75 | * fix: friendship_id column type 76 | 77 | ## v3.4.5 78 | 79 | * fix: use custom model for downvotes 80 | * fix: read pivot id 81 | 82 | ## v3.4.4 83 | 84 | * fix: in interaction table add update_at timestamp column 85 | 86 | ## v3.4.3 87 | 88 | * feat: allow empty model namespace 89 | * fix: add helper methods for all models 90 | * feat: allow all models to be configured 91 | * fix: use models from config 92 | * fix: set default user model to User 93 | * along with other fixes 94 | 95 | ## v3.4.2 96 | 97 | * feat: add view relation 98 | * feat: add CanBeViewed trait 99 | * feat: add CanView trait 100 | * docs: extend readme 101 | * docs: add missing events 102 | * fix: fix typo 103 | * feat: extend relation map 104 | 105 | ## v3.4.1 106 | 107 | * fix: add option for custom column name 108 | * feat: add helper method to get user model name 109 | * fix: use helper method to get user model name 110 | * fix: remove obsolete imports 111 | * fix: add Str import 112 | * fix: fix comments in config 113 | * fix: use helper method to get user model name 114 | * fix: use new helper method to get user model name 115 | * fix: use new helper method get user model name 116 | * fix: use new helper method get user model name 117 | * fix: use new helper method get user model name 118 | * fix: use new helper method get user model name 119 | 120 | ## v3.4.0 121 | 122 | * minor fix. Replaced `str_plural` with `Str::plural`. 123 | 124 | ## v3.3.1 125 | 126 | * Added `ratings()` & `ratingsPure()` to CanBeRated model 127 | * Added user model name to configs `user_model_class_name` 128 | * Added user relation to InteractionRelation model 129 | 130 | ## v3.3.0 131 | 132 | * Fixed the logic of allTypes post-fixed functions. 133 | 134 | ## v3.2.0 135 | 136 | * Removed avoiding querying ratings when the type is set to *overall* 137 | * Fixed the value of `userSumRatingReadable()` function in CanBeRated trait. 138 | * Fixed `userSumRatingReadable()` function in `CanBeRated` trait. 139 | * Add new functions to `CanBeRated` trait: 140 | * averageRatingAllTypes() 141 | * sumRatingAllTypes() 142 | * sumRatingAllTypesReadable() 143 | * userAverageRatingAllTypes() 144 | * userSumRatingAllTypes() 145 | * userSumRatingAllTypesReadable() 146 | * ratingPercentAllTypes() 147 | * getAverageRatingAllTypesAttribute() 148 | * getSumRatingAllTypesAttribute() 149 | * getUserAverageRatingAllTypesAttribute() 150 | * getUserSumRatingAllTypesAttribute() 151 | 152 | * Added a proper param type hint for the $target param in the following traits 153 | * CanBeFavorited 154 | * CanBeFollowed 155 | * CanBeLiked 156 | * CanBeRated 157 | * CanBeSubscribed 158 | * CanBeVoted 159 | * CanFavorite 160 | * CanFollow 161 | * CanLike 162 | * CanRate 163 | * CanSubscribe 164 | * CanVote 165 | 166 | ## v3.1.0 167 | 168 | * Made the `user_id` FK type dynamic and part of configurations 169 | 170 | ## v3.0.0 171 | 172 | * Changed the package's company - since I renamed my brand 173 | 174 | ## v2.0.0 175 | 176 | * PHP 7.1 is the minimum requirement now 177 | * Supporting Laravel 5.8 by replacing `Event::fire` with `Event::dispatch` 178 | * Fixing an issue with foreign key constraint when running migrations 179 | 180 | ## v1.2.0 181 | 182 | * Adding rating system feature 183 | * Remote soft deletion and updated_at timestamp 184 | * Adding `numberToReadable()` function to as helper 185 | * Enhance logic by adding more columns 186 | * Adding `CanBeFavorited` trait now includes: 187 | * `favoriters_count` favoriters count 188 | * `favoritersCountReadable()` return favoriters count in readable format 189 | * `favoriters_count_readable` favoriters count readable attribute added 190 | * Adding `CanBeFollowed` trait now includes: 191 | * `followers_count` followers count 192 | * `followersCountReadable()` return followers count in readable format 193 | * `followers_count_readable` followers count readable attribute added 194 | * Adding `CanBeLiked` trait now includes: 195 | * `likers_count` likers count 196 | * `likersCountFormmated()` changed return type to readable number, and adding an alias to it `likersCountReadable()` 197 | * `likers_count_readable` likers count readable attribute added 198 | * Adding `CanBeSubscribed` trait now includes: 199 | * `subscribers_count` subscribers count 200 | * `subscribersCountReadable()` return subscribers count in readable format 201 | * `subscribers_count_readable` subscribers count readable attribute added 202 | * Adding `CanBeSubscribed` trait now includes: 203 | * `voters_count` voters count 204 | * `votersCountReadable()` return voters count in readable format 205 | * `voters_count_readable` voters count readable attribute added 206 | * Adding `CanBeSubscribed` trait now includes: 207 | * `upvoters_count` upvoters count 208 | * `upvotersCountReadable()` return upvoters count in readable format 209 | * `upvoters_count_readable` upvoters count readable attribute added 210 | * Adding `CanBeSubscribed` trait now includes: 211 | * `downvoters_count` downvoters count 212 | * `downvotersCountReadable()` return downvoters count in readable format 213 | * `downvoters_count_readable` downvoters count readable attribute added 214 | 215 | ## v1.1.0 216 | 217 | * Fix errors 218 | * Enhance logic 219 | * Smart/less configurations 220 | 221 | ## v1.0.1 222 | 223 | * Minor change in the name of migrations files 224 | 225 | ## v1.0.0 226 | 227 | * Initial Release 228 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First of all, **thank you** for contributing! 4 | 5 | Here are a few rules to follow in order to ease code reviews and merging: 6 | 7 | - follow [PSR-1](http://www.php-fig.org/psr/1/) and [PSR-2](http://www.php-fig.org/psr/2/) 8 | - run the test suite 9 | - write (or update) unit tests when applicable 10 | - write documentation for new features 11 | - use [commit messages that make sense](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 12 | 13 | One may ask you to [squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) too. This is used to "clean" your pull request before merging it (we don't want commits such as `fix tests`, `fix 2`, `fix 3`, etc.). 14 | 15 | When creating your pull request on GitHub, please write a description which gives the context and/or explains why you are creating it. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Mohamed Kawsara - Multicaret 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Acquaintances 2 | 3 | [![Total Downloads](https://img.shields.io/packagist/dt/multicaret/laravel-acquaintances.svg?style=flat-square)](https://packagist.org/packages/multicaret/laravel-acquaintances) 4 | [![Latest Version](https://img.shields.io/github/release/multicaret/laravel-acquaintances.svg?style=flat-square)](https://github.com/multicaret/laravel-acquaintances/releases) 5 | [![License](https://poser.pugx.org/multicaret/laravel-acquaintances/license.svg?style=flat-square)](https://packagist.org/packages/multicaret/laravel-acquaintances) 6 | 7 |

8 | 9 | [Laravel News Article](https://laravel-news.com/manage-friendships-likes-and-more-with-the-acquaintances-laravel-package) 10 | 11 | Supports Laravel 12, with no dependencies 12 | 13 | ## TL;DR 14 | 15 | Gives eloquent models: 16 | 17 | - Friendships & Groups ability 18 | - Interactions ability such as: 19 | - Likes 20 | - Favorites 21 | - Reporting 22 | - Votes (up/down) 23 | - Subscribe 24 | - Follow 25 | - Ratings 26 | - Views 27 | 28 | Take this example: 29 | 30 | ```php 31 | $user1 = User::find(1); 32 | $user2 = User::find(2); 33 | 34 | $user1->befriend($user2); 35 | $user2->acceptFriendRequest($user1); 36 | 37 | // The messy breakup :( 38 | $user2->unfriend($user1); 39 | 40 | ``` 41 | 42 | 1. [Introduction](#introduction) 43 | 1. [Installation](#installation) 44 | 2. [Friendships:](#friendships) 45 | * [Friend Requests](#friend-requests) 46 | * [Check Friend Requests](#check-friend-requests) 47 | * [Retrieve Friend Requests](#retrieve-friend-requests) 48 | * [Retrieve Friends](#retrieve-friends) 49 | * [Friend Groups](#friend-groups) 50 | 3. [Interactions](#interactions) 51 | * [Traits Usage](#traits-usage) 52 | * [Follow](#follow) 53 | * [Rate](#rate) 54 | * [Like](#like) 55 | * [Favorite](#favorite) 56 | * [Subscribe](#subscribe) 57 | * [Vote](#vote) 58 | * [View](#view) 59 | * [Parameters](#parameters) 60 | * [Query relations](#query-relations) 61 | * [Working with model](#working-with-model) 62 | 4. [Events](#events) 63 | 5. [Contributing](#contributing) 64 | 65 | ## Introduction 66 | 67 | This light package gives Eloquent models the ability to manage their acquaintances and other cool useful stuff. You can 68 | easily design your social-like System (Facebook, Twitter, Foursquare...etc). 69 | 70 | ##### Acquaintances includes: 71 | 72 | - Send Friend Requests 73 | - Accept Friend Requests 74 | - Deny Friend Requests 75 | - Block a User 76 | - Group Friends 77 | - Rate a User or a Model, supporting multiple aspects 78 | - Follow a User or a Model 79 | - Like a User or a Model 80 | - Subscribe a User or a Model 81 | - Favorite a User or a Model 82 | - Vote (Upvote & Downvote a User or a Model) 83 | - View a User or a Model 84 | 85 | --- 86 | 87 | ## Installation 88 | 89 | First, install the package through Composer. 90 | 91 | ```sh 92 | composer require multicaret/laravel-acquaintances 93 | ``` 94 | 95 | Laravel 5.8 and up => version 2.x (branch master) 96 | 97 | Laravel 5.7 and below => version 1.x (branch v1) 98 | 99 | Publish config and migrations: 100 | 101 | ```sh 102 | php artisan vendor:publish --provider="Multicaret\Acquaintances\AcquaintancesServiceProvider" 103 | ``` 104 | 105 | Configure the published config in: 106 | 107 | ``` 108 | config/acquaintances.php 109 | ``` 110 | 111 | Finally, migrate the database to create the table: 112 | 113 | ```sh 114 | php artisan migrate 115 | ``` 116 | 117 | --- 118 | 119 | ## Setup a Model 120 | 121 | Example: 122 | 123 | ```php 124 | use Multicaret\Acquaintances\Traits\Friendable; 125 | use Multicaret\Acquaintances\Traits\CanFollow; 126 | use Multicaret\Acquaintances\Traits\CanBeFollowed; 127 | use Multicaret\Acquaintances\Traits\CanLike; 128 | use Multicaret\Acquaintances\Traits\CanBeLiked; 129 | use Multicaret\Acquaintances\Traits\CanRate; 130 | use Multicaret\Acquaintances\Traits\CanBeRated; 131 | //... 132 | 133 | class User extends Model 134 | { 135 | use Friendable; 136 | use CanFollow, CanBeFollowed; 137 | use CanLike, CanBeLiked; 138 | use CanRate, CanBeRated; 139 | //... 140 | } 141 | ``` 142 | 143 | All available APIs are listed below for Friendships & Interactions. 144 | 145 | 146 | --- 147 | 148 | ## Friendships: 149 | 150 | ### Friend Requests: 151 | 152 | Add `Friendable` Trait to User model. 153 | 154 | ```php 155 | use Multicaret\Acquaintances\Traits\Friendable; 156 | 157 | class User extends Model 158 | { 159 | use Friendable; 160 | } 161 | ``` 162 | 163 | #### Send a Friend Request 164 | 165 | ```php 166 | $user->befriend($recipient); 167 | ``` 168 | 169 | #### Accept a Friend Request 170 | 171 | ```php 172 | $user->acceptFriendRequest($sender); 173 | ``` 174 | 175 | #### Deny a Friend Request 176 | 177 | ```php 178 | $user->denyFriendRequest($sender); 179 | ``` 180 | 181 | #### Remove Friend 182 | 183 | ```php 184 | $user->unfriend($friend); 185 | ``` 186 | 187 | #### Block a Model 188 | 189 | ```php 190 | $user->blockFriend($friend); 191 | ``` 192 | 193 | #### Unblock a Model 194 | 195 | ```php 196 | $user->unblockFriend($friend); 197 | ``` 198 | 199 | #### Check if Model is Friend with another Model 200 | 201 | ```php 202 | $user->isFriendWith($friend); 203 | ``` 204 | 205 | ### Check Friend Requests: 206 | 207 | #### Check if Model has a pending friend request from another Model 208 | 209 | ```php 210 | $user->hasFriendRequestFrom($sender); 211 | ``` 212 | 213 | #### Check if Model has already sent a friend request to another Model 214 | 215 | ```php 216 | $user->hasSentFriendRequestTo($recipient); 217 | ``` 218 | 219 | #### Check if Model has blocked another Model 220 | 221 | ```php 222 | $user->hasBlocked($friend); 223 | ``` 224 | 225 | #### Check if Model is blocked by another Model 226 | 227 | ```php 228 | $user->isBlockedBy($friend); 229 | ``` 230 | 231 | --- 232 | 233 | ### Retrieve Friend Requests: 234 | 235 | #### Get a single friendship 236 | 237 | ```php 238 | $user->getFriendship($friend); 239 | ``` 240 | 241 | #### Get a list of all Friendships 242 | 243 | ```php 244 | $user->getAllFriendships(); 245 | $user->getAllFriendships($group_name, $perPage = 20, $fields = ['id','name']); 246 | ``` 247 | 248 | #### Get a list of pending Friendships 249 | 250 | ```php 251 | $user->getPendingFriendships(); 252 | $user->getPendingFriendships($group_name, $perPage = 20, $fields = ['id','name']); 253 | ``` 254 | 255 | #### Get a list of accepted Friendships 256 | 257 | ```php 258 | $user->getAcceptedFriendships(); 259 | $user->getAcceptedFriendships($group_name, $perPage = 20, $fields = ['id','name']); 260 | ``` 261 | 262 | #### Get a list of denied Friendships 263 | 264 | ```php 265 | $user->getDeniedFriendships(); 266 | $user->getDeniedFriendships($perPage = 20, $fields = ['id','name']); 267 | ``` 268 | 269 | #### Get a list of blocked Friendships in total 270 | 271 | ```php 272 | $user->getBlockedFriendships(); 273 | $user->getBlockedFriendships($perPage = 20, $fields = ['id','name']); 274 | ``` 275 | 276 | #### Get a list of blocked Friendships by current user 277 | 278 | ```php 279 | $user->getBlockedFriendshipsByCurrentUser(); 280 | $user->getBlockedFriendshipsByCurrentUser($perPage = 20, $fields = ['id','name']); 281 | ``` 282 | 283 | #### Get a list of blocked Friendships by others 284 | 285 | ```php 286 | $user->getBlockedFriendshipsByOtherUsers(); 287 | $user->getBlockedFriendshipsByOtherUsers($perPage = 20, $fields = ['id','name']); 288 | ``` 289 | 290 | #### Get a list of pending Friend Requests 291 | 292 | ```php 293 | $user->getFriendRequests(); 294 | ``` 295 | 296 | #### Get the number of Friends 297 | 298 | ```php 299 | $user->getFriendsCount(); 300 | ``` 301 | 302 | #### Get the number of Pending Requests 303 | 304 | ```php 305 | $user->getPendingsCount(); 306 | ``` 307 | 308 | #### Get the number of mutual Friends with another user 309 | 310 | ```php 311 | $user->getMutualFriendsCount($otherUser); 312 | ``` 313 | 314 | ## Retrieve Friends: 315 | 316 | To get a collection of friend models (ex. User) use the following methods: 317 | 318 | #### `getFriends()` 319 | 320 | ```php 321 | $user->getFriends(); 322 | // or paginated 323 | $user->getFriends($perPage = 20, $group_name); 324 | // or paginated with certain fields 325 | $user->getFriends($perPage = 20, $group_name, $fields = ['id','name']); 326 | // or paginated with cursor & certain fields 327 | $user->getFriends($perPage = 20, $group_name, $fields = ['id','name'], $cursor = true); 328 | ``` 329 | 330 | Parameters: 331 | 332 | * `$perPage`: integer (default: `0`), Get values paginated 333 | * `$group_name`: string (default: `''`), Get collection of Friends in specific group paginated 334 | * `$fields`: array (default: `['*']`), Specify the desired fields to query. 335 | 336 | #### `getFriendsOfFriends()` 337 | 338 | ```php 339 | $user->getFriendsOfFriends(); 340 | // or 341 | $user->getFriendsOfFriends($perPage = 20); 342 | // or 343 | $user->getFriendsOfFriends($perPage = 20, $fields = ['id','name']); 344 | ``` 345 | 346 | Parameters: 347 | 348 | * `$perPage`: integer (default: `0`), Get values paginated 349 | * `$fields`: array (default: `['*']`), Specify the desired fields to query. 350 | 351 | #### `getMutualFriends()` 352 | 353 | Get mutual Friends with another user 354 | 355 | ```php 356 | $user->getMutualFriends($otherUser); 357 | // or 358 | $user->getMutualFriends($otherUser, $perPage = 20); 359 | // or 360 | $user->getMutualFriends($otherUser, $perPage = 20, $fields = ['id','name']); 361 | ``` 362 | 363 | Parameters: 364 | 365 | * `$other`: Model (required), The Other user model to check mutual friends with 366 | * `$perPage`: integer (default: `0`), Get values paginated 367 | * `$fields`: array (default: `['*']`), Specify the desired fields to query. 368 | 369 | ## Friend Groups: 370 | 371 | The friend groups are defined in the `config/acquaintances.php` file. The package comes with a few default groups. To 372 | modify them, or add your own, you need to specify a `slug` and a `key`. 373 | 374 | ```php 375 | // config/acquaintances.php 376 | //... 377 | 'groups' => [ 378 | 'acquaintances' => 0, 379 | 'close_friends' => 1, 380 | 'family' => 2 381 | ]; 382 | ``` 383 | 384 | Since you've configured friend groups, you can group/ungroup friends using the following methods. 385 | 386 | #### Group a Friend 387 | 388 | ```php 389 | $user->groupFriend($friend, $group_name); 390 | ``` 391 | 392 | #### Remove a Friend from family group 393 | 394 | ```php 395 | $user->ungroupFriend($friend, 'family'); 396 | ``` 397 | 398 | #### Remove a Friend from all groups 399 | 400 | ```php 401 | $user->ungroupFriend($friend); 402 | ``` 403 | 404 | #### Get the number of Friends in specific group 405 | 406 | ```php 407 | $user->getFriendsCount($group_name); 408 | ``` 409 | 410 | #### To filter `friendships` by group you can pass a group slug. 411 | 412 | ```php 413 | $user->getAllFriendships($group_name); 414 | $user->getAcceptedFriendships($group_name); 415 | $user->getPendingFriendships($group_name); 416 | ... 417 | ``` 418 | 419 | ## Interactions 420 | 421 | ### Traits Usage: 422 | 423 | Add `CanXXX` Traits to User model. 424 | 425 | ```php 426 | use Multicaret\Acquaintances\Traits\CanFollow; 427 | use Multicaret\Acquaintances\Traits\CanLike; 428 | use Multicaret\Acquaintances\Traits\CanFavorite; 429 | use Multicaret\Acquaintances\Traits\CanSubscribe; 430 | use Multicaret\Acquaintances\Traits\CanVote; 431 | 432 | class User extends Model 433 | { 434 | use CanFollow, CanLike, CanFavorite, CanSubscribe, CanVote; 435 | } 436 | ``` 437 | 438 | Add `CanBeXXX` Trait to target model, such as 'Post' or 'Book' ...: 439 | 440 | ```php 441 | use Multicaret\Acquaintances\Traits\CanBeLiked; 442 | use Multicaret\Acquaintances\Traits\CanBeFavorited; 443 | use Multicaret\Acquaintances\Traits\CanBeVoted; 444 | use Multicaret\Acquaintances\Traits\CanBeRated; 445 | 446 | class Post extends Model 447 | { 448 | use CanBeLiked, CanBeFavorited, CanBeVoted, CanBeRated; 449 | } 450 | ``` 451 | 452 | All available APIs are listed below. 453 | 454 | ### Follow 455 | 456 | #### `\Multicaret\Acquaintances\Traits\CanFollow` 457 | 458 | ```php 459 | $user->follow($targets); 460 | $user->unfollow($targets); 461 | $user->toggleFollow($targets); 462 | $user->followings()->get(); // App\User:class 463 | $user->followings(App\Post::class)->get(); 464 | $user->isFollowing($target); 465 | $object->followingCount(); // or as attribute $object->following_count 466 | $object->followingCountReadable(); // return readable number with precision, i.e: 5.2K 467 | ``` 468 | 469 | #### `\Multicaret\Acquaintances\Traits\CanBeFollowed` 470 | 471 | ```php 472 | $object->followers()->get(); 473 | $object->isFollowedBy($user); 474 | $object->followersCount(); // or as attribute $object->followers_count 475 | $object->followersCountReadable(); // return readable number with precision, i.e: 5.2K 476 | ``` 477 | 478 | ### Rate 479 | 480 | #### `\Multicaret\Acquaintances\Traits\CanRate` 481 | 482 | ```php 483 | // Rate type in the following line will be 484 | // the same as the one specified 485 | // in config('acquaintances.rating.defaults.type') 486 | // if your app is using a single type of rating on your model, 487 | // like one factor only, then simply use the rate() as it's shown here, 488 | // and if you have multiple factors then 489 | // take a look the examples exactly below this these ones. 490 | $user->rate($targets); 491 | $user->unrate($targets); 492 | $user->toggleRate($targets); 493 | $user->ratings()->get(); // App\User:class 494 | $user->ratings(App\Post::class)->get(); 495 | $user->hasRated($target); 496 | 497 | // Some Examples on how to rate the object based on different factors (rating type) 498 | $user->setRateType('bedside-manners')->rate($target, 4); 499 | $user->setRateType('waiting-time')->rate($target, 3); 500 | $user->setRateType('quality')->rate($target, 4); 501 | $user->setRateType('delivery-time')->rate($target, 2); 502 | $user->setRateType('communication')->rate($target, 5); 503 | // Remember that you can always use the functions on $target which have this phrase "AllTypes" in them. check the below section for more details 504 | ``` 505 | 506 | #### `\Multicaret\Acquaintances\Traits\CanBeRated` 507 | 508 | ```php 509 | $object->raters()->get(); 510 | $object->isRatedBy($user); 511 | 512 | $object->averageRating(); // or as attribute $object->average_rating 513 | $object->averageRatingAllTypes(); // or as attribute $object->average_rating_all_types 514 | 515 | $object->sumRating(); // or as attribute $object->sum_rating 516 | $object->sumRatingAllTypes(); // or as attribute $object->sum_rating_all_types_all_types 517 | 518 | $object->sumRatingReadable(); // return readable number with precision, i.e: 5.2K 519 | $object->sumRatingAllTypesReadable(); // return readable number with precision, i.e: 5.2K 520 | 521 | 522 | $object->ratingPercent($max = 5); // calculating the percentage based on the passed coefficient 523 | $object->ratingPercentAllTypes($max = 5); // calculating the percentage based on the passed coefficient 524 | 525 | // User Related: 526 | 527 | $object->userAverageRatingAllTypes(); // or as attribute $object->user_average_rating_all_types 528 | 529 | $object->userSumRatingAllTypes(); // or as attribute $object->user_sum_rating_all_types 530 | 531 | $object->userSumRatingReadable(); // return readable number with precision, i.e: 5.2K 532 | $object->userSumRatingAllTypesReadable(); // return readable number with precision, i.e: 5.2K 533 | 534 | 535 | ``` 536 | 537 | ### Like 538 | 539 | #### `\Multicaret\Acquaintances\Traits\CanLike` 540 | 541 | ```php 542 | $user->like($targets); 543 | $user->unlike($targets); 544 | $user->toggleLike($targets); 545 | $user->hasLiked($target); 546 | $user->likes()->get(); // default object: App\User:class 547 | $user->likes(App\Post::class)->get(); 548 | ``` 549 | 550 | #### `\Multicaret\Acquaintances\Traits\CanBeLiked` 551 | 552 | ```php 553 | $object->likers()->get(); 554 | $object->fans()->get(); // or $object->fans. it's an alias of likers() 555 | $object->isLikedBy($user); 556 | $object->likersCount(); // or as attribute $object->likers_count 557 | $object->likersCountReadable(); // return readable number with precision, i.e: 5.2K 558 | ``` 559 | 560 | ### Favorite 561 | 562 | #### `\Multicaret\Acquaintances\Traits\CanFavorite` 563 | 564 | ```php 565 | $user->favorite($targets); 566 | $user->unfavorite($targets); 567 | $user->toggleFavorite($targets); 568 | $user->hasFavorited($target); 569 | $user->favorites()->get(); // App\User:class 570 | $user->favorites(App\Post::class)->get(); 571 | ``` 572 | 573 | #### `\Multicaret\Acquaintances\Traits\CanBeFavorited` 574 | 575 | ```php 576 | $object->favoriters()->get(); // or $object->favoriters 577 | $object->isFavoritedBy($user); 578 | $object->favoritersCount(); // or as attribute $object->favoriters_count 579 | $object->favoritersCountReadable(); // return readable number with precision, i.e: 5.2K 580 | ``` 581 | 582 | 583 | ### Reporting 584 | 585 | #### `\Multicaret\Acquaintances\Traits\CanReport` 586 | 587 | ```php 588 | $user->report($targets); 589 | $user->unreport($targets); 590 | $user->toggleReport($targets); 591 | $user->hasReported($target); 592 | $user->reports()->get(); // App\User:class 593 | $user->reports(App\Post::class)->get(); 594 | ``` 595 | 596 | #### `\Multicaret\Acquaintances\Traits\CanBeReported` 597 | 598 | ```php 599 | $object->reporters()->get(); // or $object->reporters 600 | $object->isReportedBy($user); 601 | $object->reportersCount(); // or as attribute $object->reporters_count 602 | $object->reportersCountReadable(); // return readable number with precision, i.e: 5.2K 603 | ``` 604 | 605 | ### Subscribe 606 | 607 | #### `\Multicaret\Acquaintances\Traits\CanSubscribe` 608 | 609 | ```php 610 | $user->subscribe($targets); 611 | $user->unsubscribe($targets); 612 | $user->toggleSubscribe($targets); 613 | $user->hasSubscribed($target); 614 | $user->subscriptions()->get(); // default object: App\User:class 615 | $user->subscriptions(App\Post::class)->get(); 616 | ``` 617 | 618 | #### `Multicaret\Acquaintances\Traits\CanBeSubscribed` 619 | 620 | ```php 621 | $object->subscribers(); // or $object->subscribers 622 | $object->isSubscribedBy($user); 623 | $object->subscribersCount(); // or as attribute $object->subscribers_count 624 | $object->subscribersCountReadable(); // return readable number with precision, i.e: 5.2K 625 | ``` 626 | 627 | ### Vote 628 | 629 | #### `\Multicaret\Acquaintances\Traits\CanVote` 630 | 631 | ```php 632 | $user->vote($target); // Vote with 'upvote' for default 633 | $user->upvote($target); 634 | $user->downvote($target); 635 | $user->cancelVote($target); 636 | $user->hasUpvoted($target); 637 | $user->hasDownvoted($target); 638 | $user->votes(App\Post::class)->get(); 639 | $user->upvotes(App\Post::class)->get(); 640 | $user->downvotes(App\Post::class)->get(); 641 | ``` 642 | 643 | #### `\Multicaret\Acquaintances\Traits\CanBeVoted` 644 | 645 | ```php 646 | $object->voters()->get(); 647 | $object->isVotedBy($user); 648 | $object->votersCount(); // or as attribute $object->voters_count 649 | $object->votersCountReadable(); // return readable number with precision, i.e: 5.2K 650 | 651 | $object->upvoters()->get(); 652 | $object->isUpvotedBy($user); 653 | $object->upvotersCount(); // or as attribute $object->upvoters_count 654 | $object->upvotersCountReadable(); // return readable number with precision, i.e: 5.2K 655 | 656 | $object->downvoters()->get(); 657 | $object->isDownvotedBy($user); 658 | $object->downvotersCount(); // or as attribute $object->downvoters_count 659 | $object->downvotersCountReadable(); // return readable number with precision, i.e: 5.2K 660 | ``` 661 | 662 | ### View 663 | 664 | #### `\Multicaret\Acquaintances\Traits\CanView` 665 | 666 | ```php 667 | $user->view($targets); 668 | $user->unview($targets); 669 | $user->toggleView($targets); 670 | $user->hasViewed($target); 671 | $user->viewers()->get(); // default object: App\User:class 672 | $user->viewers(App\Post::class)->get(); 673 | ``` 674 | 675 | #### `\Multicaret\Acquaintances\Traits\CanBeViewed` 676 | 677 | ```php 678 | $object->viewers()->get(); 679 | $object->isViewedBy($user); 680 | $object->viewersCount(); // or as attribute $object->viewers_count 681 | $object->viewersCountReadable(); // return readable number with precision, i.e: 5.2K 682 | ``` 683 | 684 | ### Parameters 685 | 686 | All the above mentioned methods of creating relationships, such as 'follow', 'like', 'unfollow', 'unlike', their syntax 687 | is as follows: 688 | 689 | ```php 690 | follow(array|int|\Illuminate\Database\Eloquent\Model $targets, $class = __CLASS__) 691 | ``` 692 | 693 | So you can call them like this: 694 | 695 | ```php 696 | // id / int|array 697 | $user->follow(1); // targets: 1, $class = App\User 698 | $user->follow(1, App\Post::class); // targets: 1, $class = App\Post 699 | $user->follow([1, 2, 3]); // targets: [1, 2, 3], $class = App\User 700 | 701 | // Model 702 | $post = App\Post::find(7); 703 | $user->follow($post); // targets: $post->id, $class = App\Post 704 | 705 | // Model array 706 | $posts = App\Post::popular()->get(); 707 | $user->follow($posts); // targets: [1, 2, ...], $class = App\Post 708 | ``` 709 | 710 | ### Query relations 711 | 712 | ```php 713 | $followers = $user->followers; 714 | $followers = $user->followers()->where('id', '>', 10)->get(); 715 | $followers = $user->followers()->orderByDesc('id')->get(); 716 | $followers = $user->followers()->paginate(10); 717 | ``` 718 | 719 | You may use the others in the same way. 720 | 721 | ### Working with model 722 | 723 | ```php 724 | use Multicaret\Acquaintances\Models\InteractionRelation; 725 | 726 | // Get most popular object 727 | // 1- All types 728 | $relations = InteractionRelation::popular()->get(); 729 | 730 | // 2- subject_type = App\Post 731 | $relations = InteractionRelation::popular(App\Post::class)->get(); 732 | 733 | // 3- subject_type = App\User 734 | $relations = InteractionRelation::popular('user')->get(); 735 | 736 | // 4- subject_type = App\Post 737 | $relations = InteractionRelation::popular('post')->get(); 738 | 739 | // 5- Pagination 740 | $relations = InteractionRelation::popular(App\Post::class)->paginate(15); 741 | 742 | ``` 743 | 744 | ## Events 745 | 746 | This is the list of the events fired by default for each action: 747 | 748 | | Event name | Fired | 749 | |-------------------------------|-----------------------------------------------| 750 | | acq.friendships.sent | When a friend request is sent | 751 | | acq.friendships.accepted | When a friend request is accepted | 752 | | acq.friendships.denied | When a friend request is denied | 753 | | acq.friendships.blocked | When a friend is blocked | 754 | | acq.friendships.unblocked | When a friend is unblocked | 755 | | acq.friendships.cancelled | When a friendship is cancelled | 756 | | acq.ratings.rate | When a an item or items get Rated | 757 | | acq.ratings.unrate | When a an item or items get unRated | 758 | | acq.vote.up | When a an item or items get upvoted | 759 | | acq.vote.down | When a an item or items get downvoted | 760 | | acq.vote.cancel | When a an item or items get vote cancellation | 761 | | acq.likes.like | When a an item or items get liked | 762 | | acq.likes.unlike | When a an item or items get unliked | 763 | | acq.followships.follow | When a an item or items get followed | 764 | | acq.followships.unfollow | When a an item or items get unfollowed | 765 | | acq.favorites.favorite | When a an item or items get favored | 766 | | acq.favorites.unfavorite | When a an item or items get unfavored | 767 | | acq.reports.report | When a an item or items get reported | 768 | | acq.reports.unreport | When a an item or items get unreported | 769 | | acq.subscriptions.subscribe | When a an item or items get subscribed | 770 | | acq.subscriptions.unsubscribe | When a an item or items get unsubscribed | 771 | | acq.views.view | When a an item or items get viewed | 772 | | acq.views.unview | When a an item or items get unviewed | 773 | 774 | ### Contributing 775 | 776 | See the [CONTRIBUTING](CONTRIBUTING.md) guide. 777 | 778 | The initial version of this library was assisted by the following 779 | repos [laravel-friendships](https://github.com/hootlex/laravel-friendships) 780 | & [laravel-follow](https://github.com/overtrue/laravel-follow). 781 | 782 | ### Change Log 783 | 784 | See the [log](CHANGELOG.md) file. 785 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multicaret/laravel-acquaintances", 3 | "description": "This light package, with no dependencies, gives Eloquent models the ability to manage friendships (with groups). And interactions such as: Likes, favorites, votes, subscribe, follow, ..etc. And it includes advanced rating system.", 4 | "keywords": [ 5 | "laravel", 6 | "friendships", 7 | "followships", 8 | "interactions", 9 | "wish-list", 10 | "like", 11 | "rate", 12 | "rating", 13 | "social-media", 14 | "friend-system", 15 | "friends", 16 | "followers", 17 | "eloquent" 18 | ], 19 | "require": { 20 | "php": ">=8.0" 21 | }, 22 | "require-dev": { 23 | "laravel/laravel": "5.*|^9.0|^10.0|^11.0", 24 | "phpunit/phpunit": "5.*", 25 | "mockery/mockery": "1.0.x-dev", 26 | "fzaninotto/faker": "~1.4", 27 | "codeclimate/php-test-reporter": "^0.3.2", 28 | "doctrine/dbal": "^2.5" 29 | }, 30 | "license": "MIT", 31 | "authors": [ 32 | { 33 | "name": "Mohamed Kawsara", 34 | "email": "mkwsra@gmail.com" 35 | } 36 | ], 37 | "autoload": { 38 | "psr-4": { 39 | "Multicaret\\Acquaintances\\": "src/" 40 | } 41 | }, 42 | "autoload-dev": { 43 | "psr-4": { 44 | "Tests\\": "tests/" 45 | }, 46 | "classmap": [ 47 | "vendor/laravel/laravel/tests/TestCase.php" 48 | ], 49 | "files": [ 50 | "tests/helpers.php" 51 | ] 52 | }, 53 | "extra": { 54 | "laravel": { 55 | "providers": [ 56 | "Multicaret\\Acquaintances\\AcquaintancesServiceProvider" 57 | ] 58 | } 59 | }, 60 | "minimum-stability": "stable" 61 | } 62 | -------------------------------------------------------------------------------- /config/acquaintances.php: -------------------------------------------------------------------------------- 1 | false, 9 | 10 | /* 11 | * Models Related. 12 | */ 13 | 'model_namespace' => (int) app()->version() <= 7 ? 'App' : 'App\Models', 14 | 'models' => [ 15 | /* 16 | * Model name of User model 17 | */ 18 | 'user' => 'User', 19 | /* 20 | * Model name of Interaction Relation model 21 | */ 22 | 'interaction_relation' => \Multicaret\Acquaintances\Models\InteractionRelation::class, 23 | /* 24 | * Model name of Interaction Relation model 25 | */ 26 | 'friendship' => \Multicaret\Acquaintances\Models\Friendship::class, 27 | /* 28 | * Model name of Interaction Relation model 29 | */ 30 | 'friendship_groups' => \Multicaret\Acquaintances\Models\FriendFriendshipGroups::class, 31 | ], 32 | 33 | 'tables' => [ 34 | /* 35 | * Table name of interactions relations. 36 | */ 37 | 'interactions' => 'interactions', 38 | /* 39 | * user foreign key column name within interactions table. 40 | */ 41 | 'interactions_user_id_fk_column_name' => 'user_id', 42 | /* 43 | * user foreign key column type within interactions table. 44 | */ 45 | 'interactions_user_id_fk_column_type' => 'unsignedBigInteger', 46 | /* 47 | * Table name of friendships relations. 48 | */ 49 | 'friendships' => 'friendships', 50 | /* 51 | * Table name of friendship Groups relations. 52 | */ 53 | 'friendship_groups' => 'friendship_groups', 54 | ], 55 | 56 | 'rating' => [ 57 | 'defaults' => [ 58 | 'amount' => 5, 59 | /* 60 | * Default type here is 'general', as longs as you have one criteria of rating a model 61 | * you can ignore this setting. 62 | * It will be the default type of rating of null is provided, if you wish to tweak this type name 63 | * use the value below as you wish. 64 | * 65 | */ 66 | 'type' => 'general', 67 | ], 68 | 'types' => [ 69 | /* Add any other type that your website/application have here, 70 | * the following added rating types are for demonstration purposes only. 71 | * There is no effect on deleting them nor adding to them, however, its a good practice 72 | * to not hard code your rating types, hence, please use this simple array 73 | */ 74 | 'delivery-time', 75 | 'quality', 76 | 'communication', 77 | 'commitment', 78 | ] 79 | ], 80 | 81 | 'friendships_groups' => [ 82 | 'acquaintances' => 0, 83 | 'close_friends' => 1, 84 | 'family' => 2 85 | ], 86 | 87 | ]; 88 | -------------------------------------------------------------------------------- /database/migrations/create_acquaintances_friendship_table.php: -------------------------------------------------------------------------------- 1 | id(); 14 | $table->morphs('sender'); 15 | $table->morphs('recipient'); 16 | $table->string('status')->default('pending')->comment('pending/accepted/denied/blocked/'); 17 | $table->timestamps(); 18 | }); 19 | 20 | } 21 | 22 | public function down() 23 | { 24 | Schema::dropIfExists(config('acquaintances.tables.friendships')); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /database/migrations/create_acquaintances_friendships_groups_table.php: -------------------------------------------------------------------------------- 1 | id(); 17 | 18 | $table->unsignedBigInteger('friendship_id')->unsigned(); 19 | $table->morphs('friend'); 20 | $table->integer('group_id')->unsigned(); 21 | 22 | $table->foreign('friendship_id') 23 | ->references('id') 24 | ->on(config('acquaintances.tables.friendships')) 25 | ->onDelete('cascade'); 26 | 27 | $table->unique(['friendship_id', 'friend_id', 'friend_type', 'group_id'], 'unique'); 28 | 29 | }); 30 | 31 | } 32 | 33 | public function down() 34 | { 35 | Schema::dropIfExists(config('acquaintances.tables.friendship_groups')); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/create_acquaintances_interactions_table.php: -------------------------------------------------------------------------------- 1 | id(); 17 | 18 | $userModel = Interaction::getUserModelName(); 19 | $userModel = (new $userModel); 20 | $userIdFkColumnName = config('acquaintances.tables.interactions_user_id_fk_column_name', 'user_id'); 21 | 22 | $userIdFkType = config('acquaintances.tables.interactions_user_id_fk_column_type'); 23 | $table->{$userIdFkType}($userIdFkColumnName)->index(); 24 | $table->morphs('subject'); 25 | $table->string('relation')->default('follow')->comment('follow/like/subscribe/favorite/upvote/downvote'); 26 | $table->double('relation_value')->nullable(); 27 | $table->string('relation_type')->nullable(); 28 | $table->timestamps(); 29 | 30 | 31 | $table->foreign($userIdFkColumnName) 32 | ->references($userModel->getKeyName()) 33 | ->on($userModel->getTable()) 34 | ->onUpdate('cascade') 35 | ->onDelete('cascade'); 36 | }); 37 | } 38 | 39 | /** 40 | * Reverse the migrations. 41 | */ 42 | public function down() 43 | { 44 | Schema::table(config('acquaintances.tables.interactions', 'interactions'), function ($table) { 45 | $table->dropForeign(config('acquaintances.tables.interactions', 'interactions').'_user_id_foreign'); 46 | }); 47 | 48 | Schema::drop(config('acquaintances.tables.interactions', 'interactions')); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/ 14 | vendor 15 | 16 | 17 | 18 | 19 | ./src 20 | 21 | vendor/ 22 | src/config 23 | src/database/migrations 24 | src/Status.php 25 | src/Follow.php 26 | src/AcquaintancesServiceProvider.php 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/AcquaintancesServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerMigrations(); 20 | } 21 | 22 | /** 23 | * Register Acquaintances's migration files. 24 | * 25 | * @return void 26 | */ 27 | protected function registerMigrations() 28 | { 29 | $config = $this->app['config']['acquaintances']; 30 | $runMigrations = is_null($config['migrations'] ?? null) 31 | ? count(\File::glob(database_path('migrations/*acquaintances*.php'))) === 0 32 | : $config['migrations']; 33 | 34 | if ($runMigrations) { 35 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); 36 | } 37 | } 38 | 39 | /** 40 | * Register any application services. 41 | * 42 | * @return void 43 | */ 44 | public function register() 45 | { 46 | $this->configure(); 47 | $this->offerPublishing(); 48 | } 49 | 50 | /** 51 | * Setup the configuration for Acquaintances. 52 | * 53 | * @return void 54 | */ 55 | protected function configure() 56 | { 57 | $this->mergeConfigFrom( 58 | __DIR__.'/../config/acquaintances.php', 'acquaintances' 59 | ); 60 | } 61 | 62 | /** 63 | * Setup the resource publishing groups for Acquaintances. 64 | * 65 | * @return void 66 | */ 67 | protected function offerPublishing() 68 | { 69 | if ($this->app->runningInConsole()) { 70 | 71 | $this->publishes([ 72 | __DIR__.'/../config/acquaintances.php' => config_path('acquaintances.php'), 73 | ], 'acquaintances-config'); 74 | 75 | $this->publishes($this->updateMigrationDate(), 'acquaintances-migrations'); 76 | } 77 | } 78 | 79 | 80 | /** 81 | * Returns existing migration file if found, else uses the current timestamp. 82 | * 83 | * @return array 84 | */ 85 | protected function updateMigrationDate(): array 86 | { 87 | $tempArray = []; 88 | $path = __DIR__.'/../database/migrations'; 89 | foreach (\File::allFiles($path) as $file) { 90 | $tempArray[$path.'/'.\File::basename($file)] = app()->databasePath()."/migrations/".date('Y_m_d_His').'_'.\File::basename($file); 91 | } 92 | 93 | return $tempArray; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Interaction.php: -------------------------------------------------------------------------------- 1 | 'follow', 39 | 'followers' => 'follow', 40 | 'likes' => 'like', 41 | 'likers' => 'like', 42 | 'favoriters' => 'favorite', 43 | 'favorites' => 'favorite', 44 | 'subscriptions' => 'subscribe', 45 | 'subscribers' => 'subscribe', 46 | 'upvotes' => 'upvote', 47 | 'upvoters' => 'upvote', 48 | 'downvotes' => 'downvote', 49 | 'downvoters' => 'downvote', 50 | 'ratingsTo' => 'rating', 51 | 'raters' => 'rating', 52 | 'views' => 'view', 53 | 'viewers' => 'view', 54 | 'reporters' => 'report', 55 | 'reports' => 'report', 56 | ]; 57 | 58 | /** 59 | * @param \Illuminate\Database\Eloquent\Model $model 60 | * @param string $relation 61 | * @param array|string|\Illuminate\Database\Eloquent\Model $target 62 | * @param string $class 63 | * 64 | * @param array $updates 65 | * 66 | * @return bool 67 | */ 68 | public static function isRelationExists(Model $model, $relation, $target, $class = null, array $updates = []) 69 | { 70 | $target = self::formatTargets($target, $class ?: self::getUserModelName(), $updates); 71 | $userIdFkColumnName = config('acquaintances.tables.interactions_user_id_fk_column_name', 'user_id'); 72 | 73 | return $model->{$relation}($target->classname) 74 | ->where($class ? config('acquaintances.tables.interactions', 75 | 'interactions').'.subject_id' : config('acquaintances.tables.interactions', 76 | 'interactions').'.'.$userIdFkColumnName, head($target->ids)) 77 | ->exists(); 78 | } 79 | 80 | /** 81 | * @param \Illuminate\Database\Eloquent\Model $model 82 | * @param string $relation 83 | * @param array|string|\Illuminate\Database\Eloquent\Model $targets 84 | * @param string $class 85 | * 86 | * @param array $updates 87 | * 88 | * @return array 89 | * @throws \Exception 90 | */ 91 | public static function attachRelations(Model $model, $relation, $targets, $class, array $updates = []) 92 | { 93 | $targets = self::attachPivotsFromRelation($model->{$relation}(), $targets, $class, $updates); 94 | 95 | return $model->{$relation}($targets->classname)->sync($targets->targets, false); 96 | } 97 | 98 | /** 99 | * @param \Illuminate\Database\Eloquent\Model $model 100 | * @param string $relation 101 | * @param array|string|\Illuminate\Database\Eloquent\Model $targets 102 | * @param string $class 103 | * 104 | * @param array $updates 105 | * 106 | * @return array 107 | */ 108 | public static function detachRelations(Model $model, $relation, $targets, $class, array $updates = []) 109 | { 110 | $targets = self::formatTargets($targets, $class, $updates); 111 | 112 | return $model->{$relation}($targets->classname)->detach($targets->ids); 113 | } 114 | 115 | /** 116 | * @param \Illuminate\Database\Eloquent\Model $model 117 | * @param string $relation 118 | * @param array|string|\Illuminate\Database\Eloquent\Model $targets 119 | * @param string $class 120 | * 121 | * @param array $updates 122 | * 123 | * @return array 124 | * @throws \Exception 125 | */ 126 | public static function toggleRelations(Model $model, $relation, $targets, $class, array $updates = []) 127 | { 128 | $targets = self::attachPivotsFromRelation($model->{$relation}(), $targets, $class, $updates); 129 | 130 | return $model->{$relation}($targets->classname)->toggle($targets->targets); 131 | } 132 | 133 | /** 134 | * @param \Illuminate\Database\Eloquent\Relations\MorphToMany $morph 135 | * @param array|string|\Illuminate\Database\Eloquent\Model $targets 136 | * @param string $class 137 | * 138 | * @param array $updates 139 | * 140 | * @return \stdClass 141 | * @throws \Exception 142 | */ 143 | public static function attachPivotsFromRelation(MorphToMany $morph, $targets, $class, array $updates = []) 144 | { 145 | $essentialUpdates = array_merge($updates, [ 146 | 'relation' => self::getRelationTypeFromRelation($morph), 147 | // 'created_at' => Carbon::now(), 148 | ]); 149 | 150 | return self::formatTargets($targets, $class, $essentialUpdates); 151 | } 152 | 153 | /** 154 | * @param array|string|\Illuminate\Database\Eloquent\Model $targets 155 | * @param string $classname 156 | * @param array $update 157 | * 158 | * @return \stdClass 159 | */ 160 | public static function formatTargets($targets, $classname, array $update = []) 161 | { 162 | $result = new stdClass(); 163 | $result->classname = $classname; 164 | 165 | if ( ! is_array($targets)) { 166 | $targets = [$targets]; 167 | } 168 | 169 | $result->ids = array_map(function ($target) use ($result) { 170 | if ($target instanceof Model) { 171 | $result->classname = get_class($target); 172 | 173 | return $target->getKey(); 174 | } 175 | 176 | return intval($target); 177 | }, $targets); 178 | 179 | $result->targets = empty($update) ? $result->ids : array_combine( 180 | $result->ids, 181 | array_pad([], count($result->ids), $update) 182 | ); 183 | 184 | return $result; 185 | } 186 | 187 | /** 188 | * @param \Illuminate\Database\Eloquent\Relations\MorphToMany $relation 189 | * 190 | * @return array 191 | * @throws \Exception 192 | * 193 | */ 194 | protected static function getRelationTypeFromRelation(MorphToMany $relation) 195 | { 196 | if ( ! \array_key_exists($relation->getRelationName(), self::$relationMap)) { 197 | throw new \Exception('Invalid relation definition.'); 198 | } 199 | 200 | return self::$relationMap[$relation->getRelationName()]; 201 | } 202 | 203 | static public function numberToReadable($number, $precision = 1, $divisors = null) 204 | { 205 | $shorthand = ''; 206 | $divisor = pow(1000, 0); 207 | if ( ! isset($divisors)) { 208 | $divisors = [ 209 | $divisor => $shorthand, // 1000^0 == 1 210 | pow(1000, 1) => 'K', // Thousand 211 | pow(1000, 2) => 'M', // Million 212 | pow(1000, 3) => 'B', // Billion 213 | pow(1000, 4) => 'T', // Trillion 214 | pow(1000, 5) => 'Qa', // Quadrillion 215 | pow(1000, 6) => 'Qi', // Quintillion 216 | ]; 217 | } 218 | foreach ($divisors as $divisor => $shorthand) { 219 | if (abs($number) < ($divisor * 1000)) { 220 | break; 221 | } 222 | } 223 | 224 | $precision = empty($shorthand) ? 0 : $precision; 225 | 226 | return number_format($number / $divisor, $precision) . $shorthand; 227 | } 228 | 229 | 230 | public static function getFullModelName($modelClassName) 231 | { 232 | if (class_exists($modelClassName)) { 233 | return Str::studly($modelClassName); 234 | } 235 | 236 | $namespace = config('acquaintances.model_namespace', 'App'); 237 | 238 | return empty($namespace) 239 | ? Str::studly($modelClassName) 240 | : $namespace.'\\'.Str::studly($modelClassName); 241 | } 242 | 243 | public static function getUserModelName() 244 | { 245 | return Interaction::getFullModelName( 246 | config( 247 | 'acquaintances.user_model_class_name', 248 | config('acquaintances.models.user', 'User') 249 | ) 250 | ); 251 | } 252 | 253 | public static function getInteractionRelationModelName() 254 | { 255 | return Interaction::getFullModelName( 256 | config( 257 | 'acquaintances.models.interaction_relation', 258 | \Multicaret\Acquaintances\Models\InteractionRelation::class 259 | ) 260 | ); 261 | } 262 | 263 | public static function getFriendshipModelName() 264 | { 265 | return Interaction::getFullModelName( 266 | config( 267 | 'acquaintances.models.friendship', 268 | \Multicaret\Acquaintances\Models\Friendship::class 269 | ) 270 | ); 271 | } 272 | 273 | public static function getFriendshipGroupsModelName() 274 | { 275 | return Interaction::getFullModelName( 276 | config( 277 | 'acquaintances.models.friendship_groups', 278 | \Multicaret\Acquaintances\Models\FriendshipGroups::class 279 | ) 280 | ); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/Models/FriendFriendshipGroups.php: -------------------------------------------------------------------------------- 1 | table = config('acquaintances.tables.friendship_groups'); 31 | 32 | parent::__construct($attributes); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Models/Friendship.php: -------------------------------------------------------------------------------- 1 | table = config('acquaintances.tables.friendships'); 25 | 26 | parent::__construct($attributes); 27 | } 28 | 29 | /** 30 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 31 | */ 32 | public function sender() 33 | { 34 | return $this->morphTo('sender'); 35 | } 36 | 37 | /** 38 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 39 | */ 40 | public function recipient() 41 | { 42 | return $this->morphTo('recipient'); 43 | } 44 | 45 | /** 46 | * @return \Illuminate\Database\Eloquent\Relations\hasMany 47 | */ 48 | public function groups() 49 | { 50 | return $this->hasMany(FriendFriendshipGroups::class, 'friendship_id'); 51 | } 52 | 53 | /** 54 | * @param Model $recipient 55 | * 56 | * @return $this 57 | */ 58 | public function fillRecipient($recipient) 59 | { 60 | return $this->fill([ 61 | 'recipient_id' => $recipient->getKey(), 62 | 'recipient_type' => $recipient->getMorphClass() 63 | ]); 64 | } 65 | 66 | /** 67 | * @param $query 68 | * @param Model $model 69 | * 70 | * @return \Illuminate\Database\Eloquent\Builder 71 | */ 72 | public function scopeWhereRecipient($query, $model) 73 | { 74 | return $query->where('recipient_id', $model->getKey()) 75 | ->where('recipient_type', $model->getMorphClass()); 76 | } 77 | 78 | /** 79 | * @param $query 80 | * @param Model $model 81 | * 82 | * @return \Illuminate\Database\Eloquent\Builder 83 | */ 84 | public function scopeWhereSender($query, $model) 85 | { 86 | return $query->where('sender_id', $model->getKey()) 87 | ->where('sender_type', $model->getMorphClass()); 88 | } 89 | 90 | /** 91 | * @param $query 92 | * @param Model $model 93 | * @param string $groupSlug 94 | * 95 | * @return \Illuminate\Database\Eloquent\Builder 96 | */ 97 | public function scopeWhereGroup($query, $model, $groupSlug) 98 | { 99 | 100 | $groupsPivotTable = config('acquaintances.tables.friendship_groups'); 101 | $friendsPivotTable = config('acquaintances.tables.friendships'); 102 | $groupsAvailable = config('acquaintances.friendships_groups', []); 103 | 104 | if ('' !== $groupSlug && isset($groupsAvailable[$groupSlug])) { 105 | 106 | $groupId = $groupsAvailable[$groupSlug]; 107 | 108 | $query->join($groupsPivotTable, 109 | function ($join) use ($groupsPivotTable, $friendsPivotTable, $groupId, $model) { 110 | $join->on($groupsPivotTable.'.friendship_id', '=', $friendsPivotTable.'.id') 111 | ->where($groupsPivotTable.'.group_id', '=', $groupId) 112 | ->where(function ($query) use ($groupsPivotTable, $friendsPivotTable, $model) { 113 | $query->where($groupsPivotTable.'.friend_id', '!=', $model->getKey()) 114 | ->where($groupsPivotTable.'.friend_type', '=', $model->getMorphClass()); 115 | }) 116 | ->orWhere($groupsPivotTable.'.friend_type', '!=', $model->getMorphClass()); 117 | }); 118 | 119 | } 120 | 121 | return $query; 122 | 123 | } 124 | 125 | /** 126 | * @param $query 127 | * @param Model $sender 128 | * @param Model $recipient 129 | * 130 | * @return \Illuminate\Database\Eloquent\Builder 131 | */ 132 | public function scopeBetweenModels($query, $sender, $recipient) 133 | { 134 | $query->where(function ($queryIn) use ($sender, $recipient) { 135 | $queryIn->where(function ($q) use ($sender, $recipient) { 136 | $q->whereSender($sender)->whereRecipient($recipient); 137 | })->orWhere(function ($q) use ($sender, $recipient) { 138 | $q->whereSender($recipient)->whereRecipient($sender); 139 | }); 140 | }); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Models/InteractionRelation.php: -------------------------------------------------------------------------------- 1 | morphTo('subject'); 33 | } 34 | 35 | /** 36 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 37 | */ 38 | public function user() 39 | { 40 | return $this->belongsTo(Interaction::getUserModelName()); 41 | } 42 | 43 | /** 44 | * @param \Illuminate\Database\Eloquent\Builder $query 45 | * @param string|null $type 46 | * 47 | * @return \Illuminate\Database\Eloquent\Builder 48 | */ 49 | public function scopePopular($query, $type = null) 50 | { 51 | $query->select('subject_id', 'subject_type', \DB::raw('COUNT(*) AS count')) 52 | ->groupBy('subject_id', 'subject_type') 53 | ->orderByDesc('count'); 54 | 55 | if ($type) { 56 | $query->where('subject_type', $this->normalizeSubjectType($type)); 57 | } 58 | 59 | return $query; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function getTable() 66 | { 67 | if ( ! $this->table) { 68 | $this->table = config('acquaintances.tables.interactions', 'interactions'); 69 | } 70 | 71 | return parent::getTable(); 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function getDates() 78 | { 79 | return [parent::CREATED_AT]; 80 | } 81 | 82 | /** 83 | * @param string $type 84 | * 85 | * @return string 86 | * @throws \InvalidArgumentException 87 | * 88 | */ 89 | protected function normalizeSubjectType($type) 90 | { 91 | $morphMap = Relation::morphMap(); 92 | 93 | if ( ! empty($morphMap) && in_array($type, $morphMap, true)) { 94 | $type = array_search($type, $morphMap, true); 95 | } 96 | 97 | if (class_exists($type)) { 98 | return $type; 99 | } 100 | 101 | $namespace = config('acquaintances.model_namespace', 'App'); 102 | 103 | $modelName = $namespace.'\\'.Str::studly($type); 104 | 105 | if ( ! class_exists($modelName)) { 106 | throw new InvalidArgumentException("Model {$modelName} not exists. Please check your config 'acquaintances.model_namespace'."); 107 | } 108 | 109 | return $modelName; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Status.php: -------------------------------------------------------------------------------- 1 | morphToMany(Interaction::getUserModelName(), 'subject', 34 | config('acquaintances.tables.interactions')) 35 | ->wherePivot('relation', '=', Interaction::RELATION_FAVORITE) 36 | ->withPivot(...Interaction::$pivotColumns) 37 | ->using(Interaction::getInteractionRelationModelName()) 38 | ->withTimestamps(); 39 | } 40 | 41 | public function favoritersCount() 42 | { 43 | return $this->favoriters()->count(); 44 | } 45 | 46 | public function getFavoritersCountAttribute() 47 | { 48 | return $this->favoritersCount(); 49 | } 50 | 51 | public function favoritersCountReadable($precision = 1, $divisors = null) 52 | { 53 | return Interaction::numberToReadable($this->favoritersCount(), $precision, $divisors); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Traits/CanBeFollowed.php: -------------------------------------------------------------------------------- 1 | morphToMany(Interaction::getUserModelName(), 'subject', 34 | config('acquaintances.tables.interactions')) 35 | ->wherePivot('relation', '=', Interaction::RELATION_FOLLOW) 36 | ->withPivot(...Interaction::$pivotColumns) 37 | ->using(Interaction::getInteractionRelationModelName()) 38 | ->withTimestamps(); 39 | } 40 | 41 | public function followersCount() 42 | { 43 | return $this->followers()->count(); 44 | } 45 | 46 | public function getFollowersCountAttribute() 47 | { 48 | return $this->followersCount(); 49 | } 50 | 51 | public function followersCountReadable($precision = 1, $divisors = null) 52 | { 53 | return Interaction::numberToReadable($this->followersCount(), $precision, $divisors); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Traits/CanBeLiked.php: -------------------------------------------------------------------------------- 1 | morphToMany(Interaction::getUserModelName(), 'subject', 33 | config('acquaintances.tables.interactions')) 34 | ->wherePivot('relation', '=', Interaction::RELATION_LIKE) 35 | ->withPivot(...Interaction::$pivotColumns) 36 | ->using(Interaction::getInteractionRelationModelName()) 37 | ->withTimestamps(); 38 | } 39 | 40 | /** 41 | * Alias of likers. 42 | * 43 | * @return mixed 44 | */ 45 | public function fans() 46 | { 47 | return $this->likers(); 48 | } 49 | 50 | public function likersCount() 51 | { 52 | return $this->likers()->count(); 53 | } 54 | 55 | public function getLikersCountAttribute() 56 | { 57 | return $this->likersCount(); 58 | } 59 | 60 | public function likersCountFormatted($precision = 1, $divisors = null) 61 | { 62 | return Interaction::numberToReadable($this->likersCount(), $precision, $divisors); 63 | } 64 | 65 | /** 66 | * Alias of likersCountFormatted. 67 | * 68 | * @param int $precision 69 | * @param null $divisors 70 | * 71 | * @return string 72 | */ 73 | public function likersCountReadable($precision = 1, $divisors = null) 74 | { 75 | return $this->likersCountFormatted($precision, $divisors); 76 | } 77 | 78 | public function getLikersCountReadableAttribute() 79 | { 80 | return $this->likersCount(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Traits/CanBeRated.php: -------------------------------------------------------------------------------- 1 | setRatedType(config('acquaintances.rating.defaults.type')); 32 | } elseif ( ! empty($type)) { 33 | $this->setRatedType($type); 34 | } 35 | 36 | /* 37 | * Todo: [minor feature] Check if the passed type exists in types array 38 | * config('acquaintances.rating.types') 39 | */ 40 | 41 | return self::$ratedType; 42 | } 43 | 44 | /** 45 | * Check if a model is isRatedBy by given user. 46 | * 47 | * @param int|array|\Illuminate\Database\Eloquent\Model $user 48 | * 49 | * @return bool 50 | */ 51 | public function isRatedBy($user) 52 | { 53 | return Interaction::isRelationExists($this, 'raters', $user); 54 | } 55 | 56 | /** 57 | * Return Raters. 58 | * 59 | * @param bool $isAllTypes 60 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 61 | */ 62 | public function raters($isAllTypes = false) 63 | { 64 | $relation = $this->morphToMany(Interaction::getUserModelName(), 'subject', 65 | config('acquaintances.tables.interactions')) 66 | ->wherePivot('relation', '=', Interaction::RELATION_RATE) 67 | ->using(Interaction::getInteractionRelationModelName()) 68 | ->withTimestamps(); 69 | 70 | if ( ! $isAllTypes) { 71 | $relation = $relation->wherePivot('relation_type', '=', $this->ratedType()); 72 | } 73 | 74 | return $relation->withPivot(...Interaction::$pivotColumns); 75 | } 76 | 77 | /** 78 | * Return ratings as interaction items with lazy loading `user` relation 79 | * for a specific type or for the default type. 80 | * 81 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 82 | */ 83 | public function ratings() 84 | { 85 | return $this->hasMany(Interaction::getInteractionRelationModelName(), 'subject_id')->with('user'); 86 | } 87 | 88 | /** 89 | * Return ratings as interaction items without lazy loading `user` relation 90 | * 91 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 92 | */ 93 | public function ratingsPure() 94 | { 95 | return $this->hasMany(Interaction::getInteractionRelationModelName(), 'subject_id'); 96 | } 97 | 98 | public function averageRating($ratingType = null) 99 | { 100 | $this->ratedType($ratingType); 101 | 102 | return $this->raters()->avg('relation_value'); 103 | } 104 | 105 | public function averageRatingAllTypes() 106 | { 107 | return $this->raters(true)->avg('relation_value'); 108 | } 109 | 110 | public function sumRating($ratingType = null) 111 | { 112 | $this->ratedType($ratingType); 113 | 114 | return $this->raters()->sum('relation_value'); 115 | } 116 | 117 | public function sumRatingAllTypes() 118 | { 119 | return $this->raters(true)->sum('relation_value'); 120 | } 121 | 122 | public function sumRatingReadable($ratingType = null, $precision = 1, $divisors = null) 123 | { 124 | return Interaction::numberToReadable($this->sumRating($ratingType), $precision, $divisors); 125 | } 126 | 127 | public function sumRatingAllTypesReadable($precision = 1, $divisors = null) 128 | { 129 | return Interaction::numberToReadable($this->sumRatingAllTypes(), $precision, $divisors); 130 | } 131 | 132 | public function userAverageRating($ratingType = null) 133 | { 134 | $this->ratedType($ratingType); 135 | 136 | return $this->raters()->where('user_id', \Auth::id())->avg('relation_value'); 137 | } 138 | 139 | public function userAverageRatingAllTypes() 140 | { 141 | return $this->raters(true)->where('user_id', \Auth::id())->avg('relation_value'); 142 | } 143 | 144 | public function userSumRating($ratingType = null) 145 | { 146 | $this->ratedType($ratingType); 147 | 148 | return $this->raters()->where('user_id', \Auth::id())->sum('relation_value'); 149 | } 150 | 151 | public function userSumRatingAllTypes() 152 | { 153 | return $this->raters(true)->where('user_id', \Auth::id())->sum('relation_value'); 154 | } 155 | 156 | public function userSumRatingReadable($ratingType = null, $precision = 1, $divisors = null) 157 | { 158 | return Interaction::numberToReadable($this->userSumRating($ratingType), $precision, $divisors); 159 | } 160 | 161 | 162 | public function userSumRatingAllTypesReadable($precision = 1, $divisors = null) 163 | { 164 | return Interaction::numberToReadable($this->userSumRatingAllTypes(), $precision, $divisors); 165 | } 166 | 167 | /** 168 | * Calculating the percentage based on the passed coefficient 169 | * Taking the default value of $max from within the config file 170 | * 171 | * @param null $max 172 | * 173 | * @param null $ratingType 174 | * 175 | * @return float|int 176 | */ 177 | public function ratingPercent($max = null, $ratingType = null) 178 | { 179 | $this->ratedType($ratingType); 180 | if (empty($max)) { 181 | $max = config('acquaintances.rating.defaults.amount'); 182 | } 183 | $quantity = $this->raters()->count(); 184 | $total = $this->sumRating(); 185 | 186 | return ($quantity * $max) > 0 ? $total / (($quantity * $max) / 100) : 0; 187 | } 188 | 189 | /** 190 | * Calculating the percentage based on the passed coefficient 191 | * Taking the default value of $max from within the config file 192 | * 193 | * @param null $max 194 | * 195 | * 196 | * @return float|int 197 | */ 198 | public function ratingPercentAllTypes($max = null) 199 | { 200 | if (empty($max)) { 201 | $max = config('acquaintances.rating.defaults.amount'); 202 | } 203 | $quantity = $this->raters(true)->count(); 204 | $total = $this->sumRating(); 205 | 206 | return ($quantity * $max) > 0 ? $total / (($quantity * $max) / 100) : 0; 207 | } 208 | 209 | public function getAverageRatingAttribute() 210 | { 211 | return $this->averageRating(); 212 | } 213 | 214 | public function getAverageRatingAllTypesAttribute() 215 | { 216 | return $this->averageRatingAllTypes(); 217 | } 218 | 219 | public function getSumRatingAttribute() 220 | { 221 | return $this->sumRating(); 222 | } 223 | 224 | public function getSumRatingAllTypesAttribute() 225 | { 226 | return $this->sumRatingAllTypes(); 227 | } 228 | 229 | public function getUserAverageRatingAttribute() 230 | { 231 | return $this->userAverageRating(); 232 | } 233 | 234 | public function getUserAverageRatingAllTypesAttribute() 235 | { 236 | return $this->userAverageRatingAllTypes(); 237 | } 238 | 239 | public function getUserSumRatingAttribute() 240 | { 241 | return $this->userSumRating(); 242 | } 243 | 244 | public function getUserSumRatingAllTypesAttribute() 245 | { 246 | return $this->userSumRatingAllTypes(); 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /src/Traits/CanBeReported.php: -------------------------------------------------------------------------------- 1 | morphToMany( 34 | Interaction::getUserModelName(), 35 | 'subject', 36 | config('acquaintances.tables.interactions') 37 | ) 38 | ->wherePivot('relation', '=', Interaction::RELATION_REPORT) 39 | ->withPivot(...Interaction::$pivotColumns) 40 | ->using(Interaction::getInteractionRelationModelName()) 41 | ->withTimestamps(); 42 | } 43 | 44 | public function reportersCount() 45 | { 46 | return $this->reporters()->count(); 47 | } 48 | 49 | public function getReportersCountAttribute() 50 | { 51 | return $this->reportersCount(); 52 | } 53 | 54 | public function reportersCountReadable($precision = 1, $divisors = null) 55 | { 56 | return Interaction::numberToReadable($this->reportersCount(), $precision, $divisors); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Traits/CanBeSubscribed.php: -------------------------------------------------------------------------------- 1 | morphToMany(Interaction::getUserModelName(), 'subject', 33 | config('acquaintances.tables.interactions')) 34 | ->wherePivot('relation', '=', Interaction::RELATION_SUBSCRIBE) 35 | ->withPivot(...Interaction::$pivotColumns) 36 | ->using(Interaction::getInteractionRelationModelName()) 37 | ->withTimestamps(); 38 | } 39 | 40 | public function subscribersCount() 41 | { 42 | return $this->subscribers()->count(); 43 | } 44 | 45 | public function getSubscribersCountAttribute() 46 | { 47 | return $this->subscribersCount(); 48 | } 49 | 50 | public function subscribersCountReadable($precision = 1, $divisors = null) 51 | { 52 | return Interaction::numberToReadable($this->subscribersCount(), $precision, $divisors); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Traits/CanBeViewed.php: -------------------------------------------------------------------------------- 1 | morphToMany(Interaction::getUserModelName(), 'subject', 33 | config('acquaintances.tables.interactions')) 34 | ->wherePivot('relation', '=', Interaction::RELATION_VIEW) 35 | ->withPivot(...Interaction::$pivotColumns) 36 | ->using(Interaction::getInteractionRelationModelName()) 37 | ->withTimestamps(); 38 | } 39 | 40 | public function viewersCount() 41 | { 42 | return $this->viewers()->count(); 43 | } 44 | 45 | public function getViewersCountAttribute() 46 | { 47 | return $this->viewersCount(); 48 | } 49 | 50 | public function viewersCountFormatted($precision = 1, $divisors = null) 51 | { 52 | return Interaction::numberToReadable($this->viewersCount(), $precision, $divisors); 53 | } 54 | 55 | /** 56 | * Alias of viewersCountFormatted. 57 | * 58 | * @param int $precision 59 | * @param null $divisors 60 | * 61 | * @return string 62 | */ 63 | public function viewersCountReadable($precision = 1, $divisors = null) 64 | { 65 | return $this->viewersCountFormatted($precision, $divisors); 66 | } 67 | 68 | public function getViewersCountReadableAttribute() 69 | { 70 | return $this->viewersCount(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Traits/CanBeVoted.php: -------------------------------------------------------------------------------- 1 | morphToMany(Interaction::getUserModelName(), 'subject', 57 | config('acquaintances.tables.interactions')) 58 | ->wherePivotIn('relation', [Interaction::RELATION_UPVOTE, Interaction::RELATION_DOWNVOTE]) 59 | ->withPivot(...Interaction::$pivotColumns) 60 | ->using(Interaction::getInteractionRelationModelName()) 61 | ->withTimestamps(); 62 | } 63 | 64 | /** 65 | * Return upvoters. 66 | * 67 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 68 | */ 69 | public function upvoters() 70 | { 71 | return $this->morphToMany(Interaction::getUserModelName(), 'subject', 72 | config('acquaintances.tables.interactions')) 73 | ->wherePivot('relation', '=', Interaction::RELATION_UPVOTE) 74 | ->withPivot(...Interaction::$pivotColumns) 75 | ->using(Interaction::getInteractionRelationModelName()) 76 | ->withTimestamps(); 77 | } 78 | 79 | /** 80 | * Return downvoters. 81 | * 82 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 83 | */ 84 | public function downvoters() 85 | { 86 | return $this->morphToMany(Interaction::getUserModelName(), 'subject', 87 | config('acquaintances.tables.interactions')) 88 | ->wherePivot('relation', '=', Interaction::RELATION_DOWNVOTE) 89 | ->withPivot(...Interaction::$pivotColumns) 90 | ->using(Interaction::getInteractionRelationModelName()) 91 | ->withTimestamps(); 92 | } 93 | 94 | public function upvotersCount() 95 | { 96 | return $this->upvoters()->count(); 97 | } 98 | 99 | public function getUpvotersCountAttribute() 100 | { 101 | return $this->upvotersCount(); 102 | } 103 | 104 | public function upvotersCountReadable($precision = 1, $divisors = null) 105 | { 106 | return Interaction::numberToReadable($this->upvotersCount(), $precision, $divisors); 107 | } 108 | 109 | public function downvotersCount() 110 | { 111 | return $this->downvoters()->count(); 112 | } 113 | 114 | public function getDownvotersCountAttribute() 115 | { 116 | return $this->downvotersCount(); 117 | } 118 | 119 | public function downvotersCountReadable($precision = 1, $divisors = null) 120 | { 121 | return Interaction::numberToReadable($this->downvotersCount(), $precision, $divisors); 122 | } 123 | 124 | public function votersCount() 125 | { 126 | return $this->voters()->count(); 127 | } 128 | 129 | public function getVotersCountAttribute() 130 | { 131 | return $this->votersCount(); 132 | } 133 | 134 | public function votersCountReadable($precision = 1, $divisors = null) 135 | { 136 | return Interaction::numberToReadable($this->votersCount(), $precision, $divisors); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/Traits/CanFavorite.php: -------------------------------------------------------------------------------- 1 | morphedByMany($class, 'subject', 80 | config('acquaintances.tables.interactions')) 81 | ->wherePivot('relation', '=', Interaction::RELATION_FAVORITE) 82 | ->withPivot(...Interaction::$pivotColumns) 83 | ->using(Interaction::getInteractionRelationModelName()) 84 | ->withTimestamps(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Traits/CanFollow.php: -------------------------------------------------------------------------------- 1 | morphedByMany( 84 | $class, 85 | 'subject', 86 | config('acquaintances.tables.interactions') 87 | ) 88 | ->wherePivot('relation', '=', Interaction::RELATION_FOLLOW) 89 | ->withPivot(...Interaction::$pivotColumns) 90 | ->using(Interaction::getInteractionRelationModelName()) 91 | ->withTimestamps(); 92 | } 93 | 94 | /** 95 | * Return following count. 96 | * 97 | * @return int 98 | */ 99 | public function followingCount() 100 | { 101 | return $this->followings()->count(); 102 | } 103 | 104 | /** 105 | * Get followingCount attribute 106 | * 107 | * @return int 108 | */ 109 | public function getFollowingCountAttribute() 110 | { 111 | return $this->followingCount(); 112 | } 113 | 114 | /** 115 | * Return followingCount in a readable format. 116 | * 117 | * @param integer $precision 118 | * @param string $divisors 119 | * @return int|float|string 120 | */ 121 | public function followingCountReadable(int $precision = 1, string $divisors = null) 122 | { 123 | return Interaction::numberToReadable($this->followingCount(), $precision, $divisors); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Traits/CanLike.php: -------------------------------------------------------------------------------- 1 | morphedByMany($class, 'subject', 84 | config('acquaintances.tables.interactions')) 85 | ->wherePivot('relation', '=', Interaction::RELATION_LIKE) 86 | ->withPivot(...Interaction::$pivotColumns) 87 | ->using(Interaction::getInteractionRelationModelName()) 88 | ->withTimestamps(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Traits/CanRate.php: -------------------------------------------------------------------------------- 1 | setRateType(config('acquaintances.rating.defaults.type')); 33 | } elseif ( ! empty($type)) { 34 | $this->setRateType($type); 35 | } 36 | 37 | /* 38 | * Todo: [minor feature] Check if the passed type exists in types array 39 | * config('acquaintances.rating.types') 40 | */ 41 | 42 | return self::$rateType; 43 | } 44 | 45 | /** 46 | * Rate an item or items. 47 | * 48 | * @param int|array|\Illuminate\Database\Eloquent\Model $targets 49 | * @param float $amount 50 | * @param null $ratingType 51 | * @param string $class 52 | * 53 | * @return array 54 | * 55 | * @throws \Exception 56 | */ 57 | public function rate($targets, float $amount, $ratingType = null, $class = __CLASS__): array 58 | { 59 | Event::dispatch('acq.ratings.rate', [$this, $targets]); 60 | 61 | $attachRelations = Interaction::attachRelations($this, 'ratingsTo', $targets, $class, [ 62 | 'relation_value' => $amount, 63 | 'relation_type' => $this->rateType($ratingType), 64 | ]); 65 | self::$rateType = config('acquaintances.rating.defaults.type'); 66 | 67 | return $attachRelations; 68 | } 69 | 70 | /** 71 | * UnRate an item or items. 72 | * 73 | * @param int|array|\Illuminate\Database\Eloquent\Model $targets 74 | * @param null $ratingType 75 | * @param string $class 76 | * 77 | * @return array 78 | * @throws \Exception 79 | */ 80 | public function unrate($targets, $ratingType = null, $class = __CLASS__) 81 | { 82 | Event::dispatch('acq.ratings.unrate', [$this, $targets]); 83 | 84 | return Interaction::detachRelations($this, 'ratingsTo', $targets, $class, [ 85 | 'relation_type' => $this->rateType($ratingType), 86 | ]); 87 | } 88 | 89 | /** 90 | * Toggle Rate an item or items. 91 | * 92 | * @param int|array|\Illuminate\Database\Eloquent\Model $targets 93 | * @param float $amount 94 | * @param null $ratingType 95 | * @param string $class 96 | * 97 | * @return array 98 | * 99 | * @throws \Exception 100 | */ 101 | public function toggleRate($targets, float $amount, $ratingType = null, $class = __CLASS__) 102 | { 103 | return Interaction::toggleRelations($this, 'ratingsTo', $targets, $class, [ 104 | 'relation_value' => $amount, 105 | 'relation_type' => $this->rateType($ratingType), 106 | ]); 107 | } 108 | 109 | /** 110 | * Check if a model is rated by given model. 111 | * 112 | * @param int|array|\Illuminate\Database\Eloquent\Model $target 113 | * @param null $ratingType 114 | * @param string $class 115 | * 116 | * @return bool 117 | */ 118 | public function hasRated($target, $ratingType = null, $class = __CLASS__) 119 | { 120 | return Interaction::isRelationExists($this, 'ratingsTo', $target, $class, [ 121 | 'relation_type' => $this->rateType($ratingType), 122 | ]); 123 | } 124 | 125 | 126 | /** 127 | * Return item likes. 128 | * 129 | * @param string $class 130 | * 131 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 132 | */ 133 | public function ratingsTo($class = __CLASS__) 134 | { 135 | $relation = $this->morphedByMany($class, 'subject', 136 | config('acquaintances.tables.interactions')) 137 | ->wherePivot('relation', '=', Interaction::RELATION_RATE) 138 | ->using(Interaction::getInteractionRelationModelName()) 139 | ->withTimestamps(); 140 | 141 | $relation = $relation->wherePivot('relation_type', '=', $this->rateType()); 142 | 143 | return $relation->withPivot(...Interaction::$pivotColumns); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/Traits/CanReport.php: -------------------------------------------------------------------------------- 1 | morphedByMany( 80 | $class, 81 | 'subject', 82 | config('acquaintances.tables.interactions') 83 | ) 84 | ->wherePivot('relation', '=', Interaction::RELATION_REPORT) 85 | ->withPivot(...Interaction::$pivotColumns) 86 | ->using(Interaction::getInteractionRelationModelName()) 87 | ->withTimestamps(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Traits/CanSubscribe.php: -------------------------------------------------------------------------------- 1 | morphedByMany($class, 'subject', 84 | config('acquaintances.tables.interactions')) 85 | ->wherePivot('relation', '=', Interaction::RELATION_SUBSCRIBE) 86 | ->withPivot(...Interaction::$pivotColumns) 87 | ->using(Interaction::getInteractionRelationModelName()) 88 | ->withTimestamps(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Traits/CanView.php: -------------------------------------------------------------------------------- 1 | morphedByMany($class, 'subject', 84 | config('acquaintances.tables.interactions')) 85 | ->wherePivot('relation', '=', Interaction::RELATION_VIEW) 86 | ->withPivot(...Interaction::$pivotColumns) 87 | ->using(Interaction::getInteractionRelationModelName()) 88 | ->withTimestamps(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Traits/CanVote.php: -------------------------------------------------------------------------------- 1 | cancelVote($targets); 29 | 30 | return Interaction::attachRelations($this, Str::plural($type), $targets, $class); 31 | } 32 | 33 | /** 34 | * Upvote an item or items. 35 | * 36 | * @param int|array|\Illuminate\Database\Eloquent\Model $targets 37 | * @param string $class 38 | * 39 | * @return array 40 | * 41 | * @throws \Exception 42 | */ 43 | public function upvote($targets, $class = __CLASS__) 44 | { 45 | Event::dispatch('acq.vote.up', [$this, $targets]); 46 | 47 | return $this->vote($targets, 'upvote', $class); 48 | } 49 | 50 | /** 51 | * Downvote an item or items. 52 | * 53 | * @param int|array|\Illuminate\Database\Eloquent\Model $targets 54 | * @param string $class 55 | * 56 | * @return array 57 | * 58 | * @throws \Exception 59 | */ 60 | public function downvote($targets, $class = __CLASS__) 61 | { 62 | Event::dispatch('acq.vote.down', [$this, $targets]); 63 | 64 | return $this->vote($targets, 'downvote', $class); 65 | } 66 | 67 | /** 68 | * Cancel vote for an item or items. 69 | * 70 | * @param int|array|\Illuminate\Database\Eloquent\Model $targets 71 | * @param string $class 72 | * 73 | * @return Multicaret\Acquaintances\Traits\CanVote 74 | */ 75 | public function cancelVote($targets, $class = __CLASS__) 76 | { 77 | Interaction::detachRelations($this, 'upvotes', $targets, $class); 78 | Interaction::detachRelations($this, 'downvotes', $targets, $class); 79 | Event::dispatch('acq.vote.cancel', [$this, $targets]); 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Check if a model is upvoted a given model. 86 | * 87 | * @param int|array|\Illuminate\Database\Eloquent\Model $target 88 | * @param string $class 89 | * 90 | * @return bool 91 | */ 92 | public function hasUpvoted($target, $class = __CLASS__) 93 | { 94 | return Interaction::isRelationExists($this, 'upvotes', $target, $class); 95 | } 96 | 97 | /** 98 | * Check if user is downvoted given item. 99 | * 100 | * @param int|array|\Illuminate\Database\Eloquent\Model $target 101 | * @param string $class 102 | * 103 | * @return bool 104 | */ 105 | public function hasDownvoted($target, $class = __CLASS__) 106 | { 107 | return Interaction::isRelationExists($this, 'downvotes', $target, $class); 108 | } 109 | 110 | /** 111 | * Return item votes. 112 | * 113 | * @param string $class 114 | * 115 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 116 | */ 117 | public function votes($class = __CLASS__) 118 | { 119 | return $this->morphedByMany($class, 'subject', 120 | config('acquaintances.tables.interactions')) 121 | ->wherePivotIn('relation', [Interaction::RELATION_UPVOTE, Interaction::RELATION_DOWNVOTE]) 122 | ->withPivot(...Interaction::$pivotColumns) 123 | ->using(Interaction::getInteractionRelationModelName()) 124 | ->withTimestamps(); 125 | } 126 | 127 | /** 128 | * Return item upvotes. 129 | * 130 | * @param string $class 131 | * 132 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 133 | */ 134 | public function upvotes($class = __CLASS__) 135 | { 136 | return $this->morphedByMany($class, 'subject', 137 | config('acquaintances.tables.interactions')) 138 | ->wherePivot('relation', '=', Interaction::RELATION_UPVOTE) 139 | ->withPivot(...Interaction::$pivotColumns) 140 | ->using(Interaction::getInteractionRelationModelName()) 141 | ->withTimestamps(); 142 | } 143 | 144 | /** 145 | * Return item downvotes. 146 | * 147 | * @param string $class 148 | * 149 | * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany 150 | */ 151 | public function downvotes($class = __CLASS__) 152 | { 153 | return $this->morphedByMany($class, 'subject', 154 | config('acquaintances.tables.interactions')) 155 | ->wherePivot('relation', '=', Interaction::RELATION_DOWNVOTE) 156 | ->withPivot(...Interaction::$pivotColumns) 157 | ->using(Interaction::getInteractionRelationModelName()) 158 | ->withTimestamps(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Traits/Friendable.php: -------------------------------------------------------------------------------- 1 | canBefriend($recipient)) { 27 | return false; 28 | } 29 | 30 | $friendshipModelName = Interaction::getFriendshipModelName(); 31 | $friendship = (new $friendshipModelName)->fillRecipient($recipient)->fill([ 32 | 'status' => Status::PENDING, 33 | ]); 34 | 35 | $this->friends()->save($friendship); 36 | 37 | Event::dispatch('acq.friendships.sent', [$this, $recipient]); 38 | 39 | return $friendship; 40 | } 41 | 42 | /** 43 | * @param Model $recipient 44 | * 45 | * @return bool 46 | */ 47 | public function unfriend(Model $recipient) 48 | { 49 | Event::dispatch('acq.friendships.cancelled', [$this, $recipient]); 50 | 51 | return $this->findFriendship($recipient)->delete(); 52 | } 53 | 54 | /** 55 | * @param Model $recipient 56 | * 57 | * @return bool 58 | */ 59 | public function hasFriendRequestFrom(Model $recipient) 60 | { 61 | return $this->findFriendship($recipient)->whereSender($recipient)->whereStatus(Status::PENDING)->exists(); 62 | } 63 | 64 | /** 65 | * @param Model $recipient 66 | * 67 | * @return bool 68 | */ 69 | public function hasSentFriendRequestTo(Model $recipient) 70 | { 71 | $friendshipModelName = Interaction::getFriendshipModelName(); 72 | 73 | return $friendshipModelName::whereRecipient($recipient)->whereSender($this)->whereStatus(Status::PENDING)->exists(); 74 | } 75 | 76 | /** 77 | * @param Model $recipient 78 | * 79 | * @return bool 80 | */ 81 | public function isFriendWith(Model $recipient) 82 | { 83 | return $this->findFriendship($recipient)->where('status', Status::ACCEPTED)->exists(); 84 | } 85 | 86 | /** 87 | * @param Model $recipient 88 | * 89 | * @return bool|int 90 | */ 91 | public function acceptFriendRequest(Model $recipient) 92 | { 93 | Event::dispatch('acq.friendships.accepted', [$this, $recipient]); 94 | 95 | return $this->findFriendship($recipient)->whereRecipient($this)->update([ 96 | 'status' => Status::ACCEPTED, 97 | ]); 98 | } 99 | 100 | /** 101 | * @param Model $recipient 102 | * 103 | * @return bool|int 104 | */ 105 | public function denyFriendRequest(Model $recipient) 106 | { 107 | Event::dispatch('acq.friendships.denied', [$this, $recipient]); 108 | 109 | return $this->findFriendship($recipient)->whereRecipient($this)->update([ 110 | 'status' => Status::DENIED, 111 | ]); 112 | } 113 | 114 | 115 | /** 116 | * @param Model $friend 117 | * @param $groupSlug 118 | * 119 | * @return bool 120 | */ 121 | public function groupFriend(Model $friend, $groupSlug) 122 | { 123 | 124 | $friendship = $this->findFriendship($friend)->whereStatus(Status::ACCEPTED)->first(); 125 | $groupsAvailable = config('acquaintances.friendships_groups', []); 126 | 127 | if ( ! isset($groupsAvailable[$groupSlug]) || empty($friendship)) { 128 | return false; 129 | } 130 | 131 | $group = $friendship->groups()->firstOrCreate([ 132 | 'friendship_id' => $friendship->id, 133 | 'group_id' => $groupsAvailable[$groupSlug], 134 | 'friend_id' => $friend->getKey(), 135 | 'friend_type' => $friend->getMorphClass(), 136 | ]); 137 | 138 | return $group->wasRecentlyCreated; 139 | } 140 | 141 | /** 142 | * @param Model $friend 143 | * @param $groupSlug 144 | * 145 | * @return bool 146 | */ 147 | public function ungroupFriend(Model $friend, $groupSlug = '') 148 | { 149 | $friendship = $this->findFriendship($friend)->first(); 150 | $groupsAvailable = config('acquaintances.friendships_groups', []); 151 | 152 | if (empty($friendship)) { 153 | return false; 154 | } 155 | 156 | $where = [ 157 | 'friendship_id' => $friendship->id, 158 | 'friend_id' => $friend->getKey(), 159 | 'friend_type' => $friend->getMorphClass(), 160 | ]; 161 | 162 | if ('' !== $groupSlug && isset($groupsAvailable[$groupSlug])) { 163 | $where['group_id'] = $groupsAvailable[$groupSlug]; 164 | } 165 | 166 | $result = $friendship->groups()->where($where)->delete(); 167 | 168 | return $result; 169 | } 170 | 171 | /** 172 | * @param Model $recipient 173 | * 174 | * @return \Multicaret\Acquaintances\Models\Friendship 175 | */ 176 | public function blockFriend(Model $recipient) 177 | { 178 | // if there is a friendship between the two users and the sender is not blocked 179 | // by the recipient user then delete the friendship 180 | if ( ! $this->isBlockedBy($recipient)) { 181 | $this->findFriendship($recipient)->delete(); 182 | } 183 | 184 | $friendshipModelName = Interaction::getFriendshipModelName(); 185 | $friendship = (new $friendshipModelName)->fillRecipient($recipient)->fill([ 186 | 'status' => Status::BLOCKED, 187 | ]); 188 | 189 | Event::dispatch('acq.friendships.blocked', [$this, $recipient]); 190 | 191 | return $this->friends()->save($friendship); 192 | } 193 | 194 | /** 195 | * @param Model $recipient 196 | * 197 | * @return mixed 198 | */ 199 | public function unblockFriend(Model $recipient) 200 | { 201 | Event::dispatch('acq.friendships.unblocked', [$this, $recipient]); 202 | 203 | return $this->findFriendship($recipient)->whereSender($this)->delete(); 204 | } 205 | 206 | /** 207 | * @param Model $recipient 208 | * 209 | * @return \Multicaret\Acquaintances\Models\Friendship 210 | */ 211 | public function getFriendship(Model $recipient) 212 | { 213 | return $this->findFriendship($recipient)->first(); 214 | } 215 | 216 | /** 217 | * @param string $groupSlug 218 | * @param int $perPage Number 219 | * @param array $fields 220 | * @param string $type 221 | * 222 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 223 | */ 224 | public function getAllFriendships( 225 | string $groupSlug = '', 226 | int $perPage = 0, 227 | array $fields = ['*'], 228 | string $type = 'all' 229 | ) { 230 | return $this->getOrPaginate($this->findFriendships(null, $groupSlug, $type), $perPage, $fields); 231 | } 232 | 233 | /** 234 | * @param string $groupSlug 235 | * @param int $perPage Number 236 | * @param array $fields 237 | * @param string $type 238 | * 239 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 240 | */ 241 | public function getPendingFriendships( 242 | string $groupSlug = '', 243 | int $perPage = 0, 244 | array $fields = ['*'], 245 | string $type = 'all' 246 | ) { 247 | return $this->getOrPaginate($this->findFriendships(Status::PENDING, $groupSlug, $type), $perPage, $fields); 248 | } 249 | 250 | /** 251 | * @param string $groupSlug 252 | * @param int $perPage Number 253 | * @param array $fields 254 | * @param string $type 255 | * 256 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 257 | */ 258 | public function getAcceptedFriendships( 259 | string $groupSlug = '', 260 | int $perPage = 0, 261 | array $fields = ['*'], 262 | string $type = 'all' 263 | ) { 264 | return $this->getOrPaginate($this->findFriendships(Status::ACCEPTED, $groupSlug, $type), $perPage, $fields); 265 | } 266 | 267 | /** 268 | * @param int $perPage Number 269 | * @param array $fields 270 | * 271 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 272 | */ 273 | public function getDeniedFriendships(int $perPage = 0, array $fields = ['*']) 274 | { 275 | return $this->getOrPaginate($this->findFriendships(Status::DENIED), $perPage, $fields); 276 | } 277 | 278 | /** 279 | * @param int $perPage Number 280 | * @param array $fields 281 | * 282 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 283 | */ 284 | public function getBlockedFriendships(int $perPage = 0, array $fields = ['*']) 285 | { 286 | return $this->getOrPaginate($this->findFriendships(Status::BLOCKED), $perPage, $fields); 287 | } 288 | 289 | public function getBlockedFriendshipsByCurrentUser(int $perPage = 0, array $fields = ['*']) 290 | { 291 | return $this->getOrPaginate($this->findFriendships(Status::BLOCKED, type: 'sender'), $perPage, $fields); 292 | } 293 | 294 | public function getBlockedFriendshipsByOtherUsers(int $perPage = 0, array $fields = ['*']) 295 | { 296 | return $this->getOrPaginate($this->findFriendships(Status::BLOCKED, type: 'recipient'), $perPage, $fields); 297 | } 298 | 299 | /** 300 | * @param Model $recipient 301 | * 302 | * @return bool 303 | */ 304 | public function hasBlocked(Model $recipient) 305 | { 306 | return $this->friends()->whereRecipient($recipient)->whereStatus(Status::BLOCKED)->exists(); 307 | } 308 | 309 | /** 310 | * @param Model $recipient 311 | * 312 | * @return bool 313 | */ 314 | public function isBlockedBy(Model $recipient) 315 | { 316 | return $recipient->hasBlocked($this); 317 | } 318 | 319 | /** 320 | * @return \Illuminate\Database\Eloquent\Collection|Friendship[] 321 | */ 322 | public function getFriendRequests() 323 | { 324 | $friendshipModelName = Interaction::getFriendshipModelName(); 325 | 326 | return $friendshipModelName::whereRecipient($this)->whereStatus(Status::PENDING)->get(); 327 | } 328 | 329 | /** 330 | * This method will not return Friendship models 331 | * It will return the 'friends' models. ex: App\User 332 | * 333 | * @param int $perPage Number 334 | * @param string $groupSlug 335 | * 336 | * @param array $fields 337 | * 338 | * @return \Illuminate\Database\Eloquent\Collection 339 | */ 340 | public function getFriends($perPage = 0, $groupSlug = '', array $fields = ['*'], bool $cursor = false) 341 | { 342 | return $this->getOrPaginate($this->getFriendsQueryBuilder($groupSlug), $perPage, $fields, $cursor); 343 | } 344 | 345 | /** 346 | * This method will not return Friendship models 347 | * It will return the 'friends' models. ex: App\User 348 | * 349 | * @param Model $other 350 | * @param int $perPage Number 351 | * 352 | * @param array $fields 353 | * 354 | * @return \Illuminate\Database\Eloquent\Collection 355 | */ 356 | public function getMutualFriends(Model $other, $perPage = 0, array $fields = ['*']) 357 | { 358 | return $this->getOrPaginate($this->getMutualFriendsQueryBuilder($other), $perPage, $fields); 359 | } 360 | 361 | /** 362 | * Get the number of friends 363 | * 364 | * @return integer 365 | */ 366 | public function getMutualFriendsCount($other) 367 | { 368 | return $this->getMutualFriendsQueryBuilder($other)->count(); 369 | } 370 | 371 | /** 372 | * Get the number of pending friend requests 373 | * 374 | * @return integer 375 | */ 376 | public function getPendingsCount() 377 | { 378 | return $this->getPendingFriendships()->count(); 379 | } 380 | 381 | /** 382 | * This method will not return Friendship models 383 | * It will return the 'friends' models. ex: App\User 384 | * 385 | * @param int $perPage Number 386 | * 387 | * @param array $fields 388 | * 389 | * @return \Illuminate\Database\Eloquent\Collection 390 | */ 391 | public function getFriendsOfFriends($perPage = 0, array $fields = ['*']) 392 | { 393 | return $this->getOrPaginate($this->getFriendsOfFriendsQueryBuilder(), $perPage, $fields); 394 | } 395 | 396 | /** 397 | * Get the number of friends 398 | * 399 | * @param string $groupSlug 400 | * @param string $type 401 | * 402 | * @return integer 403 | */ 404 | public function getFriendsCount($groupSlug = '', $type = 'all') 405 | { 406 | $friendsCount = $this->findFriendships(Status::ACCEPTED, $groupSlug, $type)->count(); 407 | 408 | return $friendsCount; 409 | } 410 | 411 | /** 412 | * @param Model $recipient 413 | * 414 | * @return bool 415 | */ 416 | public function canBefriend($recipient) 417 | { 418 | // if user has Blocked the recipient and changed his mind 419 | // he can send a friend request after unblocking 420 | if ($this->hasBlocked($recipient)) { 421 | $this->unblockFriend($recipient); 422 | 423 | return true; 424 | } 425 | 426 | // if sender has a friendship with the recipient return false 427 | if ($friendship = $this->getFriendship($recipient)) { 428 | // if previous friendship was Denied then let the user send fr 429 | if ($friendship->status != Status::DENIED) { 430 | return false; 431 | } 432 | } 433 | 434 | return true; 435 | } 436 | 437 | /** 438 | * @param Model $recipient 439 | * 440 | * @return \Illuminate\Database\Eloquent\Builder 441 | */ 442 | private function findFriendship(Model $recipient) 443 | { 444 | $friendshipModelName = Interaction::getFriendshipModelName(); 445 | 446 | return $friendshipModelName::betweenModels($this, $recipient); 447 | } 448 | 449 | /** 450 | * @param $status 451 | * @param string $groupSlug 452 | * @param string $type 453 | * 454 | * @return \Illuminate\Database\Eloquent\Builder 455 | */ 456 | public function findFriendships($status = null, string $groupSlug = '', string $type = 'all') 457 | { 458 | $friendshipModelName = Interaction::getFriendshipModelName(); 459 | $query = $friendshipModelName::where(function ($query) use ($type) { 460 | switch ($type) { 461 | case 'all': 462 | $query->where(function ($q) { 463 | $q->whereSender($this); 464 | }) 465 | ->orWhere(function ($q) { 466 | $q->whereRecipient($this); 467 | }); 468 | break; 469 | case 'sender': 470 | $query->where(function ($q) { 471 | $q->whereSender($this); 472 | }); 473 | break; 474 | case 'recipient': 475 | $query->where(function ($q) { 476 | $q->whereRecipient($this); 477 | }); 478 | break; 479 | } 480 | })->whereGroup($this, $groupSlug); 481 | 482 | //if $status is passed, add where clause 483 | if ( ! is_null($status)) { 484 | $query->where('status', $status); 485 | } 486 | 487 | return $query; 488 | } 489 | 490 | /** 491 | * Get the query builder of the 'friend' model 492 | * 493 | * @param string $groupSlug 494 | * 495 | * @return \Illuminate\Database\Eloquent\Builder 496 | */ 497 | public function getFriendsQueryBuilder($groupSlug = '') 498 | { 499 | $friendships = $this->findFriendships(Status::ACCEPTED, $groupSlug)->get(['sender_id', 'recipient_id']); 500 | $recipients = $friendships->pluck('recipient_id')->all(); 501 | $senders = $friendships->pluck('sender_id')->all(); 502 | 503 | return $this->where('id', '!=', $this->getKey()) 504 | ->whereIn('id', array_merge($recipients, $senders)); 505 | } 506 | 507 | /** 508 | * Get the query builder of the 'friend' model 509 | * 510 | * @return \Illuminate\Database\Eloquent\Builder 511 | */ 512 | public function getMutualFriendsQueryBuilder(Model $other) 513 | { 514 | $user1['friendships'] = $this->findFriendships(Status::ACCEPTED)->get(['sender_id', 'recipient_id']); 515 | $user1['recipients'] = $user1['friendships']->pluck('recipient_id')->all(); 516 | $user1['senders'] = $user1['friendships']->pluck('sender_id')->all(); 517 | 518 | $user2['friendships'] = $other->findFriendships(Status::ACCEPTED)->get(['sender_id', 'recipient_id']); 519 | $user2['recipients'] = $user2['friendships']->pluck('recipient_id')->all(); 520 | $user2['senders'] = $user2['friendships']->pluck('sender_id')->all(); 521 | 522 | $mutualFriendships = array_unique( 523 | array_intersect( 524 | array_merge($user1['recipients'], $user1['senders']), 525 | array_merge($user2['recipients'], $user2['senders']) 526 | ) 527 | ); 528 | 529 | return $this->whereNotIn('id', [$this->getKey(), $other->getKey()]) 530 | ->whereIn('id', $mutualFriendships); 531 | } 532 | 533 | /** 534 | * Get the query builder for friendsOfFriends ('friend' model) 535 | * 536 | * @param string $groupSlug 537 | * 538 | * @return \Illuminate\Database\Eloquent\Builder 539 | */ 540 | public function getFriendsOfFriendsQueryBuilder($groupSlug = '') 541 | { 542 | $friendships = $this->findFriendships(Status::ACCEPTED)->get(['sender_id', 'recipient_id']); 543 | $recipients = $friendships->pluck('recipient_id')->all(); 544 | $senders = $friendships->pluck('sender_id')->all(); 545 | 546 | $friendIds = array_unique(array_merge($recipients, $senders)); 547 | 548 | $friendshipModelName = Interaction::getFriendshipModelName(); 549 | $fofs = $friendshipModelName::where('status', Status::ACCEPTED) 550 | ->where(function ($query) use ($friendIds) { 551 | $query->where(function ($q) use ($friendIds) { 552 | $q->whereIn('sender_id', $friendIds); 553 | })->orWhere(function ($q) use ($friendIds) { 554 | $q->whereIn('recipient_id', $friendIds); 555 | }); 556 | }) 557 | ->whereGroup($this, $groupSlug) 558 | ->get(['sender_id', 'recipient_id']); 559 | 560 | $fofIds = array_unique( 561 | array_merge($fofs->pluck('sender_id')->all(), $fofs->pluck('recipient_id')->all()) 562 | ); 563 | 564 | return $this->whereIn('id', $fofIds)->whereNotIn('id', $friendIds); 565 | } 566 | 567 | /** 568 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 569 | */ 570 | public function friends() 571 | { 572 | $friendshipModelName = Interaction::getFriendshipModelName(); 573 | 574 | return $this->morphMany($friendshipModelName, 'sender'); 575 | } 576 | 577 | /** 578 | * @return \Illuminate\Database\Eloquent\Relations\MorphMany 579 | */ 580 | public function groups() 581 | { 582 | $friendshipGroupsModelName = Interaction::getFriendshipGroupsModelName(); 583 | 584 | return $this->morphMany($friendshipGroupsModelName, 'friend'); 585 | } 586 | 587 | protected function getOrPaginate($builder, $perPage, array $fields = ['*'], bool $cursor = false) 588 | { 589 | if ($perPage == 0) { 590 | return $builder->get($fields); 591 | } 592 | 593 | if ($cursor) { 594 | return $builder->cursorPaginate($perPage, $fields); 595 | } 596 | 597 | return $builder->paginate($perPage, $fields); 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/FriendshipsEventsTest.php: -------------------------------------------------------------------------------- 1 | sender = createUser(); 17 | $this->recipient = createUser(); 18 | } 19 | 20 | public function tearDown(): void 21 | { 22 | Mockery::close(); 23 | } 24 | 25 | /** @test */ 26 | public function friend_request_is_sent() 27 | { 28 | Event::shouldReceive('dispatch')->once()->withArgs(['friendships.sent', Mockery::any()]); 29 | 30 | $this->sender->befriend($this->recipient); 31 | } 32 | 33 | /** @test */ 34 | public function friend_request_is_accepted() 35 | { 36 | $this->sender->befriend($this->recipient); 37 | Event::shouldReceive('dispatch')->once()->withArgs(['friendships.accepted', Mockery::any()]); 38 | 39 | $this->recipient->acceptFriendRequest($this->sender); 40 | } 41 | 42 | /** @test */ 43 | public function friend_request_is_denied() 44 | { 45 | $this->sender->befriend($this->recipient); 46 | Event::shouldReceive('dispatch')->once()->withArgs(['friendships.denied', Mockery::any()]); 47 | 48 | $this->recipient->denyFriendRequest($this->sender); 49 | } 50 | 51 | /** @test */ 52 | public function friend_is_blocked() 53 | { 54 | $this->sender->befriend($this->recipient); 55 | $this->recipient->acceptFriendRequest($this->sender); 56 | Event::shouldReceive('dispatch')->once()->withArgs(['friendships.blocked', Mockery::any()]); 57 | 58 | $this->recipient->blockFriend($this->sender); 59 | } 60 | 61 | /** @test */ 62 | public function friend_is_unblocked() 63 | { 64 | $this->sender->befriend($this->recipient); 65 | $this->recipient->acceptFriendRequest($this->sender); 66 | $this->recipient->blockFriend($this->sender); 67 | Event::shouldReceive('dispatch')->once()->withArgs(['friendships.unblocked', Mockery::any()]); 68 | 69 | $this->recipient->unblockFriend($this->sender); 70 | } 71 | 72 | /** @test */ 73 | public function friendship_is_cancelled() 74 | { 75 | $this->sender->befriend($this->recipient); 76 | $this->recipient->acceptFriendRequest($this->sender); 77 | Event::shouldReceive('dispatch')->once()->withArgs(['friendships.cancelled', Mockery::any()]); 78 | 79 | $this->recipient->unfriend($this->sender); 80 | } 81 | } -------------------------------------------------------------------------------- /tests/FriendshipsGroupsTest.php: -------------------------------------------------------------------------------- 1 | befriend($recipient); 24 | $recipient->acceptFriendRequest($sender); 25 | 26 | $this->assertTrue((boolean) $recipient->groupFriend($sender, 'acquaintances')); 27 | $this->assertTrue((boolean) $sender->groupFriend($recipient, 'family')); 28 | 29 | // it only adds a friend to a group once 30 | $this->assertFalse((boolean) $sender->groupFriend($recipient, 'family')); 31 | 32 | // expect that users have been attached to specified groups 33 | $this->assertCount(1, $sender->getFriends(0, 'family')); 34 | $this->assertCount(1, $recipient->getFriends(0, 'acquaintances')); 35 | 36 | $this->assertEquals($recipient->id, $sender->getFriends(0, 'family')->first()->id); 37 | $this->assertEquals($sender->id, $recipient->getFriends(0, 'acquaintances')->first()->id); 38 | 39 | } 40 | 41 | /** @test */ 42 | public function user_cannot_add_a_non_friend_to_a_group() 43 | { 44 | $sender = createUser(); 45 | $stranger = createUser(); 46 | 47 | $this->assertFalse((boolean) $sender->groupFriend($stranger, 'family')); 48 | $this->assertCount(0, $sender->getFriends(0, 'family')); 49 | } 50 | 51 | /** @test */ 52 | public function user_can_remove_a_friend_from_group() 53 | { 54 | $sender = createUser(); 55 | $recipient = createUser(); 56 | 57 | $sender->befriend($recipient); 58 | $recipient->acceptFriendRequest($sender); 59 | 60 | $recipient->groupFriend($sender, 'acquaintances'); 61 | $recipient->groupFriend($sender, 'family'); 62 | 63 | $this->assertEquals(1, $recipient->ungroupFriend($sender, 'acquaintances')); 64 | 65 | $this->assertCount(0, $sender->getFriends(0, 'acquaintances')); 66 | 67 | // expect that friend has been removed from acquaintances but not family 68 | $this->assertCount(0, $recipient->getFriends(0, 'acquaintances')); 69 | $this->assertCount(1, $recipient->getFriends(0, 'family')); 70 | } 71 | 72 | /** @test */ 73 | public function user_cannot_remove_a_non_existing_friend_from_group() 74 | { 75 | $sender = createUser(); 76 | $recipient = createUser(); 77 | $recipient2 = createUser(); 78 | 79 | $sender->befriend($recipient); 80 | 81 | $this->assertEquals(0, $recipient->ungroupFriend($sender, 'acquaintances')); 82 | $this->assertEquals(0, $recipient2->ungroupFriend($sender, 'acquaintances')); 83 | } 84 | 85 | /** @test */ 86 | public function user_can_remove_a_friend_from_all_groups() 87 | { 88 | $sender = createUser(); 89 | $recipient = createUser(); 90 | 91 | $sender->befriend($recipient); 92 | $recipient->acceptFriendRequest($sender); 93 | 94 | $sender->groupFriend($recipient, 'family'); 95 | $sender->groupFriend($recipient, 'acquaintances'); 96 | 97 | $sender->ungroupFriend($recipient); 98 | 99 | $this->assertCount(0, $sender->getFriends(0, 'family')); 100 | $this->assertCount(0, $sender->getFriends(0, 'acquaintances')); 101 | } 102 | 103 | /** @test */ 104 | public function it_returns_friends_of_a_group() 105 | { 106 | $sender = createUser(); 107 | $recipients = createUser([], 10); 108 | 109 | foreach ($recipients as $key => $recipient) { 110 | 111 | $sender->befriend($recipient); 112 | $recipient->acceptFriendRequest($sender); 113 | 114 | if ($key % 2 === 0) { 115 | $sender->groupFriend($recipient, 'family'); 116 | } 117 | 118 | } 119 | 120 | $this->assertCount(5, $sender->getFriends(0, 'family')); 121 | $this->assertCount(10, $sender->getFriends()); 122 | } 123 | 124 | 125 | /** @test */ 126 | public function it_returns_all_user_friendships_by_group() 127 | { 128 | $sender = createUser(); 129 | $recipients = createUser([], 5); 130 | 131 | foreach ($recipients as $key => $recipient) { 132 | 133 | $sender->befriend($recipient); 134 | 135 | if ($key < 4) { 136 | 137 | $recipient->acceptFriendRequest($sender); 138 | if ($key < 3) { 139 | $sender->groupFriend($recipient, 'acquaintances'); 140 | } else { 141 | $sender->groupFriend($recipient, 'family'); 142 | } 143 | 144 | } else { 145 | $recipient->denyFriendRequest($sender); 146 | } 147 | 148 | } 149 | 150 | //Assertions 151 | 152 | $this->assertCount(3, $sender->getAllFriendships('acquaintances')); 153 | $this->assertCount(1, $sender->getAllFriendships('family')); 154 | $this->assertCount(0, $sender->getAllFriendships('close_friends')); 155 | $this->assertCount(5, $sender->getAllFriendships('whatever')); 156 | } 157 | 158 | 159 | /** @test */ 160 | public function it_returns_accepted_user_friendships_by_group() 161 | { 162 | $sender = createUser(); 163 | $recipients = createUser([], 4); 164 | 165 | foreach ($recipients as $recipient) { 166 | $sender->befriend($recipient); 167 | } 168 | 169 | $recipients[0]->acceptFriendRequest($sender); 170 | $recipients[1]->acceptFriendRequest($sender); 171 | $recipients[2]->denyFriendRequest($sender); 172 | 173 | $sender->groupFriend($recipients[0], 'family'); 174 | $sender->groupFriend($recipients[1], 'family'); 175 | 176 | $this->assertCount(2, $sender->getAcceptedFriendships('family')); 177 | 178 | } 179 | 180 | /** @test */ 181 | public function it_returns_accepted_user_friendships_number_by_group() 182 | { 183 | $sender = createUser(); 184 | $recipients = createUser([], 20)->chunk(5); 185 | 186 | foreach ($recipients->shift() as $recipient) { 187 | $sender->befriend($recipient); 188 | $recipient->acceptFriendRequest($sender); 189 | $sender->groupFriend($recipient, 'acquaintances'); 190 | } 191 | 192 | //Assertions 193 | 194 | $this->assertEquals(5, $sender->getFriendsCount('acquaintances')); 195 | $this->assertEquals(0, $sender->getFriendsCount('family')); 196 | $this->assertEquals(0, $recipient->getFriendsCount('acquaintances')); 197 | $this->assertEquals(0, $recipient->getFriendsCount('family')); 198 | } 199 | 200 | 201 | /** @test */ 202 | public function it_returns_user_friends_by_group_per_page() 203 | { 204 | $sender = createUser(); 205 | $recipients = createUser([], 6); 206 | 207 | foreach ($recipients as $recipient) { 208 | $sender->befriend($recipient); 209 | } 210 | 211 | $recipients[0]->acceptFriendRequest($sender); 212 | $recipients[1]->acceptFriendRequest($sender); 213 | 214 | $recipients[2]->denyFriendRequest($sender); 215 | 216 | $recipients[3]->acceptFriendRequest($sender); 217 | $recipients[4]->acceptFriendRequest($sender); 218 | 219 | $sender->groupFriend($recipients[0], 'acquaintances'); 220 | $sender->groupFriend($recipients[1], 'acquaintances'); 221 | $sender->groupFriend($recipients[3], 'acquaintances'); 222 | $sender->groupFriend($recipients[4], 'acquaintances'); 223 | 224 | $sender->groupFriend($recipients[0], 'close_friends'); 225 | $sender->groupFriend($recipients[3], 'close_friends'); 226 | 227 | $sender->groupFriend($recipients[4], 'family'); 228 | 229 | //Assertions 230 | 231 | $this->assertCount(2, $sender->getFriends(2, 'acquaintances')); 232 | $this->assertCount(4, $sender->getFriends(0, 'acquaintances')); 233 | $this->assertCount(4, $sender->getFriends(10, 'acquaintances')); 234 | 235 | $this->assertCount(2, $sender->getFriends(0, 'close_friends')); 236 | $this->assertCount(1, $sender->getFriends(1, 'close_friends')); 237 | 238 | $this->assertCount(1, $sender->getFriends(0, 'family')); 239 | 240 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriends(0, 'acquaintances')); 241 | } 242 | 243 | 244 | } 245 | -------------------------------------------------------------------------------- /tests/FriendshipsTest.php: -------------------------------------------------------------------------------- 1 | befriend($recipient); 16 | 17 | $this->assertCount(1, $recipient->getFriendRequests()); 18 | } 19 | 20 | /** @test */ 21 | public function user_can_not_send_a_friend_request_if_frienship_is_pending() 22 | { 23 | $sender = createUser(); 24 | $recipient = createUser(); 25 | $sender->befriend($recipient); 26 | $sender->befriend($recipient); 27 | $sender->befriend($recipient); 28 | 29 | $this->assertCount(1, $recipient->getFriendRequests()); 30 | } 31 | 32 | 33 | /** @test */ 34 | public function user_can_send_a_friend_request_if_frienship_is_denied() 35 | { 36 | $sender = createUser(); 37 | $recipient = createUser(); 38 | 39 | $sender->befriend($recipient); 40 | $recipient->denyFriendRequest($sender); 41 | 42 | $sender->befriend($recipient); 43 | 44 | $this->assertCount(1, $recipient->getFriendRequests()); 45 | } 46 | 47 | /** @test */ 48 | public function user_can_remove_a_friend_request() 49 | { 50 | $sender = createUser(); 51 | $recipient = createUser(); 52 | 53 | $sender->befriend($recipient); 54 | $this->assertCount(1, $recipient->getFriendRequests()); 55 | 56 | $sender->unfriend($recipient); 57 | $this->assertCount(0, $recipient->getFriendRequests()); 58 | 59 | // Can resend friend request after deleted 60 | $sender->befriend($recipient); 61 | $this->assertCount(1, $recipient->getFriendRequests()); 62 | 63 | $recipient->acceptFriendRequest($sender); 64 | $this->assertEquals(true, $recipient->isFriendWith($sender)); 65 | // Can remove friend request after accepted 66 | $sender->unfriend($recipient); 67 | $this->assertEquals(false, $recipient->isFriendWith($sender)); 68 | } 69 | 70 | /** @test */ 71 | public function user_is_friend_with_another_user_if_accepts_a_friend_request() 72 | { 73 | $sender = createUser(); 74 | $recipient = createUser(); 75 | //send fr 76 | $sender->befriend($recipient); 77 | //accept fr 78 | $recipient->acceptFriendRequest($sender); 79 | 80 | $this->assertTrue($recipient->isFriendWith($sender)); 81 | $this->assertTrue($sender->isFriendWith($recipient)); 82 | //fr has been delete 83 | $this->assertCount(0, $recipient->getFriendRequests()); 84 | } 85 | 86 | /** @test */ 87 | public function user_is_not_friend_with_another_user_until_he_accepts_a_friend_request() 88 | { 89 | $sender = createUser(); 90 | $recipient = createUser(); 91 | //send fr 92 | $sender->befriend($recipient); 93 | 94 | $this->assertFalse($recipient->isFriendWith($sender)); 95 | $this->assertFalse($sender->isFriendWith($recipient)); 96 | } 97 | 98 | /** @test */ 99 | public function user_has_friend_request_from_another_user_if_he_received_a_friend_request() 100 | { 101 | $sender = createUser(); 102 | $recipient = createUser(); 103 | //send fr 104 | $sender->befriend($recipient); 105 | 106 | $this->assertTrue($recipient->hasFriendRequestFrom($sender)); 107 | $this->assertFalse($sender->hasFriendRequestFrom($recipient)); 108 | } 109 | 110 | /** @test */ 111 | public function user_has_sent_friend_request_to_this_user_if_he_already_sent_request() 112 | { 113 | $sender = createUser(); 114 | $recipient = createUser(); 115 | //send fr 116 | $sender->befriend($recipient); 117 | 118 | $this->assertFalse($recipient->hasSentFriendRequestTo($sender)); 119 | $this->assertTrue($sender->hasSentFriendRequestTo($recipient)); 120 | } 121 | 122 | /** @test */ 123 | public function user_has_not_friend_request_from_another_user_if_he_accepted_the_friend_request() 124 | { 125 | $sender = createUser(); 126 | $recipient = createUser(); 127 | //send fr 128 | $sender->befriend($recipient); 129 | //accept fr 130 | $recipient->acceptFriendRequest($sender); 131 | 132 | $this->assertFalse($recipient->hasFriendRequestFrom($sender)); 133 | $this->assertFalse($sender->hasFriendRequestFrom($recipient)); 134 | } 135 | 136 | /** @test */ 137 | public function user_cannot_accept_his_own_friend_request() 138 | { 139 | $sender = createUser(); 140 | $recipient = createUser(); 141 | 142 | //send fr 143 | $sender->befriend($recipient); 144 | 145 | $sender->acceptFriendRequest($recipient); 146 | $this->assertFalse($recipient->isFriendWith($sender)); 147 | } 148 | 149 | /** @test */ 150 | public function user_can_deny_a_friend_request() 151 | { 152 | $sender = createUser(); 153 | $recipient = createUser(); 154 | $sender->befriend($recipient); 155 | 156 | $recipient->denyFriendRequest($sender); 157 | 158 | $this->assertFalse($recipient->isFriendWith($sender)); 159 | 160 | //fr has been delete 161 | $this->assertCount(0, $recipient->getFriendRequests()); 162 | $this->assertCount(1, $sender->getDeniedFriendships()); 163 | } 164 | 165 | /** @test */ 166 | public function user_can_block_another_user() 167 | { 168 | $sender = createUser(); 169 | $recipient = createUser(); 170 | 171 | $sender->blockFriend($recipient); 172 | 173 | $this->assertTrue($recipient->isBlockedBy($sender)); 174 | $this->assertTrue($sender->hasBlocked($recipient)); 175 | //sender is not blocked by receipient 176 | $this->assertFalse($sender->isBlockedBy($recipient)); 177 | $this->assertFalse($recipient->hasBlocked($sender)); 178 | } 179 | 180 | /** @test */ 181 | public function user_can_unblock_a_blocked_user() 182 | { 183 | $sender = createUser(); 184 | $recipient = createUser(); 185 | 186 | $sender->blockFriend($recipient); 187 | $sender->unblockFriend($recipient); 188 | 189 | $this->assertFalse($recipient->isBlockedBy($sender)); 190 | $this->assertFalse($sender->hasBlocked($recipient)); 191 | } 192 | 193 | /** @test */ 194 | public function user_block_is_permanent_unless_blocker_decides_to_unblock() 195 | { 196 | $sender = createUser(); 197 | $recipient = createUser(); 198 | 199 | $sender->blockFriend($recipient); 200 | $this->assertTrue($recipient->isBlockedBy($sender)); 201 | 202 | // now recipient blocks sender too 203 | $recipient->blockFriend($sender); 204 | 205 | // expect that both users have blocked each other 206 | $this->assertTrue($sender->isBlockedBy($recipient)); 207 | $this->assertTrue($recipient->isBlockedBy($sender)); 208 | 209 | $sender->unblockFriend($recipient); 210 | 211 | $this->assertTrue($sender->isBlockedBy($recipient)); 212 | $this->assertFalse($recipient->isBlockedBy($sender)); 213 | 214 | $recipient->unblockFriend($sender); 215 | $this->assertFalse($sender->isBlockedBy($recipient)); 216 | $this->assertFalse($recipient->isBlockedBy($sender)); 217 | } 218 | 219 | /** @test */ 220 | public function user_can_send_friend_request_to_user_who_is_blocked() 221 | { 222 | $sender = createUser(); 223 | $recipient = createUser(); 224 | 225 | $sender->blockFriend($recipient); 226 | $sender->befriend($recipient); 227 | $sender->befriend($recipient); 228 | 229 | $this->assertCount(1, $recipient->getFriendRequests()); 230 | } 231 | 232 | /** @test */ 233 | public function it_returns_all_user_friendships() 234 | { 235 | $sender = createUser(); 236 | $recipients = createUser([], 3); 237 | 238 | foreach ($recipients as $recipient) { 239 | $sender->befriend($recipient); 240 | } 241 | 242 | $recipients[0]->acceptFriendRequest($sender); 243 | $recipients[1]->acceptFriendRequest($sender); 244 | $recipients[2]->denyFriendRequest($sender); 245 | $this->assertCount(3, $sender->getAllFriendships()); 246 | } 247 | 248 | /** @test */ 249 | public function it_returns_accepted_user_friendships_number() 250 | { 251 | $sender = createUser(); 252 | $recipients = createUser([], 3); 253 | 254 | foreach ($recipients as $recipient) { 255 | $sender->befriend($recipient); 256 | } 257 | 258 | $recipients[0]->acceptFriendRequest($sender); 259 | $recipients[1]->acceptFriendRequest($sender); 260 | $recipients[2]->denyFriendRequest($sender); 261 | $this->assertEquals(2, $sender->getFriendsCount()); 262 | } 263 | 264 | /** @test */ 265 | public function it_returns_accepted_user_friendships() 266 | { 267 | $sender = createUser(); 268 | $recipients = createUser([], 3); 269 | 270 | foreach ($recipients as $recipient) { 271 | $sender->befriend($recipient); 272 | } 273 | 274 | $recipients[0]->acceptFriendRequest($sender); 275 | $recipients[1]->acceptFriendRequest($sender); 276 | $recipients[2]->denyFriendRequest($sender); 277 | $this->assertCount(2, $sender->getAcceptedFriendships()); 278 | } 279 | 280 | /** @test */ 281 | public function it_returns_only_accepted_user_friendships() 282 | { 283 | $sender = createUser(); 284 | $recipients = createUser([], 4); 285 | 286 | foreach ($recipients as $recipient) { 287 | $sender->befriend($recipient); 288 | } 289 | 290 | $recipients[0]->acceptFriendRequest($sender); 291 | $recipients[1]->acceptFriendRequest($sender); 292 | $recipients[2]->denyFriendRequest($sender); 293 | $this->assertCount(2, $sender->getAcceptedFriendships()); 294 | 295 | $this->assertCount(1, $recipients[0]->getAcceptedFriendships()); 296 | $this->assertCount(1, $recipients[1]->getAcceptedFriendships()); 297 | $this->assertCount(0, $recipients[2]->getAcceptedFriendships()); 298 | $this->assertCount(0, $recipients[3]->getAcceptedFriendships()); 299 | } 300 | 301 | /** @test */ 302 | public function it_returns_pending_user_friendships() 303 | { 304 | $sender = createUser(); 305 | $recipients = createUser([], 3); 306 | 307 | foreach ($recipients as $recipient) { 308 | $sender->befriend($recipient); 309 | } 310 | 311 | $recipients[0]->acceptFriendRequest($sender); 312 | $this->assertCount(2, $sender->getPendingFriendships()); 313 | } 314 | 315 | /** @test */ 316 | public function it_returns_denied_user_friendships() 317 | { 318 | $sender = createUser(); 319 | $recipients = createUser([], 3); 320 | 321 | foreach ($recipients as $recipient) { 322 | $sender->befriend($recipient); 323 | } 324 | 325 | $recipients[0]->acceptFriendRequest($sender); 326 | $recipients[1]->acceptFriendRequest($sender); 327 | $recipients[2]->denyFriendRequest($sender); 328 | $this->assertCount(1, $sender->getDeniedFriendships()); 329 | } 330 | 331 | /** @test */ 332 | public function it_returns_blocked_user_friendships() 333 | { 334 | $sender = createUser(); 335 | $recipients = createUser([], 3); 336 | 337 | foreach ($recipients as $recipient) { 338 | $sender->befriend($recipient); 339 | } 340 | 341 | $recipients[0]->acceptFriendRequest($sender); 342 | $recipients[1]->acceptFriendRequest($sender); 343 | $recipients[2]->blockFriend($sender); 344 | $this->assertCount(1, $sender->getBlockedFriendships()); 345 | } 346 | 347 | /** @test */ 348 | public function it_returns_user_friends() 349 | { 350 | $sender = createUser(); 351 | $recipients = createUser([], 4); 352 | 353 | foreach ($recipients as $recipient) { 354 | $sender->befriend($recipient); 355 | } 356 | 357 | $recipients[0]->acceptFriendRequest($sender); 358 | $recipients[1]->acceptFriendRequest($sender); 359 | $recipients[2]->denyFriendRequest($sender); 360 | 361 | $this->assertCount(2, $sender->getFriends()); 362 | $this->assertCount(1, $recipients[1]->getFriends()); 363 | $this->assertCount(0, $recipients[2]->getFriends()); 364 | $this->assertCount(0, $recipients[3]->getFriends()); 365 | 366 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriends()); 367 | } 368 | 369 | /** @test */ 370 | public function it_returns_user_friends_per_page() 371 | { 372 | $sender = createUser(); 373 | $recipients = createUser([], 6); 374 | 375 | foreach ($recipients as $recipient) { 376 | $sender->befriend($recipient); 377 | } 378 | 379 | $recipients[0]->acceptFriendRequest($sender); 380 | $recipients[1]->acceptFriendRequest($sender); 381 | $recipients[2]->denyFriendRequest($sender); 382 | $recipients[3]->acceptFriendRequest($sender); 383 | $recipients[4]->acceptFriendRequest($sender); 384 | 385 | 386 | $this->assertCount(2, $sender->getFriends(2)); 387 | $this->assertCount(4, $sender->getFriends(0)); 388 | $this->assertCount(4, $sender->getFriends(10)); 389 | $this->assertCount(1, $recipients[1]->getFriends()); 390 | $this->assertCount(0, $recipients[2]->getFriends()); 391 | $this->assertCount(0, $recipients[5]->getFriends(2)); 392 | 393 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriends()); 394 | } 395 | 396 | /** @test */ 397 | public function it_returns_user_friends_of_friends() 398 | { 399 | $sender = createUser(); 400 | $recipients = createUser([], 2); 401 | $fofs = createUser([], 5)->chunk(3); 402 | 403 | foreach ($recipients as $recipient) { 404 | $sender->befriend($recipient); 405 | $recipient->acceptFriendRequest($sender); 406 | 407 | //add some friends to each recipient too 408 | foreach ($fofs->shift() as $fof) { 409 | $recipient->befriend($fof); 410 | $fof->acceptFriendRequest($recipient); 411 | } 412 | } 413 | 414 | $this->assertCount(2, $sender->getFriends()); 415 | $this->assertCount(4, $recipients[0]->getFriends()); 416 | $this->assertCount(3, $recipients[1]->getFriends()); 417 | 418 | $this->assertCount(5, $sender->getFriendsOfFriends()); 419 | 420 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getFriendsOfFriends()); 421 | } 422 | 423 | /** @test */ 424 | public function it_returns_user_mutual_friends() 425 | { 426 | $sender = createUser(); 427 | $recipients = createUser([], 2); 428 | $fofs = createUser([], 5)->chunk(3); 429 | 430 | foreach ($recipients as $recipient) { 431 | $sender->befriend($recipient); 432 | $recipient->acceptFriendRequest($sender); 433 | 434 | //add some friends to each recipient too 435 | foreach ($fofs->shift() as $fof) { 436 | $recipient->befriend($fof); 437 | $fof->acceptFriendRequest($recipient); 438 | $fof->befriend($sender); 439 | $sender->acceptFriendRequest($fof); 440 | } 441 | } 442 | 443 | $this->assertCount(3, $sender->getMutualFriends($recipients[0])); 444 | $this->assertCount(3, $recipients[0]->getMutualFriends($sender)); 445 | 446 | $this->assertCount(2, $sender->getMutualFriends($recipients[1])); 447 | $this->assertCount(2, $recipients[1]->getMutualFriends($sender)); 448 | 449 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getMutualFriends($recipients[0])); 450 | } 451 | 452 | /** @test */ 453 | public function it_returns_user_mutual_friends_per_page() 454 | { 455 | $sender = createUser(); 456 | $recipients = createUser([], 2); 457 | $fofs = createUser([], 8)->chunk(5); 458 | 459 | foreach ($recipients as $recipient) { 460 | $sender->befriend($recipient); 461 | $recipient->acceptFriendRequest($sender); 462 | 463 | //add some friends to each recipient too 464 | foreach ($fofs->shift() as $fof) { 465 | $recipient->befriend($fof); 466 | $fof->acceptFriendRequest($recipient); 467 | $fof->befriend($sender); 468 | $sender->acceptFriendRequest($fof); 469 | } 470 | } 471 | 472 | $this->assertCount(2, $sender->getMutualFriends($recipients[0], 2)); 473 | $this->assertCount(5, $sender->getMutualFriends($recipients[0], 0)); 474 | $this->assertCount(5, $sender->getMutualFriends($recipients[0], 10)); 475 | $this->assertCount(2, $recipients[0]->getMutualFriends($sender, 2)); 476 | $this->assertCount(5, $recipients[0]->getMutualFriends($sender, 0)); 477 | $this->assertCount(5, $recipients[0]->getMutualFriends($sender, 10)); 478 | 479 | $this->assertCount(1, $recipients[1]->getMutualFriends($recipients[0], 10)); 480 | 481 | $this->containsOnlyInstancesOf(\App\User::class, $sender->getMutualFriends($recipients[0], 2)); 482 | } 483 | 484 | /** @test */ 485 | public function it_returns_user_mutual_friends_number() 486 | { 487 | $sender = createUser(); 488 | $recipients = createUser([], 2); 489 | $fofs = createUser([], 5)->chunk(3); 490 | 491 | foreach ($recipients as $recipient) { 492 | $sender->befriend($recipient); 493 | $recipient->acceptFriendRequest($sender); 494 | 495 | //add some friends to each recipient too 496 | foreach ($fofs->shift() as $fof) { 497 | $recipient->befriend($fof); 498 | $fof->acceptFriendRequest($recipient); 499 | $fof->befriend($sender); 500 | $sender->acceptFriendRequest($fof); 501 | } 502 | } 503 | 504 | $this->assertEquals(3, $sender->getMutualFriendsCount($recipients[0])); 505 | $this->assertEquals(3, $recipients[0]->getMutualFriendsCount($sender)); 506 | 507 | $this->assertEquals(2, $sender->getMutualFriendsCount($recipients[1])); 508 | $this->assertEquals(2, $recipients[1]->getMutualFriendsCount($sender)); 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /tests/Stub_User.php: -------------------------------------------------------------------------------- 1 | append_config([ 4 | 'Multicaret\Acquaintances\AcquaintancesServiceProvider' 5 | ]), 6 | ]; 7 | -------------------------------------------------------------------------------- /tests/config/database.php: -------------------------------------------------------------------------------- 1 | 'mysql', 4 | 'connections' => [ 5 | 'mysql' => [ 6 | 'driver' => 'mysql2', 7 | 'host' => '127.0.0.1', 8 | 'database' => 'acquaintances_test', 9 | 'username' => 'travis', 10 | 'password' => '', 11 | 'charset' => 'utf8', 12 | 'collation' => 'utf8_unicode_ci', 13 | 'prefix' => '', 14 | 'strict' => false, 15 | ] 16 | ], 17 | ]; 18 | 19 | -------------------------------------------------------------------------------- /tests/config/friendships.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'friendships' => 'friendships', 7 | 'friendship_groups' => 'user_friendship_groups' 8 | ], 9 | 10 | 'groups' => [ 11 | 'acquaintances' => 0, 12 | 'close_friends' => 1, 13 | 'family' => 2 14 | ] 15 | 16 | ]; -------------------------------------------------------------------------------- /tests/helpers.php: -------------------------------------------------------------------------------- 1 | create($overrides); 13 | if (count($users) == 1) { 14 | return $users->first(); 15 | } 16 | 17 | return $users; 18 | } 19 | --------------------------------------------------------------------------------