1 | <?php |
||
2 | |||
3 | /* |
||
4 | * @copyright 2014 Mautic Contributors. All rights reserved |
||
5 | * @author Mautic |
||
6 | * |
||
7 | * @link http://mautic.org |
||
8 | * |
||
9 | * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html |
||
10 | */ |
||
11 | |||
12 | namespace Mautic\LeadBundle\Entity; |
||
13 | |||
14 | use Doctrine\Common\Collections\ArrayCollection; |
||
15 | use Doctrine\DBAL\Connection; |
||
16 | use Doctrine\DBAL\Exception\DriverException; |
||
17 | use Doctrine\DBAL\Query\QueryBuilder; |
||
18 | use Mautic\CoreBundle\Entity\CommonRepository; |
||
19 | use Mautic\CoreBundle\Helper\DateTimeHelper; |
||
20 | use Mautic\CoreBundle\Helper\SearchStringHelper; |
||
21 | use Mautic\LeadBundle\Event\LeadBuildSearchEvent; |
||
22 | use Mautic\LeadBundle\LeadEvents; |
||
23 | use Mautic\PointBundle\Model\TriggerModel; |
||
24 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
||
25 | |||
26 | class LeadRepository extends CommonRepository implements CustomFieldRepositoryInterface |
||
27 | { |
||
28 | use CustomFieldRepositoryTrait { |
||
29 | prepareDbalFieldsForSave as defaultPrepareDbalFieldsForSave; |
||
30 | } |
||
31 | |||
32 | use ExpressionHelperTrait; |
||
33 | use OperatorListTrait; |
||
34 | |||
35 | /** |
||
36 | * @var EventDispatcherInterface |
||
37 | */ |
||
38 | protected $dispatcher; |
||
39 | |||
40 | /** |
||
41 | * @var array |
||
42 | */ |
||
43 | private $availableSocialFields = []; |
||
44 | |||
45 | /** |
||
46 | * @var array |
||
47 | */ |
||
48 | private $availableSearchFields = []; |
||
49 | |||
50 | /** |
||
51 | * Required to get the color based on a lead's points. |
||
52 | * |
||
53 | * @var TriggerModel |
||
54 | */ |
||
55 | private $triggerModel; |
||
56 | |||
57 | /** |
||
58 | * Used by search functions to search social profiles. |
||
59 | */ |
||
60 | public function setAvailableSocialFields(array $fields) |
||
61 | { |
||
62 | $this->availableSocialFields = $fields; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Used by search functions to search using aliases as commands. |
||
67 | */ |
||
68 | public function setAvailableSearchFields(array $fields) |
||
69 | { |
||
70 | $this->availableSearchFields = $fields; |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * Sets trigger model. |
||
75 | */ |
||
76 | public function setTriggerModel(TriggerModel $triggerModel) |
||
77 | { |
||
78 | $this->triggerModel = $triggerModel; |
||
79 | } |
||
80 | |||
81 | public function setDispatcher(EventDispatcherInterface $dispatcher) |
||
82 | { |
||
83 | $this->dispatcher = $dispatcher; |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * Get a list of leads based on field value. |
||
88 | * |
||
89 | * @param string $field |
||
90 | * @param string $value |
||
91 | * @param ?int $ignoreId |
||
92 | * @param bool $indexByColumn |
||
93 | * |
||
94 | * @return array |
||
95 | */ |
||
96 | public function getLeadsByFieldValue($field, $value, $ignoreId = null, $indexByColumn = false) |
||
97 | { |
||
98 | $results = $this->getEntities([ |
||
99 | 'qb' => $this->buildQueryForGetLeadsByFieldValue($field, $value, $ignoreId), |
||
100 | 'ignore_paginator' => true, |
||
101 | ]); |
||
102 | |||
103 | if (!$indexByColumn) { |
||
104 | return $results; |
||
105 | } |
||
106 | |||
107 | return array_combine(array_map(function (Lead $lead) use ($field) { |
||
108 | return $lead->getFieldValue($field); |
||
109 | }, $results), $results); |
||
110 | } |
||
111 | |||
112 | /** |
||
113 | * Builds the query for the getLeadsByFieldValue method. |
||
114 | * |
||
115 | * @internal |
||
116 | * |
||
117 | * @param string $field |
||
118 | * @param string $value |
||
119 | * @param ?int $ignoreId |
||
120 | * |
||
121 | * @return QueryBuilder |
||
122 | */ |
||
123 | protected function buildQueryForGetLeadsByFieldValue($field, $value, $ignoreId = null) |
||
124 | { |
||
125 | $col = 'l.'.$field; |
||
126 | |||
127 | $q = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
128 | ->select('l.id') |
||
129 | ->from(MAUTIC_TABLE_PREFIX.'leads', 'l'); |
||
130 | |||
131 | if ($ignoreId) { |
||
132 | $q->where('l.id != :ignoreId') |
||
133 | ->setParameter('ignoreId', $ignoreId); |
||
134 | } |
||
135 | |||
136 | if (is_array($value)) { |
||
137 | /** |
||
138 | * Bind each value to specific named parameters. |
||
139 | * |
||
140 | * @see https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/query-builder.html#line-number-0a267d5a2c69797a7656aae33fcc140d16b0a566-72 |
||
141 | */ |
||
142 | $valueParams = []; |
||
143 | for ($i = 0; $i < count($value); ++$i) { |
||
144 | $valueParams[':'.$this->generateRandomParameterName()] = $value[$i]; |
||
145 | } |
||
146 | |||
147 | $q->andWhere( |
||
148 | $q->expr()->in($col, array_keys($valueParams)) |
||
149 | ); |
||
150 | |||
151 | foreach ($valueParams as $param => $value) { |
||
152 | $q->setParameter(ltrim($param, ':'), $value); |
||
153 | } |
||
154 | |||
155 | return $q; |
||
156 | } |
||
157 | |||
158 | $q->andWhere("$col = :search")->setParameter('search', $value); |
||
159 | |||
160 | return $q; |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * @param $email |
||
165 | * |
||
166 | * @return Lead[] |
||
167 | */ |
||
168 | public function getContactsByEmail($email) |
||
169 | { |
||
170 | $contacts = $this->getLeadsByFieldValue('email', $email); |
||
171 | |||
172 | // Attempt to search for contacts without a + suffix |
||
173 | if (empty($contacts) && preg_match('#^(.*?)\+(.*?)@(.*?)$#', $email, $parts)) { |
||
174 | $email = $parts[1].'@'.$parts[3]; |
||
175 | $contacts = $this->getLeadsByFieldValue('email', $email); |
||
176 | } |
||
177 | |||
178 | return $contacts; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * @param string[] $emails |
||
183 | * |
||
184 | * @return int[]|array |
||
185 | */ |
||
186 | public function getContactIdsByEmails(array $emails): array |
||
187 | { |
||
188 | $result = $this->getEntityManager() |
||
189 | ->createQuery(" |
||
190 | SELECT c.id |
||
191 | FROM Mautic\LeadBundle\Entity\Lead c |
||
192 | WHERE c.email IN (:emails) |
||
193 | ") |
||
194 | ->setParameter(':emails', $emails, Connection::PARAM_STR_ARRAY) |
||
195 | ->getArrayResult(); |
||
196 | |||
197 | return array_map( |
||
198 | function ($row) { |
||
199 | return (int) $row['id']; |
||
200 | }, |
||
201 | $result |
||
202 | ); |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * Get a list of lead entities. |
||
207 | * |
||
208 | * @param $uniqueFieldsWithData |
||
209 | * @param int $leadId |
||
210 | * @param int $limit |
||
211 | * |
||
212 | * @return array |
||
213 | */ |
||
214 | public function getLeadsByUniqueFields($uniqueFieldsWithData, $leadId = null, $limit = null) |
||
215 | { |
||
216 | $q = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
217 | ->select('l.*') |
||
218 | ->from(MAUTIC_TABLE_PREFIX.'leads', 'l'); |
||
219 | |||
220 | // loop through the fields and |
||
221 | foreach ($uniqueFieldsWithData as $col => $val) { |
||
222 | $q->orWhere("l.$col = :".$col) |
||
223 | ->setParameter($col, $val); |
||
224 | } |
||
225 | |||
226 | // if we have a lead ID lets use it |
||
227 | if (!empty($leadId)) { |
||
228 | // make sure that its not the id we already have |
||
229 | $q->andWhere('l.id != :leadId') |
||
230 | ->setParameter('leadId', $leadId); |
||
231 | } |
||
232 | |||
233 | if ($limit) { |
||
0 ignored issues
–
show
|
|||
234 | $q->setMaxResults($limit); |
||
235 | } |
||
236 | |||
237 | $results = $q->execute()->fetchAll(); |
||
238 | |||
239 | // Collect the IDs |
||
240 | $leads = []; |
||
241 | foreach ($results as $r) { |
||
242 | $leads[$r['id']] = $r; |
||
243 | } |
||
244 | |||
245 | // Get entities |
||
246 | $q = $this->getEntityManager()->createQueryBuilder() |
||
247 | ->select('l') |
||
248 | ->from(Lead::class, 'l'); |
||
249 | |||
250 | $q->where( |
||
251 | $q->expr()->in('l.id', ':ids') |
||
252 | ) |
||
253 | ->setParameter('ids', array_keys($leads)) |
||
254 | ->orderBy('l.dateAdded', 'DESC') |
||
255 | ->addOrderBy('l.id', 'DESC'); |
||
256 | $entities = $q->getQuery() |
||
257 | ->getResult(); |
||
258 | |||
259 | /** @var Lead $lead */ |
||
260 | foreach ($entities as $lead) { |
||
261 | $lead->setAvailableSocialFields($this->availableSocialFields); |
||
262 | if (!empty($this->triggerModel)) { |
||
263 | $lead->setColor($this->triggerModel->getColorForLeadPoints($lead->getPoints())); |
||
264 | } |
||
265 | |||
266 | $lead->setFields( |
||
267 | $this->formatFieldValues($leads[$lead->getId()]) |
||
268 | ); |
||
269 | } |
||
270 | |||
271 | return $entities; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Get list of lead Ids by unique field data. |
||
276 | * |
||
277 | * @param $uniqueFieldsWithData is an array of columns & values to filter by |
||
278 | * @param int $leadId is the current lead id. Added to query to skip and find other leads |
||
279 | * |
||
280 | * @return array |
||
281 | */ |
||
282 | public function getLeadIdsByUniqueFields($uniqueFieldsWithData, $leadId = null) |
||
283 | { |
||
284 | $q = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
285 | ->select('l.id') |
||
286 | ->from(MAUTIC_TABLE_PREFIX.'leads', 'l'); |
||
287 | |||
288 | // loop through the fields and |
||
289 | foreach ($uniqueFieldsWithData as $col => $val) { |
||
290 | $q->orWhere("l.$col = :".$col) |
||
291 | ->setParameter($col, $val); |
||
292 | } |
||
293 | |||
294 | // if we have a lead ID lets use it |
||
295 | if (!empty($leadId)) { |
||
296 | // make sure that its not the id we already have |
||
297 | $q->andWhere('l.id != '.$leadId); |
||
298 | } |
||
299 | |||
300 | return $q->execute()->fetchAll(); |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * @param string $email |
||
305 | * @param bool $all Set to true to return all matching lead id's |
||
306 | * |
||
307 | * @return array|null |
||
308 | */ |
||
309 | public function getLeadByEmail($email, $all = false) |
||
310 | { |
||
311 | $q = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
312 | ->select('l.id') |
||
313 | ->from(MAUTIC_TABLE_PREFIX.'leads', 'l') |
||
314 | ->where('email = :search') |
||
315 | ->setParameter('search', $email); |
||
316 | |||
317 | $result = $q->execute()->fetchAll(); |
||
318 | |||
319 | if (count($result)) { |
||
320 | return $all ? $result : $result[0]; |
||
321 | } else { |
||
322 | return; |
||
323 | } |
||
324 | } |
||
325 | |||
326 | /** |
||
327 | * Get leads by IP address. |
||
328 | * |
||
329 | * @param $ip |
||
330 | * @param bool $byId |
||
331 | * |
||
332 | * @return array |
||
333 | */ |
||
334 | public function getLeadsByIp($ip, $byId = false) |
||
335 | { |
||
336 | $q = $this->createQueryBuilder('l') |
||
337 | ->leftJoin('l.ipAddresses', 'i'); |
||
338 | $col = ($byId) ? 'i.id' : 'i.ipAddress'; |
||
339 | $q->where($col.' = :ip') |
||
340 | ->setParameter('ip', $ip) |
||
341 | ->orderBy('l.dateAdded', 'DESC'); |
||
342 | $results = $q->getQuery()->getResult(); |
||
343 | |||
344 | /** @var Lead $lead */ |
||
345 | foreach ($results as $lead) { |
||
346 | $lead->setAvailableSocialFields($this->availableSocialFields); |
||
347 | } |
||
348 | |||
349 | return $results; |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * @param $id |
||
354 | * |
||
355 | * @return array |
||
356 | */ |
||
357 | public function getLead($id) |
||
358 | { |
||
359 | $fq = $this->getEntityManager()->getConnection()->createQueryBuilder(); |
||
360 | $fq->select('l.*') |
||
361 | ->from(MAUTIC_TABLE_PREFIX.'leads', 'l') |
||
362 | ->where('l.id = '.$id); |
||
363 | $results = $fq->execute()->fetchAll(); |
||
364 | |||
365 | return (isset($results[0])) ? $results[0] : []; |
||
366 | } |
||
367 | |||
368 | /** |
||
369 | * {@inheritdoc} |
||
370 | * |
||
371 | * @param int $id |
||
372 | * |
||
373 | * @return mixed|null |
||
374 | */ |
||
375 | public function getEntity($id = 0) |
||
376 | { |
||
377 | try { |
||
378 | $q = $this->createQueryBuilder($this->getTableAlias()); |
||
379 | if (is_array($id)) { |
||
380 | $this->buildSelectClause($q, $id); |
||
381 | $contactId = (int) $id['id']; |
||
382 | } else { |
||
383 | $q->select('l, u, i') |
||
384 | ->leftJoin('l.ipAddresses', 'i') |
||
385 | ->leftJoin('l.owner', 'u'); |
||
386 | $contactId = $id; |
||
387 | } |
||
388 | $q->andWhere($this->getTableAlias().'.id = :id') |
||
389 | ->setParameter('id', (int) $contactId); |
||
390 | $entity = $q->getQuery()->getSingleResult(); |
||
391 | } catch (\Exception $e) { |
||
392 | $entity = null; |
||
393 | } |
||
394 | |||
395 | if (null === $entity) { |
||
396 | return $entity; |
||
397 | } |
||
398 | |||
399 | if ($entity->getFields()) { |
||
400 | // Pulled from Doctrine memory so don't make unnecessary queries as this has already happened |
||
401 | return $entity; |
||
402 | } |
||
403 | |||
404 | if (!empty($this->triggerModel)) { |
||
405 | $entity->setColor($this->triggerModel->getColorForLeadPoints($entity->getPoints())); |
||
406 | } |
||
407 | |||
408 | $fieldValues = $this->getFieldValues($id); |
||
409 | $entity->setFields($fieldValues); |
||
410 | |||
411 | $entity->setAvailableSocialFields($this->availableSocialFields); |
||
412 | |||
413 | return $entity; |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * Get a contact entity with the primary company data populated. |
||
418 | * |
||
419 | * The primary company data will be a flat array on the entity |
||
420 | * with a key of `primaryCompany` |
||
421 | * |
||
422 | * @param mixed $entity |
||
423 | * |
||
424 | * @return mixed|null |
||
425 | */ |
||
426 | public function getEntityWithPrimaryCompany($entity) |
||
427 | { |
||
428 | if (is_int($entity)) { |
||
429 | $entity = $this->getEntity($entity); |
||
430 | } |
||
431 | |||
432 | if ($entity instanceof Lead) { |
||
433 | $id = $entity->getId(); |
||
434 | $companies = $this->getEntityManager()->getRepository(Company::class)->getCompaniesForContacts([$id]); |
||
435 | |||
436 | if (!empty($companies[$id])) { |
||
437 | $primary = null; |
||
438 | |||
439 | foreach ($companies as $company) { |
||
440 | if (isset($company['is_primary']) && 1 == $company['is_primary']) { |
||
441 | $primary = $company; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | if (empty($primary)) { |
||
446 | $primary = $companies[$id][0]; |
||
447 | } |
||
448 | |||
449 | $entity->setPrimaryCompany($primary); |
||
450 | } |
||
451 | } |
||
452 | |||
453 | return $entity; |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * Get a list of leads. |
||
458 | * |
||
459 | * @return array |
||
460 | */ |
||
461 | public function getEntities(array $args = []) |
||
462 | { |
||
463 | $contacts = $this->getEntitiesWithCustomFields( |
||
464 | 'lead', |
||
465 | $args, |
||
466 | function ($r) { |
||
467 | if (!empty($this->triggerModel)) { |
||
468 | $r->setColor($this->triggerModel->getColorForLeadPoints($r->getPoints())); |
||
469 | } |
||
470 | $r->setAvailableSocialFields($this->availableSocialFields); |
||
471 | } |
||
472 | ); |
||
473 | |||
474 | $contactCount = isset($contacts['results']) ? count($contacts['results']) : count($contacts); |
||
475 | if ($contactCount && (!empty($args['withPrimaryCompany']) || !empty($args['withChannelRules']))) { |
||
476 | $withTotalCount = (array_key_exists('withTotalCount', $args) && $args['withTotalCount']); |
||
477 | /** @var Lead[] $tmpContacts */ |
||
478 | $tmpContacts = ($withTotalCount) ? $contacts['results'] : $contacts; |
||
479 | |||
480 | $withCompanies = !empty($args['withPrimaryCompany']); |
||
481 | $withPreferences = !empty($args['withChannelRules']); |
||
482 | $contactIds = array_keys($tmpContacts); |
||
483 | |||
484 | if ($withCompanies) { |
||
485 | $companies = $this->getEntityManager()->getRepository(Company::class)->getCompaniesForContacts($contactIds); |
||
486 | } |
||
487 | |||
488 | if ($withPreferences) { |
||
489 | /** @var FrequencyRuleRepository $frequencyRepo */ |
||
490 | $frequencyRepo = $this->getEntityManager()->getRepository(FrequencyRule::class); |
||
491 | $frequencyRules = $frequencyRepo->getFrequencyRules(null, $contactIds); |
||
492 | |||
493 | /** @var DoNotContactRepository $dncRepository */ |
||
494 | $dncRepository = $this->getEntityManager()->getRepository(DoNotContact::class); |
||
495 | $dncRules = $dncRepository->getChannelList(null, $contactIds); |
||
496 | } |
||
497 | |||
498 | foreach ($contactIds as $id) { |
||
499 | if ($withCompanies && isset($companies[$id]) && !empty($companies[$id])) { |
||
500 | $primary = null; |
||
501 | |||
502 | // Try to find the primary company |
||
503 | foreach ($companies[$id] as $company) { |
||
504 | if (1 == $company['is_primary']) { |
||
505 | $primary = $company; |
||
506 | } |
||
507 | } |
||
508 | |||
509 | // If no primary was found, just grab the first |
||
510 | if (empty($primary)) { |
||
511 | $primary = $companies[$id][0]; |
||
512 | } |
||
513 | |||
514 | if (is_array($tmpContacts[$id])) { |
||
515 | $tmpContacts[$id]['primaryCompany'] = $primary; |
||
516 | } elseif ($tmpContacts[$id] instanceof Lead) { |
||
517 | $tmpContacts[$id]->setPrimaryCompany($primary); |
||
518 | } |
||
519 | } |
||
520 | |||
521 | if ($withPreferences) { |
||
522 | $contactFrequencyRules = (isset($frequencyRules[$id])) ? $frequencyRules[$id] : []; |
||
523 | $contactDncRules = (isset($dncRules[$id])) ? $dncRules[$id] : []; |
||
524 | |||
525 | $channelRules = Lead::generateChannelRules($contactFrequencyRules, $contactDncRules); |
||
526 | if (is_array($tmpContacts[$id])) { |
||
527 | $tmpContacts[$id]['channelRules'] = $channelRules; |
||
528 | } elseif ($tmpContacts[$id] instanceof Lead) { |
||
529 | $tmpContacts[$id]->setChannelRules($channelRules); |
||
530 | } |
||
531 | } |
||
532 | } |
||
533 | |||
534 | if ($withTotalCount) { |
||
535 | $contacts['results'] = $tmpContacts; |
||
536 | } else { |
||
537 | $contacts = $tmpContacts; |
||
538 | } |
||
539 | } |
||
540 | |||
541 | return $contacts; |
||
542 | } |
||
543 | |||
544 | /** |
||
545 | * @return array |
||
546 | */ |
||
547 | public function getFieldGroups() |
||
548 | { |
||
549 | return ['core', 'social', 'personal', 'professional']; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * @return \Doctrine\DBAL\Query\QueryBuilder |
||
554 | */ |
||
555 | public function getEntitiesDbalQueryBuilder() |
||
556 | { |
||
557 | $alias = $this->getTableAlias(); |
||
558 | |||
559 | return $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
560 | ->from(MAUTIC_TABLE_PREFIX.'leads', $alias) |
||
561 | ->leftJoin($alias, MAUTIC_TABLE_PREFIX.'users', 'u', 'u.id = '.$alias.'.owner_id'); |
||
562 | } |
||
563 | |||
564 | /** |
||
565 | * @param $order |
||
566 | * |
||
567 | * @return \Doctrine\ORM\QueryBuilder |
||
568 | */ |
||
569 | public function getEntitiesOrmQueryBuilder($order) |
||
570 | { |
||
571 | $alias = $this->getTableAlias(); |
||
572 | $q = $this->getEntityManager()->createQueryBuilder(); |
||
573 | $q->select($alias.', u, i,'.$order) |
||
574 | ->from('MauticLeadBundle:Lead', $alias, $alias.'.id') |
||
575 | ->leftJoin($alias.'.ipAddresses', 'i') |
||
576 | ->leftJoin($alias.'.owner', 'u') |
||
577 | ->indexBy($alias, $alias.'.id'); |
||
578 | |||
579 | return $q; |
||
580 | } |
||
581 | |||
582 | /** |
||
583 | * Get contacts for a specific channel entity. |
||
584 | * |
||
585 | * @param $args - same as getEntity/getEntities |
||
586 | * @param $joinTable |
||
587 | * @param $entityId |
||
588 | * @param array $filters |
||
589 | * @param string $entityColumnName |
||
590 | * @param array $additionalJoins [ ['type' => 'join|leftJoin', 'from_alias' => '', 'table' => '', 'condition' => ''], ... ] |
||
591 | * |
||
592 | * @return array |
||
593 | */ |
||
594 | public function getEntityContacts($args, $joinTable, $entityId, $filters = [], $entityColumnName = 'id', array $additionalJoins = null, $contactColumnName = 'lead_id') |
||
595 | { |
||
596 | $qb = $this->getEntitiesDbalQueryBuilder(); |
||
597 | |||
598 | if (empty($contactColumnName)) { |
||
599 | $contactColumnName = 'lead_id'; |
||
600 | } |
||
601 | |||
602 | $joinCondition = $qb->expr()->andX( |
||
603 | $qb->expr()->eq($this->getTableAlias().'.id', 'entity.'.$contactColumnName) |
||
604 | ); |
||
605 | |||
606 | if ($entityId && $entityColumnName) { |
||
607 | $joinCondition->add( |
||
608 | $qb->expr()->eq("entity.{$entityColumnName}", (int) $entityId) |
||
609 | ); |
||
610 | } |
||
611 | |||
612 | if (!empty($joinTable)) { |
||
613 | $qb->join( |
||
614 | $this->getTableAlias(), |
||
615 | MAUTIC_TABLE_PREFIX.$joinTable, |
||
616 | 'entity', |
||
617 | $joinCondition |
||
618 | ); |
||
619 | } |
||
620 | |||
621 | if (is_array($additionalJoins)) { |
||
622 | foreach ($additionalJoins as $t) { |
||
623 | $qb->{$t['type']}( |
||
624 | $t['from_alias'], |
||
625 | MAUTIC_TABLE_PREFIX.$t['table'], |
||
626 | $t['alias'], |
||
627 | $t['condition'] |
||
628 | ); |
||
629 | } |
||
630 | } |
||
631 | |||
632 | if ($filters) { |
||
633 | $expr = $qb->expr()->andX(); |
||
634 | foreach ($filters as $column => $value) { |
||
635 | if (is_array($value)) { |
||
636 | $this->buildWhereClauseFromArray($qb, [$value]); |
||
637 | } else { |
||
638 | if (false === strpos($column, '.')) { |
||
639 | $column = "entity.$column"; |
||
640 | } |
||
641 | |||
642 | $expr->add( |
||
643 | $qb->expr()->eq($column, $qb->createNamedParameter($value)) |
||
644 | ); |
||
645 | $qb->andWhere($expr); |
||
646 | } |
||
647 | } |
||
648 | } |
||
649 | |||
650 | $args['qb'] = $qb; |
||
651 | |||
652 | return $this->getEntities($args); |
||
653 | } |
||
654 | |||
655 | /** |
||
656 | * Adds the "catch all" where clause to the QueryBuilder. |
||
657 | * |
||
658 | * @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q |
||
659 | * @param $filter |
||
660 | * |
||
661 | * @return array |
||
662 | */ |
||
663 | protected function addCatchAllWhereClause($q, $filter) |
||
664 | { |
||
665 | $columns = array_merge( |
||
666 | [ |
||
667 | 'l.firstname', |
||
668 | 'l.lastname', |
||
669 | 'l.email', |
||
670 | 'l.company', |
||
671 | 'l.city', |
||
672 | 'l.state', |
||
673 | 'l.zipcode', |
||
674 | 'l.country', |
||
675 | ], |
||
676 | $this->availableSocialFields |
||
677 | ); |
||
678 | |||
679 | return $this->addStandardCatchAllWhereClause($q, $filter, $columns); |
||
680 | } |
||
681 | |||
682 | /** |
||
683 | * Adds the command where clause to the QueryBuilder. |
||
684 | * |
||
685 | * @param \Doctrine\ORM\QueryBuilder|\Doctrine\DBAL\Query\QueryBuilder $q |
||
686 | * @param $filter |
||
687 | * |
||
688 | * @return array |
||
689 | */ |
||
690 | protected function addSearchCommandWhereClause($q, $filter) |
||
691 | { |
||
692 | $command = $filter->command; |
||
693 | $string = $filter->string; |
||
694 | $unique = $this->generateRandomParameterName(); |
||
695 | $returnParameter = false; //returning a parameter that is not used will lead to a Doctrine error |
||
696 | list($expr, $parameters) = parent::addSearchCommandWhereClause($q, $filter); |
||
697 | |||
698 | //DBAL QueryBuilder does not have an expr()->not() function; boo!! |
||
699 | |||
700 | // This will be switched by some commands that use join tables as NOT EXISTS queries will be used |
||
701 | $exprType = ($filter->not) ? 'negate_expr' : 'expr'; |
||
702 | |||
703 | $operators = $this->getFilterExpressionFunctions(); |
||
704 | $operators = array_merge($operators, [ |
||
705 | 'x' => [ |
||
706 | 'expr' => 'andX', |
||
707 | 'negate_expr' => 'orX', |
||
708 | ], |
||
709 | 'null' => [ |
||
710 | 'expr' => 'isNull', |
||
711 | 'negate_expr' => 'isNotNull', |
||
712 | ], |
||
713 | ]); |
||
714 | |||
715 | $innerJoinTables = (isset($this->advancedFilterCommands[$command]) |
||
716 | && SearchStringHelper::COMMAND_NEGATE !== $this->advancedFilterCommands[$command]); |
||
717 | $likeExpr = $operators['like'][$exprType]; |
||
718 | $eqExpr = $operators['='][$exprType]; |
||
719 | $nullExpr = $operators['null'][$exprType]; |
||
720 | $inExpr = $operators['in'][$exprType]; |
||
721 | $xExpr = $operators['x'][$exprType]; |
||
722 | |||
723 | switch ($command) { |
||
724 | case $this->translator->trans('mautic.lead.lead.searchcommand.isanonymous'): |
||
725 | case $this->translator->trans('mautic.lead.lead.searchcommand.isanonymous', [], null, 'en_US'): |
||
726 | $expr = $q->expr()->$nullExpr('l.date_identified'); |
||
727 | break; |
||
728 | case $this->translator->trans('mautic.core.searchcommand.ismine'): |
||
729 | case $this->translator->trans('mautic.core.searchcommand.ismine', [], null, 'en_US'): |
||
730 | $expr = $q->expr()->$eqExpr('l.owner_id', $this->currentUser->getId()); |
||
731 | break; |
||
732 | case $this->translator->trans('mautic.lead.lead.searchcommand.isunowned'): |
||
733 | case $this->translator->trans('mautic.lead.lead.searchcommand.isunowned', [], null, 'en_US'): |
||
734 | $expr = $q->expr()->orX( |
||
735 | $q->expr()->$eqExpr('l.owner_id', 0), |
||
736 | $q->expr()->$nullExpr('l.owner_id') |
||
737 | ); |
||
738 | break; |
||
739 | case $this->translator->trans('mautic.lead.lead.searchcommand.owner'): |
||
740 | case $this->translator->trans('mautic.lead.lead.searchcommand.owner', [], null, 'en_US'): |
||
741 | $expr = $q->expr()->orX( |
||
742 | $q->expr()->$likeExpr('u.first_name', ':'.$unique), |
||
743 | $q->expr()->$likeExpr('u.last_name', ':'.$unique) |
||
744 | ); |
||
745 | $returnParameter = true; |
||
746 | break; |
||
747 | case $this->translator->trans('mautic.core.searchcommand.name'): |
||
748 | case $this->translator->trans('mautic.core.searchcommand.name', [], null, 'en_US'): |
||
749 | $expr = $q->expr()->orX( |
||
750 | $q->expr()->$likeExpr('l.firstname', ":$unique"), |
||
751 | $q->expr()->$likeExpr('l.lastname', ":$unique") |
||
752 | ); |
||
753 | $returnParameter = true; |
||
754 | break; |
||
755 | case $this->translator->trans('mautic.core.searchcommand.email'): |
||
756 | case $this->translator->trans('mautic.core.searchcommand.email', [], null, 'en_US'): |
||
757 | $expr = $q->expr()->$likeExpr('l.email', ":$unique"); |
||
758 | $returnParameter = true; |
||
759 | break; |
||
760 | case $this->translator->trans('mautic.lead.lead.searchcommand.list'): |
||
761 | case $this->translator->trans('mautic.lead.lead.searchcommand.list', [], null, 'en_US'): |
||
762 | $this->applySearchQueryRelationship( |
||
763 | $q, |
||
764 | [ |
||
765 | [ |
||
766 | 'from_alias' => 'l', |
||
767 | 'table' => 'lead_lists_leads', |
||
768 | 'alias' => 'list_lead', |
||
769 | 'condition' => 'l.id = list_lead.lead_id', |
||
770 | ], |
||
771 | [ |
||
772 | 'from_alias' => 'list_lead', |
||
773 | 'table' => 'lead_lists', |
||
774 | 'alias' => 'list', |
||
775 | 'condition' => 'list_lead.leadlist_id = list.id', |
||
776 | ], |
||
777 | ], |
||
778 | $innerJoinTables, |
||
779 | $this->generateFilterExpression($q, 'list.alias', $eqExpr, $unique, ($filter->not) ? true : null, |
||
780 | // orX for filter->not either manuall removed or is null |
||
781 | $q->expr()->$xExpr( |
||
782 | $q->expr()->$eqExpr('list_lead.manually_removed', 0) |
||
783 | ) |
||
784 | ) |
||
785 | ); |
||
786 | $filter->strict = true; |
||
787 | $returnParameter = true; |
||
788 | |||
789 | break; |
||
790 | case $this->translator->trans('mautic.core.searchcommand.ip'): |
||
791 | case $this->translator->trans('mautic.core.searchcommand.ip', [], null, 'en_US'): |
||
792 | $this->applySearchQueryRelationship( |
||
793 | $q, |
||
794 | [ |
||
795 | [ |
||
796 | 'from_alias' => 'l', |
||
797 | 'table' => 'lead_ips_xref', |
||
798 | 'alias' => 'ip_lead', |
||
799 | 'condition' => 'l.id = ip_lead.lead_id', |
||
800 | ], |
||
801 | [ |
||
802 | 'from_alias' => 'ip_lead', |
||
803 | 'table' => 'ip_addresses', |
||
804 | 'alias' => 'ip', |
||
805 | 'condition' => 'ip_lead.ip_id = ip.id', |
||
806 | ], |
||
807 | ], |
||
808 | $innerJoinTables, |
||
809 | $this->generateFilterExpression($q, 'ip.ip_address', $likeExpr, $unique, null) |
||
810 | ); |
||
811 | $returnParameter = true; |
||
812 | |||
813 | break; |
||
814 | case $this->translator->trans('mautic.lead.lead.searchcommand.duplicate'): |
||
815 | case $this->translator->trans('mautic.lead.lead.searchcommand.duplicate', [], null, 'en_US'): |
||
816 | $prateek = explode('+', $string); |
||
817 | $imploder = []; |
||
818 | |||
819 | foreach ($prateek as $value) { |
||
820 | $list = $this->getEntityManager()->getRepository(LeadList::class)->findOneByAlias($value); |
||
821 | $imploder[] = ((!empty($list)) ? (int) $list->getId() : 0); |
||
822 | } |
||
823 | |||
824 | //logic. In query, Sum(manually_removed) should be less than the current) |
||
825 | $pluck = count($imploder); |
||
826 | $imploder = (string) (implode(',', $imploder)); |
||
827 | |||
828 | $sq = $this->getEntityManager()->getConnection()->createQueryBuilder(); |
||
829 | $sq->select('duplicate.lead_id') |
||
830 | ->from(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 'duplicate') |
||
831 | ->where( |
||
832 | $q->expr()->andX( |
||
833 | $q->expr()->in('duplicate.leadlist_id', $imploder), |
||
834 | $q->expr()->eq('duplicate.manually_removed', 0) |
||
835 | ) |
||
836 | ) |
||
837 | ->groupBy('duplicate.lead_id') |
||
838 | ->having("COUNT(duplicate.lead_id) = $pluck"); |
||
839 | |||
840 | $expr = $q->expr()->$inExpr('l.id', sprintf('(%s)', $sq->getSQL())); |
||
841 | $returnParameter = true; |
||
842 | |||
843 | break; |
||
844 | case $this->translator->trans('mautic.lead.lead.searchcommand.tag'): |
||
845 | case $this->translator->trans('mautic.lead.lead.searchcommand.tag', [], null, 'en_US'): |
||
846 | $this->applySearchQueryRelationship( |
||
847 | $q, |
||
848 | [ |
||
849 | [ |
||
850 | 'from_alias' => 'l', |
||
851 | 'table' => 'lead_tags_xref', |
||
852 | 'alias' => 'xtag', |
||
853 | 'condition' => 'l.id = xtag.lead_id', |
||
854 | ], |
||
855 | [ |
||
856 | 'from_alias' => 'xtag', |
||
857 | 'table' => 'lead_tags', |
||
858 | 'alias' => 'tag', |
||
859 | 'condition' => 'xtag.tag_id = tag.id', |
||
860 | ], |
||
861 | ], |
||
862 | $innerJoinTables, |
||
863 | $this->generateFilterExpression($q, 'tag.tag', $likeExpr, $unique, null) |
||
864 | ); |
||
865 | $returnParameter = true; |
||
866 | break; |
||
867 | case $this->translator->trans('mautic.lead.lead.searchcommand.company'): |
||
868 | case $this->translator->trans('mautic.lead.lead.searchcommand.company', [], null, 'en_US'): |
||
869 | $this->applySearchQueryRelationship( |
||
870 | $q, |
||
871 | [ |
||
872 | [ |
||
873 | 'from_alias' => 'l', |
||
874 | 'table' => 'companies_leads', |
||
875 | 'alias' => 'comp_lead', |
||
876 | 'condition' => 'l.id = comp_lead.lead_id', |
||
877 | ], |
||
878 | [ |
||
879 | 'from_alias' => 'comp_lead', |
||
880 | 'table' => 'companies', |
||
881 | 'alias' => 'comp', |
||
882 | 'condition' => 'comp_lead.company_id = comp.id', |
||
883 | ], |
||
884 | ], |
||
885 | $innerJoinTables, |
||
886 | $this->generateFilterExpression($q, 'comp.companyname', $likeExpr, $unique, null) |
||
887 | ); |
||
888 | $returnParameter = true; |
||
889 | break; |
||
890 | case $this->translator->trans('mautic.lead.lead.searchcommand.stage'): |
||
891 | case $this->translator->trans('mautic.lead.lead.searchcommand.stage', [], null, 'en_US'): |
||
892 | $this->applySearchQueryRelationship( |
||
893 | $q, |
||
894 | [ |
||
895 | [ |
||
896 | 'from_alias' => 'l', |
||
897 | 'table' => 'stages', |
||
898 | 'alias' => 's', |
||
899 | 'condition' => 'l.stage_id = s.id', |
||
900 | ], |
||
901 | ], |
||
902 | $innerJoinTables, |
||
903 | $this->generateFilterExpression($q, 's.name', $likeExpr, $unique, null) |
||
904 | ); |
||
905 | $returnParameter = true; |
||
906 | break; |
||
907 | default: |
||
908 | if (in_array($command, $this->availableSearchFields)) { |
||
909 | $expr = $q->expr()->$likeExpr("l.$command", ":$unique"); |
||
910 | } |
||
911 | $returnParameter = true; |
||
912 | break; |
||
913 | } |
||
914 | |||
915 | if ($this->dispatcher) { |
||
916 | $event = new LeadBuildSearchEvent($filter->string, $filter->command, $unique, $filter->not, $q); |
||
917 | $this->dispatcher->dispatch(LeadEvents::LEAD_BUILD_SEARCH_COMMANDS, $event); |
||
918 | if ($event->isSearchDone()) { |
||
919 | $returnParameter = $event->getReturnParameters(); |
||
920 | $filter->strict = $event->getStrict(); |
||
921 | $expr = $event->getSubQuery(); |
||
922 | $parameters = array_merge($parameters, $event->getParameters()); |
||
923 | } |
||
924 | } |
||
925 | |||
926 | if ($returnParameter) { |
||
927 | $string = ($filter->strict) ? $filter->string : "{$filter->string}%"; |
||
928 | $parameters[$unique] = $string; |
||
929 | } |
||
930 | |||
931 | return [ |
||
932 | $expr, |
||
933 | $parameters, |
||
934 | ]; |
||
935 | } |
||
936 | |||
937 | /** |
||
938 | * Returns the array of search commands. |
||
939 | * |
||
940 | * @return array |
||
941 | */ |
||
942 | public function getSearchCommands() |
||
943 | { |
||
944 | $commands = [ |
||
945 | 'mautic.lead.lead.searchcommand.isanonymous', |
||
946 | 'mautic.core.searchcommand.ismine', |
||
947 | 'mautic.lead.lead.searchcommand.isunowned', |
||
948 | 'mautic.lead.lead.searchcommand.list', |
||
949 | 'mautic.core.searchcommand.name', |
||
950 | 'mautic.lead.lead.searchcommand.company', |
||
951 | 'mautic.core.searchcommand.email', |
||
952 | 'mautic.lead.lead.searchcommand.owner', |
||
953 | 'mautic.core.searchcommand.ip', |
||
954 | 'mautic.lead.lead.searchcommand.tag', |
||
955 | 'mautic.lead.lead.searchcommand.stage', |
||
956 | 'mautic.lead.lead.searchcommand.duplicate', |
||
957 | 'mautic.lead.lead.searchcommand.email_sent', |
||
958 | 'mautic.lead.lead.searchcommand.email_read', |
||
959 | 'mautic.lead.lead.searchcommand.email_queued', |
||
960 | 'mautic.lead.lead.searchcommand.email_pending', |
||
961 | 'mautic.lead.lead.searchcommand.page_source', |
||
962 | 'mautic.lead.lead.searchcommand.page_source_id', |
||
963 | 'mautic.lead.lead.searchcommand.page_id', |
||
964 | 'mautic.lead.lead.searchcommand.sms_sent', |
||
965 | 'mautic.lead.lead.searchcommand.web_sent', |
||
966 | 'mautic.lead.lead.searchcommand.mobile_sent', |
||
967 | ]; |
||
968 | |||
969 | if (!empty($this->availableSearchFields)) { |
||
970 | $commands = array_merge($commands, $this->availableSearchFields); |
||
971 | } |
||
972 | |||
973 | return array_merge($commands, parent::getSearchCommands()); |
||
974 | } |
||
975 | |||
976 | /** |
||
977 | * Returns the array of columns with the default order. |
||
978 | * |
||
979 | * @return array |
||
980 | */ |
||
981 | protected function getDefaultOrder() |
||
982 | { |
||
983 | return [ |
||
984 | ['l.last_active', 'DESC'], |
||
985 | ]; |
||
986 | } |
||
987 | |||
988 | /** |
||
989 | * Updates lead's lastActive with now date/time. |
||
990 | * |
||
991 | * @param int $leadId |
||
992 | */ |
||
993 | public function updateLastActive($leadId) |
||
994 | { |
||
995 | $dt = new DateTimeHelper(); |
||
996 | $fields = ['last_active' => $dt->toUtcString()]; |
||
997 | |||
998 | $this->getEntityManager()->getConnection()->update(MAUTIC_TABLE_PREFIX.'leads', $fields, ['id' => $leadId]); |
||
999 | } |
||
1000 | |||
1001 | /** |
||
1002 | * Gets the ID of the latest ID. |
||
1003 | * |
||
1004 | * @return int |
||
1005 | */ |
||
1006 | public function getMaxLeadId() |
||
1007 | { |
||
1008 | $result = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
1009 | ->select('max(id) as max_lead_id') |
||
1010 | ->from(MAUTIC_TABLE_PREFIX.'leads', 'l') |
||
1011 | ->execute()->fetchAll(); |
||
1012 | |||
1013 | return $result[0]['max_lead_id']; |
||
1014 | } |
||
1015 | |||
1016 | /** |
||
1017 | * Gets names, signature and email of the user(lead owner). |
||
1018 | * |
||
1019 | * @param int $ownerId |
||
1020 | * |
||
1021 | * @return array|false |
||
1022 | */ |
||
1023 | public function getLeadOwner($ownerId) |
||
1024 | { |
||
1025 | if (!$ownerId) { |
||
1026 | return false; |
||
1027 | } |
||
1028 | |||
1029 | $q = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
1030 | ->select('u.id, u.first_name, u.last_name, u.email, u.position, u.signature') |
||
1031 | ->from(MAUTIC_TABLE_PREFIX.'users', 'u') |
||
1032 | ->where('u.id = :ownerId') |
||
1033 | ->setParameter('ownerId', (int) $ownerId); |
||
1034 | |||
1035 | $result = $q->execute()->fetch(); |
||
1036 | |||
1037 | // Fix the HTML markup |
||
1038 | if (is_array($result)) { |
||
1039 | foreach ($result as &$field) { |
||
1040 | $field = html_entity_decode($field); |
||
1041 | } |
||
1042 | } |
||
1043 | |||
1044 | return $result; |
||
1045 | } |
||
1046 | |||
1047 | /** |
||
1048 | * Check lead owner. |
||
1049 | * |
||
1050 | * @param array $ownerIds |
||
1051 | * |
||
1052 | * @return array|false |
||
1053 | */ |
||
1054 | public function checkLeadOwner(Lead $lead, $ownerIds = []) |
||
1055 | { |
||
1056 | if (empty($ownerIds)) { |
||
1057 | return false; |
||
1058 | } |
||
1059 | |||
1060 | $q = $this->getEntityManager()->getConnection()->createQueryBuilder(); |
||
1061 | $q->select('u.id') |
||
1062 | ->from(MAUTIC_TABLE_PREFIX.'users', 'u') |
||
1063 | ->join('u', MAUTIC_TABLE_PREFIX.'leads', 'l', 'l.owner_id = u.id') |
||
1064 | ->where( |
||
1065 | $q->expr()->andX( |
||
1066 | $q->expr()->in('u.id', ':ownerIds'), |
||
1067 | $q->expr()->eq('l.id', ':leadId') |
||
1068 | ) |
||
1069 | ) |
||
1070 | ->setParameter('ownerIds', implode(',', $ownerIds)) |
||
1071 | ->setParameter('leadId', $lead->getId()); |
||
1072 | |||
1073 | return (bool) $q->execute()->fetchColumn(); |
||
0 ignored issues
–
show
|
|||
1074 | } |
||
1075 | |||
1076 | /** |
||
1077 | * @return array |
||
1078 | */ |
||
1079 | public function getContacts(array $contactIds) |
||
1080 | { |
||
1081 | $qb = $this->getEntityManager()->getConnection()->createQueryBuilder(); |
||
1082 | |||
1083 | $qb->select('l.*')->from(MAUTIC_TABLE_PREFIX.'leads', 'l') |
||
1084 | ->where( |
||
1085 | $qb->expr()->in('l.id', $contactIds) |
||
1086 | ); |
||
1087 | |||
1088 | $results = $qb->execute()->fetchAll(); |
||
1089 | |||
1090 | if ($results) { |
||
1091 | $contacts = []; |
||
1092 | foreach ($results as $result) { |
||
1093 | $contacts[$result['id']] = $result; |
||
1094 | } |
||
1095 | |||
1096 | return $contacts; |
||
1097 | } |
||
1098 | |||
1099 | return []; |
||
1100 | } |
||
1101 | |||
1102 | /** |
||
1103 | * @return ArrayCollection |
||
1104 | */ |
||
1105 | public function getContactCollection(array $ids) |
||
1106 | { |
||
1107 | if (empty($ids)) { |
||
1108 | return new ArrayCollection(); |
||
1109 | } |
||
1110 | |||
1111 | $contacts = $this->getEntities( |
||
1112 | [ |
||
1113 | 'filter' => [ |
||
1114 | 'force' => [ |
||
1115 | [ |
||
1116 | 'column' => 'l.id', |
||
1117 | 'expr' => 'in', |
||
1118 | 'value' => $ids, |
||
1119 | ], |
||
1120 | ], |
||
1121 | ], |
||
1122 | 'orderBy' => 'l.id', |
||
1123 | 'orderByDir' => 'asc', |
||
1124 | 'withPrimaryCompany' => true, |
||
1125 | 'withChannelRules' => true, |
||
1126 | ] |
||
1127 | ); |
||
1128 | |||
1129 | return new ArrayCollection($contacts); |
||
1130 | } |
||
1131 | |||
1132 | /** |
||
1133 | * @return string |
||
1134 | */ |
||
1135 | public function getTableAlias() |
||
1136 | { |
||
1137 | return 'l'; |
||
1138 | } |
||
1139 | |||
1140 | /** |
||
1141 | * Get the count of identified contacts. |
||
1142 | * |
||
1143 | * @return int |
||
1144 | */ |
||
1145 | public function getIdentifiedContactCount() |
||
1146 | { |
||
1147 | $qb = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
1148 | ->select('count(*)') |
||
1149 | ->from($this->getTableName(), $this->getTableAlias()); |
||
1150 | |||
1151 | $qb->where( |
||
1152 | $qb->expr()->isNotNull($this->getTableAlias().'.date_identified') |
||
1153 | ); |
||
1154 | |||
1155 | return (int) $qb->execute()->fetchColumn(); |
||
1156 | } |
||
1157 | |||
1158 | /** |
||
1159 | * Get the next contact after an specific ID; mainly used in deduplication. |
||
1160 | * |
||
1161 | * @return Lead |
||
1162 | */ |
||
1163 | public function getNextIdentifiedContact($lastId) |
||
1164 | { |
||
1165 | $alias = $this->getTableAlias(); |
||
1166 | $qb = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
1167 | ->select("$alias.id") |
||
1168 | ->from($this->getTableName(), $this->getTableAlias()); |
||
1169 | |||
1170 | $qb->where( |
||
1171 | $qb->expr()->andX( |
||
1172 | $qb->expr()->gt("$alias.id", (int) $lastId), |
||
1173 | $qb->expr()->isNotNull("$alias.date_identified") |
||
1174 | ) |
||
1175 | ) |
||
1176 | ->orderBy("$alias.id") |
||
1177 | ->setMaxResults(1); |
||
1178 | |||
1179 | $next = $qb->execute()->fetchColumn(); |
||
1180 | |||
1181 | return ($next) ? $this->getEntity($next) : null; |
||
1182 | } |
||
1183 | |||
1184 | /** |
||
1185 | * @param array $tables $tables[0] should be primary table |
||
1186 | * @param bool $innerJoinTables |
||
1187 | * @param null $whereExpression |
||
1188 | * @param null $having |
||
1189 | */ |
||
1190 | public function applySearchQueryRelationship(QueryBuilder $q, array $tables, $innerJoinTables, $whereExpression = null, $having = null) |
||
1191 | { |
||
1192 | $primaryTable = $tables[0]; |
||
1193 | unset($tables[0]); |
||
1194 | |||
1195 | $joinType = ($innerJoinTables) ? 'join' : 'leftJoin'; |
||
1196 | |||
1197 | $this->useDistinctCount = true; |
||
1198 | $joins = $q->getQueryPart('join'); |
||
1199 | if (!preg_match('/"'.preg_quote($primaryTable['alias'], '/').'"/i', json_encode($joins))) { |
||
1200 | $q->$joinType( |
||
1201 | $primaryTable['from_alias'], |
||
1202 | MAUTIC_TABLE_PREFIX.$primaryTable['table'], |
||
1203 | $primaryTable['alias'], |
||
1204 | $primaryTable['condition'] |
||
1205 | ); |
||
1206 | } |
||
1207 | foreach ($tables as $table) { |
||
1208 | $q->$joinType($table['from_alias'], MAUTIC_TABLE_PREFIX.$table['table'], $table['alias'], $table['condition']); |
||
1209 | } |
||
1210 | |||
1211 | if ($whereExpression) { |
||
1212 | $q->andWhere($whereExpression); |
||
1213 | } |
||
1214 | |||
1215 | if ($having) { |
||
1216 | $q->andHaving($having); |
||
1217 | } |
||
1218 | $q->groupBy('l.id'); |
||
1219 | } |
||
1220 | |||
1221 | /** |
||
1222 | * @param $id |
||
1223 | * @param int $tries |
||
1224 | */ |
||
1225 | protected function updateContactPoints(array $changes, $id, $tries = 1) |
||
1226 | { |
||
1227 | $qb = $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
1228 | ->update(MAUTIC_TABLE_PREFIX.'leads') |
||
1229 | ->where('id = '.$id); |
||
1230 | |||
1231 | $ph = 0; |
||
1232 | // Keep operator in same order as was used in Lead::adjustPoints() in order to be congruent with what was calculated in PHP |
||
1233 | // Again ignoring Aunt Sally here (PEMDAS) |
||
1234 | foreach ($changes as $operator => $points) { |
||
1235 | $qb->set('points', 'points '.$operator.' :points'.$ph) |
||
1236 | ->setParameter('points'.$ph, $points, \PDO::PARAM_INT); |
||
1237 | |||
1238 | ++$ph; |
||
1239 | } |
||
1240 | |||
1241 | try { |
||
1242 | $qb->execute(); |
||
1243 | } catch (DriverException $exception) { |
||
1244 | $message = $exception->getMessage(); |
||
1245 | |||
1246 | if (false !== strpos($message, 'Deadlock') && $tries <= 3) { |
||
1247 | ++$tries; |
||
1248 | |||
1249 | $this->updateContactPoints($changes, $id, $tries); |
||
1250 | } |
||
1251 | } |
||
1252 | |||
1253 | // Query new points |
||
1254 | return (int) $this->getEntityManager()->getConnection()->createQueryBuilder() |
||
1255 | ->select('l.points') |
||
1256 | ->from(MAUTIC_TABLE_PREFIX.'leads', 'l') |
||
1257 | ->where('l.id = '.$id) |
||
1258 | ->execute() |
||
1259 | ->fetchColumn(); |
||
1260 | } |
||
1261 | |||
1262 | /** |
||
1263 | * @param Lead $entity |
||
1264 | */ |
||
1265 | protected function postSaveEntity($entity) |
||
1266 | { |
||
1267 | // Check if points need to be appended |
||
1268 | if ($entity->getPointChanges()) { |
||
1269 | $newPoints = $this->updateContactPoints($entity->getPointChanges(), $entity->getId()); |
||
1270 | |||
1271 | // Set actual points so that code using getPoints knows the true value |
||
1272 | $entity->setActualPoints($newPoints); |
||
1273 | |||
1274 | $changes = $entity->getChanges(); |
||
1275 | |||
1276 | if (isset($changes['points'])) { |
||
1277 | // Let's adjust the points to be more accurate in the change log |
||
1278 | $changes['points'][1] = $newPoints; |
||
1279 | $entity->setChanges($changes); |
||
1280 | } |
||
1281 | } |
||
1282 | } |
||
1283 | |||
1284 | /** |
||
1285 | * @param $fields |
||
1286 | */ |
||
1287 | protected function prepareDbalFieldsForSave(&$fields) |
||
1288 | { |
||
1289 | // Do not save points as they are handled by postSaveEntity |
||
1290 | unset($fields['points']); |
||
1291 | |||
1292 | $this->defaultPrepareDbalFieldsForSave($fields); |
||
1293 | } |
||
1294 | } |
||
1295 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
integer
values, zero is a special case, in particular the following results might be unexpected: