Spaces:
No application file
No application file
namespace Mautic\ReportBundle\Event; | |
use Doctrine\DBAL\Query\Expression\ExpressionBuilder; | |
use Doctrine\DBAL\Query\QueryBuilder; | |
use Mautic\ChannelBundle\Helper\ChannelListHelper; | |
use Mautic\ReportBundle\Entity\Report; | |
use Mautic\ReportBundle\Model\ReportModel; | |
class ReportGeneratorEvent extends AbstractReportEvent | |
{ | |
public const CATEGORY_PREFIX = 'c'; | |
public const CONTACT_PREFIX = 'l'; | |
public const COMPANY_PREFIX = 'comp'; | |
public const COMPANY_LEAD_PREFIX = 'companies_lead'; | |
public const IP_ADDRESS_PREFIX = 'i'; | |
private array $selectColumns = []; | |
/** | |
* contentTemplate. | |
* | |
* @var string | |
*/ | |
private $contentTemplate; | |
private ?ExpressionBuilder $filterExpression = null; | |
private ?array $sortedFilters = null; | |
public function __construct( | |
Report $report, | |
private array $options, /** | |
* QueryBuilder object. | |
*/ | |
private QueryBuilder $queryBuilder, | |
private ChannelListHelper $channelListHelper | |
) { | |
$this->report = $report; | |
$this->context = $report->getSource(); | |
} | |
/** | |
* Fetch the QueryBuilder object. | |
* | |
* @return QueryBuilder | |
* | |
* @throws \RuntimeException | |
*/ | |
public function getQueryBuilder() | |
{ | |
return $this->queryBuilder; | |
} | |
/** | |
* Set the QueryBuilder object. | |
* | |
* @return $this | |
*/ | |
public function setQueryBuilder(QueryBuilder $queryBuilder) | |
{ | |
$this->queryBuilder = $queryBuilder; | |
return $this; | |
} | |
/** | |
* Fetch the ContentTemplate path. | |
* | |
* @return QueryBuilder | |
* | |
* @throws \RuntimeException | |
*/ | |
public function getContentTemplate() | |
{ | |
if ($this->contentTemplate) { | |
return $this->contentTemplate; | |
} | |
// Default content template | |
return '@MauticReport/Report/details.html.twig'; | |
} | |
/** | |
* Set the ContentTemplate path. | |
* | |
* @param string $contentTemplate | |
* | |
* @return $this | |
*/ | |
public function setContentTemplate($contentTemplate) | |
{ | |
$this->contentTemplate = $contentTemplate; | |
return $this; | |
} | |
/** | |
* @return array | |
*/ | |
public function getSelectColumns() | |
{ | |
return $this->selectColumns; | |
} | |
/** | |
* Set custom select columns with aliases based on report settings. | |
* | |
* @return $this | |
*/ | |
public function setSelectColumns(array $selectColumns) | |
{ | |
$this->selectColumns = $selectColumns; | |
return $this; | |
} | |
/** | |
* @return array | |
*/ | |
public function getOptions() | |
{ | |
return $this->options; | |
} | |
/** | |
* @return $this | |
*/ | |
public function setOptions(array $options) | |
{ | |
$this->options = array_merge($this->options, $options); | |
return $this; | |
} | |
/** | |
* @return ExpressionBuilder|null | |
*/ | |
public function getFilterExpression() | |
{ | |
return $this->filterExpression; | |
} | |
/** | |
* @return $this | |
*/ | |
public function setFilterExpression(ExpressionBuilder $filterExpression) | |
{ | |
$this->filterExpression = $filterExpression; | |
return $this; | |
} | |
/** | |
* Add category left join. | |
* | |
* @param string $prefix | |
* @param string $categoryPrefix | |
* | |
* @return $this | |
*/ | |
public function addCategoryLeftJoin(QueryBuilder $queryBuilder, $prefix, $categoryPrefix = self::CATEGORY_PREFIX) | |
{ | |
if ($this->usesColumnWithPrefix($categoryPrefix)) { | |
$queryBuilder->leftJoin($prefix, MAUTIC_TABLE_PREFIX.'categories', $categoryPrefix, $categoryPrefix.'.id = '.$prefix.'.category_id'); | |
} | |
return $this; | |
} | |
/** | |
* Add lead left join. | |
* | |
* @param string $prefix | |
* @param string $leadPrefix | |
* | |
* @return $this | |
*/ | |
public function addLeadLeftJoin(QueryBuilder $queryBuilder, $prefix, $leadPrefix = self::CONTACT_PREFIX) | |
{ | |
if ($this->usesColumnWithPrefix($leadPrefix) | |
|| $this->usesColumnWithPrefix(self::IP_ADDRESS_PREFIX) | |
|| $this->usesColumnWithPrefix(self::COMPANY_PREFIX) | |
|| $this->usesColumn('cmp.name') | |
|| $this->usesColumn('clel.campaign_id') | |
) { | |
$queryBuilder->leftJoin($prefix, MAUTIC_TABLE_PREFIX.'leads', $leadPrefix, $leadPrefix.'.id = '.$prefix.'.lead_id'); | |
} | |
return $this; | |
} | |
/** | |
* Add IP left join. | |
* | |
* @param string $prefix | |
* @param string $ipPrefix | |
* | |
* @return $this | |
*/ | |
public function addIpAddressLeftJoin(QueryBuilder $queryBuilder, $prefix, $ipPrefix = self::IP_ADDRESS_PREFIX) | |
{ | |
if ($this->usesColumnWithPrefix($ipPrefix)) { | |
$queryBuilder->leftJoin($prefix, MAUTIC_TABLE_PREFIX.'ip_addresses', $ipPrefix, $ipPrefix.'.id = '.$prefix.'.ip_id'); | |
} | |
return $this; | |
} | |
/** | |
* Add IP left join with lead join. | |
* | |
* @param string $ipXrefPrefix | |
* @param string $ipPrefix | |
* @param string $leadPrefix | |
* | |
* @return $this | |
*/ | |
public function addLeadIpAddressLeftJoin(QueryBuilder $queryBuilder, $ipXrefPrefix = 'lip', $ipPrefix = self::IP_ADDRESS_PREFIX, $leadPrefix = self::CONTACT_PREFIX) | |
{ | |
if ($this->usesColumnWithPrefix($ipPrefix)) { | |
$this->addIpAddressLeftJoin($queryBuilder, $ipXrefPrefix, $ipPrefix); | |
$queryBuilder->leftJoin($leadPrefix, MAUTIC_TABLE_PREFIX.'lead_ips_xref', $ipXrefPrefix, $ipXrefPrefix.'.lead_id = '.$leadPrefix.'.id'); | |
} | |
return $this; | |
} | |
/** | |
* Add IP left join. | |
* | |
* @param string $prefix | |
* @param string $channel | |
* @param string $leadPrefix | |
* @param string $onColumn | |
* | |
* @return $this | |
*/ | |
public function addCampaignByChannelJoin(QueryBuilder $queryBuilder, $prefix, $channel, $leadPrefix = self::CONTACT_PREFIX, $onColumn = 'id') | |
{ | |
if ($this->usesColumn('cmp.name') || $this->usesColumn('clel.campaign_id')) { | |
$condition = "clel.channel='{$channel}' AND {$prefix}.{$onColumn} = clel.channel_id AND clel.lead_id = {$leadPrefix}.id"; | |
$queryBuilder->leftJoin($prefix, MAUTIC_TABLE_PREFIX.'campaign_lead_event_log', 'clel', $condition); | |
$queryBuilder->leftJoin('clel', MAUTIC_TABLE_PREFIX.'campaigns', 'cmp', 'cmp.id = clel.campaign_id'); | |
} | |
return $this; | |
} | |
/** | |
* Join channel columns. | |
* | |
* @param string $prefix | |
* | |
* @return $this | |
*/ | |
public function addChannelLeftJoins(QueryBuilder $queryBuilder, $prefix) | |
{ | |
foreach ($this->channelListHelper->getChannels() as $channel => $details) { | |
if (!array_key_exists(ReportModel::CHANNEL_FEATURE, $details)) { | |
continue; | |
} | |
$reportDetails = $details[ReportModel::CHANNEL_FEATURE]; | |
if (!array_key_exists('table', $reportDetails)) { | |
continue; | |
} | |
$channelParameter = 'channelParameter'.$channel; | |
$queryBuilder->leftJoin( | |
$prefix, | |
MAUTIC_TABLE_PREFIX.$reportDetails['table'], | |
$channel, | |
$prefix.'.channel_id = '.$channel.'.id AND '.$prefix.'.channel = :'.$channelParameter | |
); | |
$queryBuilder->setParameter($channelParameter, $channel); | |
} | |
return $this; | |
} | |
/** | |
* Add company left join. | |
*/ | |
public function addCompanyLeftJoin(QueryBuilder $queryBuilder, string $companyPrefix = self::COMPANY_PREFIX, string $contactPrefix = self::CONTACT_PREFIX): void | |
{ | |
if ($this->usesColumnWithPrefix($companyPrefix) || $this->usesColumnWithPrefix(self::COMPANY_LEAD_PREFIX)) { | |
if ($this->isJoined($queryBuilder, MAUTIC_TABLE_PREFIX.'companies_leads', 'l', self::COMPANY_LEAD_PREFIX)) { | |
return; | |
} | |
$queryBuilder->leftJoin('l', MAUTIC_TABLE_PREFIX.'companies_leads', self::COMPANY_LEAD_PREFIX, $contactPrefix.'.id ='.self::COMPANY_LEAD_PREFIX.'.lead_id'); | |
$queryBuilder->leftJoin(self::COMPANY_LEAD_PREFIX, MAUTIC_TABLE_PREFIX.'companies', $companyPrefix, self::COMPANY_LEAD_PREFIX.'.company_id = '.$companyPrefix.'.id'); | |
} | |
} | |
/** | |
* Apply date filters to the query. | |
* | |
* @param string $dateColumn | |
* @param string $tablePrefix | |
* @param bool $dateOnly | |
* | |
* @return $this | |
* | |
* @throws \Exception | |
*/ | |
public function applyDateFilters(QueryBuilder $queryBuilder, $dateColumn, $tablePrefix = 't', $dateOnly = false) | |
{ | |
if ($tablePrefix) { | |
$tablePrefix .= '.'; | |
} | |
if (empty($this->options['dateFrom'])) { | |
$this->options['dateFrom'] = new \DateTime(); | |
$this->options['dateFrom']->modify('-30 days'); | |
} | |
if (empty($this->options['dateTo'])) { | |
$this->options['dateTo'] = new \DateTime(); | |
} | |
if ($dateOnly) { | |
$queryBuilder->andWhere(sprintf('%1$s IS NULL OR (DATE(%1$s) BETWEEN :dateFrom AND :dateTo)', $tablePrefix.$dateColumn)); | |
$queryBuilder->setParameter('dateFrom', $this->options['dateFrom']->format('Y-m-d')); | |
$queryBuilder->setParameter('dateTo', $this->options['dateTo']->format('Y-m-d')); | |
} else { | |
$queryBuilder->andWhere(sprintf('%1$s IS NULL OR (%1$s BETWEEN :dateFrom AND :dateTo)', $tablePrefix.$dateColumn)); | |
$queryBuilder->setParameter('dateFrom', $this->options['dateFrom']->format('Y-m-d H:i:s')); | |
$queryBuilder->setParameter('dateTo', $this->options['dateTo']->format('Y-m-d H:i:s')); | |
} | |
return $this; | |
} | |
public function hasColumnWithPrefix(string $prefix): bool | |
{ | |
$columns = $this->getReport()->getSelectAndAggregatorAndOrderAndGroupByColumns(); | |
$pattern = "/^{$prefix}\./"; | |
return count(preg_grep($pattern, $columns)) > 0; | |
} | |
/** | |
* Returns true if the report uses the column anywhere in the query. | |
* | |
* @param string|array $column | |
*/ | |
public function usesColumn($column): bool | |
{ | |
return $this->hasColumn($column) || $this->hasFilter($column); | |
} | |
/** | |
* Returns true if the report uses the prefix anywhere in the query. | |
*/ | |
public function usesColumnWithPrefix(string $prefix): bool | |
{ | |
if ($this->hasColumnWithPrefix($prefix)) { | |
return true; | |
} | |
$this->buildSortedFilters(); | |
$pattern = "/^{$prefix}\./"; | |
return count(preg_grep($pattern, array_keys($this->sortedFilters))) > 0; | |
} | |
/** | |
* Check if the report has a specific column. | |
* | |
* @param array|string $column | |
*/ | |
public function hasColumn($column): bool | |
{ | |
$columns = $this->getReport()->getSelectAndAggregatorAndOrderAndGroupByColumns(); | |
if (is_array($column)) { | |
foreach ($column as $checkMe) { | |
if (in_array($checkMe, $columns, true)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
return in_array($column, $columns, true); | |
} | |
/** | |
* Check if the report has a specific filter. | |
* | |
* @param array|string $column | |
*/ | |
public function hasFilter($column): bool | |
{ | |
$this->buildSortedFilters(); | |
if (is_array($column)) { | |
foreach ($column as $checkMe) { | |
if (isset($this->sortedFilters[$checkMe])) { | |
return true; | |
} | |
} | |
return false; | |
} | |
return isset($this->sortedFilters[$column]); | |
} | |
/** | |
* Get filter value from a specific filter. | |
* | |
* @param string $column | |
* | |
* @return mixed | |
* | |
* @throws \UnexpectedValueException | |
*/ | |
public function getFilterValue($column) | |
{ | |
return $this->getReport()->getFilterValue($column); | |
} | |
/** | |
* Get filter values from a specific filter. | |
* | |
* @param string $column | |
* | |
* @throws \UnexpectedValueException | |
*/ | |
public function getFilterValues($column): array | |
{ | |
return $this->getReport()->getFilterValues($column); | |
} | |
/** | |
* Check if the report has a groupBy columns selected. | |
*/ | |
public function hasGroupBy(): bool | |
{ | |
if (!empty($this->getReport()->getGroupBy())) { | |
return true; | |
} | |
return false; | |
} | |
public function createParameterName(): string | |
{ | |
$alpha_numeric = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; | |
return substr(str_shuffle($alpha_numeric), 0, 8); | |
} | |
private function buildSortedFilters(): void | |
{ | |
if (null !== $this->sortedFilters) { | |
return; | |
} | |
$this->sortedFilters = []; | |
$filters = (array) $this->getReport()->getFilters(); | |
foreach ($filters as $field) { | |
$this->sortedFilters[$field['column']] = true; | |
} | |
} | |
private function isJoined(QueryBuilder $query, string $table, string $fromAlias, string $alias): bool | |
{ | |
$queryParts = $query->getQueryParts(); | |
$joins = !empty($queryParts) && $queryParts['join'] ? $queryParts['join'] : null; | |
if (empty($joins) || (!empty($joins) && empty($joins[$fromAlias]))) { // @phpstan-ignore-line | |
return false; | |
} | |
foreach ($joins[$fromAlias] as $join) { | |
if ($join['joinTable'] == $table && $join['joinAlias'] == $alias) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} | |