I'm trying to register a user with AJAX.
I created an event listener on FOSUserEvents::REGISTRATION_SUCCESS
So I'm trying to know is an AJAX request has been made but the response on my client side doesn't satisfy me.
Here my event listener, note that the response sent is a test so of course there should be no "else" condition.
<?php
namespace SE\AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Ajax listener on FOS UserBundle registration
*/
class RegistrationListener implements EventSubscriberInterface
{
private $router;
public function __construct(RequestStack $RequestStack)
{
$this->requestStack = $RequestStack;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess'
);
}
public function onRegistrationSuccess()
{
$request = $this->requestStack->getCurrentRequest();
if ($request->isXmlHttpRequest()) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
}
else{
$array = array( 'success' => false ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
}
}
}
services.yml:
se.app.listener.registration:
class: SE\AppBundle\EventListener\RegistrationListener
arguments: ["#request_stack"]
tags:
- { name: kernel.event_subscriber }
javascript:
// Submit the request
$.ajax({
type : 'POST',
url : url,
data : data,
success : function(data, status, object) {
console.log('success');
console.log(data);
},
error: function(data, status, object){
console.log('error');
console.log(data);
}
});
Firstly the weird thing is that it goes in the error condition.
The console.log (data) is returned the DOM of the registration success page :
...
<p>Congrats brieuc.tribouillet7777#gmail.com, your account is now activated.</p>
...
So does this logic should be here or should I override the controller? What am I doing wrong?
Because of the level of the REGISTRATION_SUCCESS event, you can't return a response directly from the EventListener.
You need to grab the FormEvent and modify its response.
Lets pass it as argument:
class RegistrationListener implements EventSubscriberInterface
{
// ...
public function onRegistrationSuccess(FormEvent $event)
{
$request = $this->requestStack->getCurrentRequest();
// Prepare your response
if ($request->isXmlHttpRequest()) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
} else {
$array = array( 'success' => false ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
}
// Send it
$event->setResponse($response);
}
}
And it should work.
Note There is an issue about this event where the response cannot be modified.
If the problem occurs, you need to set a low priority in your event subscribing:
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => [
['onRegistrationSuccess', -10],
],
];
}
See #1799.
EDIT
Note You should use a JsonResponse instead of json_encode your data and set the Content-Type manually.
To grab the form itself and its eventual errors, you can do this:
public function onRegistrationSuccess(FormEvent $event)
{
$form = $event->getForm();
if (count($validationErrors = $form->getErrors()) == 0) {
return $event->setResponse(new JsonResponse(['success' => true]));
}
// There is some errors, prepare a failure response
$body = [];
// Add the errors in your response body
foreach ($validationErrors as $error) {
$body[] = [
'property' => $error->getPropertyPath() // The field
'message' => $error->getMessage() // The error message
];
}
// Set the status to Bad Request in order to grab it in front (i.e $.ajax({ ...}).error(...))
$response = new JsonResponse($body, 400);
$event->setResponse($response);
}
But because it's a success event, you may need to override the method itself.
Related
I am doing Paypal integration in Laravel. I have used composer require srmklive/paypal to install the srmklive/paypal package in this project.
When I press the PayPal button, I get this error:
Here is my code:
code from blade file:
paypal.Buttons({
createOrder: function(data, actions) {
return fetch('api/paypal/order/create/', {
method: 'post',
body:JSON.stringify({
"value":100
})
}).then(function(res) {
return res.json();
}).then(function(orderData) {
return orderData.id;
});
},
onApprove: function(data, actions) {
return fetch('/api/paypal/order/capture/', {
method: 'post',
body: JSON.stringify({
orderID: data.orderID
})
}).then(function(res) {
return res.json();
}).then(function(orderData) {
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
return actions.restart();
}
if (errorDetail) {
var msg = 'Sorry, your transaction could not be processed.';
return alert(msg); // Show a failure message (try to avoid alerts in production environments)
}
});
}
}).render('#paypal-button-container');
code from paymentController:
class PaymentController extends Controller
{
public function create(Request $request){
$data = json_decode($request->getContent(), true);
$provider = \PayPal::setProvider();
$provider->setApiCredentials(config('paypal'));
$token = $provider->getAccessToken();
$provider->setAccessToken($token);
$price = Plan::getSubscriptionPrice($data['value']);
$description = Plan::getSubscriptionDescription($data['value']);
$order = $provider->createOrder([
"intent" => "CAPTURE",
"purchase_units" => [
[
"amount" => [
"currency_code" => "USD",
"value" => $price
],
"description" => $description
]
]
]);
return response()->json($order);
}
public function capture(Request $request) {
$data = json_decode($request->getContent(), true);
$orderId = $data['orderID'];
$provider = \PayPal::setProvider();
$provider->setApiCredentials(config('paypal'));
$token = $provider->getAccessToken();
$provider->setAccessToken($token);
$result = $provider->capturePaymentOrder($orderId);
return response()->json($result);
}
}
How can I solve this error?
The route api/paypal/order/create/ is returning/outputting text that is not JSON, such as an HTML error page or something else that begins with an HTML tag.
The route must only output JSON, and must contain a valid id from the PayPal API.
when I try to get data in ajax, the returned object is empty
I send the id of the data I want to get in js :
function selectMessage(id) {
$.ajax({
url: '{{ path('back_translation_update') }}',
method: 'GET',
data: {id: id}
}).done(function (response) {
console.log(response)
})
}
$('.updateMessage').click(function (evt) {
evt.stopPropagation()
selectMessage($(this).data('id'))
})
in the controller I look for the data to return :
/**
* #Route("/update", name="back_translation_update", methods="GET|POST")
*/
public function getById(Request $request): Response
{
if ($request->isXMLHttpRequest()) {
$id = $request->get('id');
// dd($id);
$message = $this->translationService->getTranslationById($id);
// return new JsonResponse(['data' => $message]);
$response = new Response();
$response->setContent(json_encode([
'data' => $message,
]));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
I use a service because with the repository I get an error: getById () must be an instance of Symfony\Component\HttpFoundation\Response
with :
$repositoryMessage = $this->em->getRepository(TranslationMessage::class);
$message = $repositoryMessage->findOneBy(['id' => $id]);
so the service will look in the database:
public function getTranslationById($translation_id)
{
$query = $this->em->createQueryBuilder()
->from(TranslationMessage::class,'message')
->select('message')
->where('message.id = ?1')
->setParameter(1, $translation_id);
$message = $query->getQuery()->getResult();
// dd($message);
return $message;
}
all the dd() give the expected values:
into getById(): the id of the row sought
into getTranslationById(): the sought object
but in the XHR, data contains an empty object: uh:
same with a new JsonResponse, commented here
what did I miss? help
Use Aurowire to get messageRepository object and use $this->json() to return JsonResponse
/**
* #Route("/update", name="back_translation_update", methods="GET|POST")
*/
public function getById(Request $request, TranslationMessageRepository $messageRepository): JsonResponse
{
$id = $request->query->get('id');
$message = $messageRepository->find($id);
if(!$message) { return new NotFoundHttpException(); }
return $this->json([
'success' => true,
'data' => $message
]);
}
Define success function instead of done function
function selectMessage(id) {
$.ajax({
url: "{{ path('back_translation_update') }}",
method: 'GET',
data: { id: id }
success: function(data) {
console.log(data)
}
})
}
The built in behavior for flashing back validation errors in Laravel does not seem to be working for my use case.
I have a (React) form that posts it's data via fetch API using this method, which reloads or redirects the page with (hopefully) any session data after the response is returned:
fetch(props.register_route, {
method: 'POST',
headers: {
'X-CSRF-Token': props.csrf,
},
body: data,
})
.then((result) => {
return result.json();
})
.then((result) => {
console.log(result);
window.location.href = result.url;
},
(error) => {
console.log(error);
});
In my controller, I validate this data but if I structure it as follows, the errors are not available as $errors in the resulting page
if ($validator->fails()) {
return redirect()->back()->withErrors($validator);
}
However if I manually flash the errors to the session and return a url instead of a redirect, suddenly the behavior works.
if ($validator->fails()) {
Session::flash('errors', $validator->errors());
return response->json([
'url' => route('register'),
], Response::HTTP_NOT_ACCEPTABLE);
}
I feel as if I must be doing something incorrectly here to have to use this workaround. I could also manually send the errors back in the response, which may be the right way to structure things in the long run.
when you are calling api from javascript or front end applications like Reactjs,Angular,android etc.. .So it expect return result should be in json format so it should be like
if ($validator->fails()) {
return response()->json( $validator->errors(),422);
}
if you not calling Method from direct laravel blade then pass response in JOSN Format.
like
https://laravel.com/docs/8.x/responses#json-responses
Or
make one ResponseManager File
<?PHP
namespace App\Libraries\utils;
class ResponseManager {
public static $response = array('flag' => true, 'data' => '', 'message' => '', 'code' => 01,);
public static function getError($data = '', $code = 10, $message = '', $flag = false) {
self::$response['flag'] = $flag;
self::$response['code'] = $code;
self::$response['data'] = $data;
self::$response['message'] = $message;
return self::$response;
}
public static function getResult($data = '', $code = 10, $message = '', $flag = true) {
self::$response['flag'] = $flag;
self::$response['code'] = $code;
self::$response['data'] = $data;
self::$response['message'] = $message;
return self::$response;
}}
Define in config/app.php
//custom class
'ResponseManager' => App\Libraries\utils\ResponseManager::class,
and then use in whole project
Error Message Like
if ($validation->fails()) {
$message = $validation->messages()->first();
return Response()->json(ResponseManager::getError('', 1, $message));
}
Success Message Like
return Response()->json(ResponseManager::getResult(null, 10, "Success"));
I have a problem I have two forms on regular form and one ajax processed form. The regular form works as needed but the ajax form passes as successful even when authentication of the server side fells.
My loginHandler function is below
public function handleLogin(Request $request) {
// init auth boolean to false
$auth = false;
// run validation rules
$validator = User::validation($request->all(), User::$login_validation_rules, User::$login_error_messages);
// get credentials
$credentials = $request->only('email','password');
if($validator->fails()) {
return back()
->withErrors($validator)
->withInput();
}
$credentials = array_merge($credentials,['activated' => 1]);
// get remember from request
$remember = $request->has('remember');
if (\Auth::attempt($credentials, $remember)) {
$auth = true;
}
if($request->ajax()) {
$response = response()->json([
'auth' => $auth,
'intended' => \URL::route('home')
]);
return $response;
}
return redirect()->intended('/');
}
My ajax code is this
$('#login-nav').validate({
rules : {
email : {
required : true,
email : true
},
password : {
required : true
}
},
messages : {
email : {
required : '<div class="alert-danger alert-validation">Email is a required field.</div>',
email : '<div class="alert-danger alert-validation">Please enter a valid Email.</div>'
},
password : {
required : '<div class="alert-danger alert-validation">Password is a required field.</div>'
}
},
submitHandler: function (form){
$.ajax({
headers: {
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('value')
},
type: $(form).attr('method'),
url: $(form).attr('action'),
data: $(form).serialize(),
dataType: 'json',
success: function (data) {
var html = '<div class="alert alert-success">Login Successful</div>';
$('#loginMsg').html(html);
return window.location = '/';
},
error: function (data) {
var html = '<div class="alert alert-danger">Email/Password is invalid</div>';
$('#loginMsg').html(html);
}
});
return false;
}
I would like to have the same behavior as my regular non ajax form. but instead of going to error in my ajax it's going to success. Any help would be greatly appreciated.
Auth::attempt will return you true or false, by default if you return a response()->json() without the 2nd parameter, it will be default to 200 (success). So based on the Auth::attempt you should either return 400 if it fails to login, then it should go into your ajax's error function.
$auth = \Auth::attempt($credentials, $remember);
if($request->ajax()) {
$responseCode = 200;
if( ! $auth) {
$responseCode = 400;
}
$response = response()->json([
'auth' => $auth,
'intended' => \URL::route('home')
], $responseCode);
return $response;
}
I am totally lost. There's only so much documentation one can read before it all starts making zero sense.
I want to be able to save form data passed from outside of my Symfony application. I have already installed FOSRestBundle, JMSSerializerBundle, NelmioCorsBundle, etc.
First off, I have a FormType that looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('requestDate')
->add('deliverDate')
->add('returnDate')
->add('created')
->add('updated')
->add('contentChangedBy')
;
}
Then I have a REST controller containing the POST method which is supposed to store the new record:
class AvRequestController extends Controller
{
...
public function postAvrequestAction(Request $request){
$entity = new AvRequest();
$form = $this->createForm(new AvRequestType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return new \Symfony\Component\HttpFoundation\JsonResponse($entity, Codes::HTTP_CREATED);
}
return new \Symfony\Component\HttpFoundation\JsonResponse($request, 400);
}
}
Here is the test with the mock ajax form data:
$('#postform').submit(function(event){
event.preventDefault();
console.log("submitted");
ajaxObject = {
url: $("#postform").attr("action"),
type: 'POST', // Can be GET, PUT, POST or DELETE only
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
contentType: "application/json; charset=UTF-8",
data: JSON.stringify({"id":2, "title":"billabong", "requestDate":"2000-01-01 11:11:11", "deliverDate": "2000-01-01 11:11:11", "returnDate": "2000-01-01 11:11:11", "created": "2000-01-01 11:11:11", "updated": "2000-01-01 11:11:11", "content_changed_by":"cpuzzuol"})
};
// ... Add callbacks depending on requests
$.ajax(ajaxObject)
.done(function(data,status,xhr) {
console.log( two );
})
.fail(function(data,status,xhr) {
console.log( status );
})
.always(function(data,status,xhr) {
console.log( data );
});
console.log("END");
});
When I submit the form, the 400 Bad Request is tripped in my POST method. Worse, my $request bag is always empty:
{"attributes":{},"request":{},"query":{},"server":{},"files":{},"cookies":{},"headers":{}}
If I do
$request->getContent()
I get my stringified data:
"{\u0022id\u0022:2,\u0022title\u0022:\u0022billabong\u0022,\u0022requestDate\u0022:\u00222000-01-01 11:11:11\u0022,\u0022deliverDate\u0022:\u00222000-01-01 11:11:11\u0022,\u0022returnDate\u0022:\u00222000-01-01 11:11:11\u0022,\u0022created\u0022:\u00222000-01-01 11:11:11\u0022,\u0022updated\u0022:\u00222000-01-01 11:11:11\u0022,\u0022content_changed_by\u0022:\u0022cpuzzuol\u0022}"
I've read that this might have something to do with FOSRestBundle's "body listener" but I've already enabled that:
body_listener: true
UPDATE
body_listener doesn't seem to play a role at all. As the answer below states, you have to create a form with a blank name since the form you are submitting from outside of the system isn't going to have the name it would normally have if it were made inside of Symfony. Also, make sure to turn off CSRF if you don't have that set up at first.
Form isValid checks also for CSRF token validation. You can turn off csrf token validation in AvRequestType.
//...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\AvRequest',
'csrf_protection' => false
));
}
//...
Also, I suggest your form has name. isValid also checks for your form name.
// form without name
public function getName()
{
return '';
}
Or
$form = $this->get('form.factory')->createNamed('', new AvRequestType(), $avRequest);
If you want to create entity, you should send data without id(from JS).
I have used "JMS serializer" to serialize my entity to json.
//Controller
public function postAvRequestAction(Request $request)
{
$avRequest = new AvRequest();
$form = $this->createForm(new AvRequestType(), $avRequest);
$form->handleRequest($request);
$form = $this->get('form.factory')->createNamed('', new AvRequestType(), $avRequest);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($avRequest);
$em->flush();
$serializer = $this->get('serializer');
$serialized = $serializer->serialize($avRequest, 'json');
return new Response($serialized);
}
return new JsonResponse(array(
'errors' => $this->getFormErrors($form)
));
}
protected function getFormErrors(Form $form)
{
$errors = array();
foreach ($form->getErrors() as $error) {
$errors['global'][] = $error->getMessage();
}
foreach ($form as $field) {
if (!$field->isValid()) {
foreach ($field->getErrors() as $error) {
$errors['fields'][$field->getName()] = $error->getMessage();
}
}
}
return $errors;
}