![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/app/bundles/LeadBundle/Entity/ |
<?php namespace Mautic\LeadBundle\Entity; use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\QueryBuilder; use Mautic\CoreBundle\Cache\ResultCacheHelper; use Mautic\CoreBundle\Cache\ResultCacheOptions; use Mautic\LeadBundle\Controller\ListController; use Mautic\LeadBundle\Helper\CustomFieldHelper; trait CustomFieldRepositoryTrait { protected $useDistinctCount = false; /** * @var array */ protected $customFieldList = []; /** * @var string */ protected $uniqueIdentifiersOperator; /** * @param string $object * @param array $args */ public function getEntitiesWithCustomFields($object, $args, $resultsCallback = null) { $skipOrdering = $args['skipOrdering'] ?? false; [$fields, $fixedFields] = $this->getCustomFieldList($object); // Fix arguments if necessary $args = $this->convertOrmProperties($this->getClassName(), $args); // DBAL /** @var QueryBuilder $dq */ $dq = $args['qb'] ?? $this->getEntitiesDbalQueryBuilder(); // Generate where clause first to know if we need to use distinct on primary ID or not $this->useDistinctCount = false; $this->buildWhereClause($dq, $args); if (!empty($args['withTotalCount']) || !isset($args['count'])) { // Distinct is required here to get the correct count when group by is used due to applied filters $countSelect = ($this->useDistinctCount) ? 'COUNT(DISTINCT('.$this->getTableAlias().'.id))' : 'COUNT('.$this->getTableAlias().'.id)'; $dq->select($countSelect.' as count'); // Advanced search filters may have set a group by and if so, let's remove it for the count. if ($groupBy = $dq->getQueryPart('groupBy')) { $dq->resetQueryPart('groupBy'); } // get a total count if (!empty($args['totalCountTtl'])) { $statement = ResultCacheHelper::executeCachedDbalQuery($this->getEntityManager()->getConnection(), $dq, new ResultCacheOptions($object.'-total-count', $args['totalCountTtl'])); } else { $statement = $dq->executeQuery(); } $result = $statement->fetchAllAssociative(); $total = ($result) ? $result[0]['count'] : 0; } else { $total = $args['count']; } if (!$total && !empty($args['withTotalCount'])) { $results = []; } else { if (isset($groupBy) && $groupBy) { $dq->groupBy($groupBy); } // now get the actual paginated results $this->buildOrderByClause($dq, $args); $this->buildLimiterClauses($dq, $args); $dq->resetQueryPart('select'); $this->buildSelectClause($dq, $args); $results = $dq->executeQuery()->fetchAllAssociative(); if (isset($args['route']) && ListController::ROUTE_SEGMENT_CONTACTS == $args['route']) { unset($args['select']); // Our purpose of getting list of ids has already accomplished. We no longer need this. } // loop over results to put fields in something that can be assigned to the entities $fieldValues = []; $groups = $this->getFieldGroups(); foreach ($results as $result) { $id = $result['id']; // unset all the columns that are not fields $this->removeNonFieldColumns($result, $fixedFields); foreach ($result as $k => $r) { if (isset($fields[$k])) { $fieldValues[$id][$fields[$k]['group']][$fields[$k]['alias']] = $fields[$k]; $fieldValues[$id][$fields[$k]['group']][$fields[$k]['alias']]['value'] = $r; } } // make sure each group key is present foreach ($groups as $g) { if (!isset($fieldValues[$id][$g])) { $fieldValues[$id][$g] = []; } } } unset($results, $fields); // get an array of IDs for ORM query $ids = array_keys($fieldValues); if (count($ids)) { if ($skipOrdering) { $alias = $this->getTableAlias(); $q = $this->getEntityManager()->createQueryBuilder(); $q->select($alias) ->from(Lead::class, $alias, $alias.'.id') ->indexBy($alias, $alias.'.id'); } else { // ORM // build the order by id since the order was applied above // unfortunately, doctrine does not have a way to natively support this and can't use MySQL's FIELD function // since we have to be cross-platform; it's way ugly // We should probably totally ditch orm for leads // This "hack" is in place to allow for custom ordering in the API. // See https://github.com/mautic/mautic/pull/7494#issuecomment-600970208 $order = '(CASE'; foreach ($ids as $count => $id) { $order .= ' WHEN '.$this->getTableAlias().'.id = '.$id.' THEN '.$count; ++$count; } $order .= ' ELSE '.$count.' END) AS HIDDEN ORD'; // ORM - generates lead entities /** @var \Doctrine\ORM\QueryBuilder $q */ $q = $this->getEntitiesOrmQueryBuilder($order, $args); $this->buildSelectClause($dq, $args); $q->orderBy('ORD', \Doctrine\Common\Collections\Criteria::ASC); } // only pull the leads as filtered via DBAL $q->where( $q->expr()->in($this->getTableAlias().'.id', ':entityIds') )->setParameter('entityIds', $ids); $results = $q->getQuery() ->useQueryCache(false) // the query contains ID's, so there is no use in caching it ->getResult(); // assign fields /** @var Lead $r */ foreach ($results as $r) { $id = $r->getId(); $r->setFields($fieldValues[$id]); if (is_callable($resultsCallback)) { $resultsCallback($r); } } } else { $results = []; } } return (!empty($args['withTotalCount'])) ? [ 'count' => $total, 'results' => $results, ] : $results; } /** * @param bool $byGroup * @param string $object * * @return array */ public function getFieldValues($id, $byGroup = true, $object = 'lead') { // use DBAL to get entity fields $q = $this->getEntitiesDbalQueryBuilder(); if (is_array($id)) { $this->buildSelectClause($q, $id); $id = $id['id']; } else { $q->select($this->getTableAlias().'.*'); } $q->where($this->getTableAlias().'.id = '.(int) $id); $values = $q->executeQuery()->fetchAssociative(); return $this->formatFieldValues($values, $byGroup, $object); } /** * Gets a list of unique values from fields for autocompletes. * * @param string $search * @param int $limit * @param int $start * * @return array */ public function getValueList($field, $search = '', $limit = 10, $start = 0) { // Includes prefix $table = $this->getEntityManager()->getClassMetadata($this->getClassName())->getTableName(); $col = $this->getTableAlias().'.'.$field; $q = $this->getEntityManager()->getConnection()->createQueryBuilder() ->select("DISTINCT $col") ->from($table, 'l'); $q->where( $q->expr()->and( $q->expr()->neq($col, $q->expr()->literal('')), $q->expr()->isNotNull($col) ) ); if (!empty($search)) { $q->andWhere("$col LIKE :search") ->setParameter('search', "{$search}%"); } $q->orderBy($col); if (!empty($limit)) { $q->setFirstResult($start) ->setMaxResults($limit); } return $q->executeQuery()->fetchAllAssociative(); } /** * Persist an array of entities. * * @param array $entities */ public function saveEntities($entities): void { foreach ($entities as $entity) { // Leads cannot be batched due to requiring the ID to update the fields $this->saveEntity($entity); } } public function saveEntity($entity, $flush = true): void { $this->preSaveEntity($entity); $this->getEntityManager()->persist($entity); if ($flush) { $this->getEntityManager()->flush($entity); } // Includes prefix $table = $this->getEntityManager()->getClassMetadata($this->getClassName())->getTableName(); $fields = $entity->getUpdatedFields(); if (method_exists($entity, 'getChanges')) { $changes = $entity->getChanges(); // remove the fields that are part of changes as they were already saved via a setter $fields = array_diff_key($fields, $changes); } $this->prepareDbalFieldsForSave($fields); if (!empty($fields)) { $this->getEntityManager()->getConnection()->update($table, $fields, ['id' => $entity->getId()]); } $this->postSaveEntity($entity); } /** * Function to remove non custom field columns from an arrayed lead row. * * @param array $fixedFields */ protected function removeNonFieldColumns(&$r, $fixedFields = []) { $baseCols = $this->getBaseColumns($this->getClassName(), true); foreach ($baseCols as $c) { if (!isset($fixedFields[$c])) { unset($r[$c]); } } unset($r['owner_id']); } /** * @param array $values * @param bool $byGroup * @param string $object */ protected function formatFieldValues($values, $byGroup = true, $object = 'lead'): array { [$fields, $fixedFields] = $this->getCustomFieldList($object); $this->removeNonFieldColumns($values, $fixedFields); // Reorder leadValues based on field order $values = array_merge(array_flip(array_keys($fields)), $values); $fieldValues = []; // loop over results to put fields in something that can be assigned to the entities foreach ($values as $k => $r) { if (isset($fields[$k])) { $r = CustomFieldHelper::fixValueType($fields[$k]['type'], $r); if (!is_null($r)) { switch ($fields[$k]['type']) { case 'number': $r = (float) $r; break; case 'boolean': $r = (int) $r; break; } } $alias = $fields[$k]['alias']; if ($byGroup) { $group = $fields[$k]['group']; $fieldValues[$group][$alias] = $fields[$k]; $fieldValues[$group][$alias]['value'] = $r; } else { $fieldValues[$alias] = $fields[$k]; $fieldValues[$alias]['value'] = $r; } unset($fields[$k]); } } if ($byGroup) { // make sure each group key is present $groups = $this->getFieldGroups(); foreach ($groups as $g) { if (!isset($fieldValues[$g])) { $fieldValues[$g] = []; } } } return $fieldValues; } /** * @param string $object * * @return array [$fields, $fixedFields] */ public function getCustomFieldList($object) { if (empty($this->customFieldList)) { // Get the list of custom fields $connection = $this->getEntityManager()->getConnection(); $fq = $connection->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') ->andWhere($fq->expr()->eq('object', ':object')) ->setParameter('published', true, 'boolean') ->setParameter('object', $object) ->addOrderBy('f.field_order', 'asc'); $result = ResultCacheHelper::executeCachedDbalQuery($connection, $fq, new ResultCacheOptions(LeadField::CACHE_NAMESPACE)); $results = $result->fetchAllAssociative(); $fields = []; $fixedFields = []; foreach ($results as $r) { $fields[$r['alias']] = $r; if ($r['is_fixed']) { $fixedFields[$r['alias']] = $r['alias']; } } $this->customFieldList = [$fields, $fixedFields]; } return $this->customFieldList; } protected function prepareDbalFieldsForSave(&$fields) { // Ensure booleans are integers foreach ($fields as $field => &$value) { if (is_bool($value)) { $fields[$field] = (int) $value; } } } /** * Inherit and use in class if required to do something to the entity prior to persisting. */ protected function preSaveEntity($entity) { // Inherit and use if required } /** * Inherit and use in class if required to do something with the entity after persisting. */ protected function postSaveEntity($entity) { // Inherit and use if required } public function setUniqueIdentifiersOperator(string $uniqueIdentifiersOperator): void { $this->uniqueIdentifiersOperator = $uniqueIdentifiersOperator; } public function getUniqueIdentifiersWherePart(): string { if ($this->uniqueIdentifiersOperatorIs(CompositeExpression::TYPE_AND)) { return 'andWhere'; } return 'orWhere'; } private function uniqueIdentifiersOperatorIs(string $operator): bool { return $this->uniqueIdentifiersOperator === $operator; } }