Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions EventListener/FormSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace MauticPlugin\CustomObjectsBundle\EventListener;

use Mautic\FormBundle\Crate\ObjectCrate;
use Mautic\FormBundle\Event\FieldCollectEvent;
use Mautic\FormBundle\Event\ObjectCollectEvent;
use Mautic\FormBundle\FormEvents;
use Mautic\FormBundle\Crate\FieldCrate;
use MauticPlugin\CustomObjectsBundle\Entity\CustomField;
use MauticPlugin\CustomObjectsBundle\Exception\NotFoundException;
use MauticPlugin\CustomObjectsBundle\Model\CustomItemModel;
use MauticPlugin\CustomObjectsBundle\Model\CustomObjectModel;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class FormSubscriber implements EventSubscriberInterface
{
private CustomObjectModel $customObjectModel;
private CustomItemModel $customItemModel;

public function __construct(
CustomObjectModel $customObjectModel,
CustomItemModel $customItemModel
) {
$this->customObjectModel = $customObjectModel;
$this->customItemModel = $customItemModel;
}

public static function getSubscribedEvents(): array
{
return [
FormEvents::ON_OBJECT_COLLECT => ['onObjectCollect', 0],
FormEvents::ON_FIELD_COLLECT => ['onFieldCollect', 0],
];
}

public function onObjectCollect(ObjectCollectEvent $event): void
{
foreach ($this->customObjectModel->fetchEntities() as $entity) {
$event->appendObject(new ObjectCrate($entity->getAlias(), $entity->getName()));
}
}

public function onFieldCollect(FieldCollectEvent $event): void
{
try {
$object = $this->customObjectModel->fetchEntityByAlias($event->getObject());
} catch (NotFoundException $e) {
// Do nothing if the custom object doesn't exist.
return;
}

$items = $this->customItemModel->fetchCustomItemsForObject($object);

if (count($items) > 0) {
foreach ($items as $item) {
$list[$item->getId()] = $item->getName();
}

$event->appendField(new FieldCrate($object->getAlias(), 'Name', 'text', ['list' => $list ?? []]));
}

foreach ($object->getCustomFields()->getValues() as $field) {
$list = $this->getCustomFieldValues($field, $items);
$event->appendField(new FieldCrate($field->getAlias(), $field->getName(), $field->getType(), ['list' => $list]));
}
}

/**
* @return array<string, mixed>
*/
private function getCustomFieldValues(CustomField $field, array $items): array
{
$list = [];

array_walk(
$items,
function ($item) use ($field, &$list) {
$itemWithCustomFieldValues = $this->customItemModel->populateCustomFields($item);
$itemCustomFieldsValues = $itemWithCustomFieldValues->getCustomFieldValues();

foreach ($itemCustomFieldsValues as $customFieldValue) {
if ($field->getAlias() === $customFieldValue->getCustomField()->getAlias()) {
$list[$item->getId()] = $customFieldValue->getValue();
}
}
}
);

return $list ?? [];
}
}
104 changes: 98 additions & 6 deletions EventListener/ReportSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\Query\QueryBuilder;
use Mautic\FormBundle\Model\FieldModel;
use Mautic\LeadBundle\Model\CompanyReportData;
use Mautic\LeadBundle\Report\FieldsBuilder;
use Mautic\ReportBundle\Event\ColumnCollectEvent;
use Mautic\ReportBundle\Event\ReportBuilderEvent;
use Mautic\ReportBundle\Event\ReportGeneratorEvent;
use Mautic\ReportBundle\Helper\ReportHelper;
Expand All @@ -18,7 +20,7 @@
use MauticPlugin\CustomObjectsBundle\Report\ReportColumnsBuilder;
use MauticPlugin\CustomObjectsBundle\Repository\CustomObjectRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class ReportSubscriber implements EventSubscriberInterface
{
Expand Down Expand Up @@ -65,13 +67,22 @@ class ReportSubscriber implements EventSubscriberInterface
*/
private $translator;

public function __construct(CustomObjectRepository $customObjectRepository, FieldsBuilder $fieldsBuilder, CompanyReportData $companyReportData, ReportHelper $reportHelper, TranslatorInterface $translator)
{
private FieldModel $fieldModel;

public function __construct(
CustomObjectRepository $customObjectRepository,
FieldsBuilder $fieldsBuilder,
CompanyReportData $companyReportData,
ReportHelper $reportHelper,
TranslatorInterface $translator,
FieldModel $fieldModel
) {
$this->customObjectRepository = $customObjectRepository;
$this->fieldsBuilder = $fieldsBuilder;
$this->companyReportData = $companyReportData;
$this->reportHelper = $reportHelper;
$this->translator = $translator;
$this->fieldModel = $fieldModel;
}

private function getCustomObjects(): ArrayCollection
Expand Down Expand Up @@ -120,8 +131,12 @@ private function sortCustomObjects(ArrayCollection $customObjects): ArrayCollect
public static function getSubscribedEvents(): array
{
return [
ReportEvents::REPORT_ON_BUILD => ['onReportBuilder', 0],
ReportEvents::REPORT_ON_GENERATE => ['onReportGenerate', 0],
ReportEvents::REPORT_ON_BUILD => ['onReportBuilder', 0],
ReportEvents::REPORT_ON_COLUMN_COLLECT => ['onReportColumnCollect', 0],
ReportEvents::REPORT_ON_GENERATE => [
['onReportGenerate', 0],
['onFormResultReportGenerate', -1],
],
];
}

Expand Down Expand Up @@ -217,6 +232,36 @@ public function onReportBuilder(ReportBuilderEvent $event): void
}
}

