I'm looking proper way to revert any given redirect command in ZF2 controller
For example in one of my function i use - $this->redirect()->toRoute('my/desire/path/to/redirect');
But after getting result I want to cancel that command and show something else to my user.
Here is my code to illustrate more precisely -
considering following method inside controller that will retrieve data for one id
public function getMyData(){
$idX = $this->params()->fromRoute('id');
$rtrn=$this->myTableGateway->getData($idX);
if($rtrn!==false){
$mnfctr['myData']=$rtrn;
$mnfctr['readMode']='yes';
return $mnfctr;
}else return $this->redirect()->toRoute('product-loader/sub-categories', array('cid'=>$this->mcep->id));
}
now in a real action inside controller
public function readAction(){
$data=$this->getMyData();
if($data instanceof \Zend\Http\PhpEnvironment\Response) return $data;
# do rest of the staff of read action
}
public function deleteAction(){
$data=$this->getMyData();
if($data instanceof \Zend\Http\PhpEnvironment\Response) return $data;
# do rest of the staff of delete action
}
now i want to show a logo of my company through the same controller
problem is if i simply redirect the logo should not draw properly
so i design my image action method as follows
public function logoAction(){
$logoFlName='Unknown Company';
$data=$this->getMyData();
#echo '$mnfctr: '.get_class($mnfctr); exit();
$response = $this->getResponse();
if($mnfctr instanceof \Zend\Http\PhpEnvironment\Response){
# in this situation i need to cancel my previous response command
$logoFlName='Invalid Company';
if($response->isRedirect()){
$this->flashMessenger()->clearCurrentMessages();
$response->getHeaders()->clearHeaders();
$response->setStatusCode(Response::STATUS_CODE_200);
}
}elseif(is_array($data) && array_key_exists('myData', $data) && $data['myData'] instanceof MyData){
# code to draw the real logo
$logoFlName='Logo of XYZ Company';
}else{ /* something wrong and can't be happen. but we really can't find logo */ }
$newImage=PLImageHelper::getLogoFromString($string2draw__not_found_image__or__logo);
$response->setContent($newImage->get('png'));
$response->getHeaders()
->addHeaderLine('Content-Transfer-Encoding', 'binary')
->addHeaderLine('Content-Type', 'image/png')
->addHeaderLine('Content-Disposition', 'filename="'.$logoFlName.'.png"')
#->addHeaderLine('Content-Length', mb_strlen($imageContent))
;
return $response;
}
I know there has a lot of way, suppose its possible to redirect to a real no-image-found.png file to show. but i want it programmatically
If you want to redirect the user to where the request is coming from you can use the following:
/** #var \Zend\Http\Request $request */
$request = $this->getRequest()
return $this->redirect()->toUrl($request->getHeaders()->get('referer')->getUri());
getUri() will return an url, for example: http://http://stackoverflow.com/posts/27318052/edit. Hope this helps.
Best possible way I get -
$response = $this->getResponse();
if($response->isRedirect()){
$response->getHeaders()->clearHeaders();
$response->setStatusCode(Response::STATUS_CODE_200);
}
Is there any other way to do it. I mean more efficient way that I can use.
Related
I want to redirect admins to /admin and members to /member when users are identified but get to the home page (/).
The controller looks like this :
public function indexAction()
{
if ($this->get('security.context')->isGranted('ROLE_ADMIN'))
{
return new RedirectResponse($this->generateUrl('app_admin_homepage'));
}
else if ($this->get('security.context')->isGranted('ROLE_USER'))
{
return new RedirectResponse($this->generateUrl('app_member_homepage'));
}
return $this->forward('AppHomeBundle:Default:home');
}
If my users are logged in, it works well, no problem. But if they are not, my i18n switch makes me get a nice exception :
The merge filter only works with arrays or hashes in
"AppHomeBundle:Default:home.html.twig".
Line that crashes :
{{ path(app.request.get('_route'), app.request.get('_route_params')|merge({'_locale': 'fr'})) }}
If I look at the app.request.get('_route_params'), it is empty, as well as app.request.get('_route').
Of course, I can solve my problem by replacing return $this->forward('AppHomeBundle:Default:home'); by return $this->homeAction();, but I don't get the point.
Are the internal requests overwritting the user request?
Note: I'm using Symfony version 2.2.1 - app/dev/debug
Edit
Looking at the Symfony's source code, when using forward, a subrequest is created and we are not in the same scope anymore.
/**
* Forwards the request to another controller.
*
* #param string $controller The controller name (a string like BlogBundle:Post:index)
* #param array $path An array of path parameters
* #param array $query An array of query parameters
*
* #return Response A Response instance
*/
public function forward($controller, array $path = array(), array $query = array())
{
$path['_controller'] = $controller;
$subRequest = $this->container->get('request')->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
By looking at the Symfony2's scopes documentation, they tell about why request is a scope itself and how to deal with it. But they don't tell about why sub-requests are created when forwarding.
Some more googling put me on the event listeners, where I learnt that the subrequests can be handled (details). Ok, for the sub-request type, but this still does not explain why user request is just removed.
My question becomes :
Why user request is removed and not copied when forwarding?
So, controller actions are separated part of logic. This functions doesn't know anything about each other. My answer is - single action handle kind of specific request (e.g. with specific uri prarams).
From SF2 docs (http://symfony.com/doc/current/book/controller.html#requests-controller-response-lifecycle):
2 The Router reads information from the request (e.g. the URI), finds a
route that matches that information, and reads the _controller
parameter from the route;
3 The controller from the matched route is
executed and the code inside the controller creates and returns a
Response object;
If your request is for path / and you wanna inside action (lets say indexAction()) handling this route, execute another controller action (e.g. fancyAction()) you should prepare fancyAction() for that. I mean about using (e.g.):
public function fancyAction($name, $color)
{
// ... create and return a Response object
}
instead:
public function fancyAction()
{
$name = $this->getRequest()->get('name');
$color = $this->getRequest()->get('color');
// ... create and return a Response object
}
Example from sf2 dosc:
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
// ... further modify the response or return it directly
return $response;
}
Please notice further modify the response.
If you really need request object, you can try:
public function indexAction()
{
// prepare $request for fancyAction
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array('request' => $request));
// ... further modify the response or return it directly
return $response;
}
public function fancyAction(Request $request)
{
// use $request
}
I have component called mycomponent
models
paypal.php
controllers
paypal.php
views
paypal
view.html.php
index.html
tmpl(folder)
default.php
index.html
In controller i have this code
<?php
// No direct access.
defined('_JEXEC') or die;
jimport('joomla.application.component.controlleradmin');
/**
* Objectdefects list controller class.
*/
class MycomponentControllerPaypal extends JControllerAdmin
{
public function paypaldetails()
{
$model = $this->getModel('paypal');
// Get token
$token = urlencode(htmlspecialchars(JRequest::getVar('token')));
if (!$token)
{
// Missing $token parameter
$app = JFactory::getApplication();
$app->enqueueMessage(JText::_('COM_INSTALLER_MSG_MISSING_TOKEN'));
}
else
{
// Install plugin
$model->paypaldetails($token);
}
}
}
In model i have this fragment of code
public function paypaldetails($token){
$environment= $this->environment;
// Add request-specific fields to the request string.
$nvpStr = "&TOKEN=$token";
// Execute the API operation; see the PPHttpPost function above.
$httpParsedResponseAr = $this->PPHttpPost('GetExpressCheckoutDetails', $nvpStr);
//var_dump($httpParsedResponseAr);
if("SUCCESS" == strtoupper($httpParsedResponseAr["ACK"]) || "SUCCESSWITHWARNING" == strtoupper($httpParsedResponseAr["ACK"])) {
$paypaldetails=array();
$paypaldetails["firstname"]= $httpParsedResponseAr['FIRSTNAME'];
$paypaldetails["lastname"] = $httpParsedResponseAr["LASTNAME"];
$paypaldetails["countrycode"] = $httpParsedResponseAr["COUNTRYCODE"];
$this->paypaldetails=$paypaldetails;
$a=$this->paypaldetails;
var_dump($a);
} else {
exit('GetExpressCheckoutDetails failed: ' . print_r($httpParsedResponseAr, true));
}
}
In view/template/default.php i have this
<?php
// no direct access
defined('_JEXEC') or die;
// Import CSS
$document = JFactory::getDocument();
$document->addStyleSheet('components/com_mycomponent/assets/css/defects.css');
$results = $this->items;
var_dump($results);
echo 'Firstname: '.$results[firstname];
echo '<br>Lastname: '.$results[lastname];
echo '<br>Countrycode: '.$results[countrycode];
When i run this url index.php?option=com_fewostar&view=paypal&task=paypal.paypaldetails&token=EC-92L7275685367793U&PayerID=TGWAUKNJLH2WL
I view first var_dump($a); located on model, but second var_dump($results); located in views/paypal/tmpl/default.php not display, and field in view not display. for any reason this url not call view. When i run this url index.php?option=com_fewostar&view=paypal code without task, view is display. but for this url
index.php?option=com_fewostar&view=paypal&task=paypal.paypaldetails&token=EC-92L7275685367793U&PayerID=TGWAUKNJLH2WL no display view. How i call view for this task, may be i need other view file, different of default.php?
I see a couple of problems here.
First, the code is not exactly using Joomla MVC style (even if it works for you, might be harder for people familiar with Joomla to debug).
Model method should be called getPaypaldetails and return something
public function getPaypaldetails()
{
// For Joomla 1.7+ use JInput instead of JRequest (deprecated)
$token = JFactory::getApplication()->input->getVar('token');
// some code
return $paypaldetails;
}
view.html.php should and get and data from model and assign to itself
public function display($tpl = null)
{
// Get some data from the models
$items = $this->model->get('paypaldetails');
// If data are incorrect, show nice error message
// ...
$this->items = $items;
}
View Layout file should be placed in /com_fewostar/views/paypal/tmpl/default.php
By default, the view is only called by the "display" task (which is the default task). Since you use your own task, you need to either redirect to the view after your task is finished or try to load the display function at the end.
I've literally downloaded Laravel today and like the looks of things but i'm struggeling on 2 things.
1) I like the controllers' actions method of analysing urls instead of using routes, it seems to keep everything together more cleanly, but lets say I want to go to
/account/account-year/
how can I write an action function for this? i.e.
function action_account-year()...
is obviously not valid syntax.
2) If i had
function action_account_year( $year, $month ) { ...
and visited
/account/account_year/
An error would be displayed about missing arguments, how do you go about making this user friendly/load diff page/display an error??
You would have to manually route the hyphenated version, e.g.
Route::get('account/account-year', 'account#account_year');
Regarding the parameters, it depends on how you are routing. You must accept the parameters in the route. If you are using full controller routing (e.g. Route::controller('account')) then the method will be passed parameters automatically.
If you are manually routing, you have to capture the params,
Route::get('account/account-year/(:num)/(:num)', 'account#account_year');
So visiting /account/account-year/1/2 would do ->account_year(1, 2)
Hope this helps.
You can think of the following possibility as well
class AccountController extends BaseController {
public function getIndex()
{
//
}
public function getAccountYear()
{
//
}
}
Now simply define a RESTful controller in your routes file in the following manner
Route::controller('account', 'AccountController');
Visiting 'account/account-year' will automatically route to the action getAccountYear
I thought I'd add this as an answer in case anyone else is looking for it:
1)
public function action_account_year($name = false, $place = false ) {
if( ... ) {
return View::make('page.error' );
}
}
2)
not a solid solutions yet:
laravel/routing/controller.php, method "response"
public function response($method, $parameters = array())
{
// The developer may mark the controller as being "RESTful" which
// indicates that the controller actions are prefixed with the
// HTTP verb they respond to rather than the word "action".
$method = preg_replace( "#\-+#", "_", $method );
if ($this->restful)
{
$action = strtolower(Request::method()).'_'.$method;
}
else
{
$action = "action_{$method}";
}
$response = call_user_func_array(array($this, $action), $parameters);
// If the controller has specified a layout view the response
// returned by the controller method will be bound to that
// view and the layout will be considered the response.
if (is_null($response) and ! is_null($this->layout))
{
$response = $this->layout;
}
return $response;
}
I'm having a small problem when trying to flash a message and redirect the user back to the previous page in Symfony 2.
I have a very simple CRUD. When new, or edit, i want to flash a message if something goes wrong in the respective create/update methods:
User --GET--> new
new --POST--> create (fails)
--REDIRECT--> new (with flash message)
I'm doing the following:
$this->container->get('session')->setFlash('error', 'myerror');
$referer = $this->getRequest()->headers->get('referer');
return new RedirectResponse($referer);
However, it's not redirecting to the correct referrer! Even though the value of referrer is correct (eg.: http://localhost/demo/2/edit/) It redirects to the index. Why?
This is an alternative version of Naitsirch and Santi their code. I realized a trait would be perfect for this functionality. Also optimized the code somewhat. I preferred to give back all the parameters including the slugs, because you might need those when generating the route.
This code runs on PHP 5.4.0 and up. You can use the trait for multiple controllers of course. If you put the trait in a seperate file make sure you name it the same as the trait, following PSR-0.
<?php
trait Referer {
private function getRefererParams() {
$request = $this->getRequest();
$referer = $request->headers->get('referer');
$baseUrl = $request->getBaseUrl();
$lastPath = substr($referer, strpos($referer, $baseUrl) + strlen($baseUrl));
return $this->get('router')->getMatcher()->match($lastPath);
}
}
class MyController extends Controller {
use Referer;
public function MyAction() {
$params = $this->getRefererParams();
return $this->redirect($this->generateUrl(
$params['_route'],
[
'slug' => $params['slug']
]
));
}
}
For symfony 3.0,flash message with redirection back to previous page,this can be done in controller.
$request->getSession()
->getFlashBag()
->add('notice', 'success');
$referer = $request->headers->get('referer');
return $this->redirect($referer);
The message from Naitsirch presented in the next url:
https://github.com/symfony/symfony/issues/2951
Seems a good solution for that you need:
public function getRefererRoute()
{
$request = $this->getRequest();
//look for the referer route
$referer = $request->headers->get('referer');
$lastPath = substr($referer, strpos($referer, $request->getBaseUrl()));
$lastPath = str_replace($request->getBaseUrl(), '', $lastPath);
$matcher = $this->get('router')->getMatcher();
$parameters = $matcher->match($lastPath);
$route = $parameters['_route'];
return $route;
}
Then with a redirect:
public function yourFunctionAction()
{
$ruta = $this->getRefererRoute();
$locale = $request->get('_locale');
$url = $this->get('router')->generate($ruta, array('_locale' => $locale));
$this->getRequest()->getSession()->setFlash('notice', "your_message");
return $this->redirect($url);
}
This works for me:
$this->redirect($request->server->get('HTTP_REFERER'));
I have similar functionality on my site. It is multilingual. Articles exists only in a single locale. When the user will try to switch to other locale, it should redirect back to previous page and flash message that that page/article doesn't exist on the requested locale.
/en/article/3 -> /fr/article/3 (404) -> Redirect(/en/article/3)
Here is my version of the script that works well on dev and prod environments:
$referer = $request->headers->get('referer')
// 'https://your-domain.com' or 'https://your-domain.com/app_dev.php'
$base = $request->getSchemeAndHttpHost() . $request->getBaseUrl();
// '/en/article/3'
$path = preg_replace('/^'. preg_quote($base, '/') .'/', '', $referer);
if ($path === $referer) {
// nothing was replaced. referer is an external site
} elseif ($path === $request->getPathInfo()) {
// current page and referer are the same (prevent redirect loop)
} else {
try {
// if this will throw an exception then the route doesn't exist
$this->container->get('router')->match(
// '/en/hello?foo=bar' -> '/en/hello'
preg_replace('/\?.*$/', '', $path)
);
// '/app_dev.php' . '/en/article/3'
$redirectUrl = $request->getBaseUrl() . $path;
return new RedirectResponse($redirectUrl);
} catch (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {}
}
I just set up a simple app, and it seems to work fine. My createAction() looks like this:
public function createAction()
{
$entity = new Pokemon();
$request = $this->getRequest();
$form = $this->createForm(new PokemonType(), $entity);
$form->bindRequest($request);
if ($entity->getName() == "pikachu")
{
$this->container->get("session")->setFlash("error", "Pikachu is not allowed");
$url = $this->getRequest()->headers->get("referer");
return new RedirectResponse($url);
}
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('pokemon_show', array('id' => $entity->getId())));
}
return $this->render('BulbasaurBundle:Pokemon:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView()
));
}
The flow goes:
User navigates to /new
User enters invalid option of "pikachu"
User clicks submit (POSTs to /create)
Application rejects the entry, adds flash message, and redirects back to /new
User sees /new with the flash message
A few things to check:
Is your route for /demo/{entityId}/edit actually working? (i.e. if you enter it in the browser, does it actually go to where you expect it to?)
Are you chaining together different redirects/forwards? I've noticed that I get unexpected (but correct) behavior when I have a controller that redirects to a URL, and the controller responsible for that URL also redirects somewhere else. I've fixed this issue by using forwards instead.
That said, if all else fails, you could just use the controller's redirect() method to manage the redirect:
public function createAction()
{
...
return $this->redirect($this->generateUrl("pokemon_new"));
...
}
Here you go, declare this as a service and it will return referer to you wherever and whenever you need it. No traits, no weird dependencies.
class Referer
{
/** #var RequestStack */
private $requestStack;
/** #var RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router)
{
$this->requestStack = $requestStack;
$this->router = $router;
}
public function getReferer() : string
{
$request = $this->requestStack->getMasterRequest();
if (null === $request)
{
return '';
}
//if you're happy with URI (and most times you are), just return it
$uri = (string)$request->headers->get('referer');
//but if you want to return route, here you go
try
{
$routeMatch = $this->router->match($uri);
}
catch (ResourceNotFoundException $e)
{
return '';
}
$route = $routeMatch['_route'];
return $route;
}
}
seems like you need to have a payload for your redirect to point to. it seems like obscure concept code to me. I would also advise you to make sure your configuration files point to the correct redirect code snippet. Check your server access file to make sure it has redirects enabled also.
I want to create a filter for my add, update, and delete actions in my controllers to automatically check if they
were called in a POST, as opposed to GET or some other method
and have the pageInstanceIDs which I set in the forms on my views
protects against xss
protects against double submission of a form
from submit button double click
from back button pressed after a submision
from a url being saved or bookmarked
Currently I extended \lithium\action\Controller using an AppController and have my add, update, and delete actions defined in there.
I also have a boolean function in my AppController that checks if the appropriate pageInstanceIDs are in session or not.
Below is my code:
public function isNotPostBack() {
// pull in the session
$pageInstanceIDs = Session::read('pageInstanceIDs');
$pageInstanceID = uniqid('', true);
$this->set(compact('pageInstanceID'));
$pageInstanceIDs[] = $pageInstanceID;
Session::write('pageInstanceIDs', $pageInstanceIDs);
// checks if this is a save operation
if ($this->request->data){
$pageInstanceIDs = Session::read('pageInstanceIDs');
$pageIDIndex = array_search($this->request->data['pageInstanceID'], $pageInstanceIDs);
if ($pageIDIndex !== false) {
// remove the key
unset($pageInstanceIDs[$pageIDIndex]);
Session::write('pageInstanceIDs', $pageInstanceIDs);
return true;
}
else
return false;
} else {
return true;
}
}
public function add() {
if (!$this->request->is('post') && exist($this->request->data())) {
$msg = "Add can only be called with http:post.";
throw new DispatchException($msg);
}
}
Then in my controllers I inherit from AppController and implement the action like so:
public function add() {
parent::add();
if (parent::isNotPostBack()){
//do work
}
return $this->render(array('layout' => false));
}
which will ensure that the form used a POST and was not double submitted (back button or click happy users). This also helps protect against XSS.
I'm aware there is a plugin for this, but I want to implement this as a filter so that my controller methods are cleaner. Implented this way, the only code in my actions are the //do work portion and the return statement.
You should probably start with a filter on lithium\action\Dispatcher::run() here is some pseudo code. Can't help too much without seeing your parent::isNotPostBack() method but this should get you on the right track.
<?php
use lithium\action\Dispatcher;
Dispatcher::applyFilter('run', function($self, $params, $chain) {
$request = $params['request'];
// Request method is in $request->method
// Post data is in $request->data
if($not_your_conditions) {
return new Response(); // set up your custom response
}
return $chain->next($self, $params, $chain); // to continue on the path of execution
});
First of all, use the integrated CSRF (XSRF) protection.
The RequestToken class creates cryptographically-secure tokens and keys that can be used to validate the authenticity of client requests.
— http://li3.me/docs/lithium/security/validation/RequestToken
Check the CSRF token this way:
if ($this->request->data && !RequestToken::check($this->request)) {
/* do your stuff */
}
You can even check the HTTP method used via is()
$this->request->is('post');
The problem of filters (for that use case) is that they are very generic. So if you don't want to write all your actions as filterable code (which might be painful and overkill), you'll have to find a way to define which method blocks what and filter the Dispatcher::_call.
For CSRF protection, I use something similar to greut's suggestion.
I have this in my extensions/action/Controller.php
protected function _init() {
parent::_init();
if ($this->request->is('post') ||
$this->request->is('put') ||
$this->request->is('delete')) {
//on add, update and delete, if the security token exists, we will verify the token
if ('' != Session::read('security.token') && !RequestToken::check($this->request)) {
RequestToken::get(array('regenerate' => true));
throw new DispatchException('There was an error submitting the form.');
}
}
}
Of course, this means you'd have to also add the following to the top of your file:
use \lithium\storage\Session;
use lithium\security\validation\RequestToken;
use lithium\action\DispatchException;
With this, I don't have to repeatedly check for CSRF.
I implemented something similar in a recent project by subclassing \lithium\action\Controller as app\controllers\ApplicationController (abstract) and applying filters to invokeMethod(), as that's how the dispatcher invokes the action methods. Here's the pertinent chunk:
namespace app\controllers;
class ApplicationController extends \lithium\action\Controller {
/**
* Essential because you cannot invoke `parent::invokeMethod()` from within the closure passed to `_filter()`... But it makes me sad.
*
* #see \lithium\action\Controller::invokeMethod()
*
* #param string $method to be invoked with $arguments
* #param array $arguments to pass to $method
*/
public function _invokeMethod($method, array $arguments = array()) {
return parent::invokeMethod($method, $arguments);
}
/**
* Overridden to make action methods filterable with `applyFilter()`
*
* #see \lithium\action\Controller::invokeMethod()
* #see \lithium\core\Object::applyFilter()
*
* #param string $method to be invoked with $arguments
* #param array $arguments to pass to $method
*/
public function invokeMethod($method, array $arguments = array()) {
return $this->_filter(__METHOD__, compact('method', 'arguments'), function($self, $params){
return $self->_invokeMethod($params['method'], $params['arguments']);
});
}
}
Then you can use applyFilter() inside of _init() to run filters on your method. Instead of checking $method in every filter, you can opt to change _filter(__METHOD__, . . .) to _filter($method, . . .), but we chose to keep the more generic filter.