Gossamer Forum
Quote Reply
Plugin hooks
Hi All,

How do I maintain the crumb and category menu on add.html and modify.html when
returning an error from within a hook?

I am trying to do a simple PRE hook into user_add_link (and others).
Code:
$mgr->install_hooks ( 'CAPTCHA', ['user_add_link', 'PRE', 'Plugins::CAPTCHA::captcha_hook']);

All the hook does, is return an error if the form doesn't validate. The problem is that when I return an error,
and the page reloads, it is missing many of the variables/tags that were defined on the original loading
of the page (before submitting). I kind of expected that I would be able to use the same subroutine for
any of these hooks (user_add_link, user_modify_link, user_signup, add_this_review, and update_this_review),
but instead, I find myself duplicating existing GT code in each separate hook.

So for example of 'user_add_link' hook, when I return an error, the add.html page reloads, but it
does not have any of the category information or crumb.
Code:
sub captcha_hook {
# -------------------------------------------------------------------
# This subroutine will be called whenever the hook 'captcha_hook' is run.
#
my $results = shift;
my $input = $IN->get_hash;

# grab the plugin settings...
my $opts = Links::Plugins->get_plugin_user_cfg('CAPTCHA');
my $user_exists = ($USER) ? $USER->{Username} : '0';

if ($user_exists and ( $opts->{user_verify} == 0) ) {
return $results;
}

my $user_pass = $IN->param('User_CAPTCHA');
$IN->delete('User_CAPTCHA');

my $captcha_result = captcha_test($user_pass);
if (exists $captcha_result->{error}) {
$PLG->action(STOP);
$results->{error} = $captcha_result->{error};
return $results;
}

$PLG->action(CONTINUE);
return $input;
}

So, Instead, I have to duplicate code to regenerate the category/crumb like this:
Code:
sub captcha_hook {
# -------------------------------------------------------------------
# This subroutine will be called whenever the hook 'captcha_hook' is run.
#
my $results = shift;
my $input = $IN->get_hash;

# grab the plugin settings...
my $opts = Links::Plugins->get_plugin_user_cfg('CAPTCHA');
my $user_exists = ($USER) ? $USER->{Username} : '0';

if ($user_exists and ( $opts->{user_verify} == 0) ) {
return $results;
}

my $user_pass = $IN->param('User_CAPTCHA');
$IN->delete('User_CAPTCHA');

my $captcha_result = captcha_test($user_pass);
if (exists $captcha_result->{error}) {
$PLG->action(STOP);
$results->{error} = $captcha_result->{error};

my @id = $IN->param('CatLinks.CategoryID');
my $category = {};
if ($CFG->{db_gen_category_list} == 1) {
require Links::Tools;
$category = Links::Tools::category_list();
$category->{Category} = sub { Links::Tools::category_list_html() };
}

print Links::SiteHTML::display('add', {
error => $captcha_result->{error},
main_title_loop => Links::Build::build('title', Links::language('LINKS_ADD'), "$CFG->{db_cgi_url}/add.cgi" . (@id ? "?ID=" . join(';ID=', @id) : '')),
%$category
});

exit;

}

$PLG->action(CONTINUE);
return $input;
}

Note that I am actually exit-ing the code in the 2nd example. It doesn't seem right, and yet
it is the only way I can get the page to reload properly after returning an error. Am I missing something?
This is the case with every hook I try to use. All page reloads are missing some data on the reload. rate_link for instance drops the crumb and title. So, do I have to duplicate GT code and call Links::SiteHTML::display() and then exit from each hook?

Ideally, I wanted my error handling to be called AFTER the GT code has performed it's error checking, and BEFORE the action is actually carried out.
Maybe GT could modify their code to display any error that is in "$results->{error}"
Currently, code executes like this:
Code:
my $results = $PLG->dispatch('user_add_link', \&add_link);
if (defined $results->{error}) {
print Links::SiteHTML::display('add', $results);
}
else {
...
}