public function onReportColumnCollect(ColumnCollectEvent $event): void
{
$object = $event->getObject();

if (!$this->customObjectRepository->checkAliasExists($object)) {
return;
}

$customObject = $this->customObjectRepository->findOneBy(['alias' => $object]);
$properties = $event->getProperties();
$customItemTableAlias = static::CUSTOM_ITEM_TABLE_ALIAS.'_'.$customObject->getId();
$customObjectColumns = $this->getCustomObjectColumns($customObject, $customItemTableAlias.'.');

array_walk(
$customObjectColumns,
function (&$item, $index) use ($customObject, $properties) {
$item['idCustomObject'] = $customObject->getId();
$item = array_merge($item, $properties);
}
);

$columns = array_merge(
$columns ?? [],
$this->addPrefixToColumnLabel($customObjectColumns, $customObject->getNameSingular()),
$parentCustomObjectColumns ?? []
);

$event->addColumns($columns);
}

private function getLeadColumns(): array
{
return $this->fieldsBuilder->getLeadFieldsColumns(static::LEADS_TABLE_ALIAS.'.');
Expand All @@ -226,7 +271,7 @@ private function getCompanyColumns(): array
{
$companyColumns = $this->companyReportData->getCompanyData();
// We don't need this column because we fetch company/lead relationships via custom objects
unset($companyColumns['companies_lead.is_primary']);
unset($companyColumns['companies_lead.is_primary'], $companyColumns['companies_lead.date_added']);

return $companyColumns;
}
Expand Down Expand Up @@ -364,4 +409,51 @@ public function onReportGenerate(ReportGeneratorEvent $event): void
$parentCustomObjectReportColumnsBuilder->setFilterColumnsCallback([$event, 'usesColumn']);
$parentCustomObjectReportColumnsBuilder->joinReportColumns($queryBuilder, static::PARENT_CUSTOM_ITEM_TABLE_ALIAS);
}

public function onFormResultReportGenerate(ReportGeneratorEvent $event): void
{
$contextFormResult = 'form.results';
$prefixFormResultTable = 'fr';
$context = $event->getContext();

if (!str_starts_with($context, $contextFormResult)) {
return;
}

$addedCustomObjects = [];
$columns = array_filter(
$event->getOptions()['columns'],
function ($elem) use (&$addedCustomObjects) {
// left one column for each custom object
$addToColumnList = key_exists('idCustomObject', $elem) && !in_array($elem['idCustomObject'], $addedCustomObjects);
$addedCustomObjects[] = $elem['idCustomObject'] ?? '';

return $addToColumnList;
}
);

$queryBuilder = $event->getQueryBuilder();

foreach ($columns as $column) {
$customObject = $this->customObjectRepository->find($column['idCustomObject']);
$field = $this->fieldModel->getEntity($column['idFormField']);

$customItemTableAlias = static::CUSTOM_ITEM_TABLE_ALIAS.'_'.$customObject->getId();

$colCustomObjectName = sprintf('`%s`.`id`', $customItemTableAlias);
$colMappedField = sprintf('`%s`.`%s`', $prefixFormResultTable, $field->getAlias());
$colCustomItemObjectId = sprintf('`%s`.`custom_object_id`', $customItemTableAlias);
$colCustomObjectId = sprintf('%s', $customObject->getId());

$joinCondition = $field->hasChoices()
? "FIND_IN_SET({$colCustomObjectName}, REPLACE({$colMappedField}, ' ', '')) > 0 AND {$colCustomItemObjectId} = {$colCustomObjectId}"
: "{$colMappedField} = {$colCustomObjectName} AND {$colCustomItemObjectId} = {$colCustomObjectId}";
$queryBuilder->leftJoin($prefixFormResultTable, CustomItem::TABLE_NAME, $customItemTableAlias, $joinCondition);

$addedCustomObjects[] = $column['idCustomObject'];
$reportColumnsBuilder = new ReportColumnsBuilder($customObject);
$reportColumnsBuilder->setFilterColumnsCallback([$event, 'usesColumn']);
$reportColumnsBuilder->joinReportColumns($queryBuilder, $customItemTableAlias);
}
}
}
35 changes: 35 additions & 0 deletions Model/CustomItemModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\DBAL\Query\QueryBuilder as DbalQueryBuilder;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Mautic\CoreBundle\Doctrine\Helper\FulltextKeyword;
use Mautic\CoreBundle\Entity\CommonRepository;
use Mautic\CoreBundle\Helper\DateTimeHelper;
Expand Down Expand Up @@ -222,6 +223,40 @@ public function fetchEntity(int $id): CustomItem
return $this->populateCustomFields($customItem);
}

