PHP header not working in foreach - php

Somewhat strange situation here
$location = 'Location: http://localhost/pages';
//header($location); exit; works
$response->header($location)->send(); exit; //doesn't work
and the $response object's class
public headers = [];
public function header($string, $replace = true, $code = 200)
{
$this->headers[] = [
'string' => $string,
'replace' => $replace,
'code' => $code
];
return $this;
}
public function send()
{
foreach ($this->headers as $header) {
header($header['string'], $header['replace'], $header['code']);
}
}
The code works fine when using vanilla header but it doesn't when using the methods. Am I missing something here?

You are returning the Location header to the browser with a 200 status code.
For a redirection to actually occur, a 3xx response code should be sent instead (usually, a 302). A 200 response code simply means "OK, content follows". For a redirection to actually take place, a 3xx response code must be given.
Your code is ultimately calling
header('Location: http://localhost/pages', true, 200);
which isn't going to result in the browser redirecting you to the desired location.
PHP itself special-cases calls to header('Location: ...') and unless otherwise specified, uses a 302 instead of leaving the response code unchanged. You may want to adjust your code to do the same to keep the same behavior as PHP.
Also, important to note that, while every HTTP response only has one response code, header() allows you to set the response code each time you call it.
Thus, if you use your code like this:
$response
->header("Location: http://localhost/pages", true, 302)
->header("SomeOtherheader: value")
->send()
;
the 302 you intended to send will get replaced with the 200 that gets set in the next call to header().
Instead, what you should do is either separate the concept of setting the status code from actually setting the header content, e.g.:
$response
->header("Location: http://localhost/pages"))
->header("SomeOtherheader: value")
->responseCode(302)
->send()
;
or instead do what header() does and treat an unspecified response code as meaning, don't change the what's already been set:
public function header($string, $replace = true, $code = false) { ... }
false (or 0) passed on to PHP's header() will indicate that.

Related

Referrer disappearing after redirect

I have a redirect script which does some tracking and then redirects the user to a destination. It looks something like this
class Redirect() {
private function init() {
// analyze parameters
(...)
$this->referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
$this->trackVisit($this->referer, $someMoreData);
$destination = $this->getUrlByParameters(...);
$this->redirectUrl = $destination;
}
private function run() {
(...)
header('Location: ' . $this->redirectUrl);
(...)
}
}
$r = new Redirect();
if($r->init()) {
$r->run();
}
What gives me headache is that i can see a referrer on my server and i am saving that in to my db but after redirecting the user it disappears. The destination and all possible subsequent redirects don't have the referrer anymore.
Referrer-Policy is set to 'no-referrer-when-downgrade' but i am always redirect to https so that should not be the issue.
I hope somebody can help me out!
If you redirect the request from a script using header("Location:...") The Å•eferrer will not be preserved. You could possibly pass the referer as a variable in query string:
header('Location: ' . $this->redirectUrl."?ref=".$_SERVER['HTTP_REFERER']);
or with &, if the URL already contains some parameters.
Then you could get the original referer on the other script using $_GET["ref"]
Or you could write headers yourself and append it before the Location header, and then send full headers.

Concrete5 Custom Ajax in Block View

On Concrete5-8.1.0 I have created a custom block with Ajax functionality based largely on the concrete5 docs - Implementing Ajax in Block View Templates. However, unlike the example I do not want to reload the block view, I want to pass specific messages based on the input. I tried a simple echo '{"msg":"ok"}'; and return '{"msg":"ok"}); as a test, but requests to the function yielded an empty response.
I found How To Send JSON Responses with Concrete5.7 and used Option 2 (for greater control of error codes) resulting in the following test code:
public function action_submit($token = false, $bID = false) {
if ($this->bID != $bID) {
return false;
}
if (Core::make('token')->validate('get_paper', $token)) {
//save to database
//removed for brevity
//send email
if ($this->emailto != '') {
//removed for brevity
}
if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
return new Response(
json_encode(array('msg' => 'ok')),
200,
['Content-Type' => 'application/json']
);
} else {
Redirect::page($page)->send();
}
}
else {
return false;
}
exit;
}
The database save and email function as expected, but the response is still empty. In Chrome Dev Tools, I see the correct Content-Type (as a test, I tried text/html and saw that change in dev tools), but no content. Interestingly, if I change the status from 200 to 500 I not only see the status change reflected in dev tools, I also see the {"msg":"ok"} content that I'm expecting, but changing the status back to 200 and the content is again empty.
It seems I'm missing something simple... I have verified that all caching is turned off within C5 (site is still in development), I have also verified the jQuery request includes cache:false, but the solution escapes me.

Why cookie isn't set in Laravel Lumen

