Why does CakePHP transform POST requests into PUT? - cakephp

I'm running CakePHP 4.4.7 using PHP 8.0.23.
I have a controller
class StrawberriesController extends AppController
{
public function eat($id = null)
{
\Cake\Log\Log::debug(json_encode($this->request->getMethod()));
$this->request->allowMethod(['get', 'post']);
...
}
}
I have a template with a form
<?= $this->Form->create($entity, [
'url' => [
'controller' => 'Strawberries',
'action' => 'eat',
$entity->id,
],
]) ?>
<?= $this->Form->text('is_asap') ?>
<?= $this->Form->submit('now') ?>
<?= $this->Form->end() ?>
When I submit the form, the debug line shows
2022-11-03 16:58:06 debug: "PUT"{
"scope": []
}
I expected to receive an HTTP POST request. Especially because using the devtools:
in the elements tab, the form element has the attribute method="post"
in the network tab, the HTTP method inside the sent request is a POST
So why is the HTTP method transformed by CakePHP from POST to PUT?
How can I receive a POST request on my controller?

When you pass an entity with an ID (an existing element) in the first parameter of your $this->Form->create() CakePHP consider that is an update of your entity. And the convention for an update is to use "PUT" or "PATCH".
You can force the POST method like this :
$this->Form->create($entity, [
'url' => ['controller' => 'Strawberries', 'action' => 'eat', $entity->id],
'type' => 'POST'
]);
Doc : https://book.cakephp.org/4/en/views/helpers/form.html#options-for-form-creation

Related

CakePHP 3.8 changing urls generated by Paginator numbers to relative

The app has some prefixed routes like so:
Router::scope('/ru', function (RouteBuilder $routes) {
$lang = 2;
$routes->setRouteClass(DashedRoute::class);
$routes->connect('/:slug', ['controller' => 'Pages', 'action' => 'pages', 'lang' => $lang])->setPass(['slug']);
$routes->fallbacks(DashedRoute::class);
});
Page in question: http://localhost:8765/ru/smi-o-nas where there are some pagination links using this method:
<?= $this->Paginator->numbers() ?>
It generates links including matched controller and action:
http://localhost:8765/ru/pages/pages/smi-o-nas?page=2
So is it that routes are not set up correctly, or there is an option to make Paginator generate relative links like this:
http://localhost:8765/ru/smi-o-nas?page=2
Looked at the docs, paginator helper code, but all I came up with is setting Paginator config url, which still does no good.
Using this:
$this->Paginator->setConfig('options.url', ['controller' => 'ru', 'action' => 'smi-o-nas']);
Simply sticks /ro/smi-o-nas/ in the middle for the link:
http://localhost:8765/ru/ru/smi-o-nas/smi-o-nas?page=2
So do I need to set up routes in a different way, or is there an option for relative urls?
Update
I figured out that a relative url would not work for paginator as it will result in urls like "localhost:8765/ru/ru/smi-o-nas/smi-o-nas?page=2?page=3"
Dodgy solution
<?php for($page_number = 1; $page_number <= $this->Paginator->counter("{{pages}}"); $page_number ++):?>
<?php if ($page_number == $this->Paginator->counter("{{page}}")): ?>
<span><?= $page_number ?></span>
<?php else : ?>
<?= $page_number ?>
<?php endif ; ?>
<?php endfor; ?>

Cakephp ajax form won't load the right view

