<?php
namespace Cms\LogBundle\Service;
use Cms\CoreBundle\Model\Interfaces\Identifiable\IdentifiableInterface;
use Cms\CoreBundle\Model\Interfaces\Loggable\LoggableInterface;
use Cms\CoreBundle\Service\ContextManager;
use Cms\CoreBundle\Util\Doctrine\EntityManager;
use Cms\LogBundle\Entity\ActivityLog;
use Doctrine\Common\Util\ClassUtils;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Http\FirewallMapInterface;
final class LoggingService implements EventSubscriberInterface
{
/**
* @var array
*/
private $logs = [];
/**
* @var array|null
*/
private $backup = null;
/**
* @var EntityManager
*/
private $em;
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var ContextManager
*/
private $contextManager;
/**
* @var FirewallMapInterface
*/
private FirewallMapInterface $firewallMap;
/**
* @param EntityManager $em
* @param RequestStack $requestStack
* @param ContextManager $contextManager
* @param FirewallMap $firewallMap
*/
public function __construct(EntityManager $em, RequestStack $requestStack, ContextManager $contextManager, FirewallMapInterface $firewallMap)
{
$this->em = $em;
$this->requestStack = $requestStack;
$this->contextManager = $contextManager;
$this->firewallMap = $firewallMap;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
KernelEvents::TERMINATE => ['onKernelTerminate'],
];
}
/**
* @param array $logs
*/
public function createLogs(array $logs)
{
foreach ($logs as $log) {
if ( ! is_array($log)) {
$log = [
$log,
];
}
call_user_func_array([$this, 'createLog'], $log);
}
}
/**
* @param IdentifiableInterface $object
* @param int $objectId Default null
*/
public function createLog($object, $objectId = null)
{
// create a new log object
$entry = new ActivityLog();
// obtain ip from master request
$ip = $this->requestStack->getMasterRequest()->headers->get('X-FORWARDED-FOR');
if (empty($ip)) {
$ip = $this->requestStack->getMasterRequest()->getClientIp();
}
$entry->setIp($ip);
// get the action
$entry->setAction(
$this->requestStack->getCurrentRequest()->get('_controller')
);
// assign id, if object has no id then use provided objectId
$entry->setObjectId($object->getId());
if (empty($entry->getObjectId())) {
$entry->setObjectId($objectId);
}
// attach the user currently logged in, could be impersonated
if ( ! empty($this->contextManager->getGlobalContext()->getEffectiveAccount())) {
$entry->setAccount(
$this->contextManager->getGlobalContext()->getEffectiveAccount()
);
}
// attach the real, logged in account
if ( ! empty($this->contextManager->getGlobalContext()->getAuthenticatedAccount())) {
$entry->setRealAccount(
$this->contextManager->getGlobalContext()->getAuthenticatedAccount()
);
}
// attach class name
$entry->setObjectClass(ClassUtils::getClass($object));
// log any extra details
if ($object instanceof LoggableInterface) {
if ( ! empty($object->getLoggableDetails())) {
$entry->setDetails(
array_merge(
$object->getLoggableDetails(),
[
// TODO: might want to do this a different way
'blame' => ( ! empty($this->contextManager->getGlobalContext()->getImpersonatedAccount()))
? [
'impersonated' => [
'id' => $this->contextManager->getGlobalContext()->getImpersonatedAccount(
)->getId(),
'email' => $this->contextManager->getGlobalContext()->getImpersonatedAccount(
)->getEmail(),
'name' => $this->contextManager->getGlobalContext()->getImpersonatedAccount(
)->getDisplayName(),
],
'authenticated' => [
'id' => $this->contextManager->getGlobalContext()->getAuthenticatedAccount(
)->getId(),
'email' => $this->contextManager->getGlobalContext()->getAuthenticatedAccount(
)->getEmail(),
'name' => $this->contextManager->getGlobalContext()->getAuthenticatedAccount(
)->getDisplayName(),
],
]
: [
'authenticated' => [
'id' => $this->contextManager->getGlobalContext()->getAuthenticatedAccount(
)->getId(),
'email' => $this->contextManager->getGlobalContext()->getAuthenticatedAccount(
)->getEmail(),
'name' => $this->contextManager->getGlobalContext()->getAuthenticatedAccount(
)->getDisplayName(),
],
]
,
]
)
);
}
}
$request = $this->requestStack->getCurrentRequest();
if ($request instanceof Request) {
$firewallName = $this->firewallMap->getFirewallConfig($request)?->getName();
if (in_array($firewallName, ['portal_api', 'portal_admin_api'], true)) {
$entry->setAuthType(ActivityLog::AUTH_TYPE__API);
} elseif ($firewallName === 'dashboard') {
$entry->setAuthType(ActivityLog::AUTH_TYPE__WEB);
}
}
// we are spooling
$this->logs[] = $entry;
}
public function backup()
{
$this->backup = $this->logs;
}
public function restore()
{
if ( ! is_array($this->backup)) {
throw new \Exception();
}
$this->logs = $this->backup;
}
public function commit()
{
$this->backup = null;
}
public function onKernelTerminate()
{
// see if we have any logs to save
if ( ! empty($this->logs)) {
// save all to db
$this->em->saveAll($this->logs);
// clear
$this->logs = [];
}
}
}