em = $em; $this->passwordEncoder = $passwordEncoder; $this->validator = $validator; $this->logger = $logger; $this->userRepository = $userRepository; parent::__construct('chill:main:import-users'); } protected function appendUserToFile(User $user) { $this->output->insertOne([ $user->getEmail(), $user->getUsername(), $user->getId(), ]); } protected function concatenateViolations(ConstraintViolationListInterface $list) { $str = []; foreach ($list as $e) { /* @var $e \Symfony\Component\Validator\ConstraintViolationInterface */ $str[] = $e->getMessage(); } return implode(';', $str); } protected function configure() { $this ->setDescription('Import users from csv file') ->setHelp("Import users from a csv file. Users are added to centers contained in the file. Headers are used to detect columns. Adding to multiple centers can be done by using a `grouping centers` file, which will group multiple centers into a signle alias, used in 'centers' column.") ->addArgument('csvfile', InputArgument::REQUIRED, 'Path to the csv file. Columns are: `username`, `email`, `center` (can contain alias), `permission group`') ->addOption('grouping-centers', null, InputOption::VALUE_OPTIONAL, 'Path to a csv file to aggregate multiple centers into a single alias') ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not commit the changes') ->addOption('csv-dump', null, InputOption::VALUE_REQUIRED, 'A path to dump a summary of the created file'); } protected function createOrGetGroupCenter(Center $center, PermissionsGroup $pg): GroupCenter { if (array_key_exists($center->getId(), $this->groupCenters)) { if (array_key_exists($pg->getId(), $this->groupCenters[$center->getId()])) { return $this->groupCenters[$center->getId()][$pg->getId()]; } } $repository = $this->em->getRepository(GroupCenter::class); $groupCenter = $repository->findOneBy([ 'center' => $center, 'permissionsGroup' => $pg, ]); if (null === $groupCenter) { $groupCenter = new GroupCenter(); $groupCenter ->setCenter($center) ->setPermissionsGroup($pg); $this->em->persist($groupCenter); } $this->groupCenters[$center->getId()][$pg->getId()] = $groupCenter; return $groupCenter; } protected function createUser($offset, $data) { $user = new User(); $user ->setEmail(trim($data['email'])) ->setUsername(trim($data['username'])) ->setEnabled(true) ->setPassword($this->passwordEncoder->encodePassword( $user, bin2hex(random_bytes(32)) )); $errors = $this->validator->validate($user); if ($errors->count() > 0) { $errorMessages = $this->concatenateViolations($errors); $this->tempOutput->writeln(sprintf('%d errors found with user with username "%s" at line %d', $errors->count(), $data['username'], $offset)); $this->tempOutput->writeln($errorMessages); throw new RuntimeException('Found errors while creating an user. ' . 'Watch messages in command output'); } $pgs = $this->getPermissionGroup($data['permission group']); $centers = $this->getCenters($data['center']); foreach ($pgs as $pg) { foreach ($centers as $center) { $groupcenter = $this->createOrGetGroupCenter($center, $pg); if (false === $user->getGroupCenters()->contains($groupcenter)) { $user->addGroupCenter($groupcenter); } } } if ($this->doChanges) { $this->em->persist($user); $this->em->flush(); } $this->logger->notice('Create user', [ 'username' => $user->getUsername(), 'id' => $user->getId(), 'nb_of_groupCenters' => $user->getGroupCenters()->count(), ]); return $user; } protected function doesUserExists($data) { if ($this->userRepository->countByUsernameOrEmail($data['username']) > 0) { return true; } if ($this->userRepository->countByUsernameOrEmail($data['email']) > 0) { return true; } return false; } protected function execute(InputInterface $input, OutputInterface $output) { $this->tempOutput = $output; $this->tempInput = $input; if ($input->getOption('dry-run')) { $this->doChanges = false; } $this->prepareWriter(); if ($input->hasOption('grouping-centers')) { $this->prepareGroupingCenters(); } try { $this->loadUsers(); } catch (Exception $e) { throw $e; } } /** * return a list of centers matching the name of alias. * * If the name match one center, this center is returned in an array. * * If the name match an alias, the centers corresponding to the alias are * returned in an array. * * If the center is not found or alias is not created, a new center is created * and suggested to user * * @param string $name the name of the center or the alias regrouping center * * @return Center[] */ protected function getCenters($name) { // sanitize $name = trim($name); if (array_key_exists($name, $this->centers)) { return $this->centers[$name]; } // search for a center with given name $center = $this->em->getRepository(Center::class) ->findOneByName($name); if ($center instanceof Center) { $this->centers[$name] = [$center]; return $this->centers[$name]; } // suggest and create $center = (new Center()) ->setName($name); $this->tempOutput->writeln("Center with name \"{$name}\" not found."); $qFormatter = $this->getHelper('question'); $question = new ConfirmationQuestion("Create a center with name \"{$name}\" ?", true); if ($qFormatter->ask($this->tempInput, $this->tempOutput, $question)) { $this->centers[$name] = [$center]; $errors = $this->validator->validate($center); if ($errors->count() > 0) { $errorMessages = $this->concatenateViolations($errors); $this->tempOutput->writeln(sprintf('%d errors found with center with name "%s"', $errors->count(), $name)); $this->tempOutput->writeln($errorMessages); throw new RuntimeException('Found errors while creating one center. ' . 'Watch messages in command output'); } $this->em->persist($center); return $this->centers[$name]; } return null; } protected function getPermissionGroup($alias) { if (array_key_exists($alias, $this->permissionGroups)) { return $this->permissionGroups[$alias]; } $permissionGroupsByName = []; foreach ($this->em->getRepository(PermissionsGroup::class) ->findAll() as $permissionGroup) { $permissionGroupsByName[$permissionGroup->getName()] = $permissionGroup; } if (count($permissionGroupsByName) === 0) { throw new RuntimeException('no permission groups found. Create them ' . 'before importing users'); } $question = new ChoiceQuestion( "To which permission groups associate with \"{$alias}\" ?", array_keys($permissionGroupsByName) ); $question ->setMultiselect(true) ->setAutocompleterValues(array_keys($permissionGroupsByName)) ->setNormalizer(static function ($value) { if (null === $value) { return ''; } return trim($value); }); $helper = $this->getHelper('question'); $keys = $helper->ask($this->tempInput, $this->tempOutput, $question); $this->tempOutput->writeln('You have chosen ' . implode(', ', $keys)); if ($helper->ask( $this->tempInput, $this->tempOutput, new ConfirmationQuestion('Are you sure ?', true) )) { foreach ($keys as $key) { $this->permissionGroups[$alias][] = $permissionGroupsByName[$key]; } return $this->permissionGroups[$alias]; } $this->logger->error('Error while responding to a a question'); $this->tempOutput->writeln('Ok, I accept, but I do not know what to do. Please try again.'); throw new RuntimeException('Error while responding to a question'); } protected function loadUsers() { $reader = Reader::createFromPath($this->tempInput->getArgument('csvfile')); $reader->setHeaderOffset(0); foreach ($reader->getRecords() as $line => $r) { $this->logger->debug('starting handling new line', [ 'line' => $line, ]); if ($this->doesUserExists($r)) { $this->tempOutput->writeln(sprintf("User with username '%s' already " . 'exists, skipping', $r['username'])); $this->logger->info('One user already exists, skipping creation', [ 'username_in_file' => $r['username'], 'email_in_file' => $r['email'], 'line' => $line, ]); continue; } $user = $this->createUser($line, $r); $this->appendUserToFile($user); } } protected function prepareGroupingCenters() { $reader = Reader::createFromPath($this->tempInput->getOption('grouping-centers')); $reader->setHeaderOffset(0); foreach ($reader->getRecords() as $r) { $this->centers[$r['alias']] = array_merge( $this->centers[$r['alias']] ?? [], $this->getCenters( $r['center'] ) ); } } protected function prepareWriter() { $this->output = $output = Writer::createFromPath($this->tempInput ->getOption('csv-dump'), 'a+'); $output->insertOne([ 'email', 'username', 'id', ]); } }