The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Mojolicious::Plugin::Qaptcha;

use Mojo::Base 'Mojolicious::Plugin';
use FindBin qw'$Bin';
use Mojo::Util 'slurp';
use File::Basename 'dirname';
use File::Spec;
use File::ShareDir;

our $VERSION = '0.09';

sub register {
  my ($self, $app, $config) = @_;
  $app->config->{$_} = $config->{$_} for keys %$config;

  $app->config->{qaptcha_url} ||= q|/qaptcha|;

  $app->helper(qaptcha_include     => \&_qaptcha_include);
  $app->helper(qaptcha_is_unlocked => \&_is_unlocked);

  my $r = $app->routes;
  $r->route($app->config->{qaptcha_url})->to(
    cb => sub {
      my $self      = shift;
      my $aResponse = {};
      $aResponse->{error} = 0;

      if ($self->param('action') && $self->param('qaptcha_key')) {
        $self->session('qaptcha_key', undef);
        if ($self->param('action') eq 'qaptcha') {
          $self->session('qaptcha_key', $self->param('qaptcha_key'));
        }
        else {
          $aResponse->{error} = 1;
        }
        return $self->render(json => $aResponse);
      }
      else {
        $aResponse->{error} = 1;
        return $self->render(json => $aResponse);
      }
    }
  );
  $r->route('/images/bg_draggable_qaptcha.jpg')->to(
    cb => sub {
      my $self = shift;
      $self->render(
        data   => slurp(&_basedir . "/bg_draggable_qaptcha.jpg"),
        format => 'jpg'
      );
    }
  );
  $app->hook(
    after_dispatch => sub {
      my $c = shift;
      $c->session('qaptcha_key', '')
        if $c->req->url->path->to_string ne $app->config->{qaptcha_url};
    }
  );
}

sub _qaptcha_include {
  my $c        = shift;
  my $url_base = shift;

  my $cfg = $c->app->config;

  my $jquery = $cfg->{inbuild_jquery}
    && $cfg->{inbuild_jquery} == 1 ? slurp(&_basedir . "/jquery.js") : '';
  my $jquery_ui
    = $cfg->{inbuild_jquery_ui} && $cfg->{inbuild_jquery_ui} == 1
    ? slurp(&_basedir . "/jquery-ui.js")
    : '';
  my $jquery_ui_touch
    = $cfg->{inbuild_jquery_ui_touch} && $cfg->{inbuild_jquery_ui_touch} == 1
    ? slurp(&_basedir . "/jquery.ui.touch.js")
    : '';
  my $qaptcha_js  = slurp(&_basedir . "/QapTcha.jquery.js");
  my $qaptcha_css = slurp(&_basedir . "/QapTcha.jquery.css");

  $cfg->{txtLock}        ||= q|Locked : form can't be submited|;
  $cfg->{txtUnlock}      ||= q|Unlocked : form can be submited|;
  $cfg->{disabledSubmit} ||= q|false|;
  $cfg->{autoRevert}     ||= q|true|;
  $cfg->{autoSubmit}     ||= q|false|;

  require Mojo::DOM;
  my $script = <<EOS;
<script type="text/javascript">
$jquery
$jquery_ui
$jquery_ui_touch
$qaptcha_js
\$(document).ready(function(){
  \$('.QapTcha').QapTcha({
    txtLock : "$cfg->{txtLock}",
    txtUnlock : "$cfg->{txtUnlock}",
    disabledSubmit : $cfg->{disabledSubmit},
    PHPfile : '$cfg->{qaptcha_url}',
    autoRevert : $cfg->{autoRevert},
    autoSubmit : $cfg->{autoSubmit}
  });
});
</script>
<style>$qaptcha_css</style>
EOS
  my $dom = Mojo::DOM->new($script);

  require Mojo::ByteStream;
  return Mojo::ByteStream->new($dom->to_xml);
}

sub _is_unlocked {
  my $self = shift;
  if ($self->session('qaptcha_key')) {
    no warnings 'uninitialized';
    if ($self->req->param($self->session('qaptcha_key')) eq '') {
      return 1;
    }
  }
  return 0;
}

sub _basedir {
  my $dir
    = File::Spec->catdir(
    File::ShareDir::dist_dir('Mojolicious-Plugin-Qaptcha'))
    // dirname(__FILE__) . "/../../../jquery";
  return $dir;
}

=head1 NAME

Mojolicious::Plugin::Qaptcha - jQuery QapTcha Plugin for Mojolicious

