Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ProjectBundle\Tests\Functional;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\ProjectBundle\Entity\Project;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* This class should simplify writing functional tests for project search functionality on various entities.
|
||||
*/
|
||||
abstract class AbstractProjectSearchTestCase extends MauticMysqlTestCase
|
||||
{
|
||||
/**
|
||||
* @param string[] $expectedEntities
|
||||
* @param string[] $unexpectedEntities
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('searchDataProvider')]
|
||||
abstract public function testProjectSearch(string $searchTerm, array $expectedEntities, array $unexpectedEntities): void;
|
||||
|
||||
/**
|
||||
* @return \Generator<string, array{searchTerm: string, expectedEntities: array<string>, unexpectedEntities: array<string>}>
|
||||
*/
|
||||
abstract public static function searchDataProvider(): \Generator;
|
||||
|
||||
/**
|
||||
* Test and assert API as well as UI.
|
||||
*
|
||||
* @param string[] $expectedEntities
|
||||
* @param string[] $unexpectedEntities
|
||||
* @param string[] $routes
|
||||
*/
|
||||
protected function searchAndAssert(string $searchTerm, array $expectedEntities, array $unexpectedEntities, array $routes): void
|
||||
{
|
||||
foreach ($routes as $route) {
|
||||
$crawler = $this->client->request(Request::METHOD_GET, $route.'?search='.urlencode($searchTerm));
|
||||
$this->assertResponseIsSuccessful();
|
||||
$isApiRequest = str_starts_with($route, '/api/');
|
||||
|
||||
$content = $isApiRequest ? $this->client->getResponse()->getContent() : $crawler->filter('body')->text();
|
||||
|
||||
foreach ($expectedEntities as $expectedEntity) {
|
||||
Assert::assertStringContainsString($expectedEntity, $content);
|
||||
}
|
||||
|
||||
foreach ($unexpectedEntities as $unexpectedEntity) {
|
||||
Assert::assertStringNotContainsString($unexpectedEntity, $content);
|
||||
}
|
||||
|
||||
if ($isApiRequest) {
|
||||
Assert::assertJson($content, 'API response should be of type JSON.');
|
||||
$this->assertProjectDataInApiResponse(json_decode($content, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function createProject(string $name): Project
|
||||
{
|
||||
$project = new Project();
|
||||
$project->setName($name);
|
||||
$this->em->persist($project);
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*/
|
||||
private function assertProjectDataInApiResponse(array $data): void
|
||||
{
|
||||
$projectData = $this->getProjectData($data);
|
||||
|
||||
if (null === $projectData) {
|
||||
return;
|
||||
}
|
||||
|
||||
Assert::assertEqualsCanonicalizing(['id', 'name'], array_keys(reset($projectData)),
|
||||
'Project data should contain only "id" and "name".');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $data
|
||||
*
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
private function getProjectData(array $data): ?array
|
||||
{
|
||||
foreach ($data as $key => $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('projects' === $key && $item) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$projectData = $this->getProjectData($item);
|
||||
|
||||
if (null !== $projectData) {
|
||||
return $projectData;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ProjectBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\ProjectBundle\Entity\Project;
|
||||
use Mautic\ProjectBundle\Model\ProjectModel;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
final class AjaxControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testCreatingProjectViaMultiselectInput(): void
|
||||
{
|
||||
$projectNames = [
|
||||
'Yellow Project',
|
||||
'Blue Project',
|
||||
'Red Project',
|
||||
];
|
||||
|
||||
/** @var ProjectModel $projectModel */
|
||||
$projectModel = self::getContainer()->get(ProjectModel::class);
|
||||
|
||||
$projects = array_map(
|
||||
static function (string $projectName) use ($projectModel) {
|
||||
$project = new Project();
|
||||
$project->setName($projectName);
|
||||
$projectModel->saveEntity($project);
|
||||
|
||||
return $project;
|
||||
},
|
||||
$projectNames
|
||||
);
|
||||
|
||||
$this->client->request(
|
||||
'POST',
|
||||
'/s/ajax?action=project:addProjects',
|
||||
[
|
||||
'newProjectNames' => json_encode(['Green Project']),
|
||||
'existingProjectIds' => json_encode([$projects[0]->getId(), $projects[1]->getId()]),
|
||||
]
|
||||
);
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$payload = json_decode($this->client->getResponse()->getContent(), true);
|
||||
|
||||
Assert::assertArrayHasKey('projects', $payload);
|
||||
|
||||
// The options are orderec alphabetically by name.
|
||||
Assert::assertSame(
|
||||
// The Blue Project is selected as it was sent as part of the existingProjectIds.
|
||||
'<option selected="selected" value="'.$projects[1]->getId().'">'.$projects[1]->getName().'</option>'.
|
||||
// The Green Project is selected as it was sent as part of the newProjectNames and should have next ID as it was created as 4th.
|
||||
'<option selected="selected" value="'.($projects[2]->getId() + 1).'">Green Project</option>'.
|
||||
// The Red Project is NOT selected as it was not sent in the AJAX request but it is listed as unselected option.
|
||||
'<option value="'.$projects[2]->getId().'">'.$projects[2]->getName().'</option>'.
|
||||
// The Yellow Project is selected as it was sent as part of the existingProjectIds.
|
||||
'<option selected="selected" value="'.$projects[0]->getId().'">'.$projects[0]->getName().'</option>',
|
||||
$payload['projects']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ProjectBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\EmailBundle\Entity\Email;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
use Mautic\ProjectBundle\Entity\Project;
|
||||
use Mautic\ProjectBundle\Model\ProjectModel;
|
||||
|
||||
final class ProjectAddEntityTest extends MauticMysqlTestCase
|
||||
{
|
||||
private Project $testProject;
|
||||
private Email $testEmail;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
/** @var ProjectModel $projectModel */
|
||||
$projectModel = self::getContainer()->get(ProjectModel::class);
|
||||
|
||||
// Create test project
|
||||
$this->testProject = new Project();
|
||||
$this->testProject->setName('Test Project for Add Entity');
|
||||
$this->testProject->setDescription('Test project for functional testing');
|
||||
$projectModel->saveEntity($this->testProject);
|
||||
|
||||
// Create test email
|
||||
/** @var EmailModel $emailModel */
|
||||
$emailModel = self::getContainer()->get(EmailModel::class);
|
||||
$this->testEmail = new Email();
|
||||
$this->testEmail->setName('Test Email for Project');
|
||||
$this->testEmail->setSubject('Test Email Subject');
|
||||
$this->testEmail->setEmailType('template');
|
||||
$this->testEmail->setTemplate('blank');
|
||||
$emailModel->saveEntity($this->testEmail);
|
||||
}
|
||||
|
||||
public function testSelectEntityTypeActionRendersModal(): void
|
||||
{
|
||||
$url = '/s/projects/selectEntityType/'.$this->testProject->getId();
|
||||
|
||||
$this->client->request('GET', $url);
|
||||
$response = $this->client->getResponse();
|
||||
$content = $response->getContent();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$this->assertStringContainsString('entityType=email', $content);
|
||||
}
|
||||
|
||||
public function testSelectEntityTypeActionNotFound(): void
|
||||
{
|
||||
$this->client->followRedirects(false);
|
||||
$this->client->request('GET', '/s/projects/selectEntityType/99999');
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertSame(404, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testAddEntityActionGetRequest(): void
|
||||
{
|
||||
$url = '/s/projects/addEntity/'.$this->testProject->getId().'?entityType=email';
|
||||
|
||||
$this->client->request('GET', $url);
|
||||
$response = $this->client->getResponse();
|
||||
$content = $response->getContent();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Should contain the form with proper structure
|
||||
$this->assertStringContainsString('name="project_add_entity"', $content);
|
||||
$this->assertStringContainsString('project_add_entity[entityType]', $content);
|
||||
$this->assertStringContainsString('project_add_entity[projectId]', $content);
|
||||
$this->assertStringContainsString('project_add_entity[entityIds][]', $content);
|
||||
}
|
||||
|
||||
public function testAddEntityActionPostWithValidData(): void
|
||||
{
|
||||
// Add email to project directly using the entity relationship
|
||||
$this->testEmail->addProject($this->testProject);
|
||||
$this->em->persist($this->testEmail);
|
||||
$this->em->flush();
|
||||
|
||||
// View project page to verify email was added
|
||||
$url = '/s/projects/view/'.$this->testProject->getId();
|
||||
$this->client->request('GET', $url);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$content = $response->getContent();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertStringContainsString($this->testProject->getName(), $content);
|
||||
$this->assertStringContainsString($this->testEmail->getName(), $content);
|
||||
}
|
||||
|
||||
public function testAddEntityActionPostWithEmptyData(): void
|
||||
{
|
||||
$url = '/s/projects/addEntity/'.$this->testProject->getId().'?entityType=email';
|
||||
|
||||
// Get the form
|
||||
$crawler = $this->client->request('GET', $url);
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Submit form with no entities selected
|
||||
$form = $crawler->filter('form')->first()->form();
|
||||
$this->client->submit($form);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testAddEntityActionPostWithCancelledForm(): void
|
||||
{
|
||||
$url = '/s/projects/addEntity/'.$this->testProject->getId().'?entityType=email';
|
||||
|
||||
// Get the form
|
||||
$crawler = $this->client->request('GET', $url);
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Submit form normally (simulating any button press)
|
||||
$form = $crawler->filter('form')->first()->form();
|
||||
$this->client->submit($form);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
|
||||
public function testAddEntityActionWithInvalidEntityType(): void
|
||||
{
|
||||
$url = '/s/projects/addEntity/'.$this->testProject->getId().'?entityType=invalid_type';
|
||||
|
||||
// Get request with invalid entity type should redirect with error
|
||||
$this->client->followRedirects();
|
||||
$this->client->request('GET', $url);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Check for error message in flashes
|
||||
$content = $response->getContent();
|
||||
$this->assertStringContainsString('Invalid entity type', $content);
|
||||
}
|
||||
|
||||
public function testAddEntityActionNotFound(): void
|
||||
{
|
||||
$this->client->followRedirects(false);
|
||||
$this->client->request('GET', '/s/projects/addEntity/99999?entityType=email');
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertSame(404, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testAddEntityActionWithoutPermission(): void
|
||||
{
|
||||
$user = $this->createAndLoginUser();
|
||||
|
||||
$url = '/s/projects/addEntity/'.$this->testProject->getId().'?entityType=email';
|
||||
$this->client->request('GET', $url);
|
||||
|
||||
$this->assertResponseStatusCodeSame(403);
|
||||
}
|
||||
|
||||
private function createAndLoginUser(): \Mautic\UserBundle\Entity\User
|
||||
{
|
||||
// Create non-admin role
|
||||
$role = new \Mautic\UserBundle\Entity\Role();
|
||||
$role->setName('Test Role');
|
||||
$role->setIsAdmin(false);
|
||||
$this->em->persist($role);
|
||||
|
||||
// Create non-admin user
|
||||
$user = new \Mautic\UserBundle\Entity\User();
|
||||
$user->setFirstName('Test');
|
||||
$user->setLastName('User');
|
||||
$user->setUsername('testuser');
|
||||
$user->setEmail('test@example.com');
|
||||
$hasher = self::getContainer()->get('security.password_hasher_factory')->getPasswordHasher($user);
|
||||
$user->setPassword($hasher->hash('password'));
|
||||
$user->setRole($role);
|
||||
$this->em->persist($user);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$this->loginUser($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ProjectBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\LeadBundle\Entity\LeadList;
|
||||
use Mautic\ProjectBundle\Entity\Project;
|
||||
use Mautic\ProjectBundle\Entity\ProjectRepository;
|
||||
use Mautic\ProjectBundle\Model\ProjectModel;
|
||||
use Mautic\UserBundle\Entity\Role;
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
|
||||
final class ProjectControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
public const USERNAME = 'johny';
|
||||
|
||||
private ProjectRepository $projectRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$projects = [
|
||||
'project1',
|
||||
'project2',
|
||||
'project3',
|
||||
'project4',
|
||||
];
|
||||
|
||||
/** @var ProjectModel $projectModel */
|
||||
$projectModel = self::getContainer()->get(ProjectModel::class);
|
||||
$this->projectRepository = $projectModel->getRepository();
|
||||
|
||||
foreach ($projects as $projectName) {
|
||||
$project = new Project();
|
||||
$project->setName($projectName);
|
||||
$projectModel->saveEntity($project);
|
||||
}
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('indexUrlsProvider')]
|
||||
public function testIndexActionDisplaysProjects(string $url): void
|
||||
{
|
||||
$this->client->request('GET', $url);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$clientResponseContent = $clientResponse->getContent();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertStringContainsString('project1', $clientResponseContent, 'The return must contain project1');
|
||||
$this->assertStringContainsString('project2', $clientResponseContent, 'The return must contain project2');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array<int, string>>
|
||||
*/
|
||||
public static function indexUrlsProvider(): iterable
|
||||
{
|
||||
yield 'non-existent page nuber' => ['/s/projects/999'];
|
||||
yield 'main index page with no number (meaning page=1)' => ['/s/projects'];
|
||||
}
|
||||
|
||||
public function testIndexActionWhenFiltered(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/projects?search=project1');
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$clientResponseContent = $clientResponse->getContent();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertStringContainsString('project1', $clientResponseContent, 'The return must contain project1');
|
||||
$this->assertStringNotContainsString('project2', $clientResponseContent, 'The return must not contain project2');
|
||||
}
|
||||
|
||||
public function testProjectDeletion(): void
|
||||
{
|
||||
$project = $this->projectRepository->findOneBy([]);
|
||||
$segment = new LeadList();
|
||||
$segment->setName('Test segment');
|
||||
$segment->setPublicName('Test segment');
|
||||
$segment->setAlias('test-segment');
|
||||
$segment->addProject($project);
|
||||
|
||||
$this->em->persist($segment);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$projectId = $project->getId();
|
||||
|
||||
$this->client->request('POST', '/s/projects/delete/'.$projectId);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSame($this->projectRepository->find($projectId), null, 'Assert that project is deleted');
|
||||
$this->assertCount(0, $this->em->find(LeadList::class, $segment->getId())->getProjects());
|
||||
}
|
||||
|
||||
public function testViewAction(): void
|
||||
{
|
||||
$project = $this->projectRepository->findOneBy([]);
|
||||
|
||||
$this->client->request('GET', '/s/projects/view/'.$project->getId());
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$clientResponseContent = $clientResponse->getContent();
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertStringContainsString($project->getName(), $clientResponseContent, 'The return must contain project');
|
||||
}
|
||||
|
||||
public function testViewActionNotFound(): void
|
||||
{
|
||||
$this->client->followRedirects(false);
|
||||
$this->client->request('GET', '/s/projects/view/99999');
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertTrue($clientResponse->isRedirection(), 'Must be redirect response.');
|
||||
}
|
||||
|
||||
public function testEditAction(): void
|
||||
{
|
||||
$projectName = 'Test project';
|
||||
$project = $this->projectRepository->findOneBy([]);
|
||||
$crawler = $this->client->request('GET', '/s/projects/edit/'.$project->getId());
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$clientResponseContent = $clientResponse->getContent();
|
||||
$this->assertTrue($clientResponse->isOk(), 'Return code must be 200.');
|
||||
$this->assertStringContainsString('Edit project: '.$project->getName(), $clientResponseContent, 'The return must contain \'Edit project\' text');
|
||||
|
||||
$form = $crawler->selectButton('Save & Close')->form();
|
||||
$form['project_entity[name]']->setValue($projectName);
|
||||
$this->client->submit($form);
|
||||
|
||||
$this->assertSame(1, $this->projectRepository->count(['name' => $projectName]));
|
||||
}
|
||||
|
||||
public function testEditActionNotFound(): void
|
||||
{
|
||||
$this->client->followRedirects(false);
|
||||
$this->client->request('GET', '/s/projects/edit/99999');
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertTrue($clientResponse->isRedirection(), 'Must be redirect response.');
|
||||
}
|
||||
|
||||
public function testNewAction(): void
|
||||
{
|
||||
$projectName = 'Test project';
|
||||
$crawler = $this->client->request('GET', '/s/projects/new');
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$form = $crawler->selectButton('Save')->form();
|
||||
$form['project_entity[name]']->setValue($projectName);
|
||||
$this->client->submit($form);
|
||||
|
||||
$this->assertSame(1, $this->projectRepository->count(['name' => $projectName]));
|
||||
}
|
||||
|
||||
public function testBatchDeleteAction(): void
|
||||
{
|
||||
$projects = $this->projectRepository->findAll();
|
||||
$projectsId = array_map(function (Project $project) {
|
||||
return $project->getId();
|
||||
}, $projects);
|
||||
$this->client->request('POST', '/s/projects/batchDelete?ids='.json_encode($projectsId));
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertEmpty($this->projectRepository->count([]), 'All projects must be deleted.');
|
||||
}
|
||||
|
||||
public function testEmptyProjectShouldThrowValidationError(): void
|
||||
{
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/projects/new');
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$buttonCrawler = $crawler->selectButton('Save & Close');
|
||||
$form = $buttonCrawler->form();
|
||||
$form->setValues(['project_entity[name]' => '']);
|
||||
$this->client->submit($form);
|
||||
$this->assertResponseIsSuccessful();
|
||||
Assert::assertStringContainsString('A name is required.', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testEditProjectWithNoPermission(): void
|
||||
{
|
||||
$this->createAndLoginUser();
|
||||
$project = $this->projectRepository->findOneBy([]);
|
||||
$this->client->request(Request::METHOD_GET, '/s/projects/edit/'.$project->getId());
|
||||
$this->assertResponseStatusCodeSame(403, (string) $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
private function createAndLoginUser(): User
|
||||
{
|
||||
// Create non-admin role
|
||||
$role = $this->createRole();
|
||||
// Create non-admin user
|
||||
$user = $this->createUser($role);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->detach($role);
|
||||
|
||||
$this->loginUser($user);
|
||||
// $this->client->setServerParameter('PHP_AUTH_USER', self::USERNAME);
|
||||
// $this->client->setServerParameter('PHP_AUTH_PW', 'mautic');
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function createRole(bool $isAdmin = false): Role
|
||||
{
|
||||
$role = new Role();
|
||||
$role->setName('Role');
|
||||
$role->setIsAdmin($isAdmin);
|
||||
|
||||
$this->em->persist($role);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
private function createUser(Role $role): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setFirstName('Jhon');
|
||||
$user->setLastName('Doe');
|
||||
$user->setUsername(self::USERNAME);
|
||||
$user->setEmail('john.doe@email.com');
|
||||
$hasher = self::getContainer()->get('security.password_hasher_factory')->getPasswordHasher($user);
|
||||
\assert($hasher instanceof PasswordHasherInterface);
|
||||
$user->setPassword($hasher->hash('mautic'));
|
||||
$user->setRole($role);
|
||||
|
||||
$this->em->persist($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user