mautic / app /bundles /LeadBundle /Entity /LeadFieldRepository.php
chrisbryan17's picture
Upload folder using huggingface_hub
d2897cd verified
<?php
namespace Mautic\LeadBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\ArrayParameterType;
use Mautic\CoreBundle\Entity\CommonRepository;
use Mautic\CoreBundle\Helper\InputHelper;
/**
* @extends CommonRepository<LeadField>
*/
class LeadFieldRepository extends CommonRepository
{
/**
* Retrieves array of aliases used to ensure unique alias for new fields.
*
* @param int $exludingId
* @param bool $publishedOnly
* @param bool $includeEntityFields
* @param string $object name of object using the custom fields
*/
public function getAliases($exludingId, $publishedOnly = false, $includeEntityFields = true, $object = 'lead'): array
{
$q = $this->_em->getConnection()->createQueryBuilder()
->select('l.alias')
->from(MAUTIC_TABLE_PREFIX.'lead_fields', 'l');
if (!empty($exludingId)) {
$q->where('l.id != :id')
->setParameter('id', $exludingId);
}
if ($publishedOnly) {
$q->andWhere(
$q->expr()->eq('is_published', ':true')
)
->setParameter('true', true, 'boolean');
}
if ($object) {
$q->andWhere(
$q->expr()->eq('l.object', ':object')
)->setParameter('object', $object);
}
$results = $q->executeQuery()->fetchAllAssociative();
$aliases = [];
foreach ($results as $item) {
$aliases[] = $item['alias'];
}
if ($includeEntityFields) {
// add lead main column names to prevent attempt to create a field with the same name
$leadRepo = $this->_em->getRepository(Lead::class)->getBaseColumns(Lead::class, true);
$aliases = array_merge($aliases, $leadRepo);
}
return $aliases;
}
/**
* @return LeadField[]
*/
public function getFieldsForObject(string $object): array
{
$queryBuilder = $this->_em->createQueryBuilder();
$queryBuilder->select($this->getTableAlias());
$queryBuilder->from($this->getEntityName(), $this->getTableAlias(), "{$this->getTableAlias()}.id");
$queryBuilder->where("{$this->getTableAlias()}.object = :object");
$queryBuilder->andWhere("{$this->getTableAlias()}.isPublished = 1");
$queryBuilder->orderBy("{$this->getTableAlias()}.label");
$queryBuilder->setParameter('object', $object);
return $queryBuilder->getQuery()->execute();
}
/**
* @return array<int|string, array<string, mixed>>
*/
public function getFields(): array
{
$fq = $this->getEntityManager()->getConnection()->createQueryBuilder();
$fq->select('f.id, f.label, f.alias, f.type, f.field_group as "group", f.object, f.is_fixed, f.properties, f.default_value')
->from(MAUTIC_TABLE_PREFIX.'lead_fields', 'f')
->where('f.is_published = :published')
->setParameter('published', true, 'boolean')
->addOrderBy('f.field_order', 'asc');
$results = $fq->executeQuery()->fetchAllAssociative();
return array_column($results, null, 'alias');
}
public function getTableAlias(): string
{
return 'f';
}
/**
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
* @param object $filter
*/
protected function addCatchAllWhereClause($q, $filter): array
{
return $this->addStandardCatchAllWhereClause(
$q,
$filter,
[
'f.label',
'f.alias',
]
);
}
/**
* @return string[][]
*/
protected function getDefaultOrder(): array
{
return [
['f.order', 'ASC'],
];
}
/**
* Get field aliases for lead table columns.
*
* @param string $object name of object using the custom fields
*/
public function getFieldAliases($object = 'lead'): array
{
$qb = $this->_em->getConnection()->createQueryBuilder();
return $qb->select('f.alias, f.is_unique_identifer as is_unique, f.type, f.object')
->from(MAUTIC_TABLE_PREFIX.'lead_fields', 'f')
->where($qb->expr()->eq('object', ':object'))
->setParameter('object', $object)
->orderBy('f.field_order', 'ASC')
->executeQuery()
->fetchAllAssociative();
}
/**
* @return ArrayCollection<int,LeadField>
*/
public function getListablePublishedFields(): ArrayCollection
{
$queryBuilder = $this->_em->createQueryBuilder();
$queryBuilder->select($this->getTableAlias());
$queryBuilder->from($this->getEntityName(), $this->getTableAlias(), "{$this->getTableAlias()}.id");
$queryBuilder->where("{$this->getTableAlias()}.isListable = 1");
$queryBuilder->andWhere("{$this->getTableAlias()}.isPublished = 1");
$queryBuilder->orderBy("{$this->getTableAlias()}.object");
return new ArrayCollection($queryBuilder->getQuery()->execute());
}
/**
* Add company left join.
*
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
*/
private function addCompanyLeftJoin($q): void
{
$q->leftJoin('l', MAUTIC_TABLE_PREFIX.'companies_leads', 'companies_lead', 'l.id = companies_lead.lead_id');
$q->leftJoin('companies_lead', MAUTIC_TABLE_PREFIX.'companies', 'company', 'companies_lead.company_id = company.id');
}
/**
* Return property by field alias and join tables.
*
* @param string $field
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
*/
public function getPropertyByField($field, $q): string
{
$columnAlias = 'l.';
// Join company tables If we're trying search by company fields
if (in_array($field, array_column($this->getFieldAliases('company'), 'alias'))) {
$this->addCompanyLeftJoin($q);
$columnAlias = 'company.';
} elseif (in_array($field, ['utm_campaign', 'utm_content', 'utm_medium', 'utm_source', 'utm_term'])) {
$q->join('l', MAUTIC_TABLE_PREFIX.'lead_utmtags', 'u', 'l.id = u.lead_id');
$columnAlias = 'u.';
}
return $columnAlias.$field;
}
/**
* Compare a form result value with defined value for defined lead.
*
* @param int $lead ID
* @param string $field alias
* @param mixed $value to compare with
* @param string $operatorExpr for WHERE clause
*
* @return bool
*/
public function compareValue($lead, $field, $value, $operatorExpr, ?string $fieldType = null)
{
$q = $this->_em->getConnection()->createQueryBuilder();
$q->select('l.id')
->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
if ('tags' === $field) {
// Special reserved tags field
$q->join('l', MAUTIC_TABLE_PREFIX.'lead_tags_xref', 'x', 'l.id = x.lead_id')
->join('x', MAUTIC_TABLE_PREFIX.'lead_tags', 't', 'x.tag_id = t.id')
->where(
$q->expr()->and(
$q->expr()->eq('l.id', ':lead'),
$q->expr()->eq('t.tag', ':value')
)
)
->setParameter('lead', (int) $lead)
->setParameter('value', $value);
$result = $q->executeQuery()->fetchAssociative();
if (('eq' === $operatorExpr) || ('like' === $operatorExpr)) {
return !empty($result['id']);
} elseif (('neq' === $operatorExpr) || ('notLike' === $operatorExpr)) {
return empty($result['id']);
} else {
return false;
}
} else {
$property = $this->getPropertyByField($field, $q);
if ('empty' === $operatorExpr || 'notEmpty' === $operatorExpr) {
$doesSupportEmptyValue = !in_array($fieldType, ['date', 'datetime'], true);
$compositeExpression = ('empty' === $operatorExpr) ?
$q->expr()->or(
$q->expr()->isNull($property),
$doesSupportEmptyValue ? $q->expr()->eq($property, $q->expr()->literal('')) : null
)
:
$q->expr()->and(
$q->expr()->isNotNull($property),
$doesSupportEmptyValue ? $q->expr()->neq($property, $q->expr()->literal('')) : null
);
$q->where(
$q->expr()->and(
$q->expr()->eq('l.id', ':lead'),
$compositeExpression
)
)
->setParameter('lead', (int) $lead);
} elseif ('regexp' === $operatorExpr || 'notRegexp' === $operatorExpr) {
if ('regexp' === $operatorExpr) {
$where = $property.' REGEXP :value';
} else {
$where = $property.' NOT REGEXP :value';
}
$q->where(
$q->expr()->and(
$q->expr()->eq('l.id', ':lead'),
$q->expr()->and($where)
)
)
->setParameter('lead', (int) $lead)
->setParameter('value', $value);
} elseif ('in' === $operatorExpr || 'notIn' === $operatorExpr) {
$property = $this->getPropertyByField($field, $q);
$fieldType = $this->findOneBy(['alias' => $field])->getType();
$values = (!is_array($value)) ? [$value] : $value;
if ('multiselect' == $fieldType) {
// multiselect field values are separated by `|` and must be queried using regexp
$operator = str_starts_with($operatorExpr, 'not') ? 'NOT REGEXP' : 'REGEXP';
$expr = $q->expr()->and(
$q->expr()->eq('l.id', ':lead')
);
// require all multiselect values in condition
$andExpr = [];
foreach ($value as $v) {
$v = $q->expr()->literal(
InputHelper::clean($v)
);
$v = trim($v, "'");
$andExpr[] = $property." $operator '\\\\|?$v\\\\|?'";
}
$expr = $expr->with($q->expr()->and(...$andExpr));
$q->where($expr)
->setParameter('lead', (int) $lead);
} else {
$expr = $q->expr()->and(
$q->expr()->eq('l.id', ':lead'),
'in' === $operatorExpr ? $q->expr()->in($property, ':values') : $q->expr()->notIn($property, ':values')
);
$q->where($expr)
->setParameter('lead', (int) $lead)
->setParameter('values', $values, ArrayParameterType::STRING);
}
} else {
$expr = $q->expr()->and(
$q->expr()->eq('l.id', ':lead')
);
if ('neq' === $operatorExpr) {
// include null
$expr = $expr->with(
$q->expr()->or(
$q->expr()->$operatorExpr($property, ':value'),
$q->expr()->isNull($property)
)
);
} else {
switch ($operatorExpr) {
case 'startsWith':
$operatorExpr = 'like';
$value = $value.'%';
break;
case 'endsWith':
$operatorExpr = 'like';
$value = '%'.$value;
break;
case 'contains':
$operatorExpr = 'like';
$value = '%'.$value.'%';
break;
}
$expr = $expr->with(
$q->expr()->$operatorExpr($property, ':value')
);
}
$q->where($expr)
->setParameter('lead', (int) $lead)
->setParameter('value', $value);
}
if (str_starts_with($property, 'u.')) {
// Match only against the latest UTM properties.
$q->orderBy('u.date_added', 'DESC');
$q->setMaxResults(1);
}
$result = $q->executeQuery()->fetchAssociative();
return !empty($result['id']);
}
}
/**
* Compare a form result value with defined date value for defined lead.
*
* @param int $lead ID
* @param int $field alias
* @param string $value to compare with
*/
public function compareDateValue($lead, $field, $value): bool
{
$q = $this->_em->getConnection()->createQueryBuilder();
$property = $this->getPropertyByField($field, $q);
$q->select('l.id')
->from(MAUTIC_TABLE_PREFIX.'leads', 'l')
->where(
$q->expr()->and(
$q->expr()->eq('l.id', ':lead'),
$q->expr()->eq($property, ':value')
)
)
->setParameter('lead', (int) $lead)
->setParameter('value', $value);
$result = $q->executeQuery()->fetchAssociative();
return !empty($result['id']);
}
/**
* Compare a form result value with defined date value ( only day and month compare for
* events such as anniversary) for defined lead.
*
* @param int $lead ID
* @param int $field alias
* @param object $value Date object to compare with
*/
public function compareDateMonthValue($lead, $field, $value): bool
{
$q = $this->_em->getConnection()->createQueryBuilder();
$q->select('l.id')
->from(MAUTIC_TABLE_PREFIX.'leads', 'l')
->where(
$q->expr()->and(
$q->expr()->eq('l.id', ':lead'),
$q->expr()->eq("MONTH(l. $field)", ':month'),
$q->expr()->eq("DAY(l. $field)", ':day')
)
)
->setParameter('lead', (int) $lead)
->setParameter('month', $value->format('m'))
->setParameter('day', $value->format('d'));
$result = $q->executeQuery()->fetchAssociative();
return !empty($result['id']);
}
public function getFieldThatIsMissingColumn(): ?LeadField
{
$qb = $this->createQueryBuilder($this->getTableAlias());
$qb->where($qb->expr()->eq("{$this->getTableAlias()}.columnIsNotCreated", 1));
$qb->orderBy("{$this->getTableAlias()}.dateAdded", \Doctrine\Common\Collections\Criteria::ASC);
$qb->setMaxResults(1);
return $qb->getQuery()->getOneOrNullResult();
}
/**
* @return LeadField[]
*/
public function getFieldsByType($type)
{
return $this->findBy(['type' => $type]);
}
/**
* @return string[]
*/
public function getSearchCommands(): array
{
$commands = [
'mautic.core.searchcommand.ispublished',
'mautic.core.searchcommand.isunpublished',
'mautic.core.searchcommand.ismine',
'mautic.lead.field.searchcommand.isindexed',
'mautic.lead.field.searchcommand.isunique',
'mautic.lead.field.searchcommand.type',
'mautic.lead.field.searchcommand.group',
];
return array_merge($commands, parent::getSearchCommands());
}
/**
* @return mixed[]
*/
public function getFieldSchemaData(string $object): array
{
return $this->_em->createQueryBuilder()
->select('f.alias, f.label, f.type, f.isUniqueIdentifer, f.charLengthLimit')
->from($this->getEntityName(), 'f', 'f.alias')
->where('f.object = :object')
->setParameter('object', $object)
->getQuery()
->execute();
}
/**
* @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q
* @param \StdClass $filter
*
* @return mixed[]
*/
protected function addSearchCommandWhereClause($q, $filter): array
{
list($expr, $parameters) = $this->addStandardSearchCommandWhereClause($q, $filter);
if ($expr) {
return [$expr, $parameters];
}
$command = $filter->command;
$unique = $this->generateRandomParameterName();
$returnParameter = false; // returning a parameter that is not used will lead to a Doctrine error
$prefix = $this->getTableAlias();
switch ($command) {
case $this->translator->trans('mautic.lead.field.searchcommand.isindexed'):
$expr = $q->expr()->eq($prefix.'.isIndex', ":$unique");
$forceParameters = [$unique => true];
$returnParameter = true;
break;
case $this->translator->trans('mautic.lead.field.searchcommand.isunique'):
$expr = $q->expr()->eq($prefix.'.isUniqueIdentifer', ":$unique");
$forceParameters = [$unique => true];
$returnParameter = true;
break;
case $this->translator->trans('mautic.lead.field.searchcommand.type'):
$forceParameters = [
$unique => $filter->string,
];
$expr = $q->expr()->like($prefix.'.type', ":$unique");
$returnParameter = true;
break;
case $this->translator->trans('mautic.lead.field.searchcommand.group'):
$forceParameters = [
$unique => $filter->string,
];
$expr = $q->expr()->like($prefix.'.group', ":$unique");
$returnParameter = true;
break;
}
if ($expr && $filter->not) {
$expr = $q->expr()->not($expr);
}
if (!empty($forceParameters)) {
$parameters = $forceParameters;
} elseif ($returnParameter) {
$string = ($filter->strict) ? $filter->string : "%{$filter->string}%";
$parameters = ["$unique" => $string];
}
return [$expr, $parameters];
}
}