CakePHP - Controller or No Controller? - php

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.

Related

CodeIgniter Ion Auth: How is is_admin() working?

I'm using the Ion_auth for CodeIgniter in my project auth library. I'm trying to make a similar function as is_admin() for other user types (there are three user types other than the admin) to check whether the logging in user is of one of those three types so I can redirect them to their respective pages. Ion_auth is using an event called is_admin but I can't understand where this is happening in the Ion_auth_model.php and how it checks whether the logged in user is admin. So how can I make other functions/events similar to this one?
I know I can just do something like query the database and check the user group but I want to do the way Ion_auth has done. That way I can also satisfy myself by actually understanding the stock code.
I am just looking at source code on github.
As lots of auth libraries store, information you are looking for in session and library itself checks session variables.
your function you are looking for is on line 447 in file application/libraries/Ion_auth.php
public function is_admin($id=false)
{
$this->ion_auth_model->trigger_events('is_admin');
$admin_group = $this->config->item('admin_group', 'ion_auth');
return $this->in_group($admin_group, $id);
}
In order to create your own I suggest you to reverse engeneer model (ion_auth_model.php)
But! Ion already has a workaround for you, this method is what you are looking for
in_group()
$group = 'gangstas';
if (!$this->ion_auth->in_group($group))
{
$this->session->set_flashdata('message', 'You must be a gangsta to view this page');
redirect('welcome/index');
}
source
The function is_admin() refers to the admin group defined in the file /application/config/ion_auth.php: $config['admin_group'] = 'admin'; // Default administrators group, use name.
The admin_group name itself is stored in the ion_auth database field groups.name.

Having an "edit" controller to deal with user editing? Does this design make sense?

Here is the flow:
User creates a text based post.
User edits a text based post (an edit page with the post info is displayed)
User submits the changes to the post (a request sent to the post controller)
Now, if I have MULTIPLE types of posts, I have to check in steps 2 and 3 that the user is indeed updating the RIGHT type of post because someone could very well alter the URL to edit a post of type A when it's really of type B. This leads to a lot of redundant code, such as ...
if(user is indeed the editor && the post type is correct) show the edit page
I think it would make a lot of sense to have an EDIT controller that does all the verification needed in the constructor (or maybe a base class?), and then calls the method. Have you encountered similar issues like this - and if not, does this make any design sense?
CodeIgniter is an MVC. That means that your controllers serve as an intermediate between your models (your data), and your view (front-end). "Edit" is an action that you do to objects, like data. Data objects should be organized within a controller, which calls the actual edit functions from the model.
I'm assuming you have a Post controller. At its core, it should have basic CRUD functions, like adding and editing posts. It should look something like this:
class Post extends CI_Controller
{
function __construct()
{
parent::__construct();
}
function index()
{
// List all posts, perhaps?
}
function add()
{
// Add a post
}
function edit($post_id)
{
// Edit a post
}
function view($post_id)
{
// View a post
}
}
That will give you the following pages:
http://example.com/post
http://example.com/post/add
http://example.com/post/view/1
http://example.com/post/edit/1
Checking for user permissions is its own chapter. If you are using a library like Tank Auth, you can check permissions like so:
if ($this->tank_auth->is_logged_in()) {
// Do stuff
}
That should go at the beginning of each function - or in the __construct(), if you want to DRY it up completely.
Good luck.

Working with related model in CakePHP