I'm just trying to call a function from a form submision (which is also call from ajax).
I want to call the add method but it's the index which always called.
My ajax view with the ajax submission form :
<?php $count = count($files); ?>
<div id="message"></div>
<i><?= __('Nombre de fichiers liés : ') . $count ?></i>
<?= $this->Form->create('FilesManager', array('enctype' => 'multipart/form-data', 'url' => array('action' => 'add'))); ?>
<?= $this->Form->input('file', array('type' => 'file', 'label' => false, 'class' => 'form-control')); ?>
<?= $this->Js->submit('Envoyer', array('update' => '#message', 'div' => false, 'type' => 'json', 'async' => false)); ?>
<?= $this->Js->writeBuffer(); ?>
i've also tried :
<?= $this->Form->create(null, array('enctype' => 'multipart/form-data', 'url' => array('controller' => 'FilesManagers', 'action' => 'add'))); ?>
<?= $this->Form->create('FilesManager/add', array('enctype' => 'multipart/form-data')); ?>
EDIT
$this->Form->create('FilesManager', array('action' => 'add'))
Don't work for me but the form generated look like :
<form action="/agralis/files_managers/add" enctype="multipart/form-data" id="FilesManagerIndexForm" method="post" accept-charset="utf-8">...<input id="submit-838644811" type="submit" value="Envoyer"><script type="text/javascript">
//<![CDATA[
$("#submit-838644811").bind("click", function (event) {$.ajax({async:false, data:$("#submit-838644811").closest("form").serialize(), dataType:"html", success:function (data, textStatus) {$("#message").html(data);}, type:"post", url:"\/agralis\/FilesManagers"});
return false;});
//]]>
</script></form>
I can see that form action look good (when i copy/paste the action in the browser url he found the method) but the url called from the ajax submit button is wrong ! How can I change that ?
The helpers are working independently
The Js helper is not aware of the Form helper, they don't interact with each other, and so the form URL is unknown to the Js helper.
The solution for that problem is rather simple, just look at the docs:
http://book.cakephp.org/2.0/en/core-libraries/helpers/js.html#JsHelper::submit
The submit() method accepts a url option where you can specify a URL in case the default one that is based on the current request doesn't fit you.
$this->Js->submit('Envoyer', array(
'url' => array('action' => 'add')
// ...
));
File uploads are not supported
The next problem that you'll encounter is that the file upload doesn't work. That's not part of the question, but I'll broach the subject.
The Js helper doesn't support generating the required JavaScript to handle file uploads, it would need to make use of the XMLHttpRequest Level 2 (xhr2) functionalities that older browsers are lacking.
You'll have to write custom JavaScript to handle that. Explanations and tutorials regarding that can be found all over the web.

CakePHP pagination custom url

I am trying to implement pagination with custom url, when i try to paginate to the next page i am getting the following error.
The requested address '/adminuser/userlist/page:2' was not found on this server.
Here adminuser is the name of the admin user and userlist is the method to get all the users under this adminuser.
The url will look like this http://domain.com/adminuser/userlist
Here is my code
In view:
<?php $this->paginator->options(array('url' =>
array('path'=>$this->params['path']))); ?>
<div class="pagination">
<ul>
<li class="prevnext disablelink">
<?php echo str_replace('/users/','',$this->paginator->prev('« Prev'));?>
<li>
<?php echo str_replace('/users/','',$this->paginator->numbers());?>
</li>
<li class="prevnext">
<?php echo str_replace('/users/','',$this->paginator->next('Next »'));?>
</ul>
</div>
routes.php
Router::connect('/:sluguser/:action', array('controller' => 'users', 'action' => 'userlist'),array('pass' => array('sluguser')));
For pagination, using the default 'paramType' => 'named' you will need to extend your route to accept also the page parameter (as well as sort and direction). In your routes.php, you can add following:
// Parse only default parameters used for CakePHP's pagination:
Router::connectNamed(false, array('default' => true));
// the route to your controller action and wildcard (*) for the possible named parameter
Router::connect('/:sluguser/:action/*', array('controller' => 'users', 'action' => 'userlist'),array('pass' => array('sluguser')));
Btw. As I have found out recently named parameters are deprecated and totally removed in CakePHP 3.x, so consider using $this->paginate['paramType'] = 'querystring'; instead. Then it will work without those additional routes above.

Multiple form with same model name on single page cakephp

