Symfony 3 Email confirmation on FOSUserBundle Profile Edit - symfony

i try to follow this forum to give email confermation to profile edit fos user bundle .
i create file
/src/AppBundle/EventListener.php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\GetResponseUserEvent;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\Mailer\MailerInterface;
use FOS\UserBundle\Util\TokenGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class ChangeProfileListener implements EventSubscriberInterface
{
private $mailer;
private $tokenGenerator;
private $router;
private $session;
private $tokenStorage;
public function __construct(
MailerInterface $mailer,
TokenGeneratorInterface $tokenGenerator,
UrlGeneratorInterface $router,
SessionInterface $session, TokenStorageInterface $tokenStorage
) {
$this->mailer = $mailer;
$this->tokenGenerator = $tokenGenerator;
$this->router = $router;
$this->session = $session;
$this->tokenStorage = $tokenStorage;
}
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::PROFILE_EDIT_INITIALIZE => 'onProfileEditInitialize',
FOSUserEvents::PROFILE_EDIT_SUCCESS => 'onProfileEditSuccess',
);
}
public function onProfileEditInitialize(GetResponseUserEvent $event)
{
// required, because when Success's event is called, session already contains new email
$this->email = $event->getUser()->getEmail();
}
public function onProfileEditSuccess(FormEvent $event)
{
$user = $event->getForm()->getData();
if ($user->getEmail() !== $this->email)
{
// disable user
$user->setEnabled(false);
// send confirmation token to new email
$user->setConfirmationToken($this->tokenGenerator->generateToken());
$this->mailer->sendConfirmationEmailMessage($user);
// force user to log-out
$this->tokenStorage->setToken();
// redirect user to check email page
$this->session->set('fos_user_send_confirmation_email/email', $user->getEmail());
$url = $this->router->generate('fos_user_registration_check_email');
$event->setResponse(new RedirectResponse($url));
}
}
}
After in service.yml
parameters:
#parameter_name: value
oc_user.email_change.listener.class: AppBundle\EventListener\ChangeProfileListener
services:
app.form.registration:
class: AppBundle\Form\RegistrationType
tags:
- { name: form.type, alias: app_user_registration }
app.form.profileedit:
class: AppBundle\Form\ProfileType
tags:
- { name: form.type, alias: app_profile_edit }
...
oc_user.email_change.listener:
class: %oc_user.email_change.listener.class%
arguments: ['#fos_user.mailer', '#fos_user.util.token_generator', '#router', '#session', '#security.token_storage']
tags:
- { name: kernel.event_subscriber }
but i have always this error
(1/1) AutowiringFailedException
Cannot autowire service "AppBundle\EventListener\ChangeProfileListener": argument "$mailer" of method "__construct()" references interface "FOS\UserBundle\Mailer\MailerInterface" but no such service exists. You should maybe alias this interface to one of these existing services: "fos_user.mailer.default", "fos_user.mailer.twig_swift", "fos_user.mailer.noop".
i have also override the form but it work
Can help me??
my config file
# app/config/config.yml
fos_user:
db_driver: orm # other valid values are 'mongodb' and 'couchdb'
firewall_name: main
user_class: AppBundle\Entity\User
from_email:
address: "%mailer_user%"
sender_name: "%mailer_user%"
registration:
form:
type: AppBundle\Form\RegistrationType
# if you are using Symfony < 2.8 you should use the type name instead
# type: app_user_registration
confirmation:
enabled: true
profile:
form:
type: AppBundle\Form\ProfileType
fos_user.mailer:
alias: 'fos_user.mailer.default'

The error message laready says it:
You should maybe alias this interface to one of these existing services: "fos_user.mailer.default", "fos_user.mailer.twig_swift", "fos_user.mailer.noop".
The error occurs in your configuration (service marked with --> <--):
oc_user.email_change.listener:
class: %oc_user.email_change.listener.class%
arguments: [--> '#fos_user.mailer', <-- '#fos_user.util.token_generator', '#router', '#session', '#security.token_storage']
tags:
- { name: kernel.event_subscriber }
You probably have to enable the notification feature in FOS UserBundle:
# app/config/config.yml
fos_user:
# ...
registration:
confirmation:
enabled: true
as is described in the docs: https://symfony.com/doc/current/bundles/FOSUserBundle/emails.html#registration-confirmation
If that doesn't help you might want to either reference one of the mentioned services in the error message or create an alias the points to one of them, e.g. fos_user.mailer.default:
fos_user.mailer:
alias: 'fos_user.mailer.default'
Then you can keep your service as is and whenever you refer to fos_user.mail it will use the service referenced in the alias.