/**
* @return array<string, mixed>
*/
public function fetchCustomItemsForObject(CustomObject $customObject): array
{
return $this->fetchEntities([
'filter' => [
'force' => [
[
'column' => CustomItem::TABLE_ALIAS.'.customObject',
'value' => $customObject->getId(),
'expr' => 'eq',
],
[
'column' => CustomItem::TABLE_ALIAS.'.isPublished',
'value' => true,
'expr' => 'eq',
],
],
],
'ignore_paginator' => true,
]);
}

/**
* @param array<string, mixed> $args
*
* @return array<string, mixed>
*/
public function fetchEntities(array $args = []): Paginator|array
{
return parent::getEntities($args);
}

/**
* Returns a list of entities (ORM).
*
Expand Down
16 changes: 12 additions & 4 deletions Tests/Unit/EventListener/ReportSubscriberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use Doctrine\DBAL\Query\QueryBuilder;
use Mautic\FormBundle\Model\FieldModel;
use Mautic\LeadBundle\Model\CompanyReportData;
use Mautic\LeadBundle\Provider\FilterOperatorProviderInterface;
use Mautic\LeadBundle\Report\FieldsBuilder;
Expand All @@ -25,6 +26,7 @@
use MauticPlugin\CustomObjectsBundle\Repository\CustomObjectRepository;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Translation\TranslatorInterface;

class ReportSubscriberTest extends TestCase
Expand Down Expand Up @@ -59,6 +61,11 @@ class ReportSubscriberTest extends TestCase
*/
private $translatorInterface;

/**
* @var MockObject|FieldModel
*/
private $fieldModelMock;

/**
* @var MockObject|FilterOperatorProviderInterface
*/
Expand Down Expand Up @@ -96,9 +103,10 @@ protected function setUp(): void
$this->customObjectRepository = $this->createMock(CustomObjectRepository::class);
$this->fieldsBuilder = $this->createMock(FieldsBuilder::class);
$this->companyReportData = $this->createMock(CompanyReportData::class);
$this->reportHelper = new ReportHelper();
$this->translatorInterface = $this->createMock(TranslatorInterface::class);
$this->reportSubscriber = new ReportSubscriber($this->customObjectRepository, $this->fieldsBuilder, $this->companyReportData, $this->reportHelper, $this->translatorInterface);
$this->reportHelper = new ReportHelper($this->createMock(EventDispatcherInterface::class));
$this->translatorInterface = $this->createMock(\Symfony\Contracts\Translation\TranslatorInterface::class);
$this->leadFieldModelMock = $this->createMock(FieldModel::class);
$this->reportSubscriber = new ReportSubscriber($this->customObjectRepository, $this->fieldsBuilder, $this->companyReportData, $this->reportHelper, $this->translatorInterface, $this->leadFieldModelMock);
$this->reportBuilderEvent = $this->createMock(ReportBuilderEvent::class);
$this->filterOperatorProviderInterface = $this->createMock(FilterOperatorProviderInterface::class);
$this->csvHelper = $this->createMock(CsvHelper::class);
Expand Down Expand Up @@ -186,7 +194,7 @@ public function testThatEventListenersAreSpecified(): void
$this->assertArrayHasKey(ReportEvents::REPORT_ON_BUILD, $events);
$this->assertArrayHasKey(ReportEvents::REPORT_ON_GENERATE, $events);
$this->assertContains('onReportBuilder', $events[ReportEvents::REPORT_ON_BUILD]);
$this->assertContains('onReportGenerate', $events[ReportEvents::REPORT_ON_GENERATE]);
$this->assertContains('onReportGenerate', $events[ReportEvents::REPORT_ON_GENERATE][0]);
}

public function testOnReportBuilderMethod(): void
Expand Down