src/Platform/ControlPanelBundle/Controller/Dashboard/StatsController.php line 485

Open in your IDE?
  1. <?php
  2. namespace Platform\ControlPanelBundle\Controller\Dashboard;
  3. use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
  4. use Cms\CoreBundle\Service\ContextManager;
  5. use Cms\CoreBundle\Util\DateTimeUtils;
  6. use Cms\LogBundle\Entity\ActivityLog;
  7. use Cms\ModuleBundle\Entity\Draft;
  8. use Cms\ModuleBundle\Service\ModuleManager;
  9. use Cms\TenantBundle\Entity\Tenant;
  10. use Cms\TenantBundle\Util\TenantDoctrineFilter;
  11. use Cms\WorkflowsBundle\Entity\Publication\ScheduledPublication;
  12. use Doctrine\DBAL\Exception;
  13. use Doctrine\ORM\QueryBuilder;
  14. use Platform\ControlPanelBundle\Controller\DashboardController;
  15. use Platform\ControlPanelBundle\Provider\WidgetStatProvider;
  16. use Platform\SecurityBundle\Entity\Login\Attempt;
  17. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  18. use Symfony\Component\Routing\Annotation\Route;
  19. use Symfony\Component\HttpFoundation\RedirectResponse;
  20. use Symfony\Contracts\Translation\TranslatorInterface;
  21. /**
  22.  * Class StatsController
  23.  * @package Platform\ControlPanelBundle\Controller\Dashboard
  24.  */
  25. final class StatsController extends DashboardController
  26. {
  27.     const ROUTES__INDEX 'platform.control_panel.dashboard.stats.index';
  28.     const ROUTES__INDEX_TENANT 'platform.control_panel.dashboard.stats.index_tenant';
  29.     const ROUTES__MODULES 'platform.control_panel.dashboard.stats.modules';
  30.     const ROUTES__MODULES_TENANT 'platform.control_panel.dashboard.stats.modules_tenant';
  31.     const ROUTES__MODULES_UNIQUE 'platform.control_panel.dashboard.stats.modules_unique';
  32.     const ROUTES__MODULES_UNIQUE_TENANT 'platform.control_panel.dashboard.stats.modules_unique_tenant';
  33.     const ROUTES__LOGINS 'platform.control_panel.dashboard.stats.logins';
  34.     const ROUTES__LOGINS_TENANT 'platform.control_panel.dashboard.stats.logins_tenant';
  35.     const ROUTES__WIDGETS 'platform.control_panel.dashboard.stats.widgets';
  36.     const ROUTES__WIDGETS_TENANT 'platform.control_panel.dashboard.stats.widgets_tenant';
  37.     const MONTHS 12;
  38.     const COLORS = array(
  39.         'ff0029',
  40.         '377eb8',
  41.         '66a61e',
  42.         '984ea3',
  43.         '00d2d5',
  44.         'ff7f00',
  45.         'af8d00',
  46.         '7f80cd',
  47.         'b3e900',
  48.         'c42e60',
  49.         'a65628',
  50.         'f781bf',
  51.         '8dd3c7',
  52.         'bebada',
  53.         'fb8072',
  54.         '80b1d3',
  55.         'fdb462',
  56.         'fccde5',
  57.         'bc80bd',
  58.         'ffed6f',
  59.         'c4eaff',
  60.         'cf8c00',
  61.         '1b9e77',
  62.         'd95f02',
  63.     );
  64.     /**
  65.      * @param Tenant|null $tenant
  66.      * @return RedirectResponse
  67.      *
  68.      * @Route(
  69.      *     "",
  70.      *     name = StatsController::ROUTES__INDEX
  71.      * )
  72.      *
  73.      * @Route(
  74.      *     "/{tenant}",
  75.      *     name = StatsController::ROUTES__INDEX_TENANT,
  76.      *     requirements = {
  77.      *         "tenant" = "[1-9]\d*"
  78.      *     }
  79.      * )
  80.      *
  81.      * @ParamConverter(
  82.      *     "tenant",
  83.      *     class = Tenant::class,
  84.      * )
  85.      */
  86.     public function indexAction(Tenant $tenant null)
  87.     {
  88.         return $this->redirectToRoute(
  89.             ( ! empty($tenant)) ? self::ROUTES__LOGINS_TENANT self::ROUTES__LOGINS,
  90.             array(
  91.                 'tenant' => ( ! empty($tenant)) ? $tenant->getId() : null,
  92.             )
  93.         );
  94.     }
  95.     /**
  96.      * @param Tenant|null $tenant
  97.      * @return DocumentScene
  98.      *
  99.      * @Route(
  100.      *     "/modules",
  101.      *     name = StatsController::ROUTES__MODULES
  102.      * )
  103.      *
  104.      * @Route(
  105.      *     "/{tenant}/modules",
  106.      *     name = StatsController::ROUTES__MODULES_TENANT,
  107.      *     requirements = {
  108.      *         "tenant" = "[1-9]\d*"
  109.      *     }
  110.      * )
  111.      *
  112.      * @ParamConverter(
  113.      *     "tenant",
  114.      *     class = Tenant::class,
  115.      * )
  116.      */
  117.     public function modulesAction(Tenant $tenant null)
  118.     {
  119.         // get the types of log entries we are looking for
  120.         $actions = array(
  121.             'Cms\ModuleBundle\Controller\ContentController::cloneAction',
  122.             'Cms\ModuleBundle\Controller\ContentController::modifyAction',
  123.             'Cms\ModuleBundle\Controller\ContentController::proxyCreateAction',
  124.         );
  125.         $start DateTimeUtils::firstOfMonth()->modify(sprintf(
  126.             '-%s months',
  127.             (self::MONTHS 1)
  128.         ));
  129.         $end DateTimeUtils::firstOfMonth()->modify('+1 months');
  130.         $labels = [];
  131.         $current = clone $start;
  132.         while ($current $end) {
  133.             $labels[] = $current->format('M Y');
  134.             $current $current->modify('+1 months');
  135.         }
  136.         // run the queries
  137.         $qb $this->getEntityManager()->createQueryBuilder()
  138.             ->select('logs.objectClass AS statsLabel, YEAR(logs.createdAt) AS statsYear, MONTH(logs.createdAt) AS statsMonth, COUNT(logs.id) AS statsValue')
  139.             ->from(ActivityLog::class, 'logs');
  140.         if ( ! empty($tenant)) {
  141.             $qb
  142.                 ->andWhere('logs.tenant = :tenant')
  143.                 ->setParameter('tenant'$tenant);
  144.         }
  145.         $qb
  146.             ->andWhere('logs.createdAt BETWEEN :start AND :end')
  147.             ->setParameter('start'$start)
  148.             ->setParameter('end'$end)
  149.             ->andWhere('logs.action IN (:actions)')
  150.             ->setParameter('actions'$actions)
  151.             ->andWhere('logs.objectClass NOT IN (:classes)')
  152.             ->setParameter('classes', array(
  153.                 ScheduledPublication::class,
  154.             ))
  155.             ->addGroupBy('statsLabel')
  156.             ->addGroupBy('statsYear')
  157.             ->addGroupBy('statsMonth')
  158.         ;
  159.         // get results
  160.         $results $this->queryResults($qb);
  161.         // massage the results into usable data
  162.         $data = array(
  163.             'labels' => $labels,
  164.             'datasets' => [],
  165.         );
  166.         $datasets = array(
  167.             'TOTAL' => array(
  168.                 'label' => 'TOTAL',
  169.                 'spanGaps' => true,
  170.                 'data' => array_fill(0count($labels), 0),
  171.                 'backgroundColor' => '#'self::COLORS[0],
  172.                 'borderColor' => '#'self::COLORS[0],
  173.                 'fill' => false,
  174.             ),
  175.         );
  176.         foreach ($results as $result) {
  177.             if (is_a($result['statsLabel'], Draft::class, true)) {
  178.                 $result['statsLabel'] = $this->getModuleManager()
  179.                     ->getModuleConfigurationForEntity($result['statsLabel'])
  180.                     ->classesProxy();
  181.             }
  182.             $label $this->getTranslator()->trans($result['statsLabel'], [], 'activity_log');
  183.             if ( ! array_key_exists($label$datasets)) {
  184.                 $datasets[$label] = array(
  185.                     'label' => $label,
  186.                     'spanGaps' => true,
  187.                     'data' => array_fill(0count($labels), 0),
  188.                     'backgroundColor' => '#'self::COLORS[count($datasets)],
  189.                     'borderColor' => '#'self::COLORS[count($datasets)],
  190.                     'fill' => false,
  191.                 );
  192.             }
  193.             $index array_search(
  194.                 DateTimeUtils::make(
  195.                     sprintf(
  196.                         '%s-%s-1 00:00:00',
  197.                         $result['statsYear'],
  198.                         $result['statsMonth']
  199.                     ),
  200.                     'Y-n-j H:i:s'
  201.                 )->format('M Y'),
  202.                 $labels
  203.             );
  204.             $datasets[$label]['data'][$index] = intval($result['statsValue']);
  205.             $datasets['TOTAL']['data'][$index] += intval($result['statsValue']);
  206.         }
  207.         ksort($datasets);
  208.         $data['datasets'] = $datasets;
  209.         return $this->view(
  210.             array(
  211.                 'tenant' => $tenant,
  212.                 'data' => $data,
  213.             )
  214.         );
  215.     }
  216.     /**
  217.      * @param Tenant|null $tenant
  218.      * @return DocumentScene
  219.      *
  220.      * @Route(
  221.      *     "/modules-unique",
  222.      *     name = StatsController::ROUTES__MODULES_UNIQUE
  223.      * )
  224.      *
  225.      * @Route(
  226.      *     "/{tenant}/modules-unique",
  227.      *     name = StatsController::ROUTES__MODULES_UNIQUE_TENANT,
  228.      *     requirements = {
  229.      *         "tenant" = "[1-9]\d*"
  230.      *     }
  231.      * )
  232.      *
  233.      * @ParamConverter(
  234.      *     "tenant",
  235.      *     class = Tenant::class,
  236.      * )
  237.      */
  238.     public function modulesUniqueAction(Tenant $tenant null)
  239.     {
  240.         // get the types of log entries we are looking for
  241.         $actions = array(
  242.             'Cms\ModuleBundle\Controller\ContentController::cloneAction',
  243.             'Cms\ModuleBundle\Controller\ContentController::modifyAction',
  244.             'Cms\ModuleBundle\Controller\ContentController::proxyCreateAction',
  245.         );
  246.         $start DateTimeUtils::firstOfMonth()->modify(sprintf(
  247.             '-%s months',
  248.             (self::MONTHS 1)
  249.         ));
  250.         $end DateTimeUtils::firstOfMonth()->modify('+1 months');
  251.         $labels = [];
  252.         $current = clone $start;
  253.         while ($current $end) {
  254.             $labels[] = $current->format('M Y');
  255.             $current $current->modify('+1 months');
  256.         }
  257.         // run the queries
  258.         $qb $this->getEntityManager()->createQueryBuilder()
  259.             ->select('logs.objectClass AS statsLabel, YEAR(logs.createdAt) AS statsYear, MONTH(logs.createdAt) AS statsMonth, COUNT(DISTINCT logs.objectId) AS statsValue')
  260.             ->from(ActivityLog::class, 'logs');
  261.         if ( ! empty($tenant)) {
  262.             $qb
  263.                 ->andWhere('logs.tenant = :tenant')
  264.                 ->setParameter('tenant'$tenant);
  265.         }
  266.         $qb
  267.             ->andWhere('logs.createdAt BETWEEN :start AND :end')
  268.             ->setParameter('start'$start)
  269.             ->setParameter('end'$end)
  270.             ->andWhere('logs.action IN (:actions)')
  271.             ->setParameter('actions'$actions)
  272.             ->andWhere('logs.objectClass NOT IN (:classes)')
  273.             ->setParameter('classes', array(
  274.                 ScheduledPublication::class,
  275.             ))
  276.             ->addGroupBy('statsLabel')
  277.             ->addGroupBy('statsYear')
  278.             ->addGroupBy('statsMonth')
  279.         ;
  280.         // get results
  281.         $results $this->queryResults($qb);
  282.         // massage the results into usable data
  283.         $data = array(
  284.             'labels' => $labels,
  285.             'datasets' => [],
  286.         );
  287.         $datasets = array(
  288.             'TOTAL' => array(
  289.                 'label' => 'TOTAL',
  290.                 'spanGaps' => true,
  291.                 'data' => array_fill(0count($labels), 0),
  292.                 'backgroundColor' => '#'self::COLORS[0],
  293.                 'borderColor' => '#'self::COLORS[0],
  294.                 'fill' => false,
  295.             ),
  296.         );
  297.         foreach ($results as $result) {
  298.             if (is_a($result['statsLabel'], Draft::class, true)) {
  299.                 $result['statsLabel'] = $this->getModuleManager()
  300.                     ->getModuleConfigurationForEntity($result['statsLabel'])
  301.                     ->classesProxy();
  302.             }
  303.             $label $this->getTranslator()->trans($result['statsLabel'], [], 'activity_log');
  304.             if ( ! array_key_exists($label$datasets)) {
  305.                 $datasets[$label] = array(
  306.                     'label' => $label,
  307.                     'spanGaps' => true,
  308.                     'data' => array_fill(0count($labels), 0),
  309.                     'backgroundColor' => '#'self::COLORS[count($datasets)],
  310.                     'borderColor' => '#'self::COLORS[count($datasets)],
  311.                     'fill' => false,
  312.                 );
  313.             }
  314.             $index array_search(
  315.                 DateTimeUtils::make(
  316.                     sprintf(
  317.                         '%s-%s-1 00:00:00',
  318.                         $result['statsYear'],
  319.                         $result['statsMonth']
  320.                     ),
  321.                     'Y-n-j H:i:s'
  322.                 )->format('M Y'),
  323.                 $labels
  324.             );
  325.             $datasets[$label]['data'][$index] = intval($result['statsValue']);
  326.             $datasets['TOTAL']['data'][$index] += intval($result['statsValue']);
  327.         }
  328.         ksort($datasets);
  329.         $data['datasets'] = array_values($datasets);
  330.         return $this->view(
  331.             array(
  332.                 'tenant' => $tenant,
  333.                 'data' => $data,
  334.             )
  335.         );
  336.     }
  337.     /**
  338.      * @param Tenant|null $tenant
  339.      * @return DocumentScene
  340.      *
  341.      * @Route(
  342.      *     "/logins",
  343.      *     name = StatsController::ROUTES__LOGINS
  344.      * )
  345.      *
  346.      * @Route(
  347.      *     "/{tenant}/logins",
  348.      *     name = StatsController::ROUTES__LOGINS_TENANT,
  349.      *     requirements = {
  350.      *         "tenant" = "[1-9]\d*"
  351.      *     }
  352.      * )
  353.      *
  354.      * @ParamConverter(
  355.      *     "tenant",
  356.      *     class = Tenant::class,
  357.      * )
  358.      */
  359.     public function loginsAction(Tenant $tenant null)
  360.     {
  361.         // get the types of log entries we are looking for
  362.         $start DateTimeUtils::firstOfMonth()->modify(sprintf(
  363.             '-%s months',
  364.             (self::MONTHS 1)
  365.         ));
  366.         $end DateTimeUtils::firstOfMonth()->modify('+1 months');
  367.         $labels = [];
  368.         $current = clone $start;
  369.         while ($current $end) {
  370.             $labels[] = $current->format('M Y');
  371.             $current $current->modify('+1 months');
  372.         }
  373.         // run the queries
  374.         $qb $this->getEntityManager()->createQueryBuilder()
  375.             ->select('\'Logins\' AS statsLabel, YEAR(logs.createdAt) AS statsYear, MONTH(logs.createdAt) AS statsMonth, COUNT(logs.account) AS statsValue')
  376.             ->from(Attempt::class, 'logs');
  377.         if ( ! empty($tenant)) {
  378.             $qb
  379.                 ->andWhere('logs.tenant = :tenant')
  380.                 ->setParameter('tenant'$tenant);
  381.         }
  382.         $qb
  383.             ->andWhere('logs.createdAt BETWEEN :start AND :end')
  384.             ->setParameter('start'$start)
  385.             ->setParameter('end'$end)
  386.             ->andWhere('logs.account IS NOT NULL')
  387.             ->addGroupBy('statsLabel')
  388.             ->addGroupBy('statsYear')
  389.             ->addGroupBy('statsMonth')
  390.         ;
  391.         // get results
  392.         $results $this->queryResults($qb);
  393.         // massage the results into usable data
  394.         $data = array(
  395.             'labels' => $labels,
  396.             'datasets' => [],
  397.         );
  398.         $datasets = [];
  399.         foreach ($results as $result) {
  400.             if (is_a($result['statsLabel'], Draft::class, true)) {
  401.                 $result['statsLabel'] = $this->getModuleManager()
  402.                     ->getModuleConfigurationForEntity($result['statsLabel'])
  403.                     ->classesProxy();
  404.             }
  405.             $label $this->getTranslator()->trans($result['statsLabel'], [], 'activity_log');
  406.             if ( ! array_key_exists($label$datasets)) {
  407.                 $datasets[$label] = array(
  408.                     'label' => $label,
  409.                     'spanGaps' => true,
  410.                     'data' => array_fill(0count($labels), 0),
  411.                     'backgroundColor' => '#'self::COLORS[count($datasets)],
  412.                     'borderColor' => '#'self::COLORS[count($datasets)],
  413.                     'fill' => false,
  414.                 );
  415.             }
  416.             $index array_search(
  417.                 DateTimeUtils::make(
  418.                     sprintf(
  419.                         '%s-%s-1 00:00:00',
  420.                         $result['statsYear'],
  421.                         $result['statsMonth']
  422.                     ),
  423.                     'Y-n-j H:i:s'
  424.                 )->format('M Y'),
  425.                 $labels
  426.             );
  427.             $datasets[$label]['data'][$index] = intval($result['statsValue']);
  428.         }
  429.         ksort($datasets);
  430.         $data['datasets'] = array_values($datasets);
  431.         return $this->view(
  432.             array(
  433.                 'tenant' => $tenant,
  434.                 'data' => $data,
  435.             )
  436.         );
  437.     }
  438.     /**
  439.      * @param Tenant|null $tenant
  440.      * @return DocumentScene
  441.      *
  442.      * @Route(
  443.      *     "/widgets",
  444.      *     name = StatsController::ROUTES__WIDGETS
  445.      * )
  446.      *
  447.      * @Route(
  448.      *     "/{tenant}/widgets",
  449.      *     name = StatsController::ROUTES__WIDGETS_TENANT,
  450.      *     requirements = {
  451.      *         "tenant" = "[1-9]\d*"
  452.      *     }
  453.      * )
  454.      *
  455.      * @ParamConverter(
  456.      *     "tenant",
  457.      *     class = Tenant::class,
  458.      * )
  459.      * @throws Exception
  460.      */
  461.     public function widgetsAction(WidgetStatProvider $widgetStatProviderTenant $tenant null)
  462.     {
  463.         return $this->view(
  464.             [
  465.                 'tenant' => $tenant,
  466.                 'data' => $widgetStatProvider->getData($tenant),
  467.             ]
  468.         );
  469.     }
  470.     /**
  471.      * @param QueryBuilder $qb
  472.      * @return array
  473.      * @throws \Exception
  474.      */
  475.     protected function queryResults(QueryBuilder $qb)
  476.     {
  477.         $this->toggleTenantFilter(false);
  478.         $results $qb->getQuery()->getScalarResult();
  479.         $this->toggleTenantFilter(true);
  480.         return $results;
  481.     }
  482.     /**
  483.      * @param bool $enabled
  484.      * @throws \Exception
  485.      */
  486.     protected function toggleTenantFilter($enabled)
  487.     {
  488.         if ($enabled === true) {
  489.             $this->getEntityManager()->getFilters()->enable(TenantDoctrineFilter::FILTER);
  490.             $filter $this->getEntityManager()->getFilters()->getFilter(TenantDoctrineFilter::FILTER);
  491.             $filter->setParameter(
  492.                 'id',
  493.                 $this->getGlobalContext()->getTenant()->getId()
  494.             );
  495.         } else if ($enabled === false) {
  496.             $this->getEntityManager()->getFilters()->disable(TenantDoctrineFilter::FILTER);
  497.         } else {
  498.             throw new \Exception();
  499.         }
  500.     }
  501.     /**
  502.      * @return ModuleManager|object
  503.      */
  504.     private function getModuleManager(): ModuleManager
  505.     {
  506.         return $this->get(__METHOD__);
  507.     }
  508. }