# captcha refresh: Provide a refresh feature to use in conjunction with the
# stock [captcha] tag by Mark Johnson <mark(at)endpoint(dot)com>
#
# To use:
#
# 1. Put this in, or include from, interchange.cfg and restart.
#
# 2. Place call to [captcha-refresh] where you would like to see the
# refresh link appear. See documentation within usertag defintion below.
#
# 3. The captcha-refresh process couples tag and actionmap definitions.
# Ensure there is no conflict with the actionmap name and any existing
# custom maps for your implementation.

UserTag  captcha-refresh  addAttr
UserTag  captcha-refresh  Routine <<EOR
use 5.10.0;
sub {
    my $opt = shift;
    my %cache = %{
        delete ($opt->{use_last}) && $::Instance->{last_captcha_build_opt} || {}
    };

    my $anc = delete ($opt->{anchor}) || 'refresh image';
    my $img_id = delete $opt->{img_id};
    my $js_func_name = delete ($opt->{js_func_name}) || 'captcha_refresh';

    my @uri = (delete ($opt->{am}) || 'captcha-refresh');

    my %unq_keys;
    @unq_keys{ keys %cache, keys %$opt } = ();
    push (@uri,
        join (
            '=',
            ($_, $::Tag->filter(urlencode => ($opt->{$_} // $cache{$_} // '')))
        )
    )
        for keys %unq_keys;

    my $url = $::Tag->area({ href => join ('/', @uri) });

    my $script = qq{
        <a href="#captcha" onclick="$js_func_name()">$anc</a>
        <script>
        function $js_func_name() {
            var ajax = new XMLHttpRequest();
            // Extra cache-busting insurance if header doesn't work
            var cb = new Date().getTime();

            ajax.onreadystatechange = function() {
                if (ajax.readyState == 4 && ajax.status == 200) {
                    document.getElementById('$img_id').src = ajax.responseText;
                }
            }

            ajax.open('GET','$url?cb='+cb,true);
            ajax.send();
            return;
        }
        </script>
    };

    return $script;
}
EOR

CodeDef  captcha-refresh  ActionMap
CodeDef  captcha-refresh  Routine  <<EOR
sub {
    my (undef, %opt) = split (m{[/=]+}, shift);
    $_ = $::Tag->filter(urldecode => $_)
        for values %opt;

    # Always name_only since we're updating the img's src param
    $opt{name_only} = 1;

    my $doc = Vend::Document->new;

    # Bust that cache for IE
    $doc->header('Cache-Control: no-cache');

    $doc->hot(1);
    $doc->write($::Tag->captcha(\%opt));
    return 0;
}
EOR

UserTag  captcha-refresh  Documentation <<EOD
=head1 NAME

Captcha refresh

=head1 DESCRIPTION

Given a call to [captcha] to generate a check image, place a subsequent call
to [captcha-refresh] where you would like to insert a hyperlink to refresh the
image. The call will bundle the necessary parameters to duplicate the
appropriate call to [captcha] into an AJAX GET back to the complementary
captcha-refresh actionmap. The actionmap will build the new image and return
the new URL to the image, to which the original <img> tag's src field will be
set.

As long as the [captcha-refresh] call is processed after the most recent call
to [captcha] itself, the calling parameters for the latter call can be easily
tied to the refresh request and not need to be duplicated. The calling order
can always be enforced via the use of [output] or, if simpler, using scratch
to store the [captcha] results and display later in the page.

=over 4

=item B<img_id> (required)

The refresh code needs to be able to reference the original image so that the
newly formed captcha image can overwrite the previous, discarded one. Thus,
your captcha image must include an "id" attribute in the <img> tag, and that
value must be included in this param.

=item B<use_last>

When true, the code will pull in the attribute list of the last non-check call
to [captcha]. In order to refresh the image under the same terms as the
original, the code needs access to the calling params used. These can either
be duplicated in the call to [captcha-refresh] or referenced via B<use_last>

=item B<anchor>

Value to use for the link itself. Defaults to 'refresh image'. To use an
image, supply the entire <img> tag.

=item B<am>

Alternate actionmap name to use to refresh the captcha image. Default is
'captcha-refresh' and is included in the .cfg build file for this tag.

=item B<js_func_name>

Name of javascript function built from this tag to handle the refresh request
and image update. Default is 'captcha_refresh'. Should only need to change if
there is a conflict with an existing JS function.

=back

You can also provide any of the options defined for [captcha] itself, which
will be necessary without use of the B<use_last> option. If B<use_last> and
explicit [captcha] options are combined, options set in [captcha-refresh] will
override those derived from the B<use_last> call.

=head1 EXAMPLE

 <form action="[area @@MV_PAGE@@]" method="post">
 ...
 <input name="mv_captcha_guess" size="6" autocomplete="off" />
 <img
    id="captcha_image"
    style="vertical-align: middle"
    src="[captcha
            function=image
            length=6
            image_location=captcha
            image_path=/images/captcha
            name_only=1
    ]"
 />
 &nbsp;
 [captcha-refresh
        img_id=captcha_image
        use_last=1
    ]
 ...
 <input type="submit" value="Submit" />

=head1 AUTHOR

Mark Johnson <mark(at)endpoint(dot)com>

=cut
EOD
