Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
<?php
namespace Mautic\ReportBundle\Controller;
use Mautic\CoreBundle\Controller\AjaxController as CommonAjaxController;
use Mautic\ReportBundle\Model\ReportModel;
use Symfony\Component\HttpFoundation\Request;
class AjaxController extends CommonAjaxController
{
/**
* Get updated data for context.
*/
public function getSourceDataAction(Request $request): \Symfony\Component\HttpFoundation\JsonResponse
{
$model = $this->getModel('report');
\assert($model instanceof ReportModel);
$context = $request->get('context');
$graphs = $model->getGraphList($context);
$columns = $model->getColumnList($context);
$filters = $model->getFilterList($context);
return $this->sendJsonResponse(
[
'columns' => $columns->choiceHtml,
'columnDefinitions' => $columns->definitions,
'filters' => $filters->choiceHtml,
'filterDefinitions' => $filters->definitions,
'filterOperators' => $filters->operatorHtml,
'graphs' => $graphs->choiceHtml,
]
);
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Mautic\ReportBundle\Controller\Api;
use Doctrine\Persistence\ManagerRegistry;
use Mautic\ApiBundle\Controller\CommonApiController;
use Mautic\ApiBundle\Helper\EntityResultHelper;
use Mautic\CoreBundle\Factory\ModelFactory;
use Mautic\CoreBundle\Helper\AppVersion;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\CoreBundle\Security\Exception\PermissionException;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\ReportBundle\Entity\Report;
use Mautic\ReportBundle\Model\ReportModel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
/**
* @extends CommonApiController<Report>
*/
class ReportApiController extends CommonApiController
{
/**
* @var ReportModel|null
*/
protected $model;
public function __construct(CorePermissions $security, Translator $translator, EntityResultHelper $entityResultHelper, RouterInterface $router, FormFactoryInterface $formFactory, AppVersion $appVersion, RequestStack $requestStack, ManagerRegistry $doctrine, ModelFactory $modelFactory, EventDispatcherInterface $dispatcher, CoreParametersHelper $coreParametersHelper, protected UserHelper $userHelper)
{
$reportModel = $modelFactory->getModel('report');
\assert($reportModel instanceof ReportModel);
$this->model = $reportModel;
$this->entityClass = Report::class;
$this->entityNameOne = 'report';
$this->entityNameMulti = 'reports';
$this->serializerGroups = ['reportList', 'reportDetails'];
parent::__construct($security, $translator, $entityResultHelper, $router, $formFactory, $appVersion, $requestStack, $doctrine, $modelFactory, $dispatcher, $coreParametersHelper);
}
/**
* Obtains a compiled report.
*
* @param int $id Report ID
*/
public function getEntityAction(Request $request, $id): Response
{
try {
if (!$this->security->isGranted($this->permissionBase.':view')) {
return $this->accessDenied();
}
} catch (PermissionException $e) {
return $this->accessDenied($e->getMessage());
}
$entity = $this->model->getEntity($id);
if (!$entity instanceof $this->entityClass) {
return $this->notFound();
}
if (
$this->security->checkPermissionExists($this->permissionBase.':viewother')
&& !$this->security->isGranted($this->permissionBase.':viewother')
&& $entity->getCreatedBy() !== $this->userHelper->getUser()->getId()
) {
return $this->accessDenied();
}
$reportData = $this->model->getReportData($entity, $this->formFactory, $this->getOptionsFromRequest($request));
// Unset keys that we don't need to send back
foreach (['graphs', 'contentTemplate', 'columns'] as $key) {
unset($reportData[$key]);
}
// Include report metadata
$reportData[$this->entityNameOne] = $entity;
return $this->handleView(
$this->view($reportData, Response::HTTP_OK)
);
}
public function getReportAction(Request $request, int $id): Response
{
return $this->getEntityAction($request, $id);
}
/**
* This method is careful to add new options from the request to keep BC.
* It originally loaded all rows without any filter or pagination applied.
*/
private function getOptionsFromRequest(Request $request): array
{
$options = ['paginate'=> false, 'ignoreGraphData' => true];
if ($request->query->has('dateFrom')) {
$options['dateFrom'] = DateTimeHelper::setTimeIfMissing($request->query->get('dateFrom'), '00:00:00');
}
if ($request->query->has('dateTo')) {
$options['dateTo'] = DateTimeHelper::setTimeIfMissing($request->query->get('dateTo'), '23:59:59');
}
if ($request->query->has('page')) {
$options['page'] = $request->query->getInt('page');
$options['paginate'] = true;
}
if ($request->query->has('limit')) {
$options['limit'] = $request->query->getInt('limit');
$options['paginate'] = true;
}
return $options;
}
}

View File

@@ -0,0 +1,925 @@
<?php
namespace Mautic\ReportBundle\Controller;
use Mautic\CoreBundle\Controller\FormController;
use Mautic\CoreBundle\EventListener\ReportSubscriber;
use Mautic\CoreBundle\Factory\PageHelperFactoryInterface;
use Mautic\CoreBundle\Form\Type\DateRangeType;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\ReportBundle\Crate\ReportDataResult;
use Mautic\ReportBundle\Entity\Report;
use Mautic\ReportBundle\Form\Type\DynamicFiltersType;
use Mautic\ReportBundle\Model\ExportResponse;
use Mautic\ReportBundle\Model\ReportModel;
use Mautic\ReportBundle\Scheduler\Model\FileHandler;
use Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
class ReportController extends FormController
{
/**
* @param int $page
*
* @return HttpFoundation\JsonResponse|HttpFoundation\RedirectResponse|Response
*/
public function indexAction(Request $request, PageHelperFactoryInterface $pageHelperFactory, $page = 1)
{
/* @type \Mautic\ReportBundle\Model\ReportModel $model */
$model = $this->getModel('report');
// set some permissions
$permissions = $this->security->isGranted(
[
'report:reports:viewown',
'report:reports:viewother',
'report:reports:create',
'report:reports:editown',
'report:reports:editother',
'report:reports:deleteown',
'report:reports:deleteother',
'report:reports:publishown',
'report:reports:publishother',
],
'RETURN_ARRAY'
);
if (!$permissions['report:reports:viewown'] && !$permissions['report:reports:viewother']) {
return $this->accessDenied();
}
$this->setListFilters();
$pageHelper = $pageHelperFactory->make('mautic.report', $page);
$limit = $pageHelper->getLimit();
$start = $pageHelper->getStart();
$search = $request->get('search', $request->getSession()->get('mautic.report.filter', ''));
$filter = ['string' => $search, 'force' => []];
$request->getSession()->set('mautic.report.filter', $search);
if (!$permissions['report:reports:viewother']) {
$filter['force'][] = ['column' => 'r.createdBy', 'expr' => 'eq', 'value' => $this->user->getId()];
}
if (!$this->security->isAdmin()) {
$filter['force'][] = ['column' => 'r.source', 'expr' => 'neq', 'value' => ReportSubscriber::CONTEXT_AUDIT_LOG];
}
$orderBy = $request->getSession()->get('mautic.report.orderby', 'r.dateModified');
$orderByDir = $request->getSession()->get('mautic.report.orderbydir', $this->getDefaultOrderDirection());
$reports = $model->getEntities(
[
'start' => $start,
'limit' => $limit,
'filter' => $filter,
'orderBy' => $orderBy,
'orderByDir' => $orderByDir,
]
);
$count = count($reports);
if ($count && $count < ($start + 1)) {
$lastPage = $pageHelper->countPage($count);
$returnUrl = $this->generateUrl('mautic_report_index', ['page' => $lastPage]);
$pageHelper->rememberPage($lastPage);
return $this->postActionRedirect(
[
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $lastPage],
'contentTemplate' => 'Mautic\ReportBundle\Controller\ReportController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
],
]
);
}
$pageHelper->rememberPage($page);
return $this->delegateView(
[
'viewParameters' => [
'searchValue' => $search,
'items' => $reports,
'totalItems' => $count,
'page' => $page,
'limit' => $limit,
'permissions' => $permissions,
'model' => $model,
'tmpl' => $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index',
'security' => $this->security,
],
'contentTemplate' => '@MauticReport/Report/list.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
'route' => $this->generateUrl('mautic_report_index', ['page' => $page]),
],
]
);
}
/**
* @param int $objectId
*/
public function cloneAction(ReportModel $model, Request $request, $objectId): Response
{
$entity = $model->getEntity($objectId);
if ($entity) {
if (!$this->security->isGranted('report:reports:create')
|| !$this->security->hasEntityAccess(
'report:reports:viewown',
'report:reports:viewother',
$entity->getCreatedBy()
)
) {
return $this->accessDenied();
}
$entity = clone $entity;
$entity->setId(null);
$entity->setIsPublished(false);
}
return $this->newAction($model, $request, $entity);
}
/**
* Deletes the entity.
*
* @return array<string, string|array<string, string>>|bool|HttpFoundation\JsonResponse|HttpFoundation\RedirectResponse|Response
*/
public function deleteAction(Request $request, $objectId)
{
$page = $request->getSession()->get('mautic.report.page', 1);
$returnUrl = $this->generateUrl('mautic_report_index', ['page' => $page]);
$flashes = [];
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\ReportBundle\Controller\ReportController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
],
];
if (Request::METHOD_POST === $request->getMethod()) {
$model = $this->getModel('report');
\assert($model instanceof ReportModel);
$entity = $model->getEntity($objectId);
$check = $this->checkEntityAccess(
$postActionVars,
$entity,
$objectId,
['report:reports:deleteown', 'report:reports:deleteother'],
$model,
'report'
);
if (true !== $check) {
return $check;
}
$model->deleteEntity($entity);
$identifier = $this->translator->trans($entity->getName());
$flashes[] = [
'type' => 'notice',
'msg' => 'mautic.core.notice.deleted',
'msgVars' => [
'%name%' => $identifier,
'%id%' => $objectId,
],
];
} // else don't do anything
return $this->postActionRedirect(
array_merge(
$postActionVars,
[
'flashes' => $flashes,
]
)
);
}
/**
* Deletes a group of entities.
*/
public function batchDeleteAction(Request $request): Response
{
$page = $request->getSession()->get('mautic.report.page', 1);
$returnUrl = $this->generateUrl('mautic_report_index', ['page' => $page]);
$flashes = [];
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\ReportBundle\Controller\ReportController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
],
];
if (Request::METHOD_POST === $request->getMethod()) {
$model = $this->getModel('report');
\assert($model instanceof ReportModel);
$ids = json_decode($request->query->get('ids', '{}'));
$deleteIds = [];
// Loop over the IDs to perform access checks pre-delete
foreach ($ids as $objectId) {
$entity = $model->getEntity($objectId);
if (null === $entity) {
$flashes[] = [
'type' => 'error',
'msg' => 'mautic.report.report.error.notfound',
'msgVars' => ['%id%' => $objectId],
];
} elseif (!$this->security->hasEntityAccess(
'report:reports:deleteown',
'report:reports:deleteother',
$entity->getCreatedBy()
)
) {
$flashes[] = $this->accessDenied(true);
} elseif ($model->isLocked($entity)) {
$flashes[] = $this->isLocked($postActionVars, $entity, 'report', true);
} else {
$deleteIds[] = $objectId;
}
}
// Delete everything we are able to
if (!empty($deleteIds)) {
$entities = $model->deleteEntities($deleteIds);
$flashes[] = [
'type' => 'notice',
'msg' => 'mautic.report.report.notice.batch_deleted',
'msgVars' => [
'%count%' => count($entities),
],
];
}
} // else don't do anything
return $this->postActionRedirect(
array_merge(
$postActionVars,
[
'flashes' => $flashes,
]
)
);
}
/**
* Generates edit form and processes post data.
*
* @param int $objectId Item ID
* @param bool $ignorePost Flag to ignore POST data
*
* @return HttpFoundation\JsonResponse|HttpFoundation\RedirectResponse|Response
*/
public function editAction(Request $request, $objectId, $ignorePost = false)
{
$model = $this->getModel('report');
\assert($model instanceof ReportModel);
$entity = $model->getEntity($objectId);
$session = $request->getSession();
$page = $session->get('mautic.report.page', 1);
// set the return URL
$returnUrl = $this->generateUrl('mautic_report_index', ['page' => $page]);
$postActionVars = [
'returnUrl' => $returnUrl,
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\ReportBundle\Controller\ReportController::indexAction',
'passthroughVars' => [
'activeLink' => 'mautic_report_index',
'mauticContent' => 'report',
],
];
// not found
$check = $this->checkEntityAccess(
$postActionVars,
$entity,
$objectId,
['report:reports:viewown', 'report:reports:viewother'],
$model,
'report'
);
if (true !== $check) {
return $check;
}
// Create the form
$action = $this->generateUrl('mautic_report_action', ['objectAction' => 'edit', 'objectId' => $objectId]);
$form = $model->createForm($entity, $this->formFactory, $action);
// /Check for a submitted form and process it
if (!$ignorePost && 'POST' == $request->getMethod()) {
$valid = false;
if (!$cancelled = $this->isFormCancelled($form)) {
// Columns have to be reset in order for Symfony to honor the new submitted order
$oldColumns = $entity->getColumns();
$entity->setColumns([]);
$oldSchedule = $entity->isScheduled() ? $entity->getSchedule() : null;
$newSchedule['schedule_unit'] = $request->request->all()['report']['scheduleUnit'];
$newSchedule['schedule_day'] = $request->request->all()['report']['scheduleDay'];
$newSchedule['schedule_month_frequency'] = $request->request->all()['report']['scheduleMonthFrequency'];
if ($oldSchedule != $newSchedule) {
$entity->setHasScheduleChanged(true);
}
$oldGraphs = $entity->getGraphs();
$entity->setGraphs([]);
if ($valid = $this->isFormValid($form)) {
// form is valid so process the data
$model->saveEntity($entity, $this->getFormButton($form, ['buttons', 'save'])->isClicked());
$this->addFlashMessage(
'mautic.core.notice.updated',
[
'%name%' => $entity->getName(),
'%menu_link%' => 'mautic_report_index',
'%url%' => $this->generateUrl(
'mautic_report_action',
[
'objectAction' => 'edit',
'objectId' => $entity->getId(),
]
),
]
);
$returnUrl = $this->generateUrl(
'mautic_report_view',
[
'objectId' => $entity->getId(),
]
);
$viewParams = ['objectId' => $entity->getId()];
$template = 'Mautic\ReportBundle\Controller\ReportController::viewAction';
} else {
// reset old columns
$entity->setColumns($oldColumns);
$entity->setGraphs($oldGraphs);
}
} else {
// unlock the entity
$model->unlockEntity($entity);
$returnUrl = $this->generateUrl('mautic_report_index', ['page' => $page]);
$viewParams = ['report' => $page];
$template = 'Mautic\ReportBundle\Controller\ReportController::indexAction';
}
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) {
// Clear session items in case columns changed
$session->remove('mautic.report.'.$entity->getId().'.orderby');
$session->remove('mautic.report.'.$entity->getId().'.orderbydir');
return $this->postActionRedirect(
array_merge(
$postActionVars,
[
'returnUrl' => $returnUrl,
'viewParameters' => $viewParams,
'contentTemplate' => $template,
]
)
);
} elseif ($valid) {
// Rebuild the form for updated columns
$form = $model->createForm($entity, $this->formFactory, $action);
}
} else {
// lock the entity
$model->lockEntity($entity);
}
return $this->delegateView(
[
'viewParameters' => [
'report' => $entity,
'form' => $form->createView(),
],
'contentTemplate' => '@MauticReport/Report/form.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
'route' => $this->generateUrl(
'mautic_report_action',
[
'objectAction' => 'edit',
'objectId' => $entity->getId(),
]
),
],
]
);
}
public function newAction(ReportModel $model, Request $request, ?Report $entity = null): Response
{
if (!$this->security->isGranted('report:reports:create')) {
return $this->accessDenied();
}
if (!($entity instanceof Report)) {
$entity = $model->getEntity();
}
$session = $request->getSession();
$page = $session->get('mautic.report.page', 1);
$action = $this->generateUrl('mautic_report_action', ['objectAction' => 'new']);
$form = $model->createForm($entity, $this->formFactory, $action);
// /Check for a submitted form and process it
if (Request::METHOD_POST === $request->getMethod()) {
$valid = false;
if (!$cancelled = $this->isFormCancelled($form)) {
if ($valid = $this->isFormValid($form)) {
// form is valid so process the data
$model->saveEntity($entity);
$this->addFlashMessage(
'mautic.core.notice.created',
[
'%name%' => $entity->getName(),
'%menu_link%' => 'mautic_report_index',
'%url%' => $this->generateUrl(
'mautic_report_action',
[
'objectAction' => 'edit',
'objectId' => $entity->getId(),
]
),
]
);
if (!$this->getFormButton($form, ['buttons', 'save'])->isClicked()) {
// return edit view so that all the session stuff is loaded
return $this->editAction($request, $entity->getId(), true);
}
$viewParameters = ['objectId' => $entity->getId()];
$returnUrl = $this->generateUrl('mautic_report_view', $viewParameters);
$template = 'Mautic\ReportBundle\Controller\ReportController::viewAction';
}
} else {
$viewParameters = ['page' => $page];
$returnUrl = $this->generateUrl('mautic_report_index', $viewParameters);
$template = 'Mautic\ReportBundle\Controller\ReportController::indexAction';
}
if ($cancelled || ($valid && $this->getFormButton($form, ['buttons', 'save'])->isClicked())) {
return $this->postActionRedirect(
[
'returnUrl' => $returnUrl,
'viewParameters' => $viewParameters,
'contentTemplate' => $template,
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
],
]
);
}
}
return $this->delegateView(
[
'viewParameters' => [
'report' => $entity,
'form' => $form->createView(),
],
'contentTemplate' => '@MauticReport/Report/form.html.twig',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
'route' => $this->generateUrl(
'mautic_report_action',
[
'objectAction' => 'new',
]
),
],
]
);
}
/**
* Shows a report.
*
* @param int $objectId Report ID
* @param int $reportPage
*
* @return HttpFoundation\JsonResponse|Response
*/
public function viewAction(Request $request, $objectId, $reportPage = 1)
{
$model = $this->getModel('report');
\assert($model instanceof ReportModel);
$entity = $model->getEntity($objectId);
$security = $this->security;
if (null === $entity) {
$page = $request->getSession()->get('mautic.report.page', 1);
return $this->postActionRedirect(
[
'returnUrl' => $this->generateUrl('mautic_report_index', ['page' => $page]),
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\ReportBundle\Controller\ReportController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
],
'flashes' => [
[
'type' => 'error',
'msg' => 'mautic.report.report.error.notfound',
'msgVars' => ['%id%' => $objectId],
],
],
]
);
} elseif (!$security->hasEntityAccess('report:reports:viewown', 'report:reports:viewother', $entity->getCreatedBy())) {
return $this->accessDenied();
}
$this->setListFilters();
$mysqlFormat = 'Y-m-d';
$session = $request->getSession();
// Init the forms
$action = $this->generateUrl('mautic_report_action', ['objectAction' => 'view', 'objectId' => $objectId]);
// Get the date range filter values from the request of from the session
$dateRangeValues = $request->query->all()['daterange'] ?? $request->request->all()['daterange'] ?? [];
if (!empty($dateRangeValues['date_from'])) {
$from = new \DateTime($dateRangeValues['date_from']);
$session->set('mautic.report.date.from', $from->format($mysqlFormat));
} elseif ($fromDate = $session->get('mautic.report.date.from')) {
$dateRangeValues['date_from'] = $fromDate;
}
if (!empty($dateRangeValues['date_to'])) {
$to = new \DateTime($dateRangeValues['date_to']);
$session->set('mautic.report.date.to', $to->format($mysqlFormat));
} elseif ($toDate = $session->get('mautic.report.date.to')) {
$dateRangeValues['date_to'] = $toDate;
}
$dateRangeForm = $this->formFactory->create(DateRangeType::class, $dateRangeValues, ['action' => $action]);
if ('POST' === $request->getMethod() && $request->request->has('daterange')) {
if ($this->isFormValid($dateRangeForm)) {
$to = new \DateTime($dateRangeForm['date_to']->getData());
$dateRangeValues['date_to'] = $to->format($mysqlFormat);
$session->set('mautic.report.date.to', $dateRangeValues['date_to']);
$from = new \DateTime($dateRangeForm['date_from']->getData());
$dateRangeValues['date_from'] = $from->format($mysqlFormat);
$session->set('mautic.report.date.from', $dateRangeValues['date_from']);
}
}
// Setup dynamic filters
$filterDefinitions = $model->getFilterList($entity->getSource());
/** @var array $dynamicFilters */
$dynamicFilters = $session->get('mautic.report.'.$objectId.'.filters', []);
$filterSettings = [];
if (count($dynamicFilters) > 0 && count($entity->getFilters()) > 0) {
foreach ($entity->getFilters() as $filter) {
foreach ($dynamicFilters as $dfcol => $dfval) {
if (1 === $filter['dynamic'] && $filter['column'] === $dfcol) {
$dynamicFilters[$dfcol]['expr'] = $filter['condition'];
break;
}
}
}
}
foreach ($dynamicFilters as $filter) {
$filterSettings[$filterDefinitions->definitions[$filter['column']]['alias']] = $filter['value'];
}
foreach ($entity->getFilters() as $filter) {
if (!isset($filter['dynamic']) || 1 !== $filter['dynamic']) {
continue;
}
$column = $filter['column'] ?? null;
$definition = (null !== $column) ? ($filterDefinitions->definitions[$column] ?? []) : [];
$alias = $definition['alias'] ?? null;
if (null === $alias || isset($filterSettings[$alias])) {
continue;
}
$value = $filter['value'] ?? null;
if ('' === $value || null === $value) {
$default = $definition['defaultValue'] ?? null;
if ('' !== $default && null !== $default) {
$value = $default;
}
}
if ('' !== $value && null !== $value) {
$filterSettings[$alias] = $value;
}
}
$dynamicFilterForm = $this->formFactory->create(
DynamicFiltersType::class,
$filterSettings,
[
'action' => $action,
'report' => $entity,
'filterDefinitions' => $filterDefinitions,
]
);
$reportData = $model->getReportData(
$entity,
$this->formFactory,
[
'dynamicFilters' => $dynamicFilters,
'paginate' => true,
'reportPage' => $reportPage,
'dateFrom' => new \DateTime($dateRangeForm->get('date_from')->getData()),
'dateTo' => new \DateTime($dateRangeForm->get('date_to')->getData()),
]
);
$reportDataResult = new ReportDataResult($reportData);
return $this->delegateView(
[
'viewParameters' => [
'data' => $reportData['data'],
'columns' => $reportData['columns'],
'dataColumns' => $reportData['dataColumns'],
'totalResults' => $reportData['totalResults'],
'debug' => $reportData['debug'],
'report' => $entity,
'reportPage' => $reportPage,
'graphs' => $reportData['graphs'],
'reportDataResult' => $reportDataResult,
'tmpl' => $request->isXmlHttpRequest() ? $request->get('tmpl', 'index') : 'index',
'limit' => $reportData['limit'],
'permissions' => $security->isGranted(
[
'report:reports:viewown',
'report:reports:viewother',
'report:reports:create',
'report:reports:editown',
'report:reports:editother',
'report:reports:deleteown',
'report:reports:deleteother',
],
'RETURN_ARRAY'
),
'dateRangeForm' => $dateRangeForm->createView(),
'dynamicFilterForm' => $dynamicFilterForm->createView(),
'enableExportPermission' => $this->security->isAdmin() || $this->security->isGranted('report:export:enable', 'MATCH_ONE'),
'baseUrl' => $this->generateUrl(
'mautic_report_view',
[
'objectId' => $objectId,
'reportPage' => $reportPage,
]
),
],
'contentTemplate' => $reportData['contentTemplate'],
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
'route' => $this->generateUrl(
'mautic_report_view',
[
'objectId' => $entity->getId(),
'reportPage' => $reportPage,
]
),
],
]
);
}
/**
* Checks access to an entity.
*
* @param array<mixed> $postActionVars
* @param array<mixed> $permissions
*
* @return array<string, string|array<string, string>>|bool|HttpFoundation\JsonResponse|HttpFoundation\RedirectResponse|Response
*/
private function checkEntityAccess(array $postActionVars, ?Report $entity, int $objectId, array $permissions, ReportModel $model, string $modelName)
{
if (null === $entity) {
return $this->postActionRedirect(
array_merge(
$postActionVars,
[
'flashes' => [
[
'type' => 'error',
'msg' => 'mautic.report.report.error.notfound',
'msgVars' => ['%id%' => $objectId],
],
],
]
)
);
} elseif (!$this->security->hasEntityAccess($permissions[0], $permissions[1], $entity->getCreatedBy())) {
return $this->accessDenied();
} elseif ($model->isLocked($entity)) {
// deny access if the entity is locked
return $this->isLocked($postActionVars, $entity, $modelName);
}
return true;
}
/**
* @param int $objectId
* @param string $format
*
* @return Response
*
* @throws \Exception
*/
public function exportAction(Request $request, $objectId, $format = 'csv')
{
/** @var ReportModel $model */
$model = $this->getModel('report');
$entity = $model->getEntity($objectId);
$security = $this->security;
if (null === $entity) {
$page = $request->getSession()->get('mautic.report.page', 1);
return $this->postActionRedirect(
[
'returnUrl' => $this->generateUrl('mautic_report_index', ['page' => $page]),
'viewParameters' => ['page' => $page],
'contentTemplate' => 'Mautic\ReportBundle\Controller\ReportController::indexAction',
'passthroughVars' => [
'activeLink' => '#mautic_report_index',
'mauticContent' => 'report',
],
'flashes' => [
[
'type' => 'error',
'msg' => 'mautic.report.report.error.notfound',
'msgVars' => ['%id%' => $objectId],
],
],
]
);
} elseif (!$security->hasEntityAccess('report:reports:viewown', 'report:reports:viewother', $entity->getCreatedBy())) {
return $this->accessDenied();
} elseif (!$this->security->isAdmin() && !$this->security->isGranted('report:export:enable', 'MATCH_ONE')) {
return $this->accessDenied();
}
$session = $request->getSession();
$fromDate = $session->get('mautic.report.date.from', (new \DateTime('-30 days'))->format('Y-m-d'));
$toDate = $session->get('mautic.report.date.to', (new \DateTime())->format('Y-m-d'));
$date = (new DateTimeHelper())->toLocalString();
$name = str_replace(' ', '_', $date).'_'.InputHelper::alphanum($entity->getName(), false, '-');
$options = ['dateFrom' => new \DateTime($fromDate), 'dateTo' => new \DateTime($toDate)];
$dynamicFilters = $session->get('mautic.report.'.$objectId.'.filters', []);
$options['dynamicFilters'] = $dynamicFilters;
if ('csv' === $format) {
$response = new HttpFoundation\StreamedResponse(
function () use ($model, $entity, $format, $options): void {
$options['paginate'] = true;
$options['ignoreGraphData'] = true;
$options['limit'] = (int) $this->coreParametersHelper->get('report_export_batch_size', 1000);
$options['page'] = 1;
$handle = fopen('php://output', 'r+');
$batchTotals = [];
$batchDataSize = 0;
do {
$reportData = $model->getReportData($entity, null, $options);
// Calculate number of pages only once
if (1 === $options['page']) {
$totalPages = (int) ceil($reportData['totalResults'] / $options['limit']);
}
// Build the data rows
$isLastBatch = (isset($totalPages) && $totalPages === $options['page']);
$reportDataResult = new ReportDataResult($reportData, $batchTotals, $batchDataSize, $isLastBatch);
// Store batch totals and size
$batchTotals = $reportDataResult->getTotals();
$batchDataSize += $reportDataResult->getDataCount();
// Note this so that it's not recalculated on each batch
$options['totalResults'] = $reportData['totalResults'];
$model->exportResults($format, $entity, $reportDataResult, $handle, $options['page']);
++$options['page'];
} while (!empty($reportData['data']));
fclose($handle);
}
);
$fileName = $name.'.'.$format;
ExportResponse::setResponseHeaders($response, $fileName);
} else {
if ('xlsx' === $format) {
$options['ignoreGraphData'] = true;
}
$reportData = $model->getReportData($entity, null, $options);
$reportDataResult = new ReportDataResult($reportData);
$response = $model->exportResults($format, $entity, $reportDataResult);
}
return $response;
}
/**
* @param int $reportId
* @param string $format
*
* @return BinaryFileResponse
*
* @throws \Exception
*/
public function downloadAction(FileHandler $fileHandler, $reportId, $format = 'csv')
{
if ('csv' !== $format) {
throw new \Exception($this->translator->trans('mautic.format.invalid', ['%format%' => $format, '%validFormats%' => 'csv']));
}
/** @var ReportModel $model */
$model = $this->getModel('report');
/** @var Report $report */
$report = $model->getEntity($reportId);
/** @var \Mautic\CoreBundle\Security\Permissions\CorePermissions $security */
$security = $this->security;
if (empty($report)) {
return $this->notFound($this->translator->trans('mautic.report.notfound', ['%id%' => $reportId]));
}
if (!$security->hasEntityAccess('report:reports:viewown', 'report:reports:viewother', $report->getCreatedBy())) {
return $this->accessDenied();
}
if (!$fileHandler->compressedCsvFileForReportExists($report)) {
if ($report->isScheduled()) {
$message = 'mautic.report.download.missing';
} else {
$message = 'mautic.report.download.missing.but.scheduled';
$report->setAsScheduledNow($this->user->getEmail());
$model->saveEntity($report);
}
return $this->notFound($this->translator->trans($message, ['%id%' => $reportId]));
}
$response = new BinaryFileResponse($fileHandler->getPathToCompressedCsvFileForReport($report));
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, "report-{$report->getId()}.zip");
return $response;
}
protected function getDefaultOrderDirection(): string
{
return 'DESC';
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Mautic\ReportBundle\Controller;
use Mautic\CoreBundle\Controller\AjaxController as CommonAjaxController;
use Mautic\CoreBundle\Service\FlashBag;
use Mautic\ReportBundle\Scheduler\Date\DateBuilder;
use Symfony\Component\HttpFoundation\JsonResponse;
class ScheduleController extends CommonAjaxController
{
public function indexAction(DateBuilder $dateBuilder, $isScheduled, $scheduleUnit, $scheduleDay, $scheduleMonthFrequency): JsonResponse
{
$dates = $dateBuilder->getPreviewDays($isScheduled, $scheduleUnit, $scheduleDay, $scheduleMonthFrequency);
$html = $this->render(
'@MauticReport/Schedule/index.html.twig',
[
'dates' => $dates,
]
)->getContent();
return $this->sendJsonResponse(
[
'html' => $html,
]
);
}
/**
* Sets report to schedule NOW if possible.
*
* @param int $reportId
*/
public function nowAction($reportId): JsonResponse
{
/** @var \Mautic\ReportBundle\Model\ReportModel $model */
$model = $this->getModel('report');
/** @var \Mautic\ReportBundle\Entity\Report $report */
$report = $model->getEntity($reportId);
/** @var \Mautic\CoreBundle\Security\Permissions\CorePermissions $security */
$security = $this->security;
if (empty($report)) {
$this->addFlashMessage('mautic.report.notfound', ['%id%' => $reportId], FlashBag::LEVEL_ERROR, 'messages');
return $this->flushFlash();
}
if (!$security->hasEntityAccess('report:reports:viewown', 'report:reports:viewother', $report->getCreatedBy())) {
$this->addFlashMessage('mautic.core.error.accessdenied', [], FlashBag::LEVEL_ERROR);
return $this->flushFlash();
}
if ($report->isScheduled()) {
$this->addFlashMessage('mautic.report.scheduled.already', ['%id%' => $reportId], FlashBag::LEVEL_ERROR);
return $this->flushFlash();
}
$report->setAsScheduledNow($this->user->getEmail());
$model->saveEntity($report);
$this->addFlashMessage(
'mautic.report.scheduled.to.now',
['%id%' => $reportId, '%email%' => $this->user->getEmail()]
);
return $this->flushFlash();
}
private function flushFlash(): JsonResponse
{
return new JsonResponse(['flashes' => $this->getFlashContent()]);
}
}