Save data to different model inside a controller - cakephp

I have a CakePHP controller named ProjectsController (and it's model named Project) and a Settings model.
Inside the ProjectsController, there is a settings function, where inside it I need to save data into the database using the Settings model. The function is the following:
public function settings($pid = null) {
if($this->request->is('post')) {
$this->loadModel('Settings');
$this->request->data['Project']['id'] = $pid;
if($this->Settings->save($this->request->data)) {
$this->Session->setFlash(__('Settings successfully set.'));
$this->redirect(array('controller' => 'projects', 'action' => 'view', $pid));
} else {
$this->Session->setFlash(__('Something went wrong! Please try again.'));
$this->redirect(array('controller' => 'projects', 'action' => 'settings', $pid));
}
}
}
But it always fails and returns Something went wrong!
I have also to say that the Settings model has a hasOne relationship with Projects
public $hasOne = 'Project';
This is my first time trying to achieve something like this so please help me and tell me what am I doing wrong here,why this is not working?
Thank you in advance!

As far as i know, a save() call, will save only the model's data. a saveAll() call will save any related (flat, first level) model's data.
You can also try $this->Model->associatedModel->save()

Related

Cakephp Auth Component - Home page redirect loop

I want to have a login form in my home page, the registered users should be redirected to users/index
with the below code, my home page is going to redirect loop
can anyone tell me where is the issue ??
Note:- infact it is perfectly working if i change the line to
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
App Controller
public function beforeFilter(){
$this->Auth->autoRedirect = false;
$this->Auth->loginAction = array('controller' => './', 'action' => 'index');
$this->Auth->loginRedirect = array('controller' => 'users', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => './', 'action' => './');
$this->Auth->authorize = 'controller';
$this->Auth->authError= 'You need permissions to access this page';
$this->Auth->allow('index');
$this->set('Auth',$this->Auth);
}
UsersController
public function login(){
$id = $this->Auth->user('id');
if(empty($id)){
if($this->request->is('post')){
if($this->Auth->login()){
$this->redirect($this->Auth->redirect());
}else{
$this->Session->setFlash('Invalid Username or password');
}
}
}else{
$this->redirect(array('action'=>'index'));
}
}
Thanks for the help...
You pretty much answered your own question here:
Note:- infact it is perfectly working if i change the line to
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
Indeed that would work and that is what it should look like. Right now, you're telling the auth component your loginAction (the action which holds your login logic) is the index action of the ./ controller (which doesn't even exist). I'm assuming you're confusing it with the loginRedirect variable, which is for setting the page to go to after successful authentication.
If you only want Registered Users to Access Your Site you could have something like this... at least, this is how I implement something similar in my site...
In your app_controller file add the following to the beginning of your beforeFilter() function
function beforeFilter(){
//Check if user was able to log in thru Auth using your form in the homepage
if($this->isLoggedIn() == TRUE){
$this->layout = 'default'
}else{
// You can created this layout with a login form and
// whatever else you need except <?php echo $content_for_layout; ?>
// Any registered user will be allowed to login using the form
// and continue on to your site using the default layout
// But it guarantees no one else can see your default site
$this->layout = "unregistered_user"
}
}
On your App_controller.php you can create this function
function isLoggedIn(){
// You can also use $this->Auth->user directly in your App's beforeFilter()
// But I just like to have functions so I can reuse
if($this->Auth->user()){
$loggedin= TRUE;
}else{
$loggedin= FALSE;
}
return $loggedin;
}
I have something similar of my site but is only used when in maintenance mode. I am still developing my site. The only problem I've seen with this way, which I have not yet have time/need to look at, is that my errors are not sent to the layout I want. Supposed a user types in http://www.mydomain.com/inexistentpage then cake transfers them to my default layout. It might be easy to fix, but I havent got time to do that yet.
NOTE: I quickly did this off the top of my head and because of it, this code is untested. However, if you have any issues please let me know and I will test it and post back.
using $this->requestAction(anotherController/action); in the view might call to another controller->action. you must ensure that the another controller->action has the right permissions. or you'll get redirect loop.
solve it by adding $this->auth->allow('action name'); to the another controller page in the beforeFilter() callback.

How to tell cakephp to go to someurl.com/action/var1/var2

Please bear with me as I am a cakephp noob. I have this app that should go to www.name.com/complexes/somecomplex/unitnumber. I can correctly get it to advance to www.name.com/complexes/somecomplex, but I don't know how to get the full path to my unit number.
Here is my controller:
class ComplexesController extends AppController {
public $name='Complexes';
public $uses=array('User', 'Complex', 'Unit');
public $layout='pagelayout';
public function view() {
$this->set('complex', strtoupper($this->params['id']));
$c=$this->Complex->find('first', array('conditions'=>array('complex_name'=>$this->params['id'])));
$this->set('complex_data', $c);
}
}
and here is my route
Router::connect('/complexes/:id', array('controller' => 'complexes', 'action' => 'view'));
Where do I write the action for calling up a specific unit? Inside my 'view' action or another action called 'unit'? And how do i tell cake to route to that?
I figured it out! I had to pass an argument, called $unit in my view() function, where I set the conditions to look for the unitnumber that I had entered in the url ($unit). so, my code ended up looking like this:
public function view($unit=null) {
$this->set('complex', strtoupper($this->params['id']));
$c=$this->Complex->find('first', array('conditions'=>array('complex_name'=>$this->params['id'])));
$this->set('complex_data', $c);
$u=$this->Unit->find('first', array('conditions'=>array('unitnum'=>$unit)));
$this->set('unit', $u);
}
and in my routes file I added this line:
Router::connect('/complexes/:id/*', array('controller' => 'complexes', 'action' => 'view'));
And it worked!

