Friday, December 4, 2009

Re: Delete confirm

Martin,

I don't have a specific technique, however I use this generic
technique all the time:

function something() { debug(func_get_args()); debug(Debugger::trace
(); die; }

or the log version

function something($one, $two, $three) { $this->log(func_get_args());
$this->log(Debugger::trace(); return parent::something($one, $two,
$three); }

in your case "somethign" would be your blackhole callback, and it'd
probably point to which of the Security component's calls to it are
causing you grief. Usually IME problems with the security component
boil down to having a stale form token, or js/something manipulating
the form structure thus invalidating the form token.

On 3 dic, 15:33, Jeremy Burns <jeremybu...@me.com> wrote:
> I thought it might be about time to jot down how I am implementing
> this (based around AD7Six's code). I have got it working *pretty much*
> as I want it, but still have a few questions. I never thought this
> would be so tough! I readily acknowledge that I am stumbling in the
> dark here at times.
>
> First, I have amended my app_controller, adding:
>
> $this->Security->blackHoleCallback = '_confirmAction';
> $this->Security->requirePost('delete');
>
> I am only really interested in trapping deletes at present, but can
> soon resort to an array of actions later if needed. This stops a
> delete request processing if it does not come from a POST. It's ideal
> for me as the delete instruction comes from a link (and hence a GET).
>
> Then I have my _confirmAction function:
>
> function _confirmAction ($reason = null) {
>         if ($reason == 'post') {
>                 $this->Security->_generateToken($this);
>                 $this->redirect(array('action' => 'deleteConfirm', $this->params
> ['pass'][0]));
>         }
>         $code = 404;
>         if ($reason == 'login') {
>                 $code = 401;
>         } else {
>                 $this->Session->setFlash('Access Denied');
>         }
>         $this->redirect(null, $code, true);
>
> }
>
> This is called when a delete is attempted by anything other than a
> POST. For my part, it safely redirects the user to the "deleteConfirm"
> action. $this->params['pass'][0] refers back to $id passed in by the
> delete link, and therefore contains the id of the record that is going
> to be deleted.
>
> Then I have my deleteConfirm action (it's in my app_controller so that
> I don't have to propagate it throughout all of my controllers):
>
> function deleteConfirm($id) {
>         $this->redirect(array('action' => 'view', $id, 'delete'));
>
> }
>
> You'll notice that I am redirecting the user to the view screen. I am
> doing this because I want to present details of the record they are
> going to delete rather than a reasonably anonymous screen. I am adding
> an unnamed parameter - 'delete'. I'll explain that a bit more in a
> second.
>
> Here's where I stumble upon my first issue. I'd love to set a variable
> called 'mode' to indicate what I am going to do in my 'view'. I have
> tried $this-set('mode', 'delete'), but it seems to be cleared after
> the redirect. Is this correct, or am I doing something wrong?
>
> Back in my controller, I have amended my view function:
>
> function view($id = null, $mode=null) {
>         $this->set('mode', empty($this->params['pass'][1]) ? 'view' : $this->params['pass'][1]);
>
>         ...
>
> Here I am inspecting the second passed parameter ($id first, then
> $mode). If it is empty I am doing a straightforward view. Therefore,
> the view renders without any reference to deleting. If, on the other
> hand, $mode is not empty, I am doing something else. The only other
> alternative at the moment is 'delete', so let's assume that's what's
> happening. The view renders, but with some changes to reflect the fact
> that I am deleting. This includes a health warning and a form to
> perform the actual delete via a POST:
>
> if ($mode == 'delete'):
>         echo $this->Html->tag('p', 'WARNING! You are about to delete the
> following ' . $type . '. If you proceed, this delete cannot be
> reversed.', array('class' => 'flashWarning'));
>         echo $this->Form->create(null, array('url' => array('action' =>
> 'delete', $id)));
>         echo $this->Form->input ($referer, array('type'  =>  'hidden',
> 'value' => $referer));
>         echo '<p><ul class="links">';
>                 echo $this->Html->tag('li', $this->Form->submit ('Yes - delete',
> array('div' => false)));
>                 echo $this->Html->tag('li', $this->Html->link ('No - cancel', array
> ('controller' => $referer)));
>         echo '</ul></p>';
>         echo $this->Form->end();
> endif;
>
> I've also added a 'cancel' link that returns the user to where they
> came from using $this->referer(). The action on the form is 'delete'.
> As it comes from a POST the security component does not block it. So
> the delete action in app_controller triggers (again, only have it in
> app_controller as it is very standard):
>
> function delete($id) {
>         if ($this->{$this->modelClass}->del($id)) {
>                 $this->Session->setFlash(__('The record was deleted.', true), true,
> array('class' => 'flashSuccess'));
>                 $this->set('mode', 'view');
>                 $this->redirect(array('action' => 'index'), null, true);
>         }
>         ...
>
> Here is where I run into my next couple of problems.
>
> First, again I'd like to set $mode back to 'view', so that my next
> function knows that it is not doing a delete. The redirect *appears*
> to reset it, so it's an unrecognised variable.
>
> Second, I can use $this->referer() to send the user back to somewhere
> meaningful (rather than just the index), but (i) if it is a view
> screen the deleted record isn't there so it's meaningless and (Ii) it
> still has the second parameter ('delete'). I *can* handle both of
> these in the view, but it isn't very elegant.
>
> It's been a long post! To sum up, this is sort of working but it is a
> bit clunky. I am sure there is a better way to pass the $mode variable
> around and I am sure there is a more elegant way to set and deal with
> $this->referer().
>
> Any thoughts (please be gentle on me!)?
>
jburns

I see a few problems with your approach.

The biggest being you need to couple your view logic, with your
delete, publish, mark as spam, whatever functions. Meaning your delete
function doesn't work at all unless the view function/ctp is defined
as you've suggested.

There are also a lot of redircts involved
/foo/delete/1 ->/foo/deleteConfirm/1 ->/foo/view/1/delete -> /foo/
delete/1 [POST]

Relying on this->referer() isn't going to work if the user reloads /
foo/view/1/delete before submitting the form - the concequence of
which would be that the user gets sent back to the homepage. You might
want to look at how the auth component takes care of that (just log
out, go to /admin/anything and look in your session).

To counter what you describe regarding index->view->delete->view
[missing] I maintain a flat list of where a user has been; and just go
up the stack until I find a page that exists. e.g.:

/
/admin
/admin/widgets
/admin/widgets/index/page:2
/admin/widgets/view/22
/admin/widgets/delete/22
/admin/widgets/view/22 [redirect back]
/admin/widgets/index/page:2 [auto redirect back, supressing any flash
messages generated by /view/22]

That's also in the component I linked to previously

hth or gives you ideas.

AD

Check out the new CakePHP Questions site http://cakeqs.org and help others with their CakePHP related questions.

You received this message because you are subscribed to the Google Groups "CakePHP" group.
To post to this group, send email to cake-php@googlegroups.com
To unsubscribe from this group, send email to
cake-php+unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/cake-php?hl=en

No comments: