Skip to content

Commit

Permalink
Add last profile viewers
Browse files Browse the repository at this point in the history
Merge pull request #5 from MichaelBelgium/last-profile-viewers
  • Loading branch information
MichaelBelgium authored Feb 2, 2019
2 parents 6efacf1 + f666104 commit 453533a
Show file tree
Hide file tree
Showing 17 changed files with 283 additions and 94 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ php flarum cache:clear

# Features
* Tracks and displays **unique** profileviews in the usercard
* Displays a list of last viewers on a user profile

# Media

![image](http://puu.sh/yxd7o.png)

![image](http://puu.sh/CCJvo.png)
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
"autoload": {
"psr-4": {
"michaelbelgium\\profileviews\\": "src/"
"Michaelbelgium\\Profileviews\\": "src/"
}
},
"extra": {
Expand Down
22 changes: 9 additions & 13 deletions extend.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
<?php
use michaelbelgium\profileviews\listeners;

use Michaelbelgium\Profileviews\Listeners\AddUserProfileViewsRelationship;
use Michaelbelgium\Profileviews\Controllers\CreateUserProfileViewController;
use Illuminate\Contracts\Events\Dispatcher;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\Api\Event\Serializing;
use Flarum\Extend\Locales;
use Flarum\Extend\Frontend;
use Flarum\Extend\Routes;

return [
(new Frontend('forum'))
->js(__DIR__. '/js/dist/forum.js'),
->js(__DIR__. '/js/dist/forum.js')
->css(__DIR__. '/less/extension.less'),

new Locales(__DIR__ . '/locale'),

(new Routes('api'))
->post('/profileview', 'profileview.create', CreateUserProfileViewController::class),

function (Dispatcher $events) {
$events->subscribe(listeners\AddProfileViewHandler::class);
$events->subscribe(listeners\AddUserProfileViewsRelationship::class);

$events->listen(Serializing::class, function (Serializing $event) {
if ($event->isSerializer(UserSerializer::class)) {
$event->attributes['views'] = $event->model->profileViews()->count();
}
});
$events->subscribe(AddUserProfileViewsRelationship::class);
}
];
2 changes: 1 addition & 1 deletion js/dist/forum.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion js/dist/forum.js.map

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions js/src/ProfileView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Model from 'flarum/Model';

export default class ProfileView extends Model {
//comes from ProfileViewSerializer (viewer(), viewedUser(), visited_at)
visitedAt = Model.attribute('visited_at', Model.transformDate);
viewer = Model.hasOne('viewer');
viewedUser = Model.hasOne('viewedUser');
}
60 changes: 57 additions & 3 deletions js/src/forum/index.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,76 @@
import app from 'flarum/app';
import User from 'flarum/models/User';
import UserPage from 'flarum/components/UserPage';
import UserCard from 'flarum/components/UserCard';
import FieldSet from 'flarum/components/FieldSet';
import icon from 'flarum/helpers/icon';
import avatar from 'flarum/helpers/avatar';
import username from 'flarum/helpers/username';
import Model from 'flarum/Model';
import ItemList from 'flarum/utils/ItemList';
import { extend } from 'flarum/extend';
import humanTime from 'flarum/utils/humanTime';
import ProfileView from '../ProfileView';

app.initializers.add('michaelbelgium-flarum-profile-views', function() {
User.prototype.views = Model.attribute('views');
app.store.models.userprofileview = ProfileView;//".userprofileview" = serializer type "userprofileview"
User.prototype.profileViews = Model.hasMany('profileViews');//comes from AddUserProfileViewsRelationship::RELATIONSHIP = php model relationship method

extend(UserCard.prototype, 'infoItems', function(items) {
const user = this.props.user;

const count = user.profileViews() === false ? 0 : user.profileViews().length;

items.add('profile-views',(
<span>
{icon('far fa-eye')}
{' '}
{app.translator.trans('flarum_profile_views.forum.user.views_count_text', {viewcount: user.views() == 0 ? '0' : user.views()})}
{app.translator.trans('flarum_profile_views.forum.user.views_count_text', {viewcount: '' + count})}
</span>
));
});

extend(UserPage.prototype, 'sidebarItems', function(items) {
const lastViewed = new ItemList();

var views = this.user.profileViews();

if(views !== false)
{
if(views.length >= 5) {
views = views.slice(0, 5);
}

views.forEach(pv => {
lastViewed.add('lastUser-' + pv.viewer().id(),
<a href={app.forum.attribute('baseUrl') + '/u/' + pv.viewer().username() }>
{avatar(pv.viewer(), {className: 'lastUser-avatar'})}
<div>
{username(pv.viewer())}
<span className="lastUser-visited" title={pv.visitedAt().toLocaleString()}>{humanTime(pv.visitedAt())}</span>
</div>
</a>
);
});
}

items.add('lastViewedUsers', FieldSet.component({
label: app.translator.trans('flarum_profile_views.forum.user.title_last_viewers'),
className: 'LastUsers',
children: lastViewed.toArray()
}));
});

extend(UserPage.prototype, 'show', function() {
if(typeof app.session.user !== 'undefined' && app.session.user.id() !== this.user.id())
{
app.request({
method: 'POST',
url: app.forum.attribute('apiUrl') + '/profileview',
data: {
viewer: app.session.user.id(),
viewedUser: this.user.id()
}
});
}
});
});
37 changes: 37 additions & 0 deletions less/extension.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
fieldset.LastUsers {
ul {
list-style-type: none;
padding: 0;

li a {
display: block;

span.lastUser-avatar {
width: 20%;
display: inline-block;
height: 35px;
line-height: 35px;
}

div {
display: inline-block;
width: 80%;

span.username {
margin-left: 6px;
}

span.lastUser-visited {
float: right;
font-size: .8em;
}
}
}

li a:hover {
div > span {
text-decoration: underline;
}
}
}
}
3 changes: 2 additions & 1 deletion locale/en.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
flarum_profile_views:
forum:
user:
views_count_text: viewed {viewcount} times
views_count_text: viewed {viewcount} times
title_last_viewers: Last profile viewers
7 changes: 7 additions & 0 deletions migrations/2019_01_12_103741_drop_ip_views.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

use Flarum\Database\Migration;

return Migration::dropColumns("user_profile_views", [
"ip" => ["string"]
]);
7 changes: 7 additions & 0 deletions migrations/2019_01_12_104853_add_visited_at_views.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

use Flarum\Database\Migration;

return Migration::addColumns("user_profile_views", [
"visited_at" => ["datetime"]
]);
66 changes: 66 additions & 0 deletions src/Listeners/AddUserProfileViewsRelationship.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
namespace Michaelbelgium\Profileviews\Listeners;

use Illuminate\Contracts\Events\Dispatcher;
use Flarum\Event\GetApiRelationship;
use Flarum\Event\GetModelRelationship;
use Flarum\User\User;
use Flarum\Api\Event\WillGetData;
use Flarum\Api\Serializer\UserSerializer;
use Flarum\Api\Controller\ShowUserController;
use Michaelbelgium\Profileviews\Models\UserProfileView;
use Michaelbelgium\Profileviews\Serializers\UserProfileViewSerializer;

class AddUserProfileViewsRelationship
{
const RELATIONSHIP = "profileViews"; //$user->profileViews()
const RELATIONSHIP_OTHER = "viewedProfiles"; //$user->viewedProfiles()

/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(WillGetData::class, [$this, 'includeTagsRelationship']);
$events->listen(GetModelRelationship::class, [$this, 'getModelRelationship']);
$events->listen(GetApiRelationship::class, [$this, 'getApiRelationship']);
}

public function getModelRelationship(GetModelRelationship $event)
{
if($event->isRelationship(User::class, self::RELATIONSHIP))
{
return $event->model->hasMany(UserProfileView::class, 'viewed_user_id');
}

if($event->isRelationship(User::class, self::RELATIONSHIP_OTHER))
{
return $event->model->hasMany(UserProfileView::class, 'viewer_id');
}
}

/**
* @param GetApiRelationship $event
* @return \Tobscure\JsonApi\Relationship|null
*/
public function getApiRelationship(GetApiRelationship $event)
{
if ($event->isRelationship(UserSerializer::class, self::RELATIONSHIP)) {
return $event->serializer->hasMany($event->model, UserProfileViewSerializer::class, self::RELATIONSHIP);
}

//todo test:
if ($event->isRelationship(UserSerializer::class, self::RELATIONSHIP_OTHER)) {
return $event->serializer->hasMany($event->model, UserProfileViewSerializer::class, self::RELATIONSHIP_OTHER);
}
}

/**
* @param WillGetData $event
*/
public function includeTagsRelationship(WillGetData $event)
{
if($event->controller->serializer == UserSerializer::class)
$event->addInclude([self::RELATIONSHIP, self::RELATIONSHIP.'.viewer', self::RELATIONSHIP.'.viewedUser']);//".x" comes from model relationship UserProfileView
}
}
22 changes: 22 additions & 0 deletions src/Models/UserProfileView.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Michaelbelgium\Profileviews\Models;

use Flarum\Database\AbstractModel;
use Flarum\User\User;

class UserProfileView extends AbstractModel
{
protected $table = 'user_profile_views';
protected $dates = ['visited_at'];

public function viewedUser()
{
return $this->belongsTo(User::class, 'viewed_user_id');
}

public function viewer()
{
return $this->belongsTo(User::class, 'viewer_id');
}
}
28 changes: 28 additions & 0 deletions src/Serializers/UserProfileViewSerializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Michaelbelgium\Profileviews\Serializers;

use Flarum\Api\Serializer\AbstractSerializer;
use Flarum\Api\Serializer\UserSerializer;

class UserProfileViewSerializer extends AbstractSerializer
{
protected $type = 'userprofileview';

protected function getDefaultAttributes($profileview)
{
return [
'visited_at' => $this->formatDate($profileview->visited_at)
];
}

protected function viewedUser($profileview)
{
return $this->hasOne($profileview, UserSerializer::class);
}

protected function viewer($profileview)
{
return $this->hasOne($profileview, UserSerializer::class);
}
}
34 changes: 34 additions & 0 deletions src/controllers/CreateUserProfileViewController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Michaelbelgium\Profileviews\Controllers;

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\JsonResponse;
use Flarum\User\User;
use Carbon\Carbon;
use Michaelbelgium\Profileviews\Models\UserProfileView;

class CreateUserProfileViewController implements RequestHandlerInterface
{
public function handle(Request $request): Response
{
$viewedUserId = array_get($request->getParsedBody(), 'viewedUser');
$viewerId = array_get($request->getParsedBody(), 'viewer');
$viewedUser = User::find($viewedUserId);

$profileView = $viewedUser->profileViews()->where('viewer_id', $viewerId)->first();

if(is_null($profileView)) {
$profileView = new UserProfileView();
$profileView->viewedUser()->associate($viewedUser);
$profileView->viewer()->associate(User::find($viewerId));
}

$profileView->visited_at = Carbon::now();
$profileView->save();

return new JsonResponse($profileView->toArray());
}
}
Loading

0 comments on commit 453533a

Please sign in to comment.