Related

Cannot autowire service in Symfony 3.4 and FosUserBundle

I try to override REGISTRATION_SUCCESS in FosUserBundle to redirect the admin on user's list after register a new user.
So I have created a new event subscriber :
<?php
namespace AppBundle\EventListener;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Mailer\MailerInterface;
use FOS\UserBundle\Util\TokenGeneratorInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class RedirectAfterRegistrationSubscriber implements EventSubscriberInterface
{
private $mailer;
private $tokenGenerator;
private $router;
public function __construct(MailerInterface $mailer, TokenGeneratorInterface $tokenGenerator, UrlGeneratorInterface $router)
{
$this->mailer = $mailer;
$this->tokenGenerator = $tokenGenerator;
$this->router = $router;
}
public function onRegistrationSuccess(FormEvent $event)
{
$user = $event->getForm()->getData();
$user->setEnabled(false);
if (null === $user->getConfirmationToken()) {
$user->setConfirmationToken($this->tokenGenerator->generateToken());
}
$this->mailer->sendConfirmationEmailMessage($user);
$url = $this->router->generate('user_index');
$event->setResponse(new RedirectResponse($url));
}
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess'
];
}
}
and the following service :
app.redirect_after_registration_subscriber:
class: AppBundle\EventListener\RedirectAfterRegistrationSubscriber
arguments: ['#fos_user.mailer', '#fos_user.util.token_generator', '#router']
tags:
- { name: kernel.event_subscriber }
I don't understand why this error appears :
Cannot autowire service "AppBundle\EventListener\RedirectAfterRegistrationSubscriber":
argument "$mailer" of method "__construct()" references interface
"FOS\UserBundle\Mailer\MailerInterface" but no such service exists. You should maybe alias
this interface to one of these existing services: "fos_user.mailer.default",
"fos_user.mailer.twig_swift", "fos_user.mailer.noop".
I suppose you are using autodiscovering of services. Something like:
# services.yaml
AppBundle\:
resource: '../src/'
...
So in addition to the #app.redirect_after_registration_subscriber that you define, Symfony defines another service with id #AppBundle\EventListener\RedirectAfterRegistrationSubscriber. Both point to AppBundle\EventListener\RedirectAfterRegistrationSubscriber class. Yet you configured the mailer parameter only for the first one.
The solution:
AppBundle\EventListener\RedirectAfterRegistrationSubscriber:
arguments: ['#fos_user.mailer', '#fos_user.util.token_generator', '#router']
tags:
- { name: kernel.event_subscriber }
With autowiring and autoconfigure you can even sypmlify to:
AppBundle\EventListener\RedirectAfterRegistrationSubscriber:
arguments:
$mailer: '#fos_user.mailer'

Get User inside a SQLFilter in Symfony 3

