306 lines
9.8 KiB
PHP
Executable File
306 lines
9.8 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Mautic\ProjectBundle\Service;
|
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
|
use Mautic\CoreBundle\Factory\ModelFactory;
|
|
use Mautic\CoreBundle\Model\FormModel;
|
|
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
|
use Mautic\ProjectBundle\DTO\EntityTypeConfig;
|
|
use Mautic\ProjectBundle\Entity\Project;
|
|
use Mautic\ProjectBundle\Event\EntityTypeModelMappingEvent;
|
|
use Mautic\ProjectBundle\Event\EntityTypeNormalizationEvent;
|
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
|
|
final class ProjectEntityLoaderService
|
|
{
|
|
/** @var array<string, EntityTypeConfig> */
|
|
private array $entityTypesCache = [];
|
|
|
|
public function __construct(
|
|
private EntityManagerInterface $em,
|
|
private TranslatorInterface $translator,
|
|
private ModelFactory $modelFactory,
|
|
private CorePermissions $security,
|
|
private EventDispatcherInterface $eventDispatcher,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @param array<string, EntityTypeConfig> $entityTypes
|
|
*
|
|
* @return array<string, array<string, mixed>>
|
|
*/
|
|
public function getProjectEntities(Project $project, array $entityTypes): array
|
|
{
|
|
$results = [];
|
|
|
|
foreach ($entityTypes as $entityType => $config) {
|
|
$repository = $config->model->getRepository();
|
|
|
|
$entities = $repository->createQueryBuilder('e')
|
|
->join('e.projects', 'p')
|
|
->where('p.id = :projectId')
|
|
->setParameter('projectId', $project->getId())
|
|
->orderBy('e.dateModified', 'DESC')
|
|
->getQuery()
|
|
->getResult();
|
|
|
|
$results[$entityType] = [
|
|
'label' => $config->label,
|
|
'entities' => $entities,
|
|
'count' => count($entities),
|
|
];
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get entity types filtered by user view permissions.
|
|
*
|
|
* @return array<string, EntityTypeConfig>
|
|
*/
|
|
public function getEntityTypesWithViewPermissions(): array
|
|
{
|
|
return $this->filterEntityTypesByPermission('view');
|
|
}
|
|
|
|
/**
|
|
* Get entity types filtered by user edit permissions.
|
|
*
|
|
* @return array<string, EntityTypeConfig>
|
|
*/
|
|
public function getEntityTypesWithEditPermissions(): array
|
|
{
|
|
return $this->filterEntityTypesByPermission('edit');
|
|
}
|
|
|
|
/**
|
|
* Get lookup results for entity type (used by EntityLookupType).
|
|
*
|
|
* @return array<int|string, string>
|
|
*/
|
|
public function getLookupResults(string $entityType, string $filter = '', int $limit = 10, int $start = 0, ?int $projectId = null): array
|
|
{
|
|
$entityTypes = $this->getEntityTypes();
|
|
|
|
if (!isset($entityTypes[$entityType])) {
|
|
return [];
|
|
}
|
|
|
|
$entityConfig = $entityTypes[$entityType];
|
|
|
|
// Check permission before proceeding
|
|
if (!$this->hasViewPermissionForEntityType($entityConfig)) {
|
|
return [];
|
|
}
|
|
$repository = $entityConfig->model->getRepository();
|
|
|
|
// Get the label column for this entity type
|
|
$labelColumn = $this->getEntityLabelColumn($entityType);
|
|
|
|
$qb = $repository->createQueryBuilder('e')
|
|
->select('e.id, e.'.$labelColumn.' as name')
|
|
->setFirstResult($start);
|
|
|
|
if ($limit > 0) {
|
|
$qb->setMaxResults($limit);
|
|
}
|
|
|
|
// Exclude entities already assigned to the specific project if projectId is provided
|
|
if ($projectId) {
|
|
// Use LEFT JOIN to find entities that are NOT in the specific project
|
|
$qb->leftJoin('e.projects', 'p', 'WITH', 'p.id = :projectId')
|
|
->andWhere('p.id IS NULL')
|
|
->setParameter('projectId', $projectId);
|
|
} else {
|
|
// Exclude entities already assigned to any project using QueryBuilder
|
|
// Use LEFT JOIN to find entities that are NOT in any project
|
|
$qb->leftJoin('e.projects', 'p')
|
|
->andWhere('p.id IS NULL');
|
|
}
|
|
|
|
// Add filter if provided
|
|
if (!empty($filter)) {
|
|
$qb->andWhere('e.'.$labelColumn.' LIKE :filter')
|
|
->setParameter('filter', '%'.$filter.'%');
|
|
}
|
|
|
|
$results = $qb->getQuery()->getArrayResult();
|
|
|
|
// Format results for ProjectModel (id => name associative array)
|
|
$choices = [];
|
|
foreach ($results as $result) {
|
|
$choices[$result['id']] = $result['name'];
|
|
}
|
|
|
|
return $choices;
|
|
}
|
|
|
|
/**
|
|
* Check if user has view permission for entity type config.
|
|
*/
|
|
public function hasViewPermissionForEntityType(EntityTypeConfig $config): bool
|
|
{
|
|
$permissionBase = $config->model->getPermissionBase();
|
|
$permissions = [
|
|
$permissionBase.':viewown',
|
|
$permissionBase.':viewother',
|
|
];
|
|
|
|
return $this->security->isGranted($permissions, 'MATCH_ONE');
|
|
}
|
|
|
|
/**
|
|
* Check if user has edit permission for entity type config.
|
|
*/
|
|
public function hasEditPermissionForEntityType(EntityTypeConfig $config): bool
|
|
{
|
|
$permissionBase = $config->model->getPermissionBase();
|
|
$permissions = [
|
|
$permissionBase.':editown',
|
|
$permissionBase.':editother',
|
|
];
|
|
|
|
return $this->security->isGranted($permissions, 'MATCH_ONE');
|
|
}
|
|
|
|
/**
|
|
* @return array<string, EntityTypeConfig>
|
|
*/
|
|
private function getEntityTypes(): array
|
|
{
|
|
if (!empty($this->entityTypesCache)) {
|
|
return $this->entityTypesCache;
|
|
}
|
|
|
|
$allMetadata = $this->em->getMetadataFactory()->getAllMetadata();
|
|
|
|
foreach ($allMetadata as $metadata) {
|
|
$entityClass = $metadata->getName();
|
|
|
|
foreach ($metadata->getAssociationMappings() as $association) {
|
|
if (
|
|
ClassMetadataInfo::MANY_TO_MANY === $association['type']
|
|
&& Project::class === $association['targetEntity']
|
|
) {
|
|
$shortName = $metadata->getReflectionClass()->getShortName();
|
|
$entityType = $this->normalizeEntityType(strtolower($shortName));
|
|
|
|
$this->entityTypesCache[$entityType] = new EntityTypeConfig(
|
|
entityClass: $entityClass,
|
|
label: $this->getEntityLabel($entityType),
|
|
model: $this->findModelForEntityType($entityType),
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort entity types alphabetically by label
|
|
uasort($this->entityTypesCache, fn (EntityTypeConfig $a, EntityTypeConfig $b) => strcasecmp($a->label, $b->label));
|
|
|
|
return $this->entityTypesCache;
|
|
}
|
|
|
|
/**
|
|
* Filter entity types by permission type.
|
|
*
|
|
* @return array<string, EntityTypeConfig>
|
|
*/
|
|
private function filterEntityTypesByPermission(string $permissionType): array
|
|
{
|
|
$allEntityTypes = $this->getEntityTypes();
|
|
$allowedEntityTypes = [];
|
|
|
|
foreach ($allEntityTypes as $entityType => $config) {
|
|
$hasPermission = 'view' === $permissionType
|
|
? $this->hasViewPermissionForEntityType($config)
|
|
: $this->hasEditPermissionForEntityType($config);
|
|
|
|
if ($hasPermission) {
|
|
$allowedEntityTypes[$entityType] = $config;
|
|
}
|
|
}
|
|
|
|
return $allowedEntityTypes;
|
|
}
|
|
|
|
/**
|
|
* Get the label column name for an entity type.
|
|
*/
|
|
private function getEntityLabelColumn(string $entityType): string
|
|
{
|
|
return match ($entityType) {
|
|
'asset', 'page' => 'title',
|
|
default => 'name',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Normalize entity type names for consistent usage.
|
|
*/
|
|
private function normalizeEntityType(string $entityType): string
|
|
{
|
|
// Create event with default mappings
|
|
$event = new EntityTypeNormalizationEvent([
|
|
'leadlist' => 'segment',
|
|
'lead' => 'contact',
|
|
'dynamiccontent' => 'dynamicContent',
|
|
'trigger' => 'pointtrigger',
|
|
]);
|
|
|
|
// Dispatch event to allow bundles to add their mappings
|
|
$this->eventDispatcher->dispatch($event);
|
|
|
|
// Return normalized type or original if no mapping exists
|
|
return $event->getNormalizedType($entityType);
|
|
}
|
|
|
|
private function getEntityLabel(string $entityType): string
|
|
{
|
|
// Create the translation key
|
|
$translationKeyString = "mautic.{$entityType}.{$entityType}";
|
|
|
|
// Get the translation
|
|
$translated = $this->translator->trans($translationKeyString);
|
|
|
|
// If translation doesn't exist (returns the key itself), return capitalized entity type
|
|
if ($translated === $translationKeyString) {
|
|
return ucfirst($entityType);
|
|
}
|
|
|
|
// Return the actual translation
|
|
return $translated;
|
|
}
|
|
|
|
private function findModelForEntityType(string $entityType): FormModel
|
|
{
|
|
// Create event with default model key mappings
|
|
$event = new EntityTypeModelMappingEvent([
|
|
'segment' => 'lead.list',
|
|
'message' => 'channel.message',
|
|
'company' => 'lead.company',
|
|
'dynamicContent' => 'dynamicContent.dynamicContent',
|
|
'pointtrigger' => 'point.trigger',
|
|
]);
|
|
|
|
// Dispatch event to allow bundles to add their model mappings
|
|
$this->eventDispatcher->dispatch($event);
|
|
|
|
// Get model key from event or use entity type as fallback
|
|
$modelKey = $event->getModelKey($entityType);
|
|
|
|
$model = $this->modelFactory->getModel($modelKey);
|
|
\assert($model instanceof FormModel);
|
|
|
|
return $model;
|
|
}
|
|
}
|