I generated the module "picture" and now I'm trying to call the action
"delete" this way:
frontend_dev.php/picture/delete/id/1
But I get this error:
500 | Internal Server Error | sfValidatorErrorSchema
_csrf_token [Required.]
stack trace at () in SF_ROOT_DIR/lib/vendor/symfony/lib/validator/
sfValidatorSchema.class.php line 110 ...
$clean = array();
$unused = array_keys($this->fields);
$errorSchema = new sfValidatorErrorSchema($this);
// check that post_max_size has not been reached
if (isset($_SERVER['CONTENT_LENGTH']) && (int) $_SERVER['CONTENT_LENGTH'] > $this-
getBytes(ini_get('post_max_size')))
at sfValidatorSchema->doClean(array('_csrf_token' => null)) in
SF_ROOT_DIR/lib/vendor/symfony/lib/validator/
sfValidatorSchema.class.php line 90 ... */
public function clean($values)
{
return $this->doClean($values);
}
/** at sfValidatorSchema->clean(array('_csrf_token' => null)) in
SF_ROOT_DIR/lib/vendor/symfony/lib/form/sfForm.class.php line 247 ...
Any idea?
Regards
Javi
sf 1.4
I would highly recommend AGAINST commenting out the CSRF protection as suggested by dxb. Because then anyone will be able to access that deletion url and delete stuff from your database. Even if you limit the route to post methods, anyone familiar with Symfony can use the url to delete stuff from your database.
I am willing to bet the problem is with your link that goes to the delete url. In your template, if you create a basic HTML link/anchor like this:
<a href="www.mysite.com/frontend_dev.php/picture/delete/id/1>click me</a>
Then a _csrf_token is not generated and is not being passed. But if you generate a link using Symfony's helpers like this:
<?php echo link_to('click me', 'picture/delete?id=1') ?>
Then the link will be generated along with some javascript that inserts a hidden input field called _csrf_token. This method will correctly pass the CSRF token to the delete action and you will be protecting yourself.
The CSRF protection is there to protect you. Learning how to use it properly is always better than commenting it out and disregarding it.
You should use Symfony 1.4's built-in ability to generate a "Delete" link which uses JavaScript to perform a legitimate POST request to your delete route and passes along a valid CSRF token.
The following example assumes you have a route called file_delete which accepts a parameter id that is a number:
// apps/frontend/config/routing.yml
file_delete:
url: /file/:id/delete
param: { module: file, action: delete }
requirements:
id: \d+
Generate a "Delete" link that properly does a POST and adds a CSRF token:
<?php
echo link_to('Delete', 'file_delete', array(
'id' => $file->getId(),
), array(
'method' => 'delete',
'confirm' => 'Are you sure you want to delete this file?',
));
?>
While the above code works, I found a bug that still exists as of Symfony 1.4.18 that can result in the you seeing _csrf_token [Required.] after clicking the Delete link. This is due to Symfony generating JavaScript with a syntax error in it if passing an apostrophe into the confirm option (e.g. Are you sure you're ready to proceed?). To confirm the JavaScript syntax error is the culprit, open your browser's Console and click the link, then watch for an error to display quickly before it redirects. In my case it was easy to remove the apostrophe from the confirm text which fixed the issue.
If you call delete action through "Delete" button in form or list, then you can try to update buggy function _method_javascript_function() in UrlHelper.php to this version
function _method_javascript_function($method)
{
$function = "var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'post'; f.action = this.href;";
$varFlag = false;
if ('post' != strtolower($method))
{
$varFlag = true;
$function .= "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
$function .= sprintf("m.setAttribute('name', 'sf_method'); m.setAttribute('value', '%s'); f.appendChild(m);", strtolower($method));
}
// CSRF protection
$form = new BaseForm();
if ($form->isCSRFProtected())
{
$function .= ($varFlag ? '' : 'var ')."m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
$function .= sprintf("m.setAttribute('name', '%s'); m.setAttribute('value', '%s'); f.appendChild(m);", $form->getCSRFFieldName(), $form->getCSRFToken());
}
$function .= "f.submit();";
return $function;
}
Related
I'm new to ZF2 and I'm willing to share how I do to retain parameter from form using url helper especially during pagination. I modify the answer from How can you add query parameters in the ZF2 url view helper
This is what I do:
AlbumController.php
// get all the query from url
$input = $form->getData();
$paginator = $this->getAlbumTable()->fetchAll();
$paginator->setCurrentPageNumber((int)$this->params()->fromQuery('page', 1));
$paginator->setItemCountPerPage(30);
// unset the 'page' query if necessary
unset($input['page']);
return array(
'form' => $form,
'paginator' => $paginator,
'routeParams' => array_filter($input) // filter empty value
);
index.phtml
echo $this->paginationControl(
$this->paginator,
'sliding',
array('partial/paginator.phtml', 'Album'),
array(
'route' => 'album',
'routeParams' => $routeParams
)
);
paginator.phtml
<a href="<?php echo $this->url(
$this->route, // your route name
array(), // any url options, e.g action
array('query' => $this->routeParams) // your query params
);
echo (empty($this->routeParams))? '?' : '&'; ?>
page=<?php echo $this->next; ?>">Next Page</a>
Please provide any better solution and correct me if I'm wrong.
Thank you
I don't have a much better solution than yours - I don't see a proper way to retain existing query params while adding some new ones. But the following is neater than manually appending & and = characters:
paginator.phtml
<a href="<?php echo $this->url(
$this->route, // your route name
array(), // any url options, e.g action
// Merge the array with your new value(s)
array('query' => array('page' => $this->next) + $this->routeParams)
); ?>">Next Page</a>
This will also ensure that if you already have a page param, it will be overwritten by the new one.
(Technically you could also use $_GET or $_POST directly and avoid passing it from the controller at all, but that doesn't seem very neat)
I am using cakePHP and Jquery UI for a small project.
I have an 'Add Item' link on my index.ctp of Item.
I want to show a jQuery UI dialog with the forum in for adding an Item when the 'Add Item' link is clicked.
So that I can make use of cakePHP's awesome FormHelper I want the controller's add() method to return the html the FormHelper creates if($this->request->is('ajax')) but this is giving me Internal Server Errors (when I Inspect Element in Chrome).
I know this isn't very MVC but my reasoning is that I want my app to work if the user has Js enabled or not, therefore the 'Add Item' link shows the add.ctp View if they don't have jQuery but if they do I've got preventDefault() in my jQuery script.
The jQuery click handler then sets up the dialog box's properties (jQueryUI Dialog Box) and then makes an ajax request to add() for the form.
I copied and pasted the FormHelper code I used on add.ctp but instead of echoing, added each line to $htmlString. I then called echo $htmlString; exit(); but here I get the 500 Internal Server Error.
If I comment out all the FormHelper lines in the add() function everything works ($htmlString is just "") so there is something going wrong here.
As I said I know this isn't very MVC-y but I can't think of another way to do it. Could I potentially get add.ctp returned rather than the copy and pasted code? That would keep the MVC model right and also if $this->layout = 'ajax' then I will only get the exact add.ctp file code right?
Thanks for your time and help in advance.
Edit - Added jQuery ajax request and cakephp Controller add() function
Jquery ajax request:
$('.add_project').click(function(event) {
//Kill default
event.preventDefault();
$.ajax({
url : $(this).attr('href'),
type : 'post',
data : 'ajax=1',
dataType: 'html',
success: function(response) {
alert(response);
//Check jquery loaded
if(jQuery.ui) {
//Fill dialog with form
$("#dialog").html(response).show();
//Show dialog
$("#dialog").dialog("open");
}
}
});
});
CakePHP Controller code:
public function add()
{
//If this has been requested with ajax then give the form that we create on add.ctp
if($this->request->is('ajax'))
{
App::import('Helper', 'Form');
$this->loadModel('Project');
$html = '';
$html += $this->Form->create('Project');
$html += $this->Form->input('name');
$html += $this->Form->input('colour', array(
'options' => array('blue' => 'Blue', 'green' => 'Green', 'red' => 'Red', 'orange' => 'Orange', 'yellow' => 'Yellow')
));
$html += $this->Form->end('Create Project');
echo html;
exit();
}
I'm pretty positive it's nothing to do with the ajax request it's more of a problem in cakephp that it doesn't like to use the FormHelper in the controller. I know this is bad MVC practice but I can't think of another way to do this dynamically (as in the fact that cakephp builds the html tags based on the Model).
Thanks again.
The 500 Internal Server error you are receiving is because you are using a Helper within a Controller.
However! you can pass a view back through your ajax request.
Create the method in your controller. Create the view with the form helper
ajaxForm.ctp
$this->Form->create('Project');
$this->Form->input('name');
$this->Form->input('colour', array(
'options' => array('blue' => 'Blue', 'green' => 'Green', 'red' => 'Red', 'orange' => 'Orange', 'yellow' => 'Yellow')
));
$this->Form->end('Create Project');
controller
public function ajaxForm(){
if($this->request->is('ajax')){
$this->layout = 'ajax'; // to avoid returning your full layout
$this->render('ajaxForm');
}
}
Separate your html and frontend code from the php backend code your add function can be empty if you aren't passing any data to the view and you won't need to check for ajax if the add function is not being called by non ajax requests. It will be dedicated to your ajax request in your view.
//controller file
App::import('Helper', 'Form');
public function add()
{
}
//App/Views/Projects/add.ctp file
echo $this->Form->create('Project');
echo $this->Form->input('name');
echo $this->Form->input('colour', array(
'options' => array('blue' => 'Blue', 'green' => 'Green', 'red' => 'Red', 'orange' => 'Orange', 'yellow' => 'Yellow')
));
echo $this->Form->end('Create Project');
How do I specify the method of an action (similar to _delete):
generator:
config:
list:
object_actions:
myaction: {label: Label, action: myaction, method: post}
This ignores my method setting and renders a get link:
Label
Whereas I want it to a "post" link, similar to _delete (with onclick attribute)
I guess your using Propel.
If you check the generator code, specifically on: generator > theme_name > template > template > _list_td_actions.php, there you'll find a pice of code like:
<?php else: ?>
<li class="sf_admin_action_<?php echo $params['class_suffix'] ?>">
<?php echo $this->addCredentialCondition($this->getLinkToAction($name, $params, true), $params) ?>
</li>
<?php endif; ?>
That's the code thats being executed when you define a custom object action like the one you described. Check $params and you may find a solution to your needs ( i think that probably you could define something like the onclick attribute value).
If you just want to have a confirmation message than probably the best way is:
generator:
config:
list:
object_actions:
myaction: {label: Label, action: myaction, confirm: "Are your sure?", params: {onclick: 'alert("Bu!");'} }
Additional parameters to link_to can be passed with 'params' option (notice 'onclick' in the example above).
maybe this will be useful
generator:
config:
list:
object_actions:
myaction:{ params: { onclick : "if(confirm('Are you sure?')){return true;}else{return false;}" } }
In a Rails app, I am loading a partial via an ajax call. (still using prototype)
The partial is a form that contains a textarea enriched with the yahoo yui_editor (similar to tinyMCE or FCKEditor)
<%= f.text_area :body, :class => 'rich_text_editor', :rows => "15", :style => "width : 90%;" %>
The yui_editor is not loaded and the textarea content is displayed as simple text when the form is loaded via an ajax call.
I tested that the yui_editor is active when the same partial is loaded directly without any ajax calls.
I know this has to do with the fact that the yui_editor javascript is not loaded but I have no idea how to solve this issue
Your help will be very much appreciated
Thanks
You need to start the YUI editor. Since the editor needs the id of the element, you need to specify a unique id in your partial.
See the YUI doc for more on the editor's parameters
Added
Are you adding the div via Ajax? In that case, you need to make the call to the YUI editor library after the div is added. Two ways to do that:
1) Your code which does the insert into the dom (with the results of the Ajax call) needs to explicitly call the YUI editor. Eg your Ajax results could include the element id of the text area, you could already know it in advance, etc.
2) You could include the script for calling the YUI editor in your Ajax results. But then you'll need to run the script(s) in the html after you've added them to the dom.
Setting innerHTML property of an element does NOT run any scripts in the html. But I have a script which does, see below.
The script is based on this SO Question
... do ajax call and get results in <body>
foo_el.innerHTML = body; // add results to the dom
exec_body_scripts(foo_el); // run any scripts in foo_el
//////////////////////////////////
function exec_body_scripts(body_el) {
// Finds and executes scripts in the dialog's body.
// Needed since innerHTML does not run scripts.
// NB: Only looks for scripts that are children or grandchildren of body_el.
// Doesn't look deeper.
function evalScript(elem) {
var data = (elem.text || elem.textContent || elem.innerHTML || "" ),
head = document.getElementsByTagName("head")[0] ||
document.documentElement,
script = document.createElement("script");
script.type = "text/javascript";
try {
script.appendChild(document.createTextNode(data)); // doesn't work on ie
} catch(e) {
// IE has funky script nodes
script.text = data;
}
head.insertBefore(script, head.firstChild);
head.removeChild(script);
};
// main section of function
var scripts = body_el.getElementsByTagName('SCRIPT'), i;
for (i = 0; scripts[i]; i++) {
evalScript(scripts[i]);
}
};
Partial example:
<% el_id = "rte_#{foo.id}"
# foo is the name of an object used by the partial. Using its id
# to ensure a unique id for the element on the page.
# Or use a simple counter "i". But in any case, the el_id must be unique
%>
<%= f.text_area :body, :class => 'rich_text_editor', :rows => "15",
:style => "width : 90%;", :id => el_id %>
<script>
(function() {
var myEditor = new YAHOO.widget.Editor('<%= el_id %>', {
height: '300px',
width: '522px',
dompath: true, //Turns on the bar at the bottom
animate: true //Animates the opening, closing and moving of Editor windows
});
myEditor.render();
})();
</script>
How do I create a link of this type:
<a href="#" onclick="document.getElementById('search').value=this.value">
using method link_to in Rails?
I couldn't figure it out from Rails docs.
You can use link_to_function (removed in Rails 4.1):
link_to_function 'My link with obtrusive JavaScript', 'alert("Oh no!")'
Or, if you absolutely need to use link_to:
link_to 'Another link with obtrusive JavaScript', '#',
:onclick => 'alert("Please no!")'
However, putting JavaScript right into your generated HTML is obtrusive, and is bad practice.
Instead, your Rails code should simply be something like this:
link_to 'Link with unobtrusive JavaScript',
'/actual/url/in/case/javascript/is/broken',
:id => 'my-link'
And assuming you're using the Prototype JS framework, JS like this in your application.js:
$('my-link').observe('click', function (event) {
alert('Hooray!');
event.stop(); // Prevent link from following through to its given href
});
Or if you're using jQuery:
$('#my-link').click(function (event) {
alert('Hooray!');
event.preventDefault(); // Prevent link from following its href
});
By using this third technique, you guarantee that the link will follow through to some other page—not just fail silently—if JavaScript is unavailable for the user. Remember, JS could be unavailable because the user has a poor internet connection (e.g., mobile device, public wifi), the user or user's sysadmin disabled it, or an unexpected JS error occurred (i.e., developer error).
To follow up on Ron's answer if using JQuery and putting it in application.js or the head section you need to wrap it in a ready() section...
$(document).ready(function() {
$('#my-link').click(function(event){
alert('Hooray!');
event.preventDefault(); // Prevent link from following its href
});
});
just use
=link_to "link", "javascript:function()"
another solution is catching onClick event and for aggregate data to js function you can
.hmtl.erb
<%= link_to "Action", 'javascript:;', class: 'my-class', data: { 'array' => %w(foo bar) } %>
.js
// handle my-class click
$('a.my-class').on('click', function () {
var link = $(this);
var array = link.data('array');
});