I’m starting with Symfony and I want to make a multi tenant application.
I want to automatically filter in my SQL queries the content according to the company of belonging of the connected user, every time a table has a link with my company table.
I found the way to create filters but I can not find a way to retrieve in this filter the information about the company of the connected user.
I use FOSuser I override it with my own User class.
my config.yml
#app\config\config.yml
doctrine:
dbal:
...
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
filters:
company:
class: 'Acme\CompanyBundle\Repository\Filters\CompanyFilter'
enabled: true
my Filter
<?php
#src\Acme\CompanyBundle\Repository\Filters\CompanyFilter.php
namespace Acme\CompanyBundle\Repository\Filters;
use Doctrine\ORM\Mapping\ClassMetaData;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Acme\UserBundle\Entity\UserEntity;
use Acme\CompanyBundle\Entity\CompanyEntity;
class CompanyFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if ($targetEntity->hasAssociation("company")) {
// here how to get the connected user ???
$company = $user->getCompany();
$idCompany = $company->getId();
return $targetTableAlias . ".company_id = '".$idCompany."'";
}
return "";
}
}
in advance thank you for your help
Set an onKernelRequest listener, pass it the token storage service, so it defines your user as parameter of your SQLFilter.
So in your services.yml add :
services:
on_request_listener:
class: Acme\CompanyBundle\EventListener\OnRequestListener
arguments: ["#doctrine.orm.entity_manager", "#security.token_storage"]
tags:
-
name: kernel.event_listener
event: kernel.request
method: onKernelRequest
Create the listener :
class OnRequestListener
{
protected $em;
protected $tokenStorage;
public function __construct($em, $tokenStorage)
{
$this->em = $em;
$this->tokenStorage = $tokenStorage;
}
public function onKernelRequest(GetResponseEvent $event)
{
if($this->tokenStorage->getToken()) {
$user = $this->tokenStorage->getToken()->getUser();
$filter = $this->em->getFilters()->enable('company');
$filter->setParameter('user', $user);
}
}
}
Then at last your SQLFilter :
<?php
#src\Acme\CompanyBundle\Repository\Filters\CompanyFilter.php
namespace Acme\CompanyBundle\Repository\Filters;
use Doctrine\ORM\Mapping\ClassMetaData;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Acme\UserBundle\Entity\UserEntity;
use Acme\CompanyBundle\Entity\CompanyEntity;
class CompanyFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if ($targetEntity->hasAssociation("company") && $this->hasParameter('user')) {
$user = $this->getParameter('user');
$company = $user->getCompany();
$idCompany = $company->getId();
return $targetTableAlias . ".company_id = '".$idCompany."'";
}
return "";
}
}

Monolog in Symfony from non-controller Class

