symfony 1.4 passing variables between templates and actions
I've got an index page which includes a call to a series of partials through a switch statement; and it works. I now need to restrict access to the partial dependent upon the user's type; furthermore, I believe my switch statement should be in the actions class according to MVC, but I can't get that to work either. This might be better explained through example:
Here's my file structure for the dashboard module:
..dashboard
..actions
..config
..templates
_admins.php
_employers.php
_employees.php
_guest.php
indexSuccess.php
Here is my current indexSuccess template (which currently works... but without restricting access if the logged user's type doesn't match the page type):
$type = sfContext::getInstance()->getUser()->getGuardUser()->getProfile()->getType()->getName();
switch($type)
{
case ('Employer'):
include_partial('dashboard/employers');
$page_user_type = "employer"; //this example line currently does not exist, it's for example purpose below
$break;
case ('Employee'):
include_partial('dashboard/employees');
break;
case ('Administrator'):
include_partial('dashboard/admins');
break;
default: include_partial('dashboard/guest');
break;
}
Here's my actions class (currently empty):
public function executeIndex(sfWebRequest $request)
{
}
Basically, what I need is the switch statement moved to the action (I think), and a forward404Unless() method added that does the following:
$logged_user = sfContext::getInstance()->getUser()->getGuardUser()->getId();
$this->forward404Unless($logged_user == $page_user_type); //where the $page_user_type variable is retrieved by the switch statement in the example line above.
I've tried using the getAttribute() and setAttribute() with no success... and I'd rather not share attempts due to embarrassment. Just a beginner here...
Any help would be appreciated. Thanks in advance.
UPDATE:
Here's more information about the switch and the different partials:
The switch renders a different partial based upon the user's type. What it doesn't do is keep other logged-in users of a different type from accessing all the other partials... which in my design, is very bad. For example: logged-in users of type "employer" may not view the partial of type "employee". Currently they can (by explicitly typing in the other url), even though they are being redirected to the appropriate page during the the index action.
The 404 page should be called when a user of the wrong type tries to access the other partial by explicitly typing in the url. That's why I was attempting to add a variable to the switch statment when the appropriate partial is called and then passing that variable to the index action which would then evaluate it and either permit the partial to be rendered, or if the user_type and partial_type did not match -> forward to a 404 page. Make sense? I hope I explained that thouroughly enough. I'm sure there is an easier way... I'm just not schooled enough to know what that might be.
I sure do appreciate your response and attempt to resolve my issue.
You should play with the credential system to block not authorized user to access a ressource.
The 'type' of your user can become the name of a credential. Then you just have to create the security.yml to handle that.
I'm having a little trouble understanding when the 404 should happen. Does this handle it?
Action:
public function executeIndex(sfWebRequest $request)
{
$this->profileType = $this->getUser()->getGuardUser()->getProfile()->getType()->getName();
$this->forward404Unless(in_array($this->profileType, array('type1', 'type2')), 'Invalid profile type');
}
It's perfectly acceptable to have a switch statement in a veiw, though if that is the entirety of indexSuccess.php you may wish to call sfAction::setTemplate, instead.
Okay, I figured this one out on my own. Here's what I did to get the desired result:
Changed the route so that it cannot be explicitly typed and accessed. Problem solved.
Related
I have a bit of a complicated issue. I could use some help.
I have a form that is being handled by the following function:
$module = request('module');
$classe = request('classe');
$horaire = request('horaire');
$date = request('date');
$students = DB::select('SELECT * FROM `etudiants` WHERE etudiants.id_classe = '.$classe);
return view('g_absence.absence',['module'=> $module, 'classe'=>$classe,'horaire'=>$horaire,'date'=>$date,'students'=>$students]);
I take the values $module, $class, $horaire, $date and $students and need to use them inside a different view: g_absence.absence. This works fine and when the view is returned I have access to said variables.
The issue is, inside the g_absence.absence view, I have another form that also needs to be handled, and because the url remains the same even tho a different view is returned, I cant make two posts for the same path.
web.php:
Route::get('/testboy', [App\Http\Controllers\g_absence::class,'index'])->name('marquer');
Route::post('/testboy',[App\Http\Controllers\g_absence::class, 'marquer']);
Route::post('/testboy',[App\Http\Controllers\g_absence::class, 'ajoutabsence']);
The first line is the one that send to the form page just a simple
return view
The second one handle the form in that view
The third one, I want it to handle the form inside the
g_absence.absence view, but they share the same path.
Excuse me if I'm being unclear, I'm a bit of a beginner in Laravel
your problem is using the same route for different methods
basically the first route gets executed every time you use the '/testboy' action that is why your second function never get's called.
you can solve this issue by changing your urls for example:
Route::post('/testboy-marquer',[App\Http\Controllers\g_absence::class, 'marquer']);
Route::post('/testboy-ajoutabsence',[App\Http\Controllers\g_absence::class, 'ajoutabsence']);
Or you can use one function that's handle both with one url by pathing additional parameter to your url depending on your function call :
Route::post('/testboy?type=marquer',[App\Http\Controllers\g_absence::class, 'ajoutabsence']);
in your function check the type :
if(request('type') == 'marquer') {
execute marquer logic here...
} else {
execute absence logic here...
}
Using method and path with two functionalities is wrong, but if you want to somehow use both routes my same method and path which I don't recommend you must let the request to pass through like a middleware in your first block of code Instead of return a view.
Recommended way is to have 2 routes with different paths or at least one route with a parameter by which you can determine what code block must be executed.
In my controllers that Gii creates it is common to see the following:
if($model->load(Yii::$app->request->post()) && $model->save()){
//.....do something such as redirect after save....//
}else
{
//.....render the form in initial state.....//
}
This works to test whether a POST is sent from my form && the model that I am specifying has saved the posted information (as I understand it).
I've done this similarly in controllers that I have created myself but in some situations this conditional gets bypassed because one or both of these conditions is failing and the form simply gets rendered in the initial state after I have submitted the form and I can see the POST going over the network.
Can someone explain why this conditional would fail? I believe the problem is with the 'Yii::$app->request->post()' because I have removed the '$model->save()' piece to test and it still bypasses the conditional.
Example code where it fails in my controller:
public function actionFreqopts()
{
$join = new FreqSubtypeJoin();
$options = new Frequency();
$model = new CreateCrystal();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
$model->insertFreqopts();
return $this->redirect(['fieldmap', 'id' => $join->id]);
} else {
return $this->render('freqopts', ['join' => $join, 'options' => $options]);
}
}
My initial thought was that I'm not specifying the correct "$model" in that I'm trying to save the posted data to FreqSubtypeJoin() in this case and the $model is CreateCrystal(); however, even when I change the model in this conditional it still fails. It would be helpful if someone could briefly explain what the method 'load' is actually doing in layman's terms if possible.
The load() method of Model class is basically populating the model with data from the user, e.g. a post query.
To do this it firstly loads your array of data in a form that matches how Yii stores your record. It assumes that the data you are trying to load is in the form
_POST['Model name']['attribute name']
This is the first thing to check, and, as long as your _POST data is actually getting to the controller, is often where load() fails, especially if you've set your own field names in the form. This is why if you change the model, the model will not load.
It then check to see what attributes can be massively assigned. This just means whether the attributes can be assigned en-mass, like in the $model->load() way, or whether they have to be set one at a time, like in
$model->title = "Some title";
To decide whether or not an attribute can be massively assigned, Yii looks at your validation rules and your scenarios. It doesn't validate them yet, but if there is a validation rule present for that attribute, in that scenario, then it assumes it can be massively assigned.
So, the next things to check is scenarios. If you've not set any, or haven't used them, then there should be no problem here. Yii will use the default scenario which contains all the attributes that you have validation rules for. If you have used scenarios, then Yii will only allow you to load the attributes that you have declared in your scenario.
The next thing to check is your validation rules. Yii will only allow you to massively assign attributes that have associated rules.
These last two will not usually cause load() to fail, you will just get an incomplete model, so if your model is not loading then I'd suggest looking at the way the data is being submitted from the form and check the array of _POST data being sent. Make sure it has the form I suggested above.
I hope this helps!
I am currently building a web app which has two models, Donor and Donation Models respectively. It has multiple user roles. When the staff user first registers a donor, I want him to be redirected to another form which allows him to fill in the Donation details(the donor is registered once the first donation is successful).
Firs of all, should I create a donation controller, from which I would redirect the user using:
return $this->redirect(array('controller'=>'donations','action'=>'add'));
For the above to work, it requires me to save the newly registered donor's id in a session like so :
$this->Session->write('id', $this->Donor->id);
So the user is redirected to 'donations/add' in the url, and this works fine.. However I think this has some flaws. I was wandering whether I should create another action inside the Donor controller called 'add_donation', which will have its respective 'View'. The idea is to be able to form a url of the sort : 'donors/add_donation/4' (4 being the donor_id ! )
This URL follows this construct: 'controller/action/id'
If anyone could shed some light on best practices, or describe any caveats to my solution(the former, using session etc.) , please do help a brother out! Ill be deeply indebted to you! Thanks in advance!
After you saved the data you can do this in the DonorsController:
$this->redirect(array(
'controller' => 'donations',
'action' => 'add',
$this->Donor->getLastInsertId()
));
There is no need to return a redirect, it's useless because you get redirected. Notice that we pass the last inserted record id as get param in the redirect. The redirect method of the controller calls by default _stop() which calls exit().
CakePHP3: There is a discussion about changing that default behavior in 3.0. Looks like in CakePHP 3.0 the redirect() won't exit() by default any more.
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
I would not use the session here, specially not by saving it to a totally meaningless and generic value named "id". If at all I would use always meaningful names and namespaces, for example Donor.lastInsertId as session key.
It's not always clear where to put things if they're related but the rule of thumb goes that things should go into the domain they belong to, which is pretty clear in this case IMHO.
Edit:
Leaving this edit here just if someone else needs it - it does not comply with the usage scenario of the asker.
If you have the user logged in at this stage, modify the add function to check if the userId passed is the same as the one logged in:
DonationsController:
public function add($donorId = null) {
// Get the donor to display it if you like to
if ($this->request->is('post')) {
if ($this->Auth->user('id') != $donorId) {
throw new InvalidArgumentException();
}
$this->request->data['Donation']['donor_id'] = $donorId;
// Save code here
}
}
You can use also the same controller using more models with uses.
Or you can also to ask to another controller with Ajax and morover to get response with Json.
I'd like to save a log access in my database with all links that the user visits.
The problem is that I can't save when is ajax request because I don't visit the link, for example when I delete a register, it's made by ajax call.
How can I globally detect when is the ajax call and save in my database?
My code to save the normal page registers (url that I visit) is:
$ip = CHttpRequest::getUserHostAddress(); // get ip
$id_user = Yii::app()->user->getId(); //get user
$url = Yii::app()->request->requestUri; //get url
$sql = "INSERT INTO log(id_user, date, hour, url, ip) VALUES (:id_user, 'now', 'now', :url, :ip)";
$rawData = Yii::app()->db->createCommand($sql)->queryAll(true, array(':id_user'=>$id_user,':url'=>$url,':ip'=>$ip));
If you override the beforeAction in your base controller (you are extending CController?) and put your code in it, it will be run on every action
If you want to differentiate if it's an ajax request you can use
Yii::app()->request->isAjaxRequest
http://www.yiiframework.com/doc/api/1.1/CHttpRequest#isAjaxRequest-detail
As mentioned in my comment and Rowan's answer, this is likely easiest to do by extending your base controller.
In the testdrive sample app, you'll notice there's a protected/components/Controller.php. If you look at the source, you'll see that that extends CController.
You'll also notice that the controllers at protected/controllers extend Controller, meaning that they'll include everything that Controller has, which by definition has everything that CController has.
So, the advice is to follows this idiom, and then in protected/components/Controller.php, override beforeAction(), which by default does nothing except return true. So in protected/components/Controller.php, just redefine beforeAction() to include your logging statements and any logic necessary.
Oh, and in response to your comment that "no url is visited", you do realize that an AJAX call is still visiting a URL of some form right? Namely the one specified via the url key? So if you really wanted, you could add the logging to whatever url that your AJAX is calling, but this is going to quickly become cumbersome, and you'll be violating the DRY principle.
I would like someone to explain me what _forward is exactly doing, I cannot see if _forward is also rendering the attached view to the action or just executing the action.
Also is it possible to pass argument to $this->action in a view script ?
More generally my problem is how to code a confirmation page, let's say the user input some stuff and you want to show him confirmation, is forward is mean for that case ?
_forward is an internal redirect. Where as _redirect sends a header that tells the client's browser to go to some other URL, _forward tells the Dispatcher to internally redirect the request somewhere else.
If you consider the normal dispatch order of:
preDispatch()
someAction()
postDispatch()
Calling _forward at any point in that progression will cause the following steps to not be executed. So if you call _forward in preDispatch(), someAction() will not be called and so on. If you _forward() in someAction() and you are using the viewRenderer action helper to render your views (you are letting the framework choose what view script to render), then no view script will be rendered in someAction().
When the request is forwarded to the new Controller / Module the entire dispatch process will be repeated there.
You can find out what action is being dispatched by using:
$action = $this->getRequest()->getParam('action');
$action will be the url form of the action so if the method is name 'someKindOfAction', $action will contain 'some-kind-of'. You can do this as well for controllers and modules.
My experience with Zend is limited and I hope I'm not showing you something you've already seen but according to the docs (12.7.6. Utility Methods):
_forward($action, $controller = null, $module = null, array $params = null): perform another action. If called in preDispatch(), the currently requested action will be skipped in favor of the new one. Otherwise, after the current action is processed, the action requested in _forward() will be executed.
So it sounds like the context of when it's called matters. In the latter case it will first execute the action from which it's been called then execute the forwarded action. The exception is when it's being called from the preDispatch handler
I think it's important to note that _forward is very inefficient, and you should always call your method directly. When you do a _forward, the init(), pre and post dispatch run again. Depending on what you have in your init, you can run (and insert) the same database record twice.
It is easy to use but wasteful. If you profile your code, and are banging your head to why everything is being called twice, _forward is the reason. If your like me and you instantiate a few objects in the init() for use throughout the class, you wind up instantiating everything twice! I did load testing on my code and I got better performance by calling the action name directly, like foo(), instead of _forward('foo');
Another off topic tip I think most people know, is it use single quotes wherever possible, sine the PHP parser has to check a string for embedded variables. I don't know how much real world performance this will give, especially if you are using an opcode cache, but it's a best practice.
Forward is ment to be used when external redirect is not the right options.
Use case (bit ankward, but best i can make up):
You have a form that can add your pet (either dog or cat). You have different models for each. You include a select in your form to select dog / cat. Then in your action you do:
if($form->isValid($_POST)){
switch($form->select->getValue()){
case "dog":
$this->_forward('add-dog','pets','default');
break;
case "cat":
$this->_forward('add-cat','pets','default');
break;
}
}
And you handle different things for cats and dogs in separate actions. The advantage of this is that ALL the parameters are sent along. In constrast when you'd used $this->_redirect() the POST parameters will be lost. That is in some cases intended behaviour (for example after adding a comment you make a redirect to comments list page to avoid double posts and the message "page needs to send data again...".
A part of the Framework docs I swear used to be there explained the dispatch workflow at a general level. Theres this diagram, but its awefully complicated to explain what _forward does.
When in an action _forward will set $request->isDispatched = false, and set up the request to call the controller/action specified in _forward. During postDispatch, the isDispatched is checked - if its false, the whole thing runs again using the new request.
So... If in your action you're manually rendering views, they'll still get rendered. Everything in the action will still happen, its just another action will ALSO happen afterwards.
[edit after question edit]
Forward is not meant for the response/confirm-after-post - use a redirect for that. $this->_helper->redirector->gotoUrl() etc.