Spaces:
No application file
No application file
| namespace Mautic\ReportBundle\Model; | |
| use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection; | |
| use Doctrine\DBAL\Query\QueryBuilder; | |
| use Doctrine\ORM\EntityManagerInterface; | |
| use Mautic\ChannelBundle\Helper\ChannelListHelper; | |
| use Mautic\CoreBundle\Helper\Chart\ChartQuery; | |
| use Mautic\CoreBundle\Helper\CoreParametersHelper; | |
| use Mautic\CoreBundle\Helper\DateTimeHelper; | |
| use Mautic\CoreBundle\Helper\InputHelper; | |
| use Mautic\CoreBundle\Helper\UserHelper; | |
| use Mautic\CoreBundle\Model\FormModel; | |
| use Mautic\CoreBundle\Security\Permissions\CorePermissions; | |
| use Mautic\CoreBundle\Translation\Translator; | |
| use Mautic\LeadBundle\Model\FieldModel; | |
| use Mautic\ReportBundle\Builder\MauticReportBuilder; | |
| use Mautic\ReportBundle\Crate\ReportDataResult; | |
| use Mautic\ReportBundle\Entity\Report; | |
| use Mautic\ReportBundle\Event\ReportBuilderEvent; | |
| use Mautic\ReportBundle\Event\ReportDataEvent; | |
| use Mautic\ReportBundle\Event\ReportEvent; | |
| use Mautic\ReportBundle\Event\ReportGraphEvent; | |
| use Mautic\ReportBundle\Event\ReportQueryEvent; | |
| use Mautic\ReportBundle\Generator\ReportGenerator; | |
| use Mautic\ReportBundle\Helper\ReportHelper; | |
| use Mautic\ReportBundle\ReportEvents; | |
| use PhpOffice\PhpSpreadsheet\Spreadsheet; | |
| use Psr\Log\LoggerInterface; | |
| use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
| use Symfony\Component\Form\FormFactoryInterface; | |
| use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; | |
| use Symfony\Component\HttpFoundation\RequestStack; | |
| use Symfony\Component\HttpFoundation\Response; | |
| use Symfony\Component\HttpFoundation\Session\Session; | |
| use Symfony\Component\HttpFoundation\Session\SessionInterface; | |
| use Symfony\Component\HttpFoundation\StreamedResponse; | |
| use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; | |
| use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | |
| use Symfony\Contracts\EventDispatcher\Event; | |
| use Twig\Environment; | |
| /** | |
| * @extends FormModel<Report> | |
| */ | |
| class ReportModel extends FormModel | |
| { | |
| public const CHANNEL_FEATURE = 'reporting'; | |
| /** | |
| * @var array | |
| */ | |
| private $reportBuilderData; | |
| /** | |
| * @var mixed | |
| */ | |
| protected $defaultPageLimit; | |
| public function __construct( | |
| CoreParametersHelper $coreParametersHelper, | |
| protected Environment $twig, | |
| protected ChannelListHelper $channelListHelper, | |
| protected FieldModel $fieldModel, | |
| protected ReportHelper $reportHelper, | |
| private CsvExporter $csvExporter, | |
| private ExcelExporter $excelExporter, | |
| EntityManagerInterface $em, | |
| CorePermissions $security, | |
| EventDispatcherInterface $dispatcher, | |
| UrlGeneratorInterface $router, | |
| Translator $translator, | |
| UserHelper $userHelper, | |
| LoggerInterface $mauticLogger, | |
| private RequestStack $requestStack | |
| ) { | |
| $this->defaultPageLimit = $coreParametersHelper->get('default_pagelimit'); | |
| parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper); | |
| } | |
| /** | |
| * @return \Mautic\ReportBundle\Entity\ReportRepository | |
| */ | |
| public function getRepository() | |
| { | |
| return $this->em->getRepository(Report::class); | |
| } | |
| public function getPermissionBase(): string | |
| { | |
| return 'report:reports'; | |
| } | |
| protected function getSession(): SessionInterface | |
| { | |
| try { | |
| return $this->requestStack->getSession(); | |
| } catch (SessionNotFoundException) { | |
| return new Session(); // in case of CLI | |
| } | |
| } | |
| /** | |
| * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException | |
| */ | |
| public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): \Symfony\Component\Form\FormInterface | |
| { | |
| if (!$entity instanceof Report) { | |
| throw new MethodNotAllowedHttpException(['Report']); | |
| } | |
| if (!empty($action)) { | |
| $options['action'] = $action; | |
| } | |
| $options = array_merge($options, [ | |
| 'table_list' => $this->getTableData('all', $entity->getSource()), | |
| 'attr' => [ | |
| 'readonly' => false, | |
| ], | |
| ]); | |
| // Fire the REPORT_ON_BUILD event off to get the table/column data | |
| $reportGenerator = new ReportGenerator($this->dispatcher, $this->em->getConnection(), $entity, $this->channelListHelper, $formFactory); | |
| return $reportGenerator->getForm($entity, $options); | |
| } | |
| public function getEntity($id = null): ?Report | |
| { | |
| if (null === $id) { | |
| return new Report(); | |
| } | |
| return parent::getEntity($id); | |
| } | |
| /** | |
| * @throws MethodNotAllowedHttpException | |
| */ | |
| protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null): ?Event | |
| { | |
| if (!$entity instanceof Report) { | |
| throw new MethodNotAllowedHttpException(['Report']); | |
| } | |
| switch ($action) { | |
| case 'pre_save': | |
| $name = ReportEvents::REPORT_PRE_SAVE; | |
| break; | |
| case 'post_save': | |
| $name = ReportEvents::REPORT_POST_SAVE; | |
| break; | |
| case 'pre_delete': | |
| $name = ReportEvents::REPORT_PRE_DELETE; | |
| break; | |
| case 'post_delete': | |
| $name = ReportEvents::REPORT_POST_DELETE; | |
| break; | |
| default: | |
| return null; | |
| } | |
| if ($this->dispatcher->hasListeners($name)) { | |
| if (empty($event)) { | |
| $event = new ReportEvent($entity, $isNew); | |
| $event->setEntityManager($this->em); | |
| } | |
| $this->dispatcher->dispatch($event, $name); | |
| return $event; | |
| } else { | |
| return null; | |
| } | |
| } | |
| /** | |
| * Build the table and graph data. | |
| * | |
| * @return mixed | |
| */ | |
| public function buildAvailableReports($context, ?string $reportSource = null) | |
| { | |
| if (empty($this->reportBuilderData[$context])) { | |
| // Check to see if all has been obtained | |
| if (isset($this->reportBuilderData['all'])) { | |
| $this->reportBuilderData[$context]['tables'] = $this->reportBuilderData['all']['tables'][$context] ?? []; | |
| $this->reportBuilderData[$context]['graphs'] = $this->reportBuilderData['all']['graphs'][$context] ?? []; | |
| } else { | |
| // build them | |
| $eventContext = ('all' == $context) ? '' : $context; | |
| $event = new ReportBuilderEvent($this->translator, $this->channelListHelper, $eventContext, $this->fieldModel->getPublishedFieldArrays(), $this->reportHelper, $reportSource); | |
| $this->dispatcher->dispatch($event, ReportEvents::REPORT_ON_BUILD); | |
| $tables = $event->getTables(); | |
| $graphs = $event->getGraphs(); | |
| if ('all' == $context) { | |
| $this->reportBuilderData[$context]['tables'] = $tables; | |
| $this->reportBuilderData[$context]['graphs'] = $graphs; | |
| } else { | |
| if (isset($tables[$context])) { | |
| $this->reportBuilderData[$context]['tables'] = $tables[$context]; | |
| } else { | |
| $this->reportBuilderData[$context]['tables'] = $tables; | |
| } | |
| if (isset($graphs[$context])) { | |
| $this->reportBuilderData[$context]['graphs'] = $graphs[$context]; | |
| } else { | |
| $this->reportBuilderData[$context]['graphs'] = $graphs; | |
| } | |
| } | |
| } | |
| } | |
| return $this->reportBuilderData[$context]; | |
| } | |
| /** | |
| * Builds the table lookup data for the report forms. | |
| * | |
| * @param string $context | |
| * | |
| * @return array | |
| */ | |
| public function getTableData($context = 'all', ?string $reportSource = null) | |
| { | |
| $data = $this->buildAvailableReports($context, $reportSource); | |
| $data = (!isset($data['tables'])) ? [] : $data['tables']; | |
| if (array_key_exists('columns', $data)) { | |
| $data['columns'] = $this->preventSameAliases($data['columns']); | |
| } | |
| return $data; | |
| } | |
| /** | |
| * Prevent same aliases using numeric suffixes for each alias. | |
| */ | |
| private function preventSameAliases(array $columns): array | |
| { | |
| $existingAliases = []; | |
| foreach ($columns as $key => $column) { | |
| $alias = $column['alias']; | |
| // Count suffixes | |
| if (!array_key_exists($alias, $existingAliases)) { | |
| $existingAliases[$alias] = 0; | |
| } else { | |
| ++$existingAliases[$alias]; | |
| } | |
| // Add numeric suffix | |
| if ($existingAliases[$alias] > 0) { | |
| $columns[$key]['alias'] = $alias.$existingAliases[$alias]; | |
| } | |
| } | |
| return $columns; | |
| } | |
| /** | |
| * @param string $context | |
| * | |
| * @return mixed | |
| */ | |
| public function getGraphData($context = 'all') | |
| { | |
| $data = $this->buildAvailableReports($context); | |
| return (!isset($data['graphs'])) ? [] : $data['graphs']; | |
| } | |
| /** | |
| * @param string $context | |
| * | |
| * @return \stdClass ['choices' => [], 'choiceHtml' => '', definitions => []] | |
| */ | |
| public function getColumnList($context, $isGroupBy = false): \stdClass | |
| { | |
| $tableData = $this->getTableData($context); | |
| $columns = $tableData['columns'] ?? []; | |
| $return = new \stdClass(); | |
| $return->choices = []; | |
| $return->choiceHtml = ''; | |
| $return->definitions = []; | |
| foreach ($columns as $column => $data) { | |
| if ($isGroupBy && ('unsubscribed' == $column || 'unsubscribed_ratio' == $column || 'unique_ratio' == $column)) { | |
| continue; | |
| } | |
| if (isset($data['label'])) { | |
| $return->choiceHtml .= "<option value=\"$column\">{$data['label']}</option>\n"; | |
| $return->choices[$column] = $data['label']; | |
| $return->definitions[$column] = $data; | |
| } | |
| } | |
| return $return; | |
| } | |
| /** | |
| * @property filterList | |
| * @property definitions | |
| * | |
| * @param string $context | |
| * | |
| * @return \stdClass[filterList => [], definitions => [], operatorChoices => [], operatorHtml => [], filterListHtml => ''] | |
| */ | |
| public function getFilterList($context = 'all'): \stdClass | |
| { | |
| $tableData = $this->getTableData($context); | |
| $return = new \stdClass(); | |
| $filters = $tableData['filters'] ?? $tableData['columns'] ?? []; | |
| $return->choices = []; | |
| $return->choiceHtml = ''; | |
| $return->definitions = []; | |
| $return->operatorHtml = []; | |
| $return->operatorChoices = []; | |
| foreach ($filters as $filter => $data) { | |
| if (isset($data['label'])) { | |
| $return->definitions[$filter] = $data; | |
| $return->choices[$filter] = $data['label']; | |
| $return->choiceHtml .= "<option value=\"$filter\">{$data['label']}</option>\n"; | |
| $return->operatorChoices[$filter] = $this->getOperatorOptions($data); | |
| $return->operatorHtml[$filter] = ''; | |
| foreach ($return->operatorChoices[$filter] as $value => $label) { | |
| $return->operatorHtml[$filter] .= "<option value=\"$value\">$label</option>\n"; | |
| } | |
| } | |
| } | |
| return $return; | |
| } | |
| /** | |
| * @param string $context | |
| * | |
| * @return \stdClass ['choices' => [], choiceHtml = ''] | |
| */ | |
| public function getGraphList($context = 'all'): \stdClass | |
| { | |
| $graphData = $this->getGraphData($context); | |
| $return = new \stdClass(); | |
| $return->choices = []; | |
| $return->choiceHtml = ''; | |
| // First sort | |
| foreach ($graphData as $key => $details) { | |
| $return->choices[$key] = $this->translator->trans($key).' ('.$this->translator->trans('mautic.report.graph.'.$details['type']).')'; | |
| } | |
| natsort($return->choices); | |
| foreach ($return->choices as $key => $value) { | |
| $return->choiceHtml .= '<option value="'.$key.'">'.$value."</option>\n"; | |
| } | |
| return $return; | |
| } | |
| /** | |
| * Export report. | |
| * | |
| * @param string $format | |
| * @param int $page | |
| * | |
| * @return StreamedResponse|Response | |
| * | |
| * @throws \Exception | |
| */ | |
| public function exportResults($format, Report $report, ReportDataResult $reportDataResult, $handle = null, $page = null) | |
| { | |
| $date = (new DateTimeHelper())->toLocalString(); | |
| $name = str_replace(' ', '_', $date).'_'.InputHelper::alphanum($report->getName(), false, '-'); | |
| switch ($format) { | |
| case 'csv': | |
| if (!is_null($handle)) { | |
| $this->csvExporter->export($reportDataResult, $handle, $page); | |
| return; | |
| } | |
| $response = new StreamedResponse( | |
| function () use ($reportDataResult): void { | |
| $handle = fopen('php://output', 'r+'); | |
| $this->csvExporter->export($reportDataResult, $handle); | |
| fclose($handle); | |
| } | |
| ); | |
| $fileName = $name.'.csv'; | |
| ExportResponse::setResponseHeaders($response, $fileName); | |
| return $response; | |
| case 'html': | |
| $content = $this->twig->render( | |
| '@MauticReport/Report/export.html.twig', | |
| [ | |
| 'pageTitle' => $name, | |
| 'report' => $report, | |
| 'reportDataResult' => $reportDataResult, | |
| ] | |
| ); | |
| return new Response($content); | |
| case 'xlsx': | |
| if (!class_exists(Spreadsheet::class)) { | |
| throw new \Exception('PHPSpreadsheet is required to export to Excel spreadsheets'); | |
| } | |
| $response = new StreamedResponse( | |
| function () use ($reportDataResult, $name): void { | |
| $this->excelExporter->export($reportDataResult, $name); | |
| } | |
| ); | |
| $fileName = $name.'.xlsx'; | |
| ExportResponse::setResponseHeaders($response, $fileName); | |
| return $response; | |
| default: | |
| return new Response(); | |
| } | |
| } | |
| /** | |
| * Get report data for view rendering. | |
| * | |
| * @return mixed[] | |
| */ | |
| public function getReportData(Report $entity, FormFactoryInterface $formFactory = null, array $options = []): array | |
| { | |
| // Clone dateFrom/dateTo because they handled separately in charts | |
| $chartDateFrom = isset($options['dateFrom']) ? clone $options['dateFrom'] : (new \DateTime('-30 days')); | |
| $chartDateTo = isset($options['dateTo']) ? clone $options['dateTo'] : (new \DateTime()); | |
| $debugData = []; | |
| if (isset($options['dateFrom'])) { | |
| // Fix date ranges if applicable | |
| if (!isset($options['dateTo'])) { | |
| $options['dateTo'] = new \DateTime(); | |
| } | |
| // Adjust dateTo to be end of day or to current hour if today | |
| $now = new \DateTime(); | |
| if ($now->format('Y-m-d') == $options['dateTo']->format('Y-m-d')) { | |
| $options['dateTo'] = $now; | |
| } else { | |
| $options['dateTo']->setTime(23, 59, 59); | |
| } | |
| // Convert date ranges to UTC for fetching tabular data | |
| $options['dateFrom']->setTimeZone(new \DateTimeZone('UTC')); | |
| $options['dateTo']->setTimeZone(new \DateTimeZone('UTC')); | |
| } | |
| $paginate = !empty($options['paginate']); | |
| $reportPage = $options['reportPage'] ?? 1; | |
| $data = $graphs = []; | |
| $reportGenerator = new ReportGenerator($this->dispatcher, $this->getConnection(), $entity, $this->channelListHelper, $formFactory); | |
| $selectedColumns = $entity->getColumns(); | |
| $totalResults = $limit = 0; | |
| // Prepare the query builder | |
| $tableDetails = $this->getTableData($entity->getSource()); | |
| $dataColumns = $dataAggregatorColumns = []; | |
| $aggregatorColumns = ($aggregators = $entity->getAggregators()) ? $aggregators : []; | |
| foreach ($aggregatorColumns as $aggregatorColumn) { | |
| $selectedColumns[] = $aggregatorColumn['column']; | |
| // add aggregator columns to dataColumns also | |
| $dataColumns[$aggregatorColumn['function'].' '.$aggregatorColumn['column']] = $aggregatorColumn['column']; | |
| $dataAggregatorColumns[$aggregatorColumn['function'].' '.$aggregatorColumn['column']] = $aggregatorColumn['column']; | |
| } | |
| // Build a reference for column to data column (without table prefix) | |
| foreach ($tableDetails['columns'] as $dbColumn => &$columnData) { | |
| $dataColumns[$columnData['alias']] = $dbColumn; | |
| } | |
| $orderBy = $this->getSession()->get('mautic.report.'.$entity->getId().'.orderby', ''); | |
| $orderByDir = $this->getSession()->get('mautic.report.'.$entity->getId().'.orderbydir', 'ASC'); | |
| $dataOptions = [ | |
| 'order' => (!empty($orderBy)) ? [$orderBy, $orderByDir] : false, | |
| 'columns' => $tableDetails['columns'], | |
| 'filters' => $tableDetails['filters'] ?? $tableDetails['columns'], | |
| 'dateFrom' => $options['dateFrom'] ?? null, | |
| 'dateTo' => $options['dateTo'] ?? null, | |
| 'dynamicFilters' => $options['dynamicFilters'] ?? [], | |
| ]; | |
| /** @var QueryBuilder $query */ | |
| $query = $reportGenerator->getQuery($dataOptions); | |
| $options['translator'] = $this->translator; | |
| $contentTemplate = $reportGenerator->getContentTemplate(); | |
| // set what page currently on so that we can return here after form submission/cancellation | |
| $this->getSession()->set('mautic.report.'.$entity->getId().'.page', $reportPage); | |
| // Reset the orderBy as it causes errors in graphs and the count query in table data | |
| $parts = $query->getQueryParts(); | |
| $order = $parts['orderBy']; | |
| $query->resetQueryPart('orderBy'); | |
| if (empty($options['ignoreGraphData'])) { | |
| $chartQuery = new ChartQuery($this->em->getConnection(), $chartDateFrom, $chartDateTo); | |
| $options['chartQuery'] = $chartQuery; | |
| // Check to see if this is an update from AJAX | |
| $selectedGraphs = (!empty($options['graphName'])) ? [$options['graphName']] : $entity->getGraphs(); | |
| if (!empty($selectedGraphs)) { | |
| $availableGraphs = $this->getGraphData($entity->getSource()); | |
| if (empty($query)) { | |
| $query = $reportGenerator->getQuery(); | |
| } | |
| $eventGraphs = []; | |
| $defaultGraphOptions = $options; | |
| $defaultGraphOptions['dateFrom'] = $chartDateFrom; | |
| $defaultGraphOptions['dateTo'] = $chartDateTo; | |
| foreach ($selectedGraphs as $g) { | |
| if (isset($availableGraphs[$g])) { | |
| $graphOptions = $availableGraphs[$g]['options'] ?? []; | |
| $graphOptions = array_merge($defaultGraphOptions, $graphOptions); | |
| $eventGraphs[$g] = [ | |
| 'options' => $graphOptions, | |
| 'type' => $availableGraphs[$g]['type'], | |
| ]; | |
| } | |
| } | |
| $event = new ReportGraphEvent($entity, $eventGraphs, $query); | |
| $this->dispatcher->dispatch($event, ReportEvents::REPORT_ON_GRAPH_GENERATE); | |
| $graphs = $event->getGraphs(); | |
| unset($defaultGraphOptions); | |
| } | |
| } | |
| $columnsAllowed = $this->getColumnList($entity->getSource()); | |
| $order = $this->getOrderBySanitized($order, $columnsAllowed); | |
| if ($order['hasOrderBy']) { | |
| $query->add('orderBy', $order['orderBy']); | |
| } | |
| // Allow plugin to manipulate the query | |
| $event = new ReportQueryEvent($entity, $query, $totalResults, $dataOptions); | |
| $this->dispatcher->dispatch($event, ReportEvents::REPORT_QUERY_PRE_EXECUTE); | |
| $query = $event->getQuery(); | |
| if (empty($options['ignoreTableData']) && !empty($selectedColumns)) { | |
| if ($paginate) { | |
| // Build the options array to pass into the query | |
| $limit = $this->getSession()->get('mautic.report.'.$entity->getId().'.limit', $this->defaultPageLimit); | |
| if (!empty($options['limit'])) { | |
| $limit = $options['limit']; | |
| $reportPage = $options['page']; | |
| } | |
| $start = (1 === $reportPage) ? 0 : (($reportPage - 1) * $limit); | |
| if ($start < 0) { | |
| $start = 0; | |
| } | |
| if (empty($options['totalResults'])) { | |
| $options['totalResults'] = $totalResults = $this->getTotalCount($query, $debugData); | |
| } else { | |
| $totalResults = $options['totalResults']; | |
| } | |
| if ($limit > 0) { | |
| $query->setFirstResult($start) | |
| ->setMaxResults($limit); | |
| } | |
| } | |
| $queryTime = microtime(true); | |
| $data = $query->execute()->fetchAllAssociative(); | |
| $queryTime = round((microtime(true) - $queryTime) * 1000); | |
| if ($queryTime >= 1000) { | |
| $queryTime *= 1000; | |
| $queryTime .= 's'; | |
| } else { | |
| $queryTime .= 'ms'; | |
| } | |
| if (!$paginate) { | |
| $totalResults = count($data); | |
| } | |
| // Allow plugin to manipulate the data | |
| $event = new ReportDataEvent($entity, $data, $totalResults, $dataOptions); | |
| $this->dispatcher->dispatch($event, ReportEvents::REPORT_ON_DISPLAY); | |
| $data = $event->getData(); | |
| } | |
| if ($this->isDebugMode()) { | |
| $debugData['query'] = $query->getSQL(); | |
| $params = $query->getParameters(); | |
| foreach ($params as $name => $param) { | |
| if (is_array($param)) { | |
| $param = implode("','", $param); | |
| } | |
| $debugData['query'] = str_replace(":$name", "'$param'", $debugData['query']); | |
| } | |
| $debugData['query_time'] = $queryTime ?? 'N/A'; | |
| } | |
| foreach ($data as $keys => $lead) { | |
| foreach ($lead as $key => $field) { | |
| $data[$keys][$key] = html_entity_decode((string) $field, ENT_QUOTES); | |
| } | |
| } | |
| return [ | |
| 'totalResults' => $totalResults, | |
| 'data' => $data, | |
| 'dataColumns' => $dataColumns, | |
| 'graphs' => $graphs, | |
| 'contentTemplate' => $contentTemplate, | |
| 'columns' => $tableDetails['columns'], | |
| 'limit' => ($paginate) ? $limit : 0, | |
| 'page' => ($paginate) ? $reportPage : 1, | |
| 'dateFrom' => $dataOptions['dateFrom'], | |
| 'dateTo' => $dataOptions['dateTo'], | |
| 'debug' => $debugData, | |
| 'aggregatorColumns' => $dataAggregatorColumns, | |
| ]; | |
| } | |
| /** | |
| * Sanitize order by array comparing it to the allowed columns. | |
| * | |
| * @param iterable<mixed> $orderBys | |
| * | |
| * @return iterable<mixed> | |
| */ | |
| private function getOrderBySanitized(iterable $orderBys, \stdClass $allowedColumns): iterable | |
| { | |
| $hasOrderBy = false; | |
| foreach ($orderBys as $key => $orderBy) { | |
| if ($this->orderByIsValid($orderBy, $allowedColumns)) { | |
| $hasOrderBy = true; | |
| continue; | |
| } | |
| $orderBys[$key] = ''; | |
| } | |
| return [ | |
| 'orderBy' => $orderBys, | |
| 'hasOrderBy' => $hasOrderBy, | |
| ]; | |
| } | |
| /** | |
| * Check if order by is valid. | |
| */ | |
| private function orderByIsValid(string $order, \stdClass $allowedColumns): bool | |
| { | |
| if (empty($order)) { | |
| return false; | |
| } | |
| $orderBy = $order; | |
| $oderByDirection = ''; | |
| if (str_contains($order, ' ')) { | |
| $orderTemp = explode(' ', $order); | |
| $orderBy = $orderTemp[0]; | |
| $oderByDirection = $orderTemp[1]; | |
| } | |
| if (!array_key_exists($orderBy, $allowedColumns->choices) || !in_array($oderByDirection, ['ASC', 'DESC', ''])) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * @return mixed[] | |
| */ | |
| public function getReportsWithGraphs(): array | |
| { | |
| $ownedBy = $this->security->isGranted('report:reports:viewother') ? null : $this->userHelper->getUser()->getId(); | |
| return $this->getRepository()->findReportsWithGraphs($ownedBy); | |
| } | |
| /** | |
| * Determine what operators should be used for the filter type. | |
| * | |
| * @return mixed|string | |
| */ | |
| private function getOperatorOptions(array $data) | |
| { | |
| if (isset($data['operators'])) { | |
| // Custom operators | |
| $options = $data['operators']; | |
| } else { | |
| $operator = $data['operatorGroup'] ?? $data['type']; | |
| if (!array_key_exists($operator, MauticReportBuilder::OPERATORS)) { | |
| $operator = 'default'; | |
| } | |
| $options = MauticReportBuilder::OPERATORS[$operator]; | |
| } | |
| foreach ($options as &$label) { | |
| $label = $this->translator->trans($label); | |
| } | |
| return $options; | |
| } | |
| private function getTotalCount(QueryBuilder $qb, array &$debugData): int | |
| { | |
| $countQb = clone $qb; | |
| $countQb->resetQueryParts(); | |
| $countQb->select('count(*)') | |
| ->from('('.$qb->getSQL().')', 'c'); | |
| if ($this->isDebugMode()) { | |
| $debugData['count_query'] = $countQb->getSQL(); | |
| } | |
| return (int) $countQb->executeQuery()->fetchOne(); | |
| } | |
| /** | |
| * @param int $segmentId | |
| */ | |
| public function getReportsIdsWithDependenciesOnSegment($segmentId): array | |
| { | |
| $search = 'lll.leadlist_id'; | |
| $filter = [ | |
| 'force' => [ | |
| ['column' => 'r.filters', 'expr' => 'LIKE', 'value'=>'%'.$search.'"%'], | |
| ], | |
| ]; | |
| $entities = $this->getEntities( | |
| [ | |
| 'filter' => $filter, | |
| ] | |
| ); | |
| $dependents = []; | |
| foreach ($entities as $entity) { | |
| $retrFilters = $entity->getFilters(); | |
| foreach ($retrFilters as $eachFilter) { | |
| if ($eachFilter['column'] == $search && $eachFilter['value'] == $segmentId) { | |
| $dependents[] = $entity->getId(); | |
| } | |
| } | |
| } | |
| return $dependents; | |
| } | |
| /** | |
| * @return array<int, int> | |
| */ | |
| public function getReportsIdsWithDependenciesOnEmail(int $emailId): array | |
| { | |
| $search = 'e.id'; | |
| $filter = [ | |
| 'force' => [ | |
| ['column' => 'r.source', 'expr' => 'IN', 'value'=> ['emails', 'email.stats']], | |
| ['column' => 'r.filters', 'expr' => 'LIKE', 'value'=>'%'.$search.'"%'], | |
| ], | |
| ]; | |
| $entities = $this->getEntities( | |
| [ | |
| 'filter' => $filter, | |
| ] | |
| ); | |
| $dependents = []; | |
| foreach ($entities as $entity) { | |
| foreach ($entity->getFilters() as $entityFilter) { | |
| if ($entityFilter['column'] == $search && $entityFilter['value'] == $emailId) { | |
| $dependents[] = $entity->getId(); | |
| } | |
| } | |
| } | |
| return array_unique($dependents); | |
| } | |
| /** | |
| * @return \Doctrine\DBAL\Connection | |
| */ | |
| private function getConnection() | |
| { | |
| $connection = $this->em->getConnection(); | |
| if ($connection instanceof PrimaryReadReplicaConnection) { | |
| $connection->ensureConnectedToReplica(); | |
| } | |
| return $connection; | |
| } | |
| protected function isDebugMode(): bool | |
| { | |
| return MAUTIC_ENV == 'dev' || $this->coreParametersHelper->get('debug'); | |
| } | |
| } | |