├── .gitignore ├── CHANGELOG.md ├── CNAME ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── _config.yml ├── composer.json ├── phpunit.xml ├── src ├── Badge.php ├── BaseBadge.php ├── BasePoint.php ├── Console │ ├── MakeBadgeCommand.php │ ├── MakePointCommand.php │ └── stubs │ │ ├── badge.stub │ │ └── point.stub ├── Events │ ├── BadgeAchieved.php │ └── PointsChanged.php ├── Exceptions │ └── LevelNotExist.php ├── Facades │ ├── Gamify.php │ └── GamifyFacade.php ├── Factories │ ├── BadgeFactory.php │ ├── GamifyGroupFactory.php │ └── PointFactory.php ├── GamifyGroup.php ├── GamifyServiceProvider.php ├── Listeners │ └── SyncBadges.php ├── Point.php ├── Traits │ ├── Gamify.php │ ├── HasBadges.php │ └── HasPoints.php ├── config │ └── gamify.php ├── helpers.php └── migrations │ └── create_gamify_tables.php.stub └── tests ├── DemoTest.php └── TestCase.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea/ 3 | composer.lock 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `ansezz/laravel-gamify` will be documented in this file 4 | 5 | ## 1.2 - 2020-04-05 6 | 7 | - Support dynamic point 8 | - Levels in the config file instead of database 9 | - Add Gamify Facade 10 | - Dispatch event when a new badge achieved 11 | - Update readme 12 | 13 | ## 1.1 - 2020-04-04 14 | 15 | - Support Level in Point 16 | - Support Group in Badge & Point 17 | - Create a badge command 18 | - Create a point command 19 | 20 | ## 1.0 - 2020-03-07 21 | 22 | - initial release 23 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | gamify.laravel-vuejs.com -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/spatie/eloquent-sortable). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | ``` bash 28 | $ vendor/bin/phpunit 29 | ``` 30 | 31 | 32 | **Happy coding**! 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Ansezz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🏆 Laravel Gamify 🕹 2 | Laravel Gamify: Gamification System with Points & Badges support 3 | 4 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/ansezz/laravel-gamify.svg)](https://packagist.org/packages/ansezz/laravel-gamify) 5 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) 6 | [![Build Status](https://img.shields.io/travis/ansezz/laravel-gamify/master.svg)](https://travis-ci.org/ansezz/laravel-gamify) 7 | [![Total Downloads](https://img.shields.io/packagist/dt/ansezz/laravel-gamify.svg)](https://packagist.org/packages/ansezz/laravel-gamify) 8 | 9 | [![Latest Version on Packagist](https://repository-images.githubusercontent.com/245541946/0a641100-742a-11ea-8362-41f2d6d52974)](https://packagist.org/packages/ansezz/laravel-gamify) 10 | 11 | 12 | Use `ansezz/laravel-gamify` to quickly add point & badges in your Laravel app. 13 | 14 | ### Installation 15 | 16 | **1** - You can install the package via composer: 17 | 18 | ```bash 19 | $ composer require ansezz/laravel-gamify 20 | ``` 21 | 22 | **2** - If you are installing on Laravel 5.4 or lower you will be needed to manually register Service Provider by adding it in `config/app.php` providers array. 23 | 24 | ```php 25 | 'providers' => [ 26 | //... 27 | Ansezz\Gamify\GamifyServiceProvider::class 28 | ] 29 | ``` 30 | 31 | In Laravel 5.5 and above the service provider automatically. 32 | 33 | **3** - Now publish the migration for gamify tables: 34 | 35 | ``` 36 | php artisan vendor:publish --provider="Ansezz\Gamify\GamifyServiceProvider" --tag="migrations" 37 | ``` 38 | 39 | *Note:* It will generate migration for `points`, `badges`, `gamify_groups`, `pointables`, `badgables` tables, you will need to run `composer require doctrine/dbal` in order to support dropping and adding columns. 40 | 41 | ```bash 42 | php artisan migrate 43 | ``` 44 | 45 | You can publish the config file: 46 | ```bash 47 | php artisan vendor:publish --provider="Ansezz\Gamify\GamifyServiceProvider" --tag="config" 48 | ``` 49 | 50 | If your payee (model who will be getting the points) model is `App\User` then you don't have to change anything in `config/gamify.php`. 51 | 52 | ### Getting Started 53 | 54 | **1.** After package installation now add the **Gamify** trait on `App\User` model or any model who acts as **user** in your app. 55 | 56 | ```php 57 | use Ansezz\Gamify\Gamify; 58 | use Illuminate\Notifications\Notifiable; 59 | use Illuminate\Foundation\Auth\User as Authenticatable; 60 | 61 | class User extends Authenticatable 62 | { 63 | use Notifiable, Gamify; 64 | ``` 65 | 66 | ## ⭐️Point 👑 67 | 68 | **2.** Next step is to create a point. 69 | > - The point class is option because we save the point in database. 70 | > - You can create a point directly in your database without class. 71 | > - Create the point class if you need to add a check before achieve the point or if you wanna define a dynamic point value. 72 | ```bash 73 | php artisan gamify:point PostCreated 74 | ``` 75 | They will ask you if you wanna create the database badge record. 76 | 77 | > class attribute in badges table will take the class with namespace in this case: `App\Gamify\Points\PostCreated` 78 | 79 | It will create a Point class named `PostCreated` under `app/Gamify/Points/` folder. 80 | 81 | ```php 82 | user(); 104 | 105 | $point = Point::find(1); 106 | 107 | // or you can use facade function 108 | Gamify::achievePoint($point); 109 | 110 | 111 | // or via HasBadge trait method 112 | $user->achievePoint($point); 113 | ``` 114 | 115 | ### Undo a given point 116 | 117 | In some cases you would want to undo a given point. 118 | 119 | ```php 120 | $user = auth()->user(); 121 | 122 | $point = Point::find(1); 123 | 124 | // or you can use facade function 125 | Gamify::undoPoint($point); 126 | 127 | // or via HasPoint trait method 128 | $user->undoPoint($point); 129 | 130 | ``` 131 | 132 | You can also pass second argument as $event (Boolean) in function `achievePoint & undoPoint ($point, $event)`, default is `true`, to disable sending `PointsChanged` event. 133 | 134 | **Pro Tip 👌** You could also hook into the Eloquent model event and give point on `created` event. Similarly, `deleted` event can be used to undo the point. 135 | 136 | ### Get total reputation 137 | 138 | To get the total user points achieved you have `achieved_points` attribute available.. 139 | 140 | ```php 141 | // get integer point 142 | $user->achieved_points; // 20 143 | ``` 144 | 145 | ### Get points history 146 | 147 | the package stores all the points event log so you can get the history of points via the following relation: 148 | 149 | ```php 150 | foreach($user->points as $point) { 151 | // name of the point type 152 | $point->name; 153 | 154 | // how many points 155 | $point->point; 156 | } 157 | ``` 158 | 159 | ### Get badges history 160 | 161 | the package stores all the badges in database so you can get the history of badges via the following relation: 162 | 163 | ```php 164 | foreach($user->badges as $badge) { 165 | // name of the point type 166 | $point->name; 167 | 168 | // how many points 169 | $point->image; 170 | } 171 | ``` 172 | 173 | 174 | #### Event on points changed 175 | 176 | Whenever user point changes it fires `\Ansezz\Gamify\Events\PointsChanged` event which has the following payload: 177 | 178 | ```php 179 | class PointsChanged implements ShouldBroadcast { 180 | 181 | ... 182 | public function __construct(Model $subject, int $point, bool $increment) 183 | { 184 | $this->subject = $subject; 185 | $this->point = $point; 186 | $this->increment = $increment; 187 | } 188 | } 189 | ``` 190 | 191 | This event also broadcast in configured channel name so you can listen to it from your frontend via socket to live update points. 192 | 193 | ## 🏅 Achievement Badges 🏆 194 | 195 | Similar to Point type you have badges. They can be given to users based on rank or any other criteria. You should define badge level in config file. 196 | 197 | ### Create a Badge 198 | 199 | To generate a badge you can run following provided command: 200 | 201 | They will ask you if you wanna create the database badge record. 202 | 203 | > class attribute in badges table will take the class with namespace in this case: `App\Gamify\Badges\PostCreated` 204 | 205 | ```bash 206 | php artisan gamify:badge PostCreated 207 | ``` 208 | 209 | It will create a BadgeType class named `PostCreated` under `app/Gamify/Badges/` folder. 210 | 211 | For each level you need to define a function by level name to check if the subject is achieve the badge, esle we use `config('gamify.badge_is_archived')` by default you can change it in you config file `gamify.php`. 212 | 213 | ```php 214 | achieved_points >= 100; 232 | } 233 | 234 | /** 235 | * @param $badge 236 | * @param $subject 237 | * 238 | * @return bool 239 | */ 240 | public function intermediate($badge, $subject) 241 | { 242 | return $subject->achieved_points >= 200; 243 | } 244 | 245 | /** 246 | * @param $badge 247 | * @param $subject 248 | * 249 | * @return bool 250 | */ 251 | public function advanced($badge, $subject) 252 | { 253 | return $subject->achieved_points >= 300; 254 | } 255 | 256 | } 257 | ``` 258 | 259 | ```php 260 | // to reset point back to zero 261 | $user->resetPoint(); 262 | ``` 263 | 264 | ### Check if badge is Achieved by subject 265 | ```php 266 | $badage = Badge::find(1); 267 | $user = auth()->user(); 268 | 269 | $badge->isAchieved($user); 270 | ``` 271 | 272 | ### Sync All badges 273 | ```php 274 | // sync all badges for current subject using Facade 275 | Gamify::syncBadges($user); 276 | 277 | // or via HasBadge trait method 278 | $user->syncBadges(); 279 | ``` 280 | 281 | ### Sync One badge 282 | ```php 283 | $badge = Badge::find(1); 284 | // sync all badges for current subject using Facade 285 | Gamify::syncBadge($badge, $user) 286 | 287 | // or via HasBadge trait method 288 | $user->syncBadge($badge); 289 | ``` 290 | 291 | #### Event on badge achieved 292 | 293 | Whenever user point changes it fires `\Ansezz\Gamify\Events\BadgeAchieved` event which has the following payload: 294 | 295 | ```php 296 | class BadgeAchieved implements ShouldBroadcast { 297 | 298 | ... 299 | public function __construct($subject, $badge) 300 | { 301 | $this->subject = $subject; 302 | $this->badge = $badge; 303 | } 304 | } 305 | ``` 306 | 307 | 308 | ### Config Gamify 309 | 310 | ```php 311 | '\Ansezz\Gamify\Point', 316 | 317 | // Broadcast on private channel 318 | 'broadcast_on_private_channel' => true, 319 | 320 | // Channel name prefix, user id will be suffixed 321 | 'channel_name' => 'user.reputation.', 322 | 323 | // Badge model 324 | 'badge_model' => '\Ansezz\Gamify\Badge', 325 | 326 | // Where all badges icon stored 327 | 'badge_icon_folder' => 'images/badges/', 328 | 329 | // Extention of badge icons 330 | 'badge_icon_extension' => '.svg', 331 | 332 | // All the levels for badge 333 | 'badge_levels' => [ 334 | 'beginner' => 1, 335 | 'intermediate' => 2, 336 | 'advanced' => 3, 337 | ], 338 | 339 | // Default level 340 | 'badge_default_level' => 1, 341 | 342 | // Badge achieved vy default if check function not exit 343 | 'badge_is_archived' => false, 344 | 345 | // point achieved vy default if check function not exit 346 | 'point_is_archived' => true, 347 | ]; 348 | 349 | ``` 350 | 351 | ### Changelog 352 | 353 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 354 | 355 | ### Testing 356 | 357 | The package contains some integration/smoke tests, set up with Orchestra. The tests can be run via phpunit. 358 | 359 | ```bash 360 | $ composer test 361 | ``` 362 | 363 | ### Contributing 364 | 365 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 366 | 367 | ### Security 368 | 369 | If you discover any security related issues, please email ansezzouaine@gmail.com instead of using the issue tracker. 370 | 371 | ### Credits 372 | 373 | - [Anass Ez-zouaine](https://github.com/ansezz) (Author) 374 | 375 | 376 | ### License 377 | 378 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 379 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ansezz/laravel-gamify", 3 | "description": "Add Gamification in laravel app with point and badges support", 4 | "homepage": "https://github.com/ansezz/laravel-gamify", 5 | "type": "library", 6 | "license": "MIT", 7 | "keywords": [ 8 | "ansezz", 9 | "laravel", 10 | "achivement", 11 | "gamify", 12 | "gamification", 13 | "reputation", 14 | "points", 15 | "badge", 16 | "reward" 17 | ], 18 | "authors": [ 19 | { 20 | "name": "Anass Ez-zouaine", 21 | "email": "ansezzouaine@gmail.com" 22 | } 23 | ], 24 | "require": { 25 | "php": ">=7.2", 26 | "laravel/framework": "~5.4.0|~5.5.0|~5.6.0|~5.7.0|~5.8.0|^6.0|^7.0|^8.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "^9.0", 30 | "orchestra/testbench": "~3.8|^4.0", 31 | "mockery/mockery": "^0.9.4 || ~1.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Ansezz\\Gamify\\": "src/" 36 | }, 37 | "files": [ 38 | "src/helpers.php" 39 | ] 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "Ansezz\\Gamify\\Tests\\": "tests/" 44 | } 45 | }, 46 | "extra": { 47 | "laravel": { 48 | "providers": [ 49 | "Ansezz\\Gamify\\GamifyServiceProvider" 50 | ], 51 | "aliases": { 52 | "Gamify": "Ansezz\\Gamify\\Facades\\GamifyFacade" 53 | } 54 | } 55 | }, 56 | "scripts": { 57 | "test": "vendor/bin/phpunit" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | tests 18 | 19 | 20 | 21 | 22 | src/ 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Badge.php: -------------------------------------------------------------------------------- 1 | class)) { 20 | return (new $this->class)->levelIsAchieved($this->level, $subject); 21 | } 22 | 23 | return config('gamify.badge_is_archived'); 24 | } 25 | 26 | 27 | /** 28 | * Point Gamify Group 29 | * 30 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 31 | */ 32 | public function gamifyGroup() 33 | { 34 | return $this->belongsTo(GamifyGroup::class); 35 | } 36 | 37 | public function getImageAttribute($value) 38 | { 39 | if (!empty($value)) { 40 | return $value; 41 | } 42 | 43 | return $this->getDefaultIcon(); 44 | } 45 | 46 | /** 47 | * Get the default icon if not provided 48 | * 49 | * @return string 50 | */ 51 | protected function getDefaultIcon() 52 | { 53 | return sprintf( 54 | '%s/%s%s', 55 | rtrim(config('gamify.badge_icon_folder', 'images/badges'), '/'), 56 | Str::kebab($this->name), 57 | config('gamify.badge_icon_extension', '.svg') 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/BaseBadge.php: -------------------------------------------------------------------------------- 1 | {$method}($this, $subject); 30 | } 31 | 32 | return config('gamify.badge_is_archived'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/BasePoint.php: -------------------------------------------------------------------------------- 1 | forget('gamify.badges.all'); 64 | 65 | if ($this->confirm('Do you wanna create database record ?')) { 66 | $name = $this->ask('Badge name?'); 67 | $description = $this->ask('Badge description?'); 68 | $group = $this->ask('Badge Group?'); 69 | $level = $this->choice('Badge Level?', config('gamify.badge_levels')); 70 | 71 | $group = GamifyGroup::firstOrCreate(['name' => $group, 'type' => 'badge']); 72 | 73 | Badge::create([ 74 | 'name' => $name, 75 | 'description' => $description, 76 | 'gamify_group_id' => $group->id, 77 | 'level' => config('gamify.badge_levels')[$level], 78 | 'class' => $this->qualifyClass($this->argument('name')), 79 | ]); 80 | } 81 | 82 | return parent::handle(); 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/Console/MakePointCommand.php: -------------------------------------------------------------------------------- 1 | confirm('Do you wanna create database record ?')) { 64 | 65 | $name = $this->ask('Point name?'); 66 | $description = $this->ask('Point description?'); 67 | $group = $this->ask('Point Group?'); 68 | $point = $this->ask('Point value?'); 69 | $allow_duplicate = false; 70 | 71 | if ($this->confirm('Allow duplicate ?')) { 72 | $allow_duplicate = true; 73 | } 74 | 75 | $group = GamifyGroup::firstOrCreate(['name' => $group, 'type' => 'point']); 76 | 77 | Point::create([ 78 | 'name' => $name, 79 | 'description' => $description, 80 | 'allow_duplicate' => $allow_duplicate, 81 | 'point' => $point, 82 | 'gamify_group_id' => $group->id, 83 | 'class' => $this->qualifyClass($this->argument('name')), 84 | ]); 85 | } 86 | 87 | 88 | return parent::handle(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Console/stubs/badge.stub: -------------------------------------------------------------------------------- 1 | achieved_points >= 100; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Console/stubs/point.stub: -------------------------------------------------------------------------------- 1 | subject = $subject; 36 | $this->badge = $badge; 37 | } 38 | 39 | /** 40 | * Get the channels the event should broadcast on. 41 | * 42 | * @return \Illuminate\Broadcasting\Channel|\Illuminate\Broadcasting\Channel[] 43 | */ 44 | public function broadcastOn() 45 | { 46 | $channelName = config('gamify.channel_name') . $this->subject->getKey(); 47 | 48 | if (config('gamify.broadcast_on_private_channel')) { 49 | return new PrivateChannel($channelName); 50 | } 51 | 52 | return new Channel($channelName); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Events/PointsChanged.php: -------------------------------------------------------------------------------- 1 | subject = $subject; 41 | $this->point = $point; 42 | $this->increment = $increment; 43 | } 44 | 45 | /** 46 | * Get the channels the event should broadcast on. 47 | * 48 | * @return \Illuminate\Broadcasting\Channel|\Illuminate\Broadcasting\Channel[] 49 | */ 50 | public function broadcastOn() 51 | { 52 | $channelName = config('gamify.channel_name') . $this->subject->getKey(); 53 | 54 | if (config('gamify.broadcast_on_private_channel')) { 55 | return new PrivateChannel($channelName); 56 | } 57 | 58 | return new Channel($channelName); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Exceptions/LevelNotExist.php: -------------------------------------------------------------------------------- 1 | filter 19 | ->isAchieved($subject) 20 | ->map->id; 21 | 22 | $sync_res = $subject->badges()->sync($badgeIds); 23 | 24 | // send event for new badges 25 | if (count($sync_res['attached']) > 0) { 26 | Badge::query()->whereIn('id', $sync_res['attached'])->get()->each(function ($badge) use ($subject) { 27 | BadgeAchieved::dispatch($subject, $badge); 28 | }); 29 | } 30 | 31 | 32 | return true; 33 | } 34 | 35 | /** 36 | * @param $badge 37 | * @param $subject 38 | * 39 | * @return bool 40 | */ 41 | public function syncBadge($badge, $subject) 42 | { 43 | if ($badge->isAchieved($subject)) { 44 | $subject->badges()->syncWithoutDetaching($badge->id); 45 | BadgeAchieved::dispatch($subject, $badge); 46 | 47 | return true; 48 | } 49 | 50 | return false; 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/Facades/GamifyFacade.php: -------------------------------------------------------------------------------- 1 | define(\Ansezz\Gamify\Badge::class, function (Faker $faker) { 8 | return [ 9 | 'name' => $faker->text(50), 10 | 'description' => $faker->text, 11 | ]; 12 | }); 13 | -------------------------------------------------------------------------------- /src/Factories/GamifyGroupFactory.php: -------------------------------------------------------------------------------- 1 | define(\Ansezz\Gamify\GamifyGroup::class, function (Faker $faker) { 8 | return [ 9 | 'name' => $faker->text(50), 10 | 'type' => $faker->randomElement(['badge', 'point']), 11 | ]; 12 | }); 13 | -------------------------------------------------------------------------------- /src/Factories/PointFactory.php: -------------------------------------------------------------------------------- 1 | define(\Ansezz\Gamify\Point::class, function (Faker $faker) { 8 | return [ 9 | 'name' => $faker->text(50), 10 | 'point' => $faker->randomNumber(), 11 | 'allow_duplicate' => $faker->boolean, 12 | ]; 13 | }); 14 | -------------------------------------------------------------------------------- /src/GamifyGroup.php: -------------------------------------------------------------------------------- 1 | publishes([ 25 | __DIR__ . '/config/gamify.php' => config_path('gamify.php'), 26 | ], 'config'); 27 | 28 | $this->mergeConfigFrom(__DIR__ . '/config/gamify.php', 'gamify'); 29 | 30 | // Publish factory 31 | $this->loadFactoriesFrom(__DIR__ . '/Factories'); 32 | 33 | // publish migration 34 | if (!class_exists('CreateGamifyTables')) { 35 | $timestamp = date('Y_m_d_His', time()); 36 | $this->publishes([ 37 | __DIR__ . '/migrations/create_gamify_tables.php.stub' => database_path("/migrations/{$timestamp}_create_gamify_tables.php"), 38 | ], 'migrations'); 39 | } 40 | 41 | // register commands 42 | if ($this->app->runningInConsole()) { 43 | $this->commands([ 44 | MakePointCommand::class, 45 | MakeBadgeCommand::class, 46 | ]); 47 | } 48 | 49 | // register event listener 50 | Event::listen(PointsChanged::class, SyncBadges::class); 51 | } 52 | 53 | /** 54 | * Register bindings in the container. 55 | * 56 | * @return void 57 | */ 58 | public function register() 59 | { 60 | // register facade 61 | $this->app->bind(Gamify::class, function ($app) { 62 | return new Gamify(); 63 | }); 64 | 65 | $this->app->alias(Gamify::class, 'gamify'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Listeners/SyncBadges.php: -------------------------------------------------------------------------------- 1 | subject->syncBadges(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Point.php: -------------------------------------------------------------------------------- 1 | belongsTo(GamifyGroup::class); 18 | } 19 | 20 | public function isAchieved($subject) 21 | { 22 | if (class_exists($this->class)) { 23 | return ((new $this->class)($this, $subject)); 24 | } 25 | 26 | return config('gamify.point_is_archived'); 27 | } 28 | 29 | /** 30 | * @param $subject 31 | * 32 | * @return \Illuminate\Config\Repository|mixed 33 | */ 34 | public function getPoints($subject) 35 | { 36 | if (class_exists($this->class)) { 37 | $class = new $this->class; 38 | if (method_exists($class, 'getDynamicPoints')) { 39 | return $class->getDynamicPoints($this, $subject); 40 | } 41 | } 42 | 43 | return $this->point; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Traits/Gamify.php: -------------------------------------------------------------------------------- 1 | morphToMany(Badge::class, 'badgable'); 17 | } 18 | 19 | 20 | /** 21 | * @param null $subject 22 | * 23 | * @return $this 24 | */ 25 | public function syncBadges($subject = null) 26 | { 27 | $subject = is_null($subject) ? $this : $subject; 28 | Gamify::syncBadges($subject); 29 | 30 | return $this; 31 | } 32 | 33 | 34 | /** 35 | * @param $badge 36 | * @param null $subject 37 | * 38 | * @return $this 39 | */ 40 | public function syncBadge($badge, $subject = null) 41 | { 42 | $subject = is_null($subject) ? $this : $subject; 43 | Gamify::syncBadge($badge, $subject); 44 | 45 | return $this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Traits/HasPoints.php: -------------------------------------------------------------------------------- 1 | morphToMany(Point::class, 'pointable')->withPivot(['achieved_points']); 16 | } 17 | 18 | public function getAchievedPointsAttribute() 19 | { 20 | return $this->points()->sum('achieved_points'); 21 | } 22 | 23 | /** 24 | * Reset a user point to zero 25 | * 26 | * @param bool $event 27 | * 28 | * @return mixed 29 | */ 30 | public function resetPoint($event = true) 31 | { 32 | $this->points()->delete(); 33 | 34 | if ($event) { 35 | PointsChanged::dispatch($this, 0, false); 36 | } 37 | 38 | return $this; 39 | } 40 | 41 | 42 | /** 43 | * @param \Ansezz\Gamify\Point $point 44 | * 45 | * @param bool $event 46 | * 47 | * @return $this 48 | */ 49 | public function achievePoint(Point $point, $event = true) 50 | { 51 | $achieved_points = $point->getPoints($this); 52 | $this->points()->attach([$point->id => ['achieved_points' => $achieved_points]]); 53 | 54 | if ($event) { 55 | PointsChanged::dispatch($this, $achieved_points, true); 56 | } 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * @param $point 63 | * @param bool $event 64 | * 65 | * @return $this 66 | */ 67 | public function undoPoint($point, $event = true) 68 | { 69 | $this->points()->detach($point); 70 | 71 | if ($event) { 72 | PointsChanged::dispatch($this, $point->getPoints($this), false); 73 | } 74 | 75 | return $this; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/config/gamify.php: -------------------------------------------------------------------------------- 1 | '\Ansezz\Gamify\Point', 6 | 7 | // Broadcast on private channel 8 | 'broadcast_on_private_channel' => true, 9 | 10 | // Channel name prefix, user id will be suffixed 11 | 'channel_name' => 'user.reputation.', 12 | 13 | // Badge model 14 | 'badge_model' => '\Ansezz\Gamify\Badge', 15 | 16 | // Where all badges icon stored 17 | 'badge_icon_folder' => 'images/badges/', 18 | 19 | // Extention of badge icons 20 | 'badge_icon_extension' => '.svg', 21 | 22 | // All the levels for badge 23 | 'badge_levels' => [ 24 | 'beginner' => 1, 25 | 'intermediate' => 2, 26 | 'advanced' => 3, 27 | ], 28 | 29 | // Default level 30 | 'badge_default_level' => 1, 31 | 32 | // Badge achieved vy default if check function not exit 33 | 'badge_is_archived' => false, 34 | 35 | // point achieved vy default if check function not exit 36 | 'point_is_archived' => true, 37 | ]; 38 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | id(); 19 | $table->string('name'); 20 | $table->string('description')->nullable(); 21 | $table->double('point'); 22 | $table->string('class')->nullable(); 23 | $table->boolean('allow_duplicate')->default(false); 24 | $table->unsignedBigInteger('gamify_group_id'); 25 | $table->timestamps(); 26 | }); 27 | 28 | // Badges Table 29 | Schema::create('badges', function (Blueprint $table) { 30 | $table->id(); 31 | $table->string('name'); 32 | $table->string('description')->nullable(); 33 | $table->string('image')->nullable(); 34 | $table->string('class')->nullable(); 35 | $table->unsignedBigInteger('level')->nullable(); 36 | $table->unsignedBigInteger('gamify_group_id')->nullable(); 37 | $table->timestamps(); 38 | }); 39 | 40 | // Group table 41 | Schema::create('gamify_groups', function (Blueprint $table) { 42 | $table->id(); 43 | $table->string('name'); 44 | $table->enum('type', ['badge', 'point']); 45 | $table->timestamps(); 46 | }); 47 | 48 | // Pointables table 49 | Schema::create('pointables', function (Blueprint $table) { 50 | $table->id(); 51 | $table->unsignedBigInteger('point_id'); 52 | $table->morphs('pointable'); 53 | $table->double('achieved_points')->default(0); 54 | $table->timestamps(); 55 | }); 56 | 57 | // Badgables table 58 | Schema::create('badgables', function (Blueprint $table) { 59 | $table->id(); 60 | $table->unsignedBigInteger('badge_id'); 61 | $table->morphs('badgable'); 62 | $table->timestamps(); 63 | }); 64 | } 65 | 66 | /** 67 | * Reverse the migrations. 68 | * 69 | * @return void 70 | */ 71 | public function down() 72 | { 73 | Schema::dropIfExists('badges'); 74 | Schema::dropIfExists('gamify_groups'); 75 | Schema::dropIfExists('points'); 76 | Schema::dropIfExists('pointables'); 77 | Schema::dropIfExists('badgables'); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/DemoTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | GamifyFacade::class, 22 | ]; 23 | } 24 | 25 | /** 26 | * Define environment setup. 27 | * 28 | * @param \Illuminate\Foundation\Application $app 29 | * 30 | * @return void 31 | */ 32 | protected function getEnvironmentSetUp($app) 33 | { 34 | // Setup default database to use sqlite :memory: 35 | $app['config']->set('database.connections.testbench', [ 36 | 'driver' => 'sqlite', 37 | 'database' => ':memory:', 38 | 'prefix' => '', 39 | ]); 40 | } 41 | } 42 | 43 | --------------------------------------------------------------------------------