I have the following class:
namespace AppBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
class UserRegistrationListener implements EventSubscriberInterface
{
protected $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
/**
* take action when registration is initialized
* set the username to a unique id
* #param \FOS\UserBundle\Event\FormEvent $event
*/
public function onRegistrationInit(UserEvent $userevent)
{
$this->logger->info("Log Something");
$user = $userevent->getUser();
$user->setUsername(uniqid());
}
}
and I have been trying for hours to log something with monolog from within it but have had no luck.
I have read much of the documentation and I believe I need to somehow 'Inject' monolog as a service. What I have read however does not seem to be clear to me.
Some details:
#config_dev.yml
monolog:
channels: [chris]
handlers:
mylog:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%_chris.log"
channels: chris
formatter: monolog.my_line_formatter
.
#services.yml
services:
monolog.my_line_formatter:
class: Monolog\Formatter\LineFormatter
arguments: [~, ~, true]
app.user_registration:
class: AppBundle\EventListener\UserRegistrationListener
arguments: [#logger] ## changed to [#monolog.logger.chris] to us custom channel
tags:
- { name: kernel.event_subscriber }
What do I have to do to get Monolog working with my formatter inside this class?
UPDATE:
#griotteau I have done what you have posted in your answer but I still get an error:
CRITICAL - Uncaught PHP Exception Symfony\Component\Debug\Exception\ContextErrorException: "Warning: Missing argument 1 for AppBundle\EventListener\UserRegistrationListener::__construct(), called in ...filepath...\app\cache\dev\appDevDebugProjectContainer.php on line 384 and defined" at ...filepath...\src\AppBundle\EventListener\UserRegistrationListener.php line 18
SOLVED ERROR I already had a service with the same class (not shown in ym question). #griotteau 's answer is correct.
You can pass arguments when you declare your service
In services.yml :
app.user_registration:
class: AppBundle\EventListener\UserRegistrationListener
arguments: [#logger]
tags:
- { name: kernel.event_subscriber }
In your class, add a constructor :
protected $logger;
public function __construct($logger)
{
$this->logger = $logger;
}
So when you want to add a log :
$this->logger->info(...);

Sonata Admin locale change with admin user's locale

I build a i18n admin site with sonata admin bundle. Now i wanna change my locale and translation with admin user's locale set. Such as , i have two admin users one is en(userA), and another is zh(UserB). user's locale is set en/zh in User admin dashboard respectively。
My admin service :
services:
sonata.admin.post:
class: Acme\StoreBundle\Admin\PostAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Content", label: "Project", label_translator_strategy: sonata.admin.label.strategy.underscore }
arguments:
- ~
- Acme\StoreBundle\Entity\Product
- ~
calls:
- [ setTranslationDomain, [AcmeStoreBundle]]
- [ setLabelTranslatorStrategy, [ #sonata.admin.label.strategy.native ]]
Then my Resources/translations/AcmeStoreBundle.en.xliff and Resources/translations/AcmeStoreBundle.zh.xliff just like so:
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="1">
<source>label.product.name</source>
<target>Product Name</target> ##---> zh is diffrent here!!!
</trans-unit>
</body>
</file>
</xliff>
Then, i loggin admin by UserA, the message (product name ) is ok. But i loggin by UserB the message is still en locale( product name) . Of course, I can change the global locale in parameters.yml (%locale%) for userB, But this is not good for userA .
So, how can i change my site's locale(message or translation) with diffrent admin's user locale ?
Thanks in advance.
You could extend the login success handler and set the user's locale in the session. For example:
# app/config/config.yml
services:
login_success_handler:
parent: security.authentication.success_handler
class: MyVendor\MyBundle\LoginSuccessHandler
UPDATE: Make sure to point to this listener in your security.yml:
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: login
check_path: login_check
success_handler: login_success_handler
Then add login success handler class:
class LoginSuccessHandler extends DefaultAuthenticationSuccessHandler
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$locale = $token->getUser()->getLocale()
$request->getSession()->set('_locale', $locale);
$request->setLocale($locale);
return parent::onAuthenticationSuccess($request, $token);
}
}
Then you could create a LocaleListener similar to or exactly the same as the one in the Symfony documentation. The only difference is, if you will never define _locale in your routes you could change:
if ($locale = $request->attributes->get('_locale')) {
$request->getSession()->set('_locale', $locale);
} else {
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
to just
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
Thank you Jason. I did that as your hint.But , the login_success_handler seem not be called completely。
my config.yml:
services:
login_success_handler:
parent: security.authentication.success_handler
class: Acme\StoreBundle\EventListener\LoginSuccessHandler
acme_locale.locale_listener:
class: Acme\StoreBundle\EventListener\LocaleListener
arguments: ["%kernel.default_locale%"]
tags:
- { name: kernel.event_subscriber }
And the src/Acme/StoreBundle/EventListener/LoginSuccessHandler.php
namespace Acme\StoreBundle\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class LoginSuccessHandler extends DefaultAuthenticationSuccessHandler
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
$locale = $token->getUser()->getLocale();
file_put_contents('/tmp/login.log', $locale, FILE_APPEND); ## I can't find the log file
$request->getSession()->set('_locale', $locale);
$request->setLocale($locale);
}
}
And src/Acme/StoreBundle/EventListener/LocaleListener.php
namespace Acme\StoreBundle\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class LocaleListener implements EventSubscriberInterface
{
private $defaultLocale;
public function __construct($defaultLocale = 'en')
{
$this->defaultLocale = $defaultLocale;
}
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (!$request->hasPreviousSession()) {
return;
}
$request->setLocale($request->getSession()->get('_locale', $this->defaultLocale));
}
public static function getSubscribedEvents()
{
return array(
// must be registered before the default Locale listener
KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
);
}
}
What's wrong with me? Thanks.

FOSUserBundle: Success target after password reset

After the user did reset his password using the password reset of FOSUserBundle, by default he is redirected to the FOSUserProfile.
I want to redirect to a different route.
Is this possible and if yes, how?
It can be done by creating a resetting subscriber:
namespace Acme\UserBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Listener responsible to change the redirection at the end of the password resetting
*/
class PasswordResettingListener implements EventSubscriberInterface {
private $router;
public function __construct(UrlGeneratorInterface $router) {
$this->router = $router;
}
public static function getSubscribedEvents() {
return [
FOSUserEvents::RESETTING_RESET_SUCCESS => 'onPasswordResettingSuccess',
];
}
public function onPasswordResettingSuccess(FormEvent $event) {
$url = $this->router->generate('homepage');
$event->setResponse(new RedirectResponse($url));
}
}
And then registering it as a service with kernel.event_subscriber tag:
# src/Acme/UserBundle/Resources/config/services.yml
services:
acme_user.password_resetting:
class: Acme\UserBundle\EventListener\PasswordResettingListener
arguments: [ #router ]
tags:
- { name: kernel.event_subscriber }
In case you are not using the FOS user profile view, there is an ugly yet simple way:
Add in your app/config/routing.yml:
fos_user_profile_show:
path: /yourpath

Resources