<?php
namespace ProtectoBundle\EventListener;
use Exception;
use Pimcore\Config;
use Pimcore\Event\Model\DataObjectEvent;
use Pimcore\Event\Model\ElementEventInterface;
use Pimcore\Mail;
use Pimcore\Model\DataObject\Representative;
use Pimcore\Model\DataObject\User;
use Pimcore\Model\Element\Service;
use Pimcore\Model\WebsiteSetting;
use Symfony\Component\HttpFoundation\Response;
class UserListener
{
/**
* Before adding a User
*
* @param ElementEventInterface $e
* @return void
*/
public function onPreAdd(ElementEventInterface $e)
{
if ($e instanceof DataObjectEvent) {
$user = $e->getObject();
if ($user instanceof User) {
// if User has no username (e.g. if User created via Pimcore Backend)
if (!$user->getUsername()) {
// Fallback => use key as username
$user->setUsername($user->getKey());
}
// override key with validated username
$user->setKey(Service::getValidKey($user->getUsername(), 'object'));
// unpublish user, so has to wait for publishing from the representative
$user->setPublished(false);
}
}
}
/**
* After adding a User
*
* @param ElementEventInterface $e
* @return void
* @throws Exception
*/
public function onPostAdd(ElementEventInterface $e)
{
if ($e instanceof DataObjectEvent) {
$user = $e->getObject();
if ($user instanceof User) {
$this->matchUserToRepresentative($user->getId());
}
}
}
/**
* Finds the matching Representative to the given User zip
*
* @param int $userId
* @return Response
* @throws Exception
*/
public function matchUserToRepresentative(int $userId): Response
{
$user = User::getById($userId);
// remove whitespaces from zip
$user->setZip(preg_replace("/\s/i", "", $user->getZip()));
$userZipStr = $user->getZip();
// country has to be filled for export representative
if (!empty($user->getCountry())) {
// for any user outside of 'Germany'
if ($user->getCountry() !== 'Germany') {
// export representative
$repExport = Representative::getByPath('/Vertreter/export');
return $this->submitMatching($user, $repExport);
}
}
// prepare zip for matching
$userZipFull = $this->getZipFull($userZipStr);
// use floatval to prevent matching errors
$userZipFloat = floatval($userZipFull);
// if User zip exists, but it's floatval is not greater than 0
if ($userZipStr && !floatval($userZipStr) > 0) {
throw new Exception('invalid zip');
}
$repListing = new Representative\Listing();
$repListing->load();
$repListingData = $repListing->getData();
foreach ($repListingData as $rep) {
foreach ($rep->getZip() as $repZip) {
// read "from" and "to" zips to prepare them like in User
$repZipFromStr = $repZip["from"]->getData();
$repZipFromFull = $this->getZipFull($repZipFromStr);
$repZipFromFloat = floatval($repZipFromFull);
$repZipToStr = $repZip["to"]->getData();
$repZipToFull = $this->getZipFull($repZipToStr);
$repZipToFloat = floatval($repZipToFull);
// if exact match - also covers the case if there is only "from" or only "to" (non-range)
if ($userZipStr === $repZipFromStr || $userZipStr === $repZipToStr) {
return $this->submitMatching($user, $rep);
} else {
// if valid range is defined
if (floatval($repZipFromStr) > 0 && floatval($repZipToStr) > 0) {
// resort asc to prevent wrong input-behaviour
$sortedRange = [$repZipFromFloat, $repZipToFloat];
sort($sortedRange);
// if User zip is in that valid range
if ($userZipFloat >= $sortedRange[0] && $userZipFloat <= $sortedRange[1]) {
return $this->submitMatching($user, $rep);
}
}
}
}
}
// default representative
$repDefault = Representative::getByPath('/Vertreter/default');
// if no representative was found yet from registering via frontend
if (!empty($user->getZip()) && !empty($user->getCountry())) {
// match default representative and send mails to it's recipients (without extra bcc for default to prevent duplicate mails!)
return $this->submitMatching($user, $repDefault, false);
}
/*
if not even yet a representative was found (e.g. if User created via Pimcore Backend),
set default representative as fallback, but DON'T send mails to it's recipients;
in this case a Pimcore Backend User has to edit, save and publish that user manually!
*/
$user->setRepresentative($repDefault);
$user->save();
return new Response();
}
/**
* saves Representative relation in the User and sends according mails to the affected.
*
* @param User $user - user to match with a representative
* @param Representative $rep - representative to save in the user
* @param bool $bccForDefault - sends mail copies to the recipients of the default representative
* @return Response
* @throws Exception
*/
public function submitMatching(User $user, Representative $rep, bool $bccForDefault = true): Response
{
// save representative first, even if following code breaks
$user->setRepresentative($rep);
$user->save();
// read main domain for userDeeplink
$mainDomain = Config::getSystemConfiguration()["general"]['domain'];
// if missing System Settings "Main Domain", cancel submit and mail sending to prevent wrong userDeeplink in mails
if (!$mainDomain) {
throw new Exception('missing "System Settings" > "Website" > "Main Domain"');
}
// determine server side protocol for userDeeplink usage
$protocol = isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ? 'https' : 'http';
// default representative
$repDefault = Representative::getByPath('/Vertreter/default');
// set mail variables for the document
$repMailParams = [
'userCustomerId' => $user->getCustomerId(),
'userForename' => $user->getForename(),
'userSurname' => $user->getSurname(),
'userCompany' => $user->getCompany(),
'userStreet' => $user->getStreet(),
'userStreetNo' => $user->getStreetNo(),
'userZip' => $user->getZip(),
'userCity' => $user->getCity(),
'userCountry' => $user->getCountry(),
'userUsername' => $user->getUsername(),
'userRegistrationDate' => date_format(date_create()->setTimestamp($user->getCreationDate()), "d.m.Y"),
'userDeeplink' => "$protocol://$mainDomain/admin/login/deeplink?object_{$user->getId()}_object"
];
if ($bccForDefault) {
// all default representative recipients get a mail that someone has registered
foreach ($repDefault->getEmailRecipients() as $repRecipient) {
// remove reference of $repMailParams and merge current representative infos
$params = array_merge($repMailParams, [
'forename' => $repDefault->getForename(),
'surname' => $repDefault->getSurname(),
'username' => $repDefault->getUsername()
]);
$this->sendMail($repRecipient["address"]->getData(), "Neue Registrierung", $params, "/Email/representativeUserRegistered");
}
}
// all matched representative recipients get a mail that someone has registered
foreach ($rep->getEmailRecipients() as $repRecipient) {
// remove reference of $repMailParams and merge current representative infos
$params = array_merge($repMailParams, [
'forename' => $rep->getForename(),
'surname' => $rep->getSurname(),
'username' => $rep->getUsername()
]);
$this->sendMail($repRecipient["address"]->getData(), "Neue Registrierung", $params, "/Email/representativeUserRegistered");
}
// set mail variables for the document
$userMailParams = [
'customerId' => $user->getCustomerId(),
'forename' => $user->getForename(),
'surname' => $user->getSurname(),
'company' => $user->getCompany(),
'street' => $user->getStreet(),
'streetNo' => $user->getStreetNo(),
'zip' => $user->getZip(),
'city' => $user->getCity(),
'country' => $user->getCountry(),
'username' => $user->getUsername(),
'registrationDate' => date_format(date_create()->setTimestamp($user->getCreationDate()), "d.m.Y")
];
// user gets confirmation mail (user is registered)
$this->sendMail($user->getUsername(), "Registrierungsbestätigung", $userMailParams, "/Email/userRegistered");
return new Response();
}
/**
* sends a mail to a recipient, with given params for a document
*
* @param string $recipient
* @param string $subject
* @param array $params
* @param string $document
* @return void
* @throws Exception
*/
private function sendMail(string $recipient, string $subject, array $params, string $document)
{
$mail = new Mail();
$mail->to($recipient);
$mail->setSubject($subject);
$mail->setDocument($document);
$mail->setParams($params);
$mail->send();
}
/**
* Description:
* - read and prepare zip as full string for error-prevention
* - floating-prefix inclusive, so the range don't gets destroyed and leading zeros don't get cut off
* - e.g. zip is 090 is greater than 0100, so 0.09000 is greater than 0.01000
* - default zip length 5 (=german zip)
*
* Functionality (in this example zipLength === 5):
* - leading zeros won't be cut off for calculation (00098 remains 00098 for calculation and won't get parsed to 98)
* - following zeros will be added automatically for calculation (12 will be calculated as 12000)
* - so a range of 00098 to 12 equals 00098-12000
*
* Explanation:
* Some German zips for example can begin with leading zeros like 01000 and 09660.
* Another valid equivalent writing form for those would be 01 and 0966.
* So the following zeros aren't necessary for spelling, but the leading zeros are!
* For better understanding, one could read those as 01*** and 0966* for example.
* Very important is, that leading zeros don't get cut off because that would manipulate the whole numeric value,
* for example 00009 to 10000 would be a range of 9 to 1 instead of 9 to 10000, because of the missing leading zeros!
*
* @param string $zip
* @param int $zipLength
* @return string
*/
public function getZipFull(string $zip, int $zipLength = 5): string
{
return str_pad("0.$zip", $zipLength + 2, "0");
}
/**
* After updating a User
*
* @param ElementEventInterface $e
* @return void
* @throws Exception
*/
public function onPostUpdate(ElementEventInterface $e)
{
if ($e instanceof DataObjectEvent) {
$user = $e->getObject();
if ($user instanceof User) {
// auto-save should NOT trigger this procedure to prevent unintentional mails - only manual saves should
if (!$e->hasArgument("isAutoSave") || ($e->hasArgument("isAutoSave") && !$e->getArgument("isAutoSave"))) {
// if missing mediaDbUrl, cancel mail and initialPublished process
if (!WebsiteSetting::getByName('mediaDbUrl')) {
throw new Exception('missing "Website Settings" > Name: "mediaDbUrl"');
}
// only if updated user gets published initially
if ($user->getPublished() && !$user->getInitialPublished()) {
// EVERY TIME a user gets published INITIALLY generate a new temporary User password string to send via mail
$newPw = bin2hex(openssl_random_pseudo_bytes(3)) . substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 4);
// Override current password!
$user->setPassword($newPw);
// set mail variables for the document incl. loginLink from WebsiteSetting
$userMailParams = [
'customerId' => $user->getCustomerId(),
'forename' => $user->getForename(),
'surname' => $user->getSurname(),
'company' => $user->getCompany(),
'street' => $user->getStreet(),
'streetNo' => $user->getStreetNo(),
'zip' => $user->getZip(),
'city' => $user->getCity(),
'country' => $user->getCountry(),
'username' => $user->getUsername(),
'newTemporaryPassword' => $newPw,
'registrationDate' => date_format(date_create()->setTimestamp($user->getCreationDate()), "d.m.Y"),
'mediaDbLink' => WebsiteSetting::getByName('mediaDbUrl')->getData()
];
// user gets confirmation mail (user is published)
$this->sendMail($user->getUsername(), "Freischaltung", $userMailParams, "/Email/userPublished");
// user shouldn't get publishing mails afterwards
$user->setInitialPublished(true);
$user->save();
}
}
}
}
}
}