my main purpose to include some pagedata on BaseController, To do so, i decided to put a protected function under basecontroller and i used it to display pages ( instead of return view('page') );
example of my code,
Under BaseController
protected function display($page = NULL,$data = NULL){
global $settings;
$general_data = array(
'activeGoogleAnalytics' => $settings['activeGoogleAnalytics']
);
$data = array_merge($data,$general_data);
echo view($page,$data);
exit; // if exit is removed, csrf error is not appear, else there is csrf error while submitting forms
}
Then, i use display function as follows in my controllers,
public function index()
{
//get some data
$this->display('login/index',array(
'someData' => '$someData',
));
}
This is actually working fine, i am merging two different arrays, and some content which are same in all pages are assigned to data array from BaseController.
My problem is whenever i submit form, i get csrf error "You cannot Perform this action". Even if i remove csrf protection from forms i got this error.
I found solution to the problem, by removing exit; from display function. But i am not sure what causes this problem or it is right decision. Because codeigniter is a framework, there should be better way of assigning page data ( which is same in all pages like website settings ).
Without using exit after echo, probably i ll face with AJAX problems in the future. Hence, i want to keep exit, or i want to learn easy way of assigning some data to view 's array from BaseController for all pages.
Related
I'm looking to send the user to another page via a controller method. The other page expects POST data.
Normally, the page is accessed with a postLink(). Is there a way to use this in the controller, perhaps with redirect()?
A little bit old but still no answer accepted so...
The answer is no, and yes.
No, there is no direct method since you cannot pass POSTed data using redirect() function.
You could use requestAction(), since you can pass data as posted (see requestAction() here for version cakePHP>=2.0).
In that case you pass an url and then an array having the key data with the posted data, something like
$this->requestAction($url, array('data' =>$this->data));or if you prefer$this->requestAction($url, array('data' =>$this->request->data));
The problem with requestAction() is that the result is environmentally as if you were generating the page of the requested action in the current controller, not in the target, resulting in not very satisfactory effects (at least not usually for me with components behaving not very nicely), so still, no.
...but Yes, you can do something very similar using the Session component.
This is how I usually do it. The flow would be something like this:
View A=>through postLink() to Action in A controller=>=>A controller request->data to Session variable=>=>action in B controller through redirect()=>=>set B controller request->data from Session variable=>=>process data in B controller action=> View B
So, in your A controller, let's say in the sentToNewPage() action you would have something like
//Action in A controller
public function sentToNewPage()
{
$this->Session->write('previousPageInfo', $this->request->data);
$url = array('plugin' => 'your_plugin', 'controller'=>'B',
'action'=>'processFromPreviousPage');
$this->redirect($url);
}
and in B controller:
//Action in B controller
public function beforeFilter()
{//not completelly necessary but handy. You can recover directly the data
//from session in the action
if($this->Session->check('previousPageInfo'))
{$this->data = $this->Session->read('previousPageInfo')};
parent::beforeFilter();
}
public function processFromPreviousPage()
{
//do what ever you want to do. Data will be in $this->data or
// if you like it better in $this->request->data
$this->processUserData($this->request->data);
//...
}
Best solution would be use javascript to redirect.
But if you want more cake I give you some tools
CakeAPI: requestAction - it allow to execute controller method of desire with parameters, if you pass 'return', it will return full view output for that action.
//very useful in views
$result = $this->requestAction('Controller/method',
array('return','user_id'=>$userId)
);
parameter will be accessible in controller via request param
$this->request->params['user_id']
Long and short is that it's not easy to emulate an HTML form with POST data and a redirect, you kind of need to set a bunch of hidden variables containing the data and automatically post the form to your destination via Javascript.
What I would do is take the processing functionality out of the function that requires POST variables, and make it generic so that you can call it from both of your functions.
Consider this rough example:
public function myPostDataAction() {
$name = $_POST['name'];
$age = $_POST['age'];
// do stuff
echo $name . ', ' . $age;
}
Let's say that is the action you are trying to post data to in this scenario, but you can't because you can't emulate those $_POST variables over a redirect without the scenario mentioned at the top here. You can do this:
public function myPostDataAction() {
$name = $_POST['name'];
$age = $_POST['age'];
// call common function
echo $this->getMyResults($name, $age);
}
// accessible from inside the controller only
private function getMyResults($name, $age) {
return $name . ', ' . $age;
}
Now you can also use that getMyResults() functionality by passing regular old variables into it:
public function myProblemFunction() {
$name = 'John';
$age = 15;
echo $this->getMyResults($name, $age);
}
Now, obviously you won't be outputting anything like that straight from the controller action, you'll be setting it to your views etc, but that's an example of how you can centralize functionality to be used in multiple locations.
For the disclaimer, this kind of thing is exactly what models are for, and you should definitely consider putting this kind of function into a model instead of a controller - it depends on your specific application.
With cakephp 2.x Use this:
$this->autoRender = false;
$request = new CakeRequest(Router::url(array('controller' => 'mycontroller','action' => 'my_action')));
$request->data('dataIndex','value');
$response = new CakeResponse();
$d = new Dispatcher();
$d->dispatch(
$request,
$response
);
but it will not redirect but dispatch to a different controller/action so if you went on /controller/oldaction it will stays the current url (no HTTP redirection is done).
You could still change the url with javascript
see: Change the URL in the browser without loading the new page using JavaScript
I'm a newbie to codeigniter and I'm attempting to write a function that would basically save a name and url to session data whenever you visited a certain page, then report it back in a small widget on the screen.
It's supposed to work as a kind of history function for what pages have been accessed and in what order. So far when working with test data it works great! however I'm trying to figure out how I can call the "add" function on each page that is visited, so we can supply the name and url of that page that was visited. Is there any way to do this? Or is there any way to report back a set of variables such as a name and url for a page after you visit it?
For example: say I visit page1, page2, page3, and page6 and I want each of those to show up in my history function. On each of those pages I would load the history view, and I would want to call the history's controller function "add(name,url)" and fill it in something like this:
add('page1','page1.php')
But I know that you're not supposed to access the controller from the history because that's not the way it's supposed to be done, however I cannot think of any better way to do this. Any help would be appreciated.
I don't know why dont you call this on every controller.
but if you want to call a function of the current controller, you have to get the instance of the current controller this way:
<?php
$CI =& get_instance();
$CI->method($param);
?>
the easiest way to do this would be to put a method in the constructor of your class. that way it will always run first thing, no matter what you are doing. remember that anything you can do in the controller -- sessions, validation, etc -- you can do in a model.
function __construct() {
parent::__construct();
// load model that has page tracker methods
$this->load->model( 'pagetracker_m' );
// call the method to track the pages, and have it return the results
if ( ! $this->history = $this->pagetracker_m->_trackpage(); ) {
echo 'Error getting history ' ; }
} // end construct
function something() {
// pass history to $data, then use echo $history->firstpage, etc in view
$data['history'] = $this->history ;
// call your view etc
}
everyone. I've started using atk4 in a personal project a couple weeks ago and have been facing some difficulties since then. This specific question I want to ask is about how to make form validations when using the CRUD component shipped with the atk4 framework.
I have already tried several different solutions, none of them solving my problem.
I have a feeling that the problem here might be that the form validation happens within the call of the method $form->isSubmitted() (am I correct?). Since when using a CRUD component within a Page we don't use that way of processing the form submission, we'd have to find alternatives to it. For example, let's say I have a Page with the following init() function:
function init() {
parent::init();
// create a CRUD and set a model to it
$crud = $this->add('CRUD');
$m = $crud->setModel('Person');
if ($crud->form) {
$fn = $crud->form->getField('first_name');
$fn->validateNotNull('The first name must not be empty.');
}
}
Even though I've added the validation to the first name field, it won't be validated. I've tried several things, unsuccessfully. I tried to extend the CRUD class and reimplement the formSubmit($form) function, adding the validation there. Even if I do it, it doesn't work.
Originally (in the CRUD class), there is the function:
function formSubmit($form){
$form->update();
$this->api->addHook('pre-render',array($this,'formSubmitSuccess'));
}
I tried to iterate through the form's fields and call its validate() method, but it didn't work. Also, if I try to do alter the function (in a MyCRUD class, let's say) like below,
function formSubmit($form){
if ($form->isSubmitted()) {
$form->update();
$this->api->addHook('pre-render',array($this,'formSubmitSuccess'));
}
}
there happens an infinite loop... Could someone help me out?
[EDIT]
One last question intimately related to this one. I've just tried to do the exact same validation proposed by romanish below but, instead of adding a CRUD to a page, I was just adding a Form, and it doesn't work -- though the CRUD does work. Instead, there happens a "Error in AJAX response: SyntaxError: Unexpected token
CRUD component respects the validation you're doing inside the model. When data is entered into the form and button is clicked, $model->update() is called.
The execution continues into beforeUpdate() hook, which is the one you need to intercept.
http://agiletoolkit.org/learn/understand/model/actions
class Model_Book extends Model_Table {
function init(){
parent::init();
// .... more definitions ...
$this->addHook('beforeSave',$this);
}
function beforeSave(){
if(strlen($this['book_name']<10))
throw $this->exception('Name of the book is too short')
->setField('book_name');
}
If model is unable to save itself and will produce exception, Form automatically show it as a field error.
Here is the code using CodeIgniter:
The problem I encounter:
The controller will have some functions call view, and it
separated, but it is still very close with the logic itself, if the
controller change to return in JSON or XML to display result, it seems
very trouble.
Seems many method, but each one is depends another.
I think it is difficult to track the code.
Please give some suggestions thank you.
*Please reminded that, it is only the controller class. the load view is actually prepare the data for the view, won't render the page. also the doXXX function call model is only use the model method, it won't have any SQL statement. The MVC is separated, but the controller also have the functions related to the view or model, make it quite messy.
class User extends CI_Controller
{
public function register()
{
//check is logged in or not
//if not logged in , show the register page
}
public function show_register_page()
{
//generate the UI needed data , and call the view to render, and will the user will post back a valid_register function
}
public function valid_register()
{
//do all the valid logic, if success,
//do the do_register
//if fail, valid_register_fail
}
public function valid_register_fail()
{
//check is logged in or not
//show the valid register fail page
}
public function show_valid_register_fail_page()
{
//generate the UI needed data , and call the view to render
}
public function do_register()
{
//insert data in the db, the Model will be called
//if something go wrong in db, show the error page
//if everything is success, show the register success
}
public function show_db_error_page()
{
//generate the UI needed data , and call the view to render
}
public function show_register_success()
{
//generate the UI needed data , and call the view to render
}
}
1. The controller will have some functions call view, and it
separated, but it is still very close with the logic itself, if the
controller change to return in JSON or XML to display result, it seems
very trouble.
Depends on how you organized your code and what you actually pass into the view (template). If that's well structured, you can have one view for HTML, one for XML and one for json, where-as json normally just encodes the view variable's (see json_encodeDocs).
2. Seems many method, but each one is depends another.
Well, just don't do it :) The names look like you wanted to "code that into". Keep it apart. Make those function actually actions that a user performs:
register - that action handles the registration process
Make a login controller out of it that handles anything you need:
login - the login action
lost_password - the lost password action
register - the registration action
activate - the registration activation action
Everything else does not belong in there. There is no need for an action to display some page - the controller itself can decide which view to pick.
Next to that you don't need to display database errors. CI takes care of that. Just put only in what's needed and keep things simple. That should help you to reduce the number of methods and the code therein as well.
3. I think it is difficult to track the code.
Sure. Too many functions with not really speaking names. Keep things simple. It's not easy, but give naming and reducing the overall logic some love.
I discovered the Security Component in CakePHP helps to prevent CSRF by adding tokens as hidden values to forms.
What I was wondering is if there was anyway to prevent duplicate form submissions using this Component or some other component/helper?
In previous projects, I used a unique hash saved in a session, which is read then deleted upon submit. A repeated submit would have that same hash and an error would be produced.
thanks
I've puted onClick event that disables the button like this:
<?= $this->Form->button('Salvar', [
'value' =>'Submit',
'onClick' => 'form.submit();this.disabled=true'
])
?>
You could implement the same type of thing in Cake as you've done before.
On submit, set a session variable that marks that form as having been submitted. Make sure to put an expiry time after it (within a few seconds should do the trick). If the session variable is there when you process the form (and you're within that expiration time), then you've got a resubmit, so don't save the form data.
I'd recommend doing this within the save(..) method of your model, so you don't need to worry about adding it in multiple code locations.
There is a feature in CakePHP 2.x in the security component that allows you to choose to either use the same security token till it expires or just once.
Place this in your controllers beforeFilter method:
$this->Security->csrfUseOnce = true;
Find more information here
#DoctorFox has already answered it with csrfUseOnce = true, but this will throw you in blackholes that you still have to manage. So the complete solution for me is :
class YourAppController extends AppController {
public $helpers = array('Html', 'Form');
public $components = array('Security');
public function beforeFilter() {
$this->Security->csrfUseOnce = true;
$this->Security->blackHoleCallback = 'blackhole';
}
public function blackhole($type) {
$this->redirect(array('action' => 'index'));
}
If there is no redirection, you are still open for double form submission.
Ref : CakePHP security component
Just do PRG Pattern..It's very simple right?! Well, at least that's what everyone says but no one posts a clear answer! It took me a week of search and digging and then the "Newbie" decided to do something on his own! Here is one way to do it in cakephp (I use 2.0.5):
Regardless of code here is the logic in steps:
1- set data
2- validate (do NOT create() yet)
3- write $this->request->data to a session variable
4- redirect to a saveData action
Inside saveData action:
5- read & save the session's variable
6- DELETE session's variable
7- create()
8- save data to model
9- redirect
Here is an example of how your code might look like.
**Attn: "ourController" and "ourModel"
public function add() {
if ($this->request->is('post')) {
if (isset($this->request->data)) {
$this->ourModel->set($this->request->data);
if ($this->ourModel->validates()) {
$this->Session->write('myData', $this->request->data);
$this->redirect(array('controller' => 'ourController',
'action' => 'saveData',
'ourModel' //optional but recommended
)
);
} else {
$this->Session->setFlash('ourModel could not be saved.');
}
}
.....//the rest of add() function
}
Then you should be redirected (on validation) to this function that redirects you again to index action or wherever your logic takes you!
public function saveData($model) {
$myData = $this->Session->read('myData');
$this->Session->delete('myData'); //extremely important
$this->$model->create();
if ($this->$model->save($myData))
// or $myData[$model] if you are dealing with multiple models
{
$this->Session->setFlash(__($model.' have been saved successfully'));
$this->redirect(array('controller' => 'ourController',
'action' => 'index'
)
);
}
} else{
$this->Session->setFlash(__($model.' could not be saved'));
}
}
}
A simple self-redirect might work but in most cases you want to redirect to a different view (e.g. to another form or to index view)
I hope this elaboration helps save time on others so not to have to waste a whole week (as in my case) just to do such functionality server-side!
Don't know about cake, but try to not display content on a POST request, do a self redirect. The double post will get solved.
The Security component should work, furthermore, you can also unset the data just after the post:
unset($this->data['yourModel']);