CakePHP Logout issues

I know this must be something really stupid, but I am having issues with logging out. I can still see the full user Auth variable after calling Auth->logout(). In my users controller I have the standard:
function login()
{
}
function logout()
{
$this->redirect($this->Auth->logout());
}
But when I call logout, in my view I can still print the User by doing this:
$auth = $this->Session->read('Auth.User');
print "<pre>";
print_r($auth);
print "</pre>";
Am I missing something basic here? Thanks!
mine:
function logout() {
$this->Session->destroy();
$this->redirect($this->Auth->logout());
}
you have not allowed the use of the logout function, and the user is redirected instead of logged out.
in your controller containing logout the function, add this in your before filter:
$this->Auth->allow('logout');
In your app controller you must define a loginAction, in case of a not authorized entry, the view is redirected to that URL
'Auth' => array(
'loginRedirect' => array('controller' => 'products', 'action' => 'all'),
'logoutRedirect' => array('controller' => 'products', 'action' => 'index'),
'loginAction' => array('controller'=>'admins', 'action'=>'login'),
)
You'll find that if you just create a beforeFilter() function in UserController with that one line, you'll break the authorization on the Users model. That is, any user will be able to do users/add, users/edit, etc. To fix this, make sure you call AppController's beforeFilter. The complete beforeFilter() function looks like this:
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('logout');
}
If CakePHP is using PHP sessions and not rolling their own, you could just clear out the session on logout via session_destroy();. Sorry I have no CakePHP experience, so I'm just going off of an assumption.
what cake version do you have? I think you have to manually clear session in Cake 1.2. In newer Cake, if the logout function is called, it would clear out Auth.User; I'm sure on that.
I can't see a reason why this shoudn't work as I use exactly the same code...
did you confirm that the method is actually called? a simple "die('xyz')" etc before the Auth logout part can confirm that your action code is triggered.

cakePHP auth component not working

I have an issue with cake's auth that I simply can't seem to get past (i've been debugging and trying different tutorials for the last two days). As far as I can see it should be very simple, the problem is whenever i try to login, it just refreshes the login page. I cannot for the life of me figure out why! My only conclusion is that there must be something (basic) which tutorials take for granted that I have missed.
Here are a couple of snippets:
users_controller.php
class UsersController extends AppController {
var $name = 'Users';
function beforeFiler() {
parent::beforeFilter();
}
function login() {
}
function logout() {
$this->Session->setFlash('You have successfully logged out.');
$this->redirect($this->Auth->logout());
}
}
app_controller.php
class AppController extends Controller {
var $helpers = array('Html','Form','Javascript');
var $components = array('Auth');
function beforeFilter() {
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'contents', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => 'contents', 'action' => 'view');
$this->Auth->loginError = 'Something went wrong';
$this->Auth->allow('register', 'view');
$this->Auth->authorize = 'controller';
$this->set('loggedIn', $this->Auth->user('id'));
}
function isAuthorized() {
return true;
}
}
login.ctp
<div class="midCol short">
<h3>Login</h3>
<div class="loginBox">
<?php e($form->create('User', array('controller'=>'users','action'=>'login')));?>
<?php
echo $this->Form->input('username');
echo $this->Form->input('password');
e($this->Form->end(array('label'=>'Login', 'class'=>'loginButton button png')));?>
</div>
</div>
Any help would be greatly appreciated, this has me tearing my hair out!
Just for documentation as I had difficulties finding an answer for CakePHP 2.x on the web. This stuff needs to be "correct" in order to use Form authentication:
The config needs to be right, e.g. in your UsersController (the fields config is really only required when names differ in the DB):
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'fields' => array(
'username' => 'username',
'password' => 'password'
),
)
)
)
);
You have to use the Form Helper: Form->create adds a hidden input field ("post"), and the names of the input fields generated by Form->input() follow a convention that the Auth component expects.
User->login must not pass custom data to Auth->login(). The Auth component will take the auth data from the form (= request).
Thanks for the advice, but I ended up scrapping it and building again from scratch. Not exactly sure why it was originally breaking, probably not calling inbuilt functions with American English!
The Auth component will redirect to the page before you logged in. If that page was the login page that's where it'll redirect to.
When you're testing, it's likely that you're refreshing the login page, so on successful login that's where you're redirected to. You can check this by trying to perform an Auth protected action after logging in.
This gives me a lot of headaches as well - I think the current functionality of the component is a little clumsy in that respect.
I had the exact same problem and found that I had to restart mySQL service. Once it was restarted I stopped getting the login page being redirected. Hope that helps.
Gonna throw something in here. I was having an almost unresolveable problem with cakephp authentication. Ended up doing some debugging around it and found that during my database prep I had created a field for the password which was perfectly able to store normal size passwords... but.... when you start applying password hashing you need a lot more. My code was fine, but I had to add a bunch more space into the VARCHAR field for the password before I could log in. If you're having a problem with authentication - make sure your password field is adequately sized and not getting truncated like mine was. Took me a whole day to find that. DOH!
Correct me if i am wrong but must there not be code for redirection or something inside the function of login
function login() {
}
should it not be something like
public function login()
{
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error(__('Invalid username or password, try again'));
}
}

