<?php
namespace Platform\ControlPanelBundle\Controller\Dashboard;
use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
use Cms\CoreBundle\Service\ContextManager;
use Cms\CoreBundle\Util\DateTimeUtils;
use Cms\LogBundle\Entity\ActivityLog;
use Cms\ModuleBundle\Entity\Draft;
use Cms\ModuleBundle\Service\ModuleManager;
use Cms\TenantBundle\Entity\Tenant;
use Cms\TenantBundle\Util\TenantDoctrineFilter;
use Cms\WorkflowsBundle\Entity\Publication\ScheduledPublication;
use Doctrine\DBAL\Exception;
use Doctrine\ORM\QueryBuilder;
use Platform\ControlPanelBundle\Controller\DashboardController;
use Platform\ControlPanelBundle\Provider\WidgetStatProvider;
use Platform\SecurityBundle\Entity\Login\Attempt;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Class StatsController
* @package Platform\ControlPanelBundle\Controller\Dashboard
*/
final class StatsController extends DashboardController
{
const ROUTES__INDEX = 'platform.control_panel.dashboard.stats.index';
const ROUTES__INDEX_TENANT = 'platform.control_panel.dashboard.stats.index_tenant';
const ROUTES__MODULES = 'platform.control_panel.dashboard.stats.modules';
const ROUTES__MODULES_TENANT = 'platform.control_panel.dashboard.stats.modules_tenant';
const ROUTES__MODULES_UNIQUE = 'platform.control_panel.dashboard.stats.modules_unique';
const ROUTES__MODULES_UNIQUE_TENANT = 'platform.control_panel.dashboard.stats.modules_unique_tenant';
const ROUTES__LOGINS = 'platform.control_panel.dashboard.stats.logins';
const ROUTES__LOGINS_TENANT = 'platform.control_panel.dashboard.stats.logins_tenant';
const ROUTES__WIDGETS = 'platform.control_panel.dashboard.stats.widgets';
const ROUTES__WIDGETS_TENANT = 'platform.control_panel.dashboard.stats.widgets_tenant';
const MONTHS = 12;
const COLORS = array(
'ff0029',
'377eb8',
'66a61e',
'984ea3',
'00d2d5',
'ff7f00',
'af8d00',
'7f80cd',
'b3e900',
'c42e60',
'a65628',
'f781bf',
'8dd3c7',
'bebada',
'fb8072',
'80b1d3',
'fdb462',
'fccde5',
'bc80bd',
'ffed6f',
'c4eaff',
'cf8c00',
'1b9e77',
'd95f02',
);
/**
* @param Tenant|null $tenant
* @return RedirectResponse
*
* @Route(
* "",
* name = StatsController::ROUTES__INDEX
* )
*
* @Route(
* "/{tenant}",
* name = StatsController::ROUTES__INDEX_TENANT,
* requirements = {
* "tenant" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function indexAction(Tenant $tenant = null)
{
return $this->redirectToRoute(
( ! empty($tenant)) ? self::ROUTES__LOGINS_TENANT : self::ROUTES__LOGINS,
array(
'tenant' => ( ! empty($tenant)) ? $tenant->getId() : null,
)
);
}
/**
* @param Tenant|null $tenant
* @return DocumentScene
*
* @Route(
* "/modules",
* name = StatsController::ROUTES__MODULES
* )
*
* @Route(
* "/{tenant}/modules",
* name = StatsController::ROUTES__MODULES_TENANT,
* requirements = {
* "tenant" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function modulesAction(Tenant $tenant = null)
{
// get the types of log entries we are looking for
$actions = array(
'Cms\ModuleBundle\Controller\ContentController::cloneAction',
'Cms\ModuleBundle\Controller\ContentController::modifyAction',
'Cms\ModuleBundle\Controller\ContentController::proxyCreateAction',
);
$start = DateTimeUtils::firstOfMonth()->modify(sprintf(
'-%s months',
(self::MONTHS - 1)
));
$end = DateTimeUtils::firstOfMonth()->modify('+1 months');
$labels = [];
$current = clone $start;
while ($current < $end) {
$labels[] = $current->format('M Y');
$current = $current->modify('+1 months');
}
// run the queries
$qb = $this->getEntityManager()->createQueryBuilder()
->select('logs.objectClass AS statsLabel, YEAR(logs.createdAt) AS statsYear, MONTH(logs.createdAt) AS statsMonth, COUNT(logs.id) AS statsValue')
->from(ActivityLog::class, 'logs');
if ( ! empty($tenant)) {
$qb
->andWhere('logs.tenant = :tenant')
->setParameter('tenant', $tenant);
}
$qb
->andWhere('logs.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end)
->andWhere('logs.action IN (:actions)')
->setParameter('actions', $actions)
->andWhere('logs.objectClass NOT IN (:classes)')
->setParameter('classes', array(
ScheduledPublication::class,
))
->addGroupBy('statsLabel')
->addGroupBy('statsYear')
->addGroupBy('statsMonth')
;
// get results
$results = $this->queryResults($qb);
// massage the results into usable data
$data = array(
'labels' => $labels,
'datasets' => [],
);
$datasets = array(
'TOTAL' => array(
'label' => 'TOTAL',
'spanGaps' => true,
'data' => array_fill(0, count($labels), 0),
'backgroundColor' => '#'. self::COLORS[0],
'borderColor' => '#'. self::COLORS[0],
'fill' => false,
),
);
foreach ($results as $result) {
if (is_a($result['statsLabel'], Draft::class, true)) {
$result['statsLabel'] = $this->getModuleManager()
->getModuleConfigurationForEntity($result['statsLabel'])
->classesProxy();
}
$label = $this->getTranslator()->trans($result['statsLabel'], [], 'activity_log');
if ( ! array_key_exists($label, $datasets)) {
$datasets[$label] = array(
'label' => $label,
'spanGaps' => true,
'data' => array_fill(0, count($labels), 0),
'backgroundColor' => '#'. self::COLORS[count($datasets)],
'borderColor' => '#'. self::COLORS[count($datasets)],
'fill' => false,
);
}
$index = array_search(
DateTimeUtils::make(
sprintf(
'%s-%s-1 00:00:00',
$result['statsYear'],
$result['statsMonth']
),
'Y-n-j H:i:s'
)->format('M Y'),
$labels
);
$datasets[$label]['data'][$index] = intval($result['statsValue']);
$datasets['TOTAL']['data'][$index] += intval($result['statsValue']);
}
ksort($datasets);
$data['datasets'] = $datasets;
return $this->view(
array(
'tenant' => $tenant,
'data' => $data,
)
);
}
/**
* @param Tenant|null $tenant
* @return DocumentScene
*
* @Route(
* "/modules-unique",
* name = StatsController::ROUTES__MODULES_UNIQUE
* )
*
* @Route(
* "/{tenant}/modules-unique",
* name = StatsController::ROUTES__MODULES_UNIQUE_TENANT,
* requirements = {
* "tenant" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function modulesUniqueAction(Tenant $tenant = null)
{
// get the types of log entries we are looking for
$actions = array(
'Cms\ModuleBundle\Controller\ContentController::cloneAction',
'Cms\ModuleBundle\Controller\ContentController::modifyAction',
'Cms\ModuleBundle\Controller\ContentController::proxyCreateAction',
);
$start = DateTimeUtils::firstOfMonth()->modify(sprintf(
'-%s months',
(self::MONTHS - 1)
));
$end = DateTimeUtils::firstOfMonth()->modify('+1 months');
$labels = [];
$current = clone $start;
while ($current < $end) {
$labels[] = $current->format('M Y');
$current = $current->modify('+1 months');
}
// run the queries
$qb = $this->getEntityManager()->createQueryBuilder()
->select('logs.objectClass AS statsLabel, YEAR(logs.createdAt) AS statsYear, MONTH(logs.createdAt) AS statsMonth, COUNT(DISTINCT logs.objectId) AS statsValue')
->from(ActivityLog::class, 'logs');
if ( ! empty($tenant)) {
$qb
->andWhere('logs.tenant = :tenant')
->setParameter('tenant', $tenant);
}
$qb
->andWhere('logs.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end)
->andWhere('logs.action IN (:actions)')
->setParameter('actions', $actions)
->andWhere('logs.objectClass NOT IN (:classes)')
->setParameter('classes', array(
ScheduledPublication::class,
))
->addGroupBy('statsLabel')
->addGroupBy('statsYear')
->addGroupBy('statsMonth')
;
// get results
$results = $this->queryResults($qb);
// massage the results into usable data
$data = array(
'labels' => $labels,
'datasets' => [],
);
$datasets = array(
'TOTAL' => array(
'label' => 'TOTAL',
'spanGaps' => true,
'data' => array_fill(0, count($labels), 0),
'backgroundColor' => '#'. self::COLORS[0],
'borderColor' => '#'. self::COLORS[0],
'fill' => false,
),
);
foreach ($results as $result) {
if (is_a($result['statsLabel'], Draft::class, true)) {
$result['statsLabel'] = $this->getModuleManager()
->getModuleConfigurationForEntity($result['statsLabel'])
->classesProxy();
}
$label = $this->getTranslator()->trans($result['statsLabel'], [], 'activity_log');
if ( ! array_key_exists($label, $datasets)) {
$datasets[$label] = array(
'label' => $label,
'spanGaps' => true,
'data' => array_fill(0, count($labels), 0),
'backgroundColor' => '#'. self::COLORS[count($datasets)],
'borderColor' => '#'. self::COLORS[count($datasets)],
'fill' => false,
);
}
$index = array_search(
DateTimeUtils::make(
sprintf(
'%s-%s-1 00:00:00',
$result['statsYear'],
$result['statsMonth']
),
'Y-n-j H:i:s'
)->format('M Y'),
$labels
);
$datasets[$label]['data'][$index] = intval($result['statsValue']);
$datasets['TOTAL']['data'][$index] += intval($result['statsValue']);
}
ksort($datasets);
$data['datasets'] = array_values($datasets);
return $this->view(
array(
'tenant' => $tenant,
'data' => $data,
)
);
}
/**
* @param Tenant|null $tenant
* @return DocumentScene
*
* @Route(
* "/logins",
* name = StatsController::ROUTES__LOGINS
* )
*
* @Route(
* "/{tenant}/logins",
* name = StatsController::ROUTES__LOGINS_TENANT,
* requirements = {
* "tenant" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
*/
public function loginsAction(Tenant $tenant = null)
{
// get the types of log entries we are looking for
$start = DateTimeUtils::firstOfMonth()->modify(sprintf(
'-%s months',
(self::MONTHS - 1)
));
$end = DateTimeUtils::firstOfMonth()->modify('+1 months');
$labels = [];
$current = clone $start;
while ($current < $end) {
$labels[] = $current->format('M Y');
$current = $current->modify('+1 months');
}
// run the queries
$qb = $this->getEntityManager()->createQueryBuilder()
->select('\'Logins\' AS statsLabel, YEAR(logs.createdAt) AS statsYear, MONTH(logs.createdAt) AS statsMonth, COUNT(logs.account) AS statsValue')
->from(Attempt::class, 'logs');
if ( ! empty($tenant)) {
$qb
->andWhere('logs.tenant = :tenant')
->setParameter('tenant', $tenant);
}
$qb
->andWhere('logs.createdAt BETWEEN :start AND :end')
->setParameter('start', $start)
->setParameter('end', $end)
->andWhere('logs.account IS NOT NULL')
->addGroupBy('statsLabel')
->addGroupBy('statsYear')
->addGroupBy('statsMonth')
;
// get results
$results = $this->queryResults($qb);
// massage the results into usable data
$data = array(
'labels' => $labels,
'datasets' => [],
);
$datasets = [];
foreach ($results as $result) {
if (is_a($result['statsLabel'], Draft::class, true)) {
$result['statsLabel'] = $this->getModuleManager()
->getModuleConfigurationForEntity($result['statsLabel'])
->classesProxy();
}
$label = $this->getTranslator()->trans($result['statsLabel'], [], 'activity_log');
if ( ! array_key_exists($label, $datasets)) {
$datasets[$label] = array(
'label' => $label,
'spanGaps' => true,
'data' => array_fill(0, count($labels), 0),
'backgroundColor' => '#'. self::COLORS[count($datasets)],
'borderColor' => '#'. self::COLORS[count($datasets)],
'fill' => false,
);
}
$index = array_search(
DateTimeUtils::make(
sprintf(
'%s-%s-1 00:00:00',
$result['statsYear'],
$result['statsMonth']
),
'Y-n-j H:i:s'
)->format('M Y'),
$labels
);
$datasets[$label]['data'][$index] = intval($result['statsValue']);
}
ksort($datasets);
$data['datasets'] = array_values($datasets);
return $this->view(
array(
'tenant' => $tenant,
'data' => $data,
)
);
}
/**
* @param Tenant|null $tenant
* @return DocumentScene
*
* @Route(
* "/widgets",
* name = StatsController::ROUTES__WIDGETS
* )
*
* @Route(
* "/{tenant}/widgets",
* name = StatsController::ROUTES__WIDGETS_TENANT,
* requirements = {
* "tenant" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "tenant",
* class = Tenant::class,
* )
* @throws Exception
*/
public function widgetsAction(WidgetStatProvider $widgetStatProvider, Tenant $tenant = null)
{
return $this->view(
[
'tenant' => $tenant,
'data' => $widgetStatProvider->getData($tenant),
]
);
}
/**
* @param QueryBuilder $qb
* @return array
* @throws \Exception
*/
protected function queryResults(QueryBuilder $qb)
{
$this->toggleTenantFilter(false);
$results = $qb->getQuery()->getScalarResult();
$this->toggleTenantFilter(true);
return $results;
}
/**
* @param bool $enabled
* @throws \Exception
*/
protected function toggleTenantFilter($enabled)
{
if ($enabled === true) {
$this->getEntityManager()->getFilters()->enable(TenantDoctrineFilter::FILTER);
$filter = $this->getEntityManager()->getFilters()->getFilter(TenantDoctrineFilter::FILTER);
$filter->setParameter(
'id',
$this->getGlobalContext()->getTenant()->getId()
);
} else if ($enabled === false) {
$this->getEntityManager()->getFilters()->disable(TenantDoctrineFilter::FILTER);
} else {
throw new \Exception();
}
}
/**
* @return ModuleManager|object
*/
private function getModuleManager(): ModuleManager
{
return $this->get(__METHOD__);
}
}