listModel = static::getContainer()->get('mautic.lead.model.list'); \assert($this->listModel instanceof ListModel); $this->listRepo = $this->listModel->getRepository(); \assert($this->listRepo instanceof LeadListRepository); /** @var LeadModel $leadModel */ $leadModel = static::getContainer()->get('mautic.lead.model.lead'); /* @var LeadRepository $leadRepo */ $this->leadRepo = $leadModel->getRepository(); } public function testUnpublishUsedSegment(): void { $filter = [[ 'glue' => 'and', 'field' => 'email', 'object' => 'lead', 'type' => 'email', 'operator' => '!empty', 'display' => '', ]]; $list1 = $this->saveSegment('s1', 's1', $filter); $filter = [[ 'object' => 'lead', 'glue' => 'and', 'field' => 'leadlist', 'type' => 'leadlist', 'operator' => 'in', 'properties' => [ 'filter' => [$list1->getId()], ], 'display' => '', ]]; $list2 = $this->saveSegment('s2', 's2', $filter); $this->em->clear(); $expectedErrorMessage = sprintf('This segment is used in %s, please go back and check segments before unpublishing', $list2->getName()); $crawler = $this->client->request(Request::METHOD_POST, '/s/ajax', ['action' => 'togglePublishStatus', 'model' => 'lead.list', 'id' => $list1->getId()]); $this->assertSame(Response::HTTP_UNPROCESSABLE_ENTITY, $this->client->getResponse()->getStatusCode()); $this->assertStringContainsString($expectedErrorMessage, $this->client->getResponse()->getContent()); $this->client->restart(); $crawler = $this->client->request(Request::METHOD_GET, '/s/segments/edit/'.$list1->getId()); $form = $crawler->selectButton('leadlist_buttons_apply')->form(); $form['leadlist[isPublished]']->setValue('0'); $crawler = $this->client->submit($form); $this->assertTrue($this->client->getResponse()->isOk()); $this->assertStringContainsString($expectedErrorMessage, $this->client->getResponse()->getContent()); } public function testUnpublishUnUsedSegment(): void { $filter = [[ 'glue' => 'and', 'field' => 'email', 'object' => 'lead', 'type' => 'email', 'operator' => '!empty', 'display' => '', ]]; $list1 = $this->saveSegment('s1', 's1', $filter); $list2 = $this->saveSegment('s2', 's2', $filter); $this->em->clear(); $crawler = $this->client->request(Request::METHOD_POST, '/s/ajax', ['action' => 'togglePublishStatus', 'model' => 'lead.list', 'id' => $list1->getId()]); $this->assertTrue($this->client->getResponse()->isOk()); $crawler = $this->client->request(Request::METHOD_GET, '/s/segments/edit/'.$list2->getId()); $form = $crawler->selectButton('leadlist_buttons_apply')->form(); $form['leadlist[isPublished]']->setValue('0'); $crawler = $this->client->submit($form); $this->assertTrue($this->client->getResponse()->isOk()); $rows = $this->listRepo->findAll(); $this->assertCount(2, $rows); $this->assertFalse($rows[0]->isPublished()); $this->assertFalse($rows[1]->isPublished()); } public function testBCSegmentWithPageHitInLeadObject(): void { $segment = $this->saveSegment( 'Legacy Url Hit segment', 's1', [ [ 'glue' => 'and', 'field' => 'hit_url', 'object' => 'lead', 'type' => 'text', 'filter' => 'unicorn', 'display' => null, 'operator' => '=', ], ] ); $this->em->clear(); $crawler = $this->client->request(Request::METHOD_GET, '/s/segments/edit/'.$segment->getId()); Assert::assertTrue($this->client->getResponse()->isOk()); Assert::assertGreaterThan(0, $crawler->filter('#leadlist_filters_0_operator option')->count()); } private function saveSegment(string $name, string $alias, array $filters = [], LeadList $segment = null): LeadList { $segment ??= new LeadList(); $segment->setName($name)->setAlias($alias)->setFilters($filters); $this->listModel->saveEntity($segment); return $segment; } /** * @throws \Exception */ public function testSegmentCount(): void { // Save segment. $filters = [ [ 'glue' => 'and', 'field' => 'email', 'object' => 'lead', 'type' => 'email', 'filter' => null, 'display' => null, 'operator' => '!empty', ], ]; $segment = $this->saveSegment('Lead List 1', 'lead-list-1', $filters); $segmentId = $segment->getId(); // Check segment count UI for no contacts. $crawler = $this->client->request(Request::METHOD_GET, '/s/segments'); $html = $this->getSegmentCountHtml($crawler, $segmentId); self::assertSame('No Contacts', $html); // Add 4 contacts. $contacts = $this->saveContacts(); $contact1Id = $contacts[0]->getId(); // Rebuild segment - set current count to the cache. $this->testSymfonyCommand('mautic:segments:update', ['-i' => $segmentId, '--env' => 'test']); // Check segment count UI for 4 contacts. $crawler = $this->client->request(Request::METHOD_GET, '/s/segments'); $html = $this->getSegmentCountHtml($crawler, $segmentId); self::assertSame('View 4 Contacts', $html); // Remove 1 contact from segment. $this->client->request(Request::METHOD_POST, '/api/segments/'.$segmentId.'/contact/'.$contact1Id.'/remove'); self::assertSame('{"success":1}', $this->client->getResponse()->getContent()); self::assertSame(200, $this->client->getResponse()->getStatusCode()); // Check segment count UI for 3 contacts. $crawler = $this->client->request(Request::METHOD_GET, '/s/segments'); $html = $this->getSegmentCountHtml($crawler, $segmentId); self::assertSame('View 3 Contacts', $html); // Add 1 contact back to segment. $parameters = ['ids' => [$contact1Id]]; $this->client->request(Request::METHOD_POST, '/api/segments/'.$segmentId.'/contacts/add', $parameters); self::assertSame('{"success":1,"details":{"'.$contact1Id.'":{"success":true}}}', $this->client->getResponse()->getContent()); self::assertSame(200, $this->client->getResponse()->getStatusCode()); // Check segment count UI for 4 contacts. $crawler = $this->client->request(Request::METHOD_GET, '/s/segments'); $html = $this->getSegmentCountHtml($crawler, $segmentId); self::assertSame('View 4 Contacts', $html); // Check segment count AJAX for 4 contacts. $parameter = ['id' => $segmentId]; $response = $this->callGetLeadCountAjaxRequest($parameter); self::assertSame('View 4 Contacts', $response['content']['html']); self::assertSame(4, $response['content']['leadCount']); self::assertSame(Response::HTTP_OK, $response['statusCode']); // Remove 1 contact from segment. $this->client->request(Request::METHOD_POST, '/api/segments/'.$segmentId.'/contact/'.$contact1Id.'/remove'); self::assertSame('{"success":1}', $this->client->getResponse()->getContent()); self::assertSame(200, $this->client->getResponse()->getStatusCode()); // Check segment count AJAX for 3 contacts. $parameter = ['id' => $segmentId]; $response = $this->callGetLeadCountAjaxRequest($parameter); self::assertSame('View 3 Contacts', $response['content']['html']); self::assertSame(3, $response['content']['leadCount']); self::assertSame(Response::HTTP_OK, $response['statusCode']); // Add 1 contact back to segment. $parameters = ['ids' => [$contact1Id]]; $this->client->request(Request::METHOD_POST, '/api/segments/'.$segmentId.'/contacts/add', $parameters); self::assertSame('{"success":1,"details":{"'.$contact1Id.'":{"success":true}}}', $this->client->getResponse()->getContent()); self::assertSame(200, $this->client->getResponse()->getStatusCode()); // Check segment count AJAX for 4 contacts. $parameter = ['id' => $segmentId]; $response = $this->callGetLeadCountAjaxRequest($parameter); self::assertSame('View 4 Contacts', $response['content']['html']); self::assertSame(4, $response['content']['leadCount']); self::assertSame(Response::HTTP_OK, $response['statusCode']); } public function testSegmentNotFoundOnAjax(): void { // Emulate invalid request parameter. $parameter = ['id' => 'ABC']; $response = $this->callGetLeadCountAjaxRequest($parameter); self::assertSame('No Contacts', $response['content']['html']); self::assertSame(0, $response['content']['leadCount']); self::assertSame(Response::HTTP_NOT_FOUND, $response['statusCode']); } /** * @return Lead[] */ private function saveContacts(int $count = 4): array { $contacts = []; for ($i = 1; $i <= $count; ++$i) { $contact = new Lead(); $contact->setFirstname('Contact '.$i)->setEmail('contact'.$i.'@example.com'); $contacts[] = $contact; } $this->leadRepo->saveEntities($contacts); return $contacts; } private function getSegmentCountHtml(Crawler $crawler, int $id): string { $content = $crawler->filter('a.col-count')->filter('a[data-id="'.$id.'"]')->html(); return trim($content); } /** * @param array $parameter * * @return array */ private function callGetLeadCountAjaxRequest(array $parameter): array { $this->client->request(Request::METHOD_POST, '/s/ajax?action=lead:getLeadCount', $parameter); $clientResponse = $this->client->getResponse(); return [ 'content' => json_decode($clientResponse->getContent(), true), 'statusCode' => $this->client->getResponse()->getStatusCode(), ]; } public function testCloneSegment(): void { $segment = $this->saveSegment( 'Clone Segment', 'clonesegment', ); $this->em->clear(); $crawler = $this->client->request(Request::METHOD_POST, '/s/segments/clone/'.$segment->getId()); $this->assertTrue($this->client->getResponse()->isOk()); $form = $crawler->selectButton('leadlist_buttons_apply')->form(); $form['leadlist[alias]']->setValue('clonesegment2'); $this->client->submit($form); $this->assertTrue($this->client->getResponse()->isOk()); $this->client->submit($form); $rows = $this->listRepo->findAll(); $this->assertCount(2, $rows); $this->assertSame('clonesegment', $rows[0]->getAlias()); $this->assertSame('clonesegment2', $rows[1]->getAlias()); } public function testSegmentFilterIcon(): void { // Save segment. $filters = [ [ 'glue' => 'and', 'field' => 'email', 'object' => 'lead', 'type' => 'email', 'filter' => null, 'display' => null, 'operator' => '!empty', ], ]; $this->saveSegment('Lead List 1', 'lead-list-1', $filters); $this->saveSegment('Lead List 2', 'lead-list-2'); // Check segment count UI for no contacts. $crawler = $this->client->request(Request::METHOD_GET, '/s/segments'); $leadListsTableRows = $crawler->filterXPath("//table[@id='leadListTable']//tbody//tr"); $this->assertEquals(2, $leadListsTableRows->count()); $secondColumnOfLine = $leadListsTableRows->first()->filterXPath('//td[2]//div//i[@class="fa fa-fw ri-filter-2-fill"]')->count(); $this->assertEquals(1, $secondColumnOfLine); $secondColumnOfLine = $leadListsTableRows->eq(1)->filterXPath('//td[2]//div//i[@class="fa fa-fw ri-filter-2-fill"]')->count(); $this->assertEquals(0, $secondColumnOfLine); } public function testSegmentWarningIcon(): void { $segmentWithOldLastRebuildDate = $this->saveSegment('Lead List 1', 'lead-list-1'); $segmentWithFreshLastRebuildDate = $this->saveSegment('Lead List 2', 'lead-list-2'); $segmentWithOldLastRebuildDateUnpublished = $this->saveSegment('Lead List 3', 'lead-list-3'); $segmentWithOldLastRebuildDate->setLastBuiltDate(new \DateTime('-1 year')); $segmentWithFreshLastRebuildDate->setLastBuiltDate(new \DateTime('now')); $segmentWithOldLastRebuildDateUnpublished->isPublished(false); $this->em->persist($segmentWithOldLastRebuildDate); $this->em->persist($segmentWithFreshLastRebuildDate); $this->em->persist($segmentWithOldLastRebuildDateUnpublished); $this->em->flush(); // Check segment count UI for no contacts. $crawler = $this->client->request(Request::METHOD_GET, '/s/segments'); $leadListsTableRows = $crawler->filterXPath("//table[@id='leadListTable']//tbody//tr"); $this->assertEquals(3, $leadListsTableRows->count()); $secondColumnOfLine = $leadListsTableRows->first()->filterXPath('//td[2]//div//i[@class="fa text-danger ri-error-warning-line-circle"]')->count(); $this->assertEquals(1, $secondColumnOfLine); $secondColumnOfLine = $leadListsTableRows->eq(1)->filterXPath('//td[2]//div//i[@class="fa text-danger ri-error-warning-line-circle"]')->count(); $this->assertEquals(0, $secondColumnOfLine); $secondColumnOfLine = $leadListsTableRows->eq(2)->filterXPath('//td[2]//div//i[@class="fa text-danger ri-error-warning-line-circle"]')->count(); $this->assertEquals(0, $secondColumnOfLine); } /** * @dataProvider dateFieldProvider */ public function testWarningOnInvalidDateField(?string $filter, bool $shouldContainError, string $operator = '='): void { $segment = $this->saveSegment( 'Date Segment', 'ds', [ [ 'glue' => 'and', 'field' => 'date_added', 'object' => 'lead', 'type' => 'date', 'filter' => $filter, 'display' => null, 'operator' => $operator, ], ] ); $this->em->clear(); $crawler = $this->client->request(Request::METHOD_GET, '/s/segments/edit/'.$segment->getId()); $form = $crawler->selectButton('leadlist_buttons_apply')->form(); $this->client->submit($form); $this->assertTrue($this->client->getResponse()->isOk()); if ($shouldContainError) { $this->assertStringContainsString('Date field filter value "'.$filter.'" is invalid', $this->client->getResponse()->getContent()); } else { $this->assertStringNotContainsString('Date field filter value', $this->client->getResponse()->getContent()); } } /** * @return array> */ public static function dateFieldProvider(): array { return [ ['Today', true], ['birthday', false], ['2023-01-01 11:00', false], ['2023-01-01 11:00:00', false], ['2023-01-01', false], ['next week', false], [null, false], ['\b\d{4}-(10|11|12)-\d{2}\b', false, 'regexp'], ]; } }