Adding a prefix to every URL in CakePHP

What's the cleanest way to add a prefix to every URL in CakePHP, like a language parameter?
http://example.com/en/controller/action
http://example.com/ru/admin/controller/action
It needs to work with "real" prefixes like admin, and ideally the bare URL /controller/action could be redirected to /DEFAULT-LANGUAGE/controller/action.
It's working in a retro-fitted application for me now, but it was kind of a hack, and I need to include the language parameter by hand in most links, which is not good.
So the question is twofold:
What's the best way to structure Routes, so the language parameter is implicitly included by default without having to be specified for each newly defined Route?
Router::connect('/:controller/:action/*', ...) should implicitly include the prefix.
The parameter should be available in $this->params['lang'] or somewhere similar to be evaluated in AppController::beforeFilter().
How to get Router::url() to automatically include the prefix in the URL, if not explicitly specified?
Router::url(array('controller' => 'foo', 'action' => 'bar')) should return /en/foo/bar
Since Controller::redirect(), Form::create() or even Router::url() directly need to have the same behavior, overriding every single function is not really an option. Html::image() for instance should produce a prefix-less URL though.
The following methods seem to call Router::url.
Controller::redirect
Controller::flash
Dispatcher::__extractParams via Object::requestAction
Helper::url
JsHelper::load_
JsHelper::redirect_
View::uuid, but only for a hash generation
Out of those it seems the Controller and Helper methods would need to be overridden, I could live without the JsHelper. My idea would be to write a general function in AppController or maybe just in bootstrap.php to handle the parameter insertion. The overridden Controller and Helper methods would use this function, as would I if I wanted to manually call Router::url. Would this be sufficient?
This is essentially all the code I implemented to solve this problem in the end (at least I think that's all ;-)):
/config/bootstrap.php
define('DEFAULT_LANGUAGE', 'jpn');
if (!function_exists('router_url_language')) {
function router_url_language($url) {
if ($lang = Configure::read('Config.language')) {
if (is_array($url)) {
if (!isset($url['language'])) {
$url['language'] = $lang;
}
if ($url['language'] == DEFAULT_LANGUAGE) {
unset($url['language']);
}
} else if ($url == '/' && $lang !== DEFAULT_LANGUAGE) {
$url.= $lang;
}
}
return $url;
}
}
/config/core.php
Configure::write('Config.language', 'jpn');
/app_helper.php
class AppHelper extends Helper {
public function url($url = null, $full = false) {
return parent::url(router_url_language($url), $full);
}
}
/app_controller.php
class AppController extends Controller {
public function beforeFilter() {
if (isset($this->params['language'])) {
Configure::write('Config.language', $this->params['language']);
}
}
public function redirect($url, $status = null, $exit = true) {
parent::redirect(router_url_language($url), $status, $exit);
}
public function flash($message, $url, $pause = 1) {
parent::flash($message, router_url_language($url), $pause);
}
}
/config/routes.php
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
Router::connect('/:language/', array('controller' => 'pages', 'action' => 'display', 'home'), array('language' => '[a-z]{3}'));
Router::connect('/:language/pages/*', array('controller' => 'pages', 'action' => 'display'), array('language' => '[a-z]{3}'));
Router::connect('/:language/:controller/:action/*', array(), array('language' => '[a-z]{3}'));
This allows default URLs like /controller/action to use the default language (JPN in my case), and URLs like /eng/controller/action to use an alternative language. This logic can be changed pretty easily in the router_url_language() function.
For this to work I also need to define two routes for each route, one containing the /:language/ parameter and one without. At least I couldn't figure out how to do it another way.
rchavik from IRC suggested this link: CakePHP URL based language switching for i18n and l10n internationalization and localization
In general, it seems that overriding Helper::url might be the solution.
An easier way might be to store the chosen language in a cookie and then not have to rewrite all the URLs. You could also potentially detect the user's browser language automatically.
However, search engines would be unlikely to pickup the various languages and you'd also lose the language if someone tried to share the link.
But love the full solution you posted, very comprehensive, thanks. :-)

Resources