It should be more like this:
Code:
my $results = $PLG->dispatch('user_add_link', \&add_link);
if (not defined $results->{error}) {
...
}
else {
# Otherwise display the add form.
if ($USER) {
$IN->param('Contact_Name') or ($IN->param('Contact_Name', $USER->{Name} || $USER->{Username}));
$IN->param('Contact_Email') or ($IN->param('Contact_Email', $USER->{Email}));
}

if ($DB->table('Category')->count == 0) {
print Links::SiteHTML::display('error', { error => Links::language('ADD_NOCATEGORIES') });
}

my $category = {};
if ($CFG->{db_gen_category_list} == 1) {
require Links::Tools;
$category = Links::Tools::category_list();
$category->{Category} = sub { Links::Tools::category_list_html() };
}
if (defined $results->{error}) {
print Links::SiteHTML::display('add', {
error => $results->{error},
main_title_loop => Links::Build::build('title', Links::language('LINKS_ADD'), "$CFG->{db_cgi_url}/add.cgi" . (@id ? "?ID=" . join(';ID=', @id) : '')),
%$category
});
}
else {
print Links::SiteHTML::display('add', {
main_title_loop => Links::Build::build('title', Links::language('LINKS_ADD'), "$CFG->{db_cgi_url}/add.cgi" . (@id ? "?ID=" . join(';ID=', @id) : '')),
%$category
});
}
}

I hope this makes sense. Again, all I am trying to do is insert an error into the stream,
and redisplay the page. Nothing fancy.
RGB World, Inc. - Software & Web Development.
rgbworld.com
Quote Reply
Re: [rgbworld] Plugin hooks In reply to
Ok, well I have the hook process figured out in my tiny mind...
The docs do explain it, but I have to read them a dozen times first.
Anyways maybe this will help someone else.

"If you are STOP'ing and don't want any other code to run, you should return the same thing the main code would return."

So, if I cancel a hook by calling $PLG->action(STOP), I have to recreate 'some' of the values that would have been returned by the main code. Be aware that every hook returns different values in addition to 'error'. I am not exactly sure which variables are needed by each form when STOPing. I am mostly concerned about whether or not I have to return any payment variables when erroring on add/modify link?

My corrected 'user_add_link' hook is below. No exit-ing :-)
Note that I am creating and returning the category_loop and main_title_loop variables, or else they will be dropped when re-displaying the page.

Code:
sub captcha_user_add_link {
# -------------------------------------------------------------------
# This subroutine will be called whenever the hook 'user_add_link' is run.
#
my $input = $IN->get_hash;
my $opts = Links::Plugins->get_plugin_user_cfg('CAPTCHA');
my $user_exists = ($USER) ? $USER->{Username} : '0';

if ($user_exists and ( $opts->{user_verify} == 0) ) {
return $input;
}

my $user_pass = $IN->param('User_CAPTCHA');
my $captcha_result = captcha_test($user_pass);

if (exists $captcha_result->{error}) {
$PLG->action(STOP);
$IN->delete('User_CAPTCHA');

my %ret;
if ($CFG->{db_gen_category_list} < 2) {
require Links::Tools;
%ret = %{Links::Tools::category_list()};
$ret{Category} = sub { Links::Tools::category_list_html() };
}
my @id = $IN->param('CatLinks.CategoryID');
$ret{main_title_loop} = Links::Build::build('title', Links::language('LINKS_ADD'), "$CFG->{db_cgi_url}/add.cgi" . (@id ? "?ID=" . join(';ID=', @id) : ''));
return { error => $captcha_result->{error}, %$input, %ret };
}
$PLG->action(CONTINUE);
return $input;
}

FYI, the hook dispatcher isn't consistent with regard to the arguments it passes into Links::SiteHTML::display() when returning from a hook. For example, after dispatching 'user_add_link', ONLY the $results are passed in, so in order to have the crumb displayed, MY hook has to stuff main_title_loop, and the entire category_loop into my return value. In contrast, the 'best' hook I have used is
'add_this_review' it calculates the $mtl and record BEFORE dispatching the hook, so it is able to pass them into Links::SiteHTML::display(). The later seems to be the better way. Hooks called from Login.pm do not use ANY results, and don't call Links::SiteHTML::display(). So for those hooks, I have to display the page myself. See the example dispatching below to try to make sense of my rambling.

Code:
my $results = $PLG->dispatch('user_add_link', \&add_link);
if (defined $results->{error}) {
print Links::SiteHTML::display('add', $results);
}

------------------------
# If we have a review to add from a form
if ($IN->param('add_this_review')) {
my $results = $PLG->dispatch('add_this_review', \&_add_this_review, $rec);

# If we have error
if (defined $results->{error}) {
print Links::SiteHTML::display('review_add', { %$results, %$rec, %title, main_title_loop => $mtl });
}

-----------------------

elsif ($input->{signup_user}) {
$PLG->dispatch('user_signup', \&signup_user);
}

Thanks for listening. I am curious why it is so inconsistent.
RGB World, Inc. - Software &amp; Web Development.
rgbworld.com