Merge branch '11-logg-user-actions' into 'master'
Resolve "Logg user actions." Closes #11 See merge request pnx/httpcb!8
This commit is contained in:
commit
a3ac12bc2d
9 changed files with 324 additions and 4 deletions
|
|
@ -30,7 +30,8 @@ use Httpcb\Auth,
|
|||
Httpcb\Menu;
|
||||
|
||||
use App\Listener\AclListener,
|
||||
App\Listener\DispatchListener;
|
||||
App\Listener\DispatchListener,
|
||||
App\Listener\ActivityLog;
|
||||
|
||||
/**
|
||||
* The FactoryDefault Dependency Injector automatically register the right services providing a full stack framework
|
||||
|
|
@ -216,6 +217,17 @@ $di->setShared('db', function () use ($di, $config) {
|
|||
return $db;
|
||||
});
|
||||
|
||||
$di->setShared('eventsManager', function () {
|
||||
|
||||
$activityLog = new ActivityLog();
|
||||
|
||||
$eventsManager = new Phalcon\Events\Manager();
|
||||
$eventsManager->attach('user', $activityLog);
|
||||
$eventsManager->attach('auth', $activityLog);
|
||||
|
||||
return $eventsManager;
|
||||
});
|
||||
|
||||
/**
|
||||
* If the configuration specify the use of metadata adapter use it or use memory otherwise
|
||||
*/
|
||||
|
|
@ -286,8 +298,11 @@ $di->set('oauth', function() use ($config) {
|
|||
return new OAuth($config);
|
||||
});
|
||||
|
||||
$di->set('auth', function() use ($config) {
|
||||
return new Auth($config);
|
||||
$di->set('auth', function() use ($di, $config) {
|
||||
$auth = new Auth($config);
|
||||
$auth->setEventsManager($di->get('eventsManager'));
|
||||
|
||||
return $auth;
|
||||
});
|
||||
|
||||
$di->set('acl', 'Httpcb\Acl');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
namespace App\Controller;
|
||||
|
||||
use App\Controller\ControllerBase,
|
||||
App\Form\UserSettings as UserSettingsForm;
|
||||
App\Form\UserSettings as UserSettingsForm,
|
||||
App\Model\Data\ActivityLog;
|
||||
|
||||
class UserController extends ControllerBase
|
||||
{
|
||||
|
|
@ -34,4 +35,14 @@ class UserController extends ControllerBase
|
|||
|
||||
$this->view->form = $form;
|
||||
}
|
||||
|
||||
public function activityAction($page = 1)
|
||||
{
|
||||
$user = $this->_getAuth()->getUser();
|
||||
|
||||
$paginator = ActivityLog::getPaginationList($user->getId(), $page);
|
||||
|
||||
$this->view->page = $paginator->getPaginate();
|
||||
$this->view->pagination_url = '/user/activity/';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@ class Auth extends Component
|
|||
// Verify password
|
||||
$hash = $user->getPassword();
|
||||
if (strlen($hash) > 1 && password_verify($password, $hash)) {
|
||||
|
||||
$this->setIdentity($user->getId());
|
||||
|
||||
$this->_eventsManager->fire('auth:onLogin', $this, 'password');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +72,9 @@ class Auth extends Component
|
|||
}
|
||||
|
||||
$this->setIdentity($user->getId());
|
||||
|
||||
$this->_eventsManager->fire('auth:onLogin', $this,
|
||||
"OAuth {$auth['provider']}");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
68
app/listeners/ActivityLog.php
Normal file
68
app/listeners/ActivityLog.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listener;
|
||||
|
||||
use Phalcon\Mvc\User\Plugin,
|
||||
Phalcon\Events\Event,
|
||||
App\Model\Data\User,
|
||||
App\Model\Data\ActivityLog as ActivityLogger,
|
||||
Httpcb\Auth;
|
||||
|
||||
class ActivityLog extends Plugin
|
||||
{
|
||||
/**
|
||||
* @var ActivityLogger
|
||||
*/
|
||||
protected $_table = null;
|
||||
|
||||
/**
|
||||
* On login event.
|
||||
*
|
||||
* @param Event $event
|
||||
* @param Auth $auth
|
||||
* @param $type
|
||||
*/
|
||||
public function onLogin(Event $event, Auth $auth, $type)
|
||||
{
|
||||
$this->_log($auth->getUser(), "Logged in ({$type})");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Event $event
|
||||
* @param User $auth
|
||||
*/
|
||||
public function onPasswordCreated(Event $event, User $user)
|
||||
{
|
||||
$this->_log($user, "Created password");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Event $event
|
||||
* @param User $auth
|
||||
*/
|
||||
public function onPasswordChanged(Event $event, User $user)
|
||||
{
|
||||
$this->_log($user, "Changed password");
|
||||
}
|
||||
|
||||
protected function _log(User $user, $message)
|
||||
{
|
||||
$ip = (new \Phalcon\Http\Request())->getClientAddress();
|
||||
|
||||
$data = [
|
||||
'user_id' => $user->getId(),
|
||||
'ip' => $ip,
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
return $this->_getLogger()->create($data);
|
||||
}
|
||||
|
||||
protected function _getLogger()
|
||||
{
|
||||
if ($this->_table === null) {
|
||||
$this->_table = new ActivityLogger();
|
||||
}
|
||||
return $this->_table;
|
||||
}
|
||||
}
|
||||
32
app/migrations/20180401083402_create_activity_log.php
Normal file
32
app/migrations/20180401083402_create_activity_log.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
class CreateActivityLog extends AbstractMigration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$table = $this->table('activity_log');
|
||||
|
||||
$table->addColumn('timestamp', 'datetime', [
|
||||
'default' => 'CURRENT_TIMESTAMP',
|
||||
'after' => 'email'
|
||||
]);
|
||||
|
||||
$table->addColumn('user_id', 'integer')
|
||||
->addForeignKey('user_id', 'user', ['id']);
|
||||
|
||||
$table->addColumn('ip', 'string', [
|
||||
'null' => true,
|
||||
'length' => 50,
|
||||
]);
|
||||
|
||||
$table->addColumn('message', 'string', [
|
||||
'null' => true,
|
||||
'length' => 255,
|
||||
]);
|
||||
|
||||
$table->save();
|
||||
}
|
||||
}
|
||||
131
app/models/Data/ActivityLog.php
Normal file
131
app/models/Data/ActivityLog.php
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Data;
|
||||
|
||||
use \Phalcon\Paginator\Adapter\QueryBuilder;
|
||||
|
||||
class ActivityLog extends Base
|
||||
{
|
||||
protected $id;
|
||||
|
||||
protected $timestamp;
|
||||
|
||||
protected $user_id;
|
||||
|
||||
protected $ip;
|
||||
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $id
|
||||
* @return User
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTimestamp()
|
||||
{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $timestamp
|
||||
* @return ActivityLog
|
||||
*/
|
||||
public function setTimestamp($timestamp)
|
||||
{
|
||||
$this->timestamp = $timestamp;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserId()
|
||||
{
|
||||
return $this->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $user_id
|
||||
* @return ActivityLog
|
||||
*/
|
||||
public function setUserId($user_id)
|
||||
{
|
||||
$this->user_id = $user_id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIp()
|
||||
{
|
||||
return $this->ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $ip
|
||||
* @return ActivityLog
|
||||
*/
|
||||
public function setIp($ip)
|
||||
{
|
||||
$this->ip = $ip;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @return ActivityLog
|
||||
*/
|
||||
public function setMessage($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $userid
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @return \Phalcon\Paginator\AdapterInterface
|
||||
*/
|
||||
public static function getPaginationList($userid, $page = 1, $limit = 30)
|
||||
{
|
||||
$builder = (new self())->getModelsManager()->createBuilder();
|
||||
|
||||
$builder->from(self::class)
|
||||
->where('user_id = :uid:', array('uid' => $userid))
|
||||
->orderBy('timestamp DESC');
|
||||
|
||||
$paginator = new QueryBuilder(array(
|
||||
'builder' => $builder,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
));
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,12 @@ class User extends Model
|
|||
public function initialize()
|
||||
{
|
||||
$this->useDynamicUpdate(true);
|
||||
|
||||
// Keep snapshots so we know if something changes.
|
||||
$this->keepSnapshots(true);
|
||||
|
||||
// Set default event manager
|
||||
$this->setEventsManager($this->getDI()->get('eventsManager'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -200,4 +206,22 @@ class User extends Model
|
|||
"bind" => [ 'v' => $value ]
|
||||
]);
|
||||
}
|
||||
|
||||
public function beforeSave()
|
||||
{
|
||||
$manager = $this->getEventsManager();
|
||||
|
||||
// EventManager exist and password field has changed.
|
||||
if ($manager && $this->hasChanged('password')) {
|
||||
|
||||
$old_value = $this->getOldSnapshotData()['password'];
|
||||
|
||||
// Empty password before
|
||||
if (strlen($old_value) < 1) {
|
||||
$manager->fire('user:onPasswordCreated', $this);
|
||||
} else {
|
||||
$manager->fire('user:onPasswordChanged', $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
<ul aria-labelledby="user-dropdown" class="navigation-user-menu-dropdown-list">
|
||||
<li>{{ link_to(['for': 'user-settings'], '<i class="icon ion-gear-a"></i> Settings') }}</li>
|
||||
<li>{{ link_to('/user/activity', '<i class="icon ion-android-list"></i> Activity') }}</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li>{{ link_to(['for': 'logout'], '<i class="icon ion-log-out"></i> Log out') }}</li>
|
||||
</ul>
|
||||
|
|
|
|||
31
app/views/user/activity.volt
Normal file
31
app/views/user/activity.volt
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
<div class="section">
|
||||
|
||||
<h3>Activity Log</h3>
|
||||
|
||||
<table class="table table-condensed table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Ip</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for item in page.items %}
|
||||
<tr>
|
||||
<td>{{ item.getTimestamp() }}</td>
|
||||
<td>{{ item.getIp() }}</td>
|
||||
<td>{{ item.getMessage() }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<nav class="text-center" aria-label="Page navigation">
|
||||
{{ partial('pagination') }}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in a new issue