I have two form on a single page: login form and register form. When I submit the register form, it validates both: form fields that are in login and registeration. How can I handle it if both form have the same model (user model)
Register form
<?php echo $this->Form->create('User', array('url' => array('controller' => 'users', 'action' => 'add'))); ?>
<?php echo $this->Form->input('username', array('label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('email', array('label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('password', array('label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('confirm_password', array('type' => 'password', 'label' => false, 'div' => false, 'class' => 'reg_input'));?>
<?php echo $this->Form->submit(__('Submit', true), array ('class' => 'reg_button', 'div' => false));
echo $this->Form->end();?>
and Login form is below
<?php echo $this->Form->create('User', array('controller' => 'users', 'action' => 'login'))?>
<?php echo $this->Form->input('User.username',array('label'=>false,'div'=>false, 'class' => 'reg_input'));?>
<?php echo $this->Form->input('User.password',array('label'=>false,'div'=>false, 'class' => 'reg_input'));?>
<?php echo $this->Form->submit(__('Log in', true), array ('class' => 'reg_button', 'div' => false)); ?>
<?php echo $this->Form->end();?>
When I submit registration form it validates both forms, I want to validate only the registration form.
How can I handle that?
I've come up with a "solution" (I find the approach dirty, but it works) for a different question (very similar to this). That other question worked with elements and views, though. I'll post the entire solution here to see if it helps someone (though I rather someone else comes with a different approach).
So, first: change the creation names for the two forms.
//for the registration
<?php echo $this->Form->create('Registration',
array('url' => array('controller' => 'users', 'action' => 'add'))); ?>
//for the login
<?php echo $this->Form->create('Login',
array('controller' => 'users', 'action' => 'login'))?>
The forms should work, look and post to the same actions, so no harm done.
Second step: I don't have your action code, so I'm going to explain what needs to be done in general
public function login() {
if ($this->request->is('post')) {
//we need to change the request->data indexes to make everything work
if (isset($this->request->data['Login'] /*that's the name we gave to the form*/)) {
$this->request->data['User'] = $this->request->data['Login'];
unset($this->request->data['Login']); //clean everything up so all work as it is working now
$this->set('formName', 'Login'); //we need to pass a reference to the view for validation display
} //if there's no 'Login' index, we can assume the request came the normal way
//your code that should work normally
}
}
Same thing for the registration (only need to change 'Login' to 'Registration').
Now, the actions should behave normally, since it has no idea we changed the form names on the view (we made sure of that changing the indexes in the action). But, if there are validation errors, the view will check for them in
$this->validationErrors['Model_with_errors']
And that 'Model_with_errors' (in this case 'User') won't be displayed in the respective forms because we've changed the names. So we need to also tweak the view. Oh! I'm assuming these both forms are in a view called index.ctp, for example, but if they are on separate files (if you're using an element or similar) I recommend add the lines of code for all the files
//preferably in the first line of the view/element (index.ctp in this example)
if (!empty($this->validationErrors['User']) && isset($formName)) {
$this->validationErrors[$formName] = $this->validationErrors['User'];
}
With that, we copy the model validation of the User to the fake-named form, and only that one. Note that if you have a third form in that view for the same model, and you use the typical $this->form->create('User'), then the validation errors will show for that one too unless you change the form name for that third one.
Doing that should work and only validate the form with the correct name.
I find this a messy approach because it involves controller-view changes. I think everything should be done by the controller, and the view shouldn't even blink about validation issues... The problem with that is that the render function of Controller.php needs to be replaced... It can be done in the AppController, but for every updgrade of Cakephp, you'll have to be careful of copying the new render function of Controller.php to the one replacing it in AppController. The advantage of that approach, though, is that the "feature" would be available for every form without having to worry about changing the views.
Well, it's just not that maintainable anyway, so better to leave it alone if it's just for this one case... If anyone is interested on how to handle this just in the controller side, though, comment and I'll post it.
You can duplicate your model and change his name and define $useTable as the same table name.
Example :
class Registration extends AppModel {
public $useTable = 'users';
You define the action in form->create like Nunser for your login form
<?php
echo $this->Form->create('User',array(
'url' => array(
'controller' => 'Users',
'action' => 'login',
'user' => true
),
'inputDefaults' => array(
'div' => false,
'label' => false
),
'novalidate'=>true,
));
?>
and your registration form
<?php
echo $this->Form->create('Registration',array(
'url' => array(
'controller' => 'Users',
'action' => 'validation_registration',
'user' => false
),
'inputDefaults' => array(
'div' => false,
'label' => false
),
'novalidate'=>true,
));
?>
In your controller define a method for registration validation and the most important define the render
public function validation_registration(){
$this->loadModel('Registration');
if($this->request->is('post')){
if($this->Registration->save($this->request->data)){
--- code ---
}else{
--- code ---
}
}
$this->render('user_login');
}
Sorry for my english ! Have a nice day ! :D
The create method on your login form is missing the 'url' key for creating the action attribute. I tried to re-create this once I fixed this and could not. Maybe that will fix it?

How to get Authentication working again in CakePHP 2.0?

After migrating a fully functional Cake 1.3 application to the recently released 2.0 version Authentication has ceased to work.
I've changed the calling of the AuthComponent and the structure of the login action according to the updated 2.0 manual, to no avail. The strange thing is the user is actually validated by $this->Auth->login() as it reaches the part of the login function where the user is redirect to the url set by $this->Auth->redirect(). After that redirect however, $this->Auth->user() returns empty (as well as AuthComponent::user()) and the user isn't logged in by the Auth component.
Cake doesn't throw any error during the process, the flash messages for 'auth' remain empty.
Users are stored in a simple database table containing id, username, email, password and timestamp columns. The passwords are hashed and I've added some users using the new Cake 2.0 methods.
This is the code of AppController.php:
<?php
class AppController extends Controller {
public $helpers = array('Session', 'Html', 'Time', 'Form', 'Text');
public $components = array('Session', 'RequestHandler', 'Auth');
public function beforeFilter() {
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'maps', 'action' => 'index');
$this->Auth->logoutRedirect = array('controller' => 'maps', 'action' => 'index');
}
}
?>
UserController.php:
<?php
class UsersController extends AppController {
public $name = 'Users';
function beforeFilter() {
parent::beforeFilter();
}
function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirect());
}
}
}
function logout() {
$this->redirect($this->Auth->logout());
}
}
?>
User.php model. I've disabled form validation for the time being after I solve this problem:
<?php
class User extends AppModel {
public $name = 'User';
}
?>
The login view:
<?php
echo $this->Form->create('User');
echo $this->Form->input('username', array('label' => 'Username', 'before' => '<p class="input" id="username">', 'after' => '</p>', 'between' => '<br />', 'div' => false));
echo $this->Form->input('password', array('label' => 'Password', 'before' => '<p class="input" id="password">', 'after' => '</p>', 'between' => '<br />', 'div' => false));
echo $this->Form->end('Login');
?>
I also tried to setting some of the Auth features in the $components variable in the AppController, which didn't work as well:
public $components = array(
'Auth'=> array(
'loginAction' => array(
'controller' => 'users',
'action' => 'login',
),
'loginRedirect' => array(
'controller' => 'maps',
'action' => 'index',
),
'authenticate' => array(
'Form' => array(
'fields' => array('username', 'password')
)
)
)
);
What's causing the problem here? Routing maybe? I've commented out all routes except:
require CAKE . 'Config' . DS . 'routes.php';
UPDATE:
After adding some debug statements in the login method in the UsersController I now know $this->Auth->user() is actually populated with the correct user after the call to $this->Auth->login(). After the redirect to another controller the login session is lost completely, however. So I still don't know what's going wrong here.
UPDATE 2
I've restarted the process of migrating by taking my working 1.3 application and running the migration console script on it like I did the last time.
This time I noticed the script stopped because of two errors relating to custom components. Component classes should extend Component now, instead of the 1.3 default: Object.
After fixing these component errors I ran the migration script again (something I neglected to do during the first migration attempt) and implemented the new AuthCompenent call. So far everything seems to be working correctly. Not sure what's different now and what went wrong the first time, as Cake didn't output any error messages.
UPDATE 3
It's getting weirder. I thought I solved it, but after transferring my code to another development machine Auth suddenly stops working. It's working on my main setup, but while testing on another it fails again following the same scenario. I've cleared the cache to be sure, but it still isn't working. Cake doesn't generate any error output.
UPDATE 4
It appears to be a Session problem on my machine. I've just set the Session to be stored in a cookie and suddenly Auth starts working again. Not sure why the default Session isn't working and I don't know where to start debugging in that case.
Only cookie sessions appear to work, defining a database session has the same result as a regular session; Auth stops working.
Try it with use_trans_sid enabled in /Config/core.php:
Configure::write('Session', array(
//'defaults' => 'php'
'defaults' => 'cake',
'cookie' => 'CAKEPHP2',
'ini' => array('session.use_trans_sid' => true)
));
Did you try also to configure the Authentication handler ?
public $components = array(
'Auth'=> array(
'authenticate' => array('Form')
)
);

Resources