=head1 SYNOPSIS

  # Mojolicious
  $app->plugin('Qaptcha', {
    inbuild_jquery          => 1,
    inbuild_jquery_ui       => 1,
    inbuild_jquery_ui_touch => 1,
    txtLock                 => "LOCKED",
    txtUnlock               => "UNLOCKED",
    disabledSubmit          => "true",
    autoRevert              => "false",
    autoSubmit              => "true",
    qaptcha_url             => '/do_unlock',
  });

  # Mojolicious::Lite
  plugin 'Qaptcha', {
    inbuild_jquery          => 1,
    inbuild_jquery_ui       => 1,
    inbuild_jquery_ui_touch => 1,
    txtLock                 => "LOCKED",
    txtUnlock               => "UNLOCKED",
    disabledSubmit          => "true",
    autoRevert              => "false",
    autoSubmit              => "true",
    qaptcha_url             => '/do_unlock',
  };

and in your templates

  @@ layouts/default.html.ep
  <!DOCTYPE html>
  <html>
  <head>
  %= qaptcha_include
  </head>
  <body>
  %= content;
  </body>
  </html>

  @@ index.html.ep
  %= layout 'default';
  <form method="post">
    <fieldset>
      <label>First Name</label> <input name="firstname" type="text"><br>
      <label>Last Name</label> <input name="lastname" type="text">
      <input name="submit" value="Submit form" style="margin-top:15px;" type="submit">
      <br />
      <!-- put a qaptcha element inside a form -->
      <div class="QapTcha"></div>

    </fieldset>
  </form>

and in your controller

  # Mojolicious::Lite
  any '/' => sub {
    my $self = shift;

    do_something if $self->qaptcha_is_unlocked;

    $self->render('index');
  };

  # Mojolicious
  sub index {
    my $self = shift;

    do_something if $self->qaptcha_is_unlocked;

    $self->render('index');
  }

=head1 DESCRIPTION

L<Mojolicious::Plugin::Qaptcha> is a L<Mojolicious> plugin.

It brings jQuery QapTcha functionality inside your html form
in an element with class 'QapTcha'.
When QapTcha is unlocked, next request has to
submit form. Otherwise QapTcha will be locked back.

=head1 METHODS

L<Mojolicious::Plugin::Qaptcha> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.

=head2 register

  $plugin->register(Mojolicious->new, $options_hash);

Register plugin in L<Mojolicious> application.


=head2 HELPERS

=over 4

=item qaptcha_include

Includes (optional configured) jquery and qaptcha javascript.

=item qaptcha_is_unlocked

Returns 1 if QapTcha is unlocked.

=back

=head2 OPTIONS

=over 4

=item inbuild_jquery

If set to 1 jQuery 1.8.2 is rendered into %= qaptcha_include.

=item inbuild_jquery_ui

If set to 1 jQuery UI - v1.8.2 is rendered into %= qaptcha_include.

=item inbuild_jquery_ui_touch

If set to 1 jQuery.UI.iPad plugin is rendered into %= qaptcha_include.

=item txtLock

Text to display for locked QapTcha

=item txtUnlock

Text to display for unlocked QapTcha

=item disabledSubmit

Add the "disabled" attribut to the submit button
default: false

=item autoRevert

Slider returns to the init-position, when the user hasn't dragged it to end
default: true

=item autoSubmit

If true, auto-submit form when the user has dragged it to the end
default: false

=item qaptcha_url

Configurable route to unlock qaptcha

=back

an example is located in L<ex/qaptcha.pl|https://github.com/hrupprecht/Mojolicious-Plugin-Qaptcha/blob/master/ex/qaptcha.pl>.

=head1 INSTALLATION

To install this module, run the following commands:

	perl Build.PL
	./Build
	./Build test
	./Build install

SUPPORT AND DOCUMENTATION

After installing, you can find documentation for this module with the
perldoc command.

    perldoc Mojolicious::Plugin::Qaptcha

You can also look for information at:

    AnnoCPAN, Annotated CPAN documentation
        http://annocpan.org/dist/Mojolicious-Plugin-Qaptcha

    CPAN Ratings
        http://cpanratings.perl.org/d/Mojolicious-Plugin-Qaptcha

    Search CPAN
        http://search.cpan.org/dist/Mojolicious-Plugin-Qaptcha/

=head1 SOURCE REPOSITORY

L<http://github.com/hrupprecht/Mojolicious-Plugin-Qaptcha>

=head1 AUTHOR

Holger Rupprecht - C<Holger.Rupprecht@gmx.de>

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2013 by Holger Rupprecht

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

1;