This is a fairly basic question about CakePHP, but since my knowledge of this framework is rather rusty, it is making me lose a lot of time.
I have a ManyToMany relation between Guest and Present. Whenever a new Guest is created and associated with a present, I would like to mark the Present as taken. If the present is already taken, some error should arise. The reason why I am not just declaring that a Guest hasMany Presents is because in the future things may change and more than one guest could associate to a present, so I prefer to avoid a Db migration.
My Guest::add() action looks like follows. It is called with a POST with the data of a new Guest and the id of an existing Present.
public function add() {
if ($this->request->is('post')) {
$id = $this->request->data['Present']['id'];
$this->Guest->create();
$present = $this->Guest->Present->findById($id);
if ($present['Present']['taken']) {
throw new ForbiddenException();
}
if ($this->Guest->save($this->request->data)) {
if ($this->Guest->Present->saveField('taken', true)) {
// Give the guest a uuid and proceed with a welcome message
$this->Guest->read();
$this->set('uuid', $this->Guest->data['Guest']['uuid']);
}
}
}
else {
throw new ForbiddenException();
}
}
What happens is that a new Guest is created (correct) and associated with the given present (correct) but when I save the taken field a new present is created instead of modifying the given one.
What is the correct way to proceed to update the current Present?
If it is of any help, I am using CakePHP 2.0
For obtaining the model data by the primary key it's better to use theIn addition read method:
$present = $this->Guest->Present->read(null, $id);
The read method sets the model's id attribute so that further calls to other methods affect the same data record, rather than creating a new one. This should solve the problem you are having.
Model callbacks tend to be better suited for these situations. You could add a beforeSave callback to the Guest class to checks if the present is already taken, and not allow the creation if it is. This way the model logic is left in the model layer and you don't need to do any extra work e.g. if the constraint has to be enforced also when existing Guests are saved, or created from different controllers or actions.
It sounds like the ID of the model you are trying to save is losing scope. You should be able to resolve your issue by updating your code:
...
if ($this->Guest->save($this->request->data)) {
$this->Guest->Present->id = $id;
if ($this->Guest->Present->saveField('taken', true)) {
...

symfony 1.4 passing variables between templates and actions

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.

CakePHP redirect on login depending on username

I have a CakePHP 1.3 application that has a login system, which works well. It uses a DB with a users table, which existed before creating this app.
I'm using Auth in my AppController. The login function looks like
function login() {}
and it's located in the users_controller.
Everything works fine, as I said, but I have problems trying to add a new functionality. I would like to, during the login process, detect if a user has introduced a specific combination of login/password (let's say admin/adminpwd). If so, the login should be succesful AND he would be taken to an admin area (/admin/index). Otherwise, the login process should work as usual.
Once in this admin area (controlled by an admin_controller), this user should be able to perform some actions exclusive to him, no to the rest of users (even if they type on the browser /admin/action).
I've read about ACL, and probably it would help with this, but it seems too complicated for what I really need. Is there any simple way to do this? I guess I should modify the login function, but I don't really know how exactly, and if there's anything else I should change... any ideas?
Yeah, ACL is pretty complicated (and powerful). But in your case, I'd suggest create a 'group' field in users table to distinguish the role of the user. So you can have more admins later if you want. It's more flexible than hard-code a certain login credential in your users_controller.
There are several things you need to do to:
Tell the Auth component to transfer control to you after the user logins, so you can determine their group and redirect them accordingly.
Check if a user in a group is accessing some other group's action: If you don't, a regular user just need to be logged in, and they can type in admin url (if they know about it) and they can do everything an admin can. This check will probably be done in before_something_() in app_controller or tap into Auth somewhere.
I don't remember all the details, but you can get everything you need in the Cake Cookbook. Good luck!
Let's just see some code...
class UsersController extends AppController {
// we're moving the variable to AppController!
public function login() {
$usrInfo = $this->Auth->user();
if (isset($usrInfo) {
// this index name might not be right. I'm going off memory please check this!
if (in_array($usrInfo['username'], $this->adminUsers)) {
// do your code here for admin users.
// could be a redirect or just changing the layout used
} else {
// is a user that is logged in but not in our admin list
}
}
}
To test if the user is logged in you would need to do something like the following:
class AppController extends Controller {
protected $adminUsers = array('joe_blow_uname', 'jane_blow_uname');
public function beforeFilter() {
$routing = Configure::read('Routing.admin');
$usrInfo = $this->Auth->user();
if (isset($this->params[$routing]) && isset($usrInfo)) {
if (!in_array($usrInfo['username'], $this->adminUsers)) {
// do code here for non-admin users using /admin prefix
}
}
}
}
Let me know if this doesn't help.
Or worse breaks something...
Edit:
This is really not the best way to do this obviously. ACL or setting up some kind of group in your database would probably be better. BUT, it is a relatively quick-n-dirty way that, for a small site, should work fine.

Categories