This question is the following of this question.
I have a message in my view who says : This site uses cookie [...] Close.
When user click on Close, an ajax request is send to the controller. The function is the following :
public function acceptCookie(Request $request)
{
if ($request->valid == 'accept') {
$response = new Response('acceptCookie');
if ($response->withCookie(cookie('acceptCookie', 'accepte', 44000))) {
return Response()->json(array('statut' => 'Succes'));
} else {
return Response()->json(array('statut' => 'Erreur'));
}
} else {
return Response()->json(array('statut' => 'Erreur'));
}
}
I haven't any error and JSON returns always {"statut":"Succes"}
Why the cookie isn't set ?
Based on the Lumen documentation, it appears as though you need to queue the cookie for a response such as the one in your example. Here's what the docs say:
Queueing A Cookie For The Next Response
If you would like to set a cookie before a response has been created,
use the Cookie::queue() method. The cookie will automatically be
attached to the final response from your application.
Cookie::queue($name, $value, $minutes);
My suggestion would be to try replacing the withCookie with queuing the cookie instead. But, you might need to rewrite the function a bit in order to accomodate because it appears as though you're trying to send to responses from one request.
Hope this works for you!
Based on Illuminate\Http\ResponseTrait line 28, the Illuminate\Http\Response::withCookie method returning $this.
/**
* Add a cookie to the response.
*
* #param \Symfony\Component\HttpFoundation\Cookie $cookie
* #return $this
*/
public function withCookie(Cookie $cookie)
{
$this->headers->setCookie($cookie);
return $this;
}
Means you have logic failure in your code.
// This always return Illuminate\Http\Response instance,
// thus it will never reach ELSE statement forever.
if ($response->withCookie(cookie('acceptCookie', 'accepte', 44000))) {
return Response()->json(array('statut' => 'Succes'));
} else {
return Response()->json(array('statut' => 'Erreur'));
}

`setCookie()` that Slim framework provides will not work if I use `exit()` after it

I need to use an ajax request to perform login. Here's the function that request goes to:
function loginAdmin() {
$app = \Slim\Slim::getInstance();
if (auth()) {
$app->setCookie('admin', TRUE);
exit(TRUE);
}
exit(NULL);
}
And I will process the ajax response to see if it's true. But the cookie wouldn't be set. If I remove exit(TRUE) the cookie can be set.
I've read the source code of Slim, the setCookie() function calls \Slim\Http\Cookies::setCookie(), which set the key and value into $data member. But I'm not sure when the cookies are sent.
But I still don't know how does exit function affect setCookie function.
You cannot send response using exit(). You should echo() the response in the route. For example something like:
function loginAdmin() {
$app = \Slim\Slim::getInstance();
if (auth()) {
$app->setCookie('admin', TRUE);
return 1;
}
return 0;
}
$app->get('/foo', function {
echo loginAdmin();
});
Above code is not a good way to do authentication, but it shows the point.

_redirect still continues php code execution

I'm working on custom module and in my IndexController.php I'd written this function to add user to database
public function addAction() {
if($this->getRequest()->getParam('name', '') == ''){
$this->_redirect('etech/user');
//die; or exit;
}
$form = $this->getRequest()->getParams();
$user = Mage::getModel('test/test');
foreach ($form as $key => $val){
$user->setData($key, $val);
}
try{
$user->save();
}catch(Exception $e){
print_r($e);
}
$this->_redirect('etech/user', array('msg'=>'success'));
}
I want to prevent users from accessing this url directly as www.example.com/index.php/etech/user/add/. For this I'd made a check if($this->getRequest()->getParam('name', '') == ''){}. The redirect is working well except the code in there keeps executing and user sees a success message which should not be seen. For this, I'd used old fashioned exit or die to stop executing the code then it doesn't even redirect.
What is the magento way to handle it? Also, as I'm using getRequest()->getParams(), it return both parameters either in get or post. Isn't any way out to get only post parametrs?
It is correct to use $this->_redirect(), but you must follow it up with a return, ideally return $this;. You could also use exit or die, as you have been doing, but as I'm sure you know it would be better to let Magento do whatever it wants to do before redirecting you.
As long as you return immediately after $this->_redirect(), you won't have any issues.
Edit: And as for the request params question, I think you can call something like $this->getRequest()->getPostData() (that was false). The general convention is to use getParams() regardless of whether the data was sent via GET or POST, because technically your code shouldn't be concerned about that.
Edit #2:
If the general convention doesn't apply and you desperately need to restrict access to your page based on POST vs. GET, here's a handy snippet from Mohammad:
public function addAction()
{
if ($this->getRequest()->isPost()) {
// echo 'post'; do your stuff
} else {
// echo 'get'; redirect
}
}

Categories