Skip to content

Commit

Permalink
Merge pull request #32 from peckadesign/30-recaptcha
Browse files Browse the repository at this point in the history
 #30 Přidat lazyload recaptchu pro formuláře
  • Loading branch information
Jakub-Fajkus authored Aug 17, 2020
2 parents c7b81c7 + 97aebfc commit a5e8bbd
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 4 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"ext-json": "*",
"nette/application": "~2.4",
"nette/forms": "~2.4",
"contributte/recaptcha": "3.1.*",
"nette/di": "~2.4",
"nette/utils": "~2.4",
"pd/utils": "^1.0"
Expand Down
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "pd-forms",
"title": "pdForms",
"description": "Customization of netteForms for use in PeckaDesign.",
"version": "3.0.7",
"version": "3.1.0",
"author": "PeckaDesign, s.r.o <support@peckadesign.cz>",
"contributors": [
"Radek Šerý <radek.sery@peckadesign.cz>",
Expand All @@ -22,10 +22,15 @@
"bugs": "https://github.com/peckadesign/pdForms/issues",
"license": "MIT",
"homepage": "https://github.com/peckadesign/pdForms",
"scripts": {
"minifyRecaptcha": "uglifyjs -o src/assets/recaptcha/nette.ajax/pdForms.recaptcha.min.js src/assets/recaptcha/nette.ajax/pdForms.recaptcha.js"
},
"dependencies": {
"jquery": ">=1.7",
"nette-forms": "~2.4.0",
"nette.ajax.js": "^2.0.0"
},
"devDependencies": {}
"devDependencies": {
"uglify-js": "~3.8.0"
}
}
4 changes: 4 additions & 0 deletions src/DI/PdFormsExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ public function loadConfiguration(): void
$builder->addDefinition($this->prefix('ruleOptionsFactory'))
->setFactory(\Pd\Forms\RuleOptionsFactory::class)
;

$builder->addDefinition($this->prefix('invisibleReCaptchaInputFactory'))
->setFactory(\Pd\Forms\InvisibleReCaptcha\InvisibleReCaptchaInputFactory::class)
;
}
}
103 changes: 103 additions & 0 deletions src/InvisibleReCaptcha/InvisibleReCaptchaInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php declare(strict_types = 1);

namespace Pd\Forms\InvisibleReCaptcha;

class InvisibleReCaptchaInput extends \Contributte\ReCaptcha\Forms\ReCaptchaField
{
/**
* @var \Contributte\ReCaptcha\ReCaptchaProvider
*/
private $provider;

/**
* @var \Nette\Application\UI\Form
*/
private $form;


/**
* @param \Contributte\ReCaptcha\ReCaptchaProvider $provider
*/
public function __construct(\Contributte\ReCaptcha\ReCaptchaProvider $provider, \Nette\Application\UI\Form $form, string $errorMessage)
{
parent::__construct($provider);

$this->setMessage($errorMessage);
$this->setRequired();

$this->provider = $provider;
$this->form = $form;
}

/**
* @return \Nette\Utils\Html<\Nette\Utils\Html|string>
*/
public function getControl(): \Nette\Utils\Html
{
$formHtmlId = \sprintf(\Nette\Forms\Controls\BaseControl::$idMask, $this->form->lookupPath()); //pozor: musi se volat az zde, protoze teprve az ted je formular pripojen do nejake komponenty a muzeme volat lookupPath().

$controlRecaptchaDiv = $this->getReCaptchaDiv();
$controlRecaptchaSrc = $this->getReCaptchaScript();
$controlRecaptchaInit = $this->getReCaptchaInitScript($formHtmlId);

$container = \Nette\Utils\Html::el('span');
$container->addHtml($controlRecaptchaDiv);
$container->addHtml($controlRecaptchaSrc);
$container->addHtml($controlRecaptchaInit);

return $container;
}

/**
* @param string|object $caption
* @return \Nette\Utils\Html<\Nette\Utils\Html|string>|string
*/
public function getLabel($caption = NULL)
{
return '';
}


/**
* @return \Nette\Utils\Html<\Nette\Utils\Html|string>
*/
private function getReCaptchaScript(): \Nette\Utils\Html
{
$script = \Nette\Utils\Html::el('script', [
'type' => 'text/javascript',
'src' => '/js/pdForms.recaptcha.min.js',

This comment has been minimized.

Copy link
@MilanPala

MilanPala Oct 2, 2020

Contributor

Pozor na to, že to není overzované. Změny v JS se tudíž neprojeví ihned, ale až po vypršení cache v proxy

])->setHtml('');

return $script;
}


/**
* @return \Nette\Utils\Html<\Nette\Utils\Html|string>
*/
private function getReCaptchaInitScript(string $formHtmlId): \Nette\Utils\Html
{
$script = \Nette\Utils\Html::el('script')->setHtml('if (typeof pdForms !== "undefined" && pdForms.recaptcha) { pdForms.recaptcha.initForm("' . $formHtmlId . '"); }');

return $script;
}


/**
* @return \Nette\Utils\Html<\Nette\Utils\Html|string>
*/
private function getReCaptchaDiv(): \Nette\Utils\Html
{
$div = \Nette\Utils\Html::el('div');
$div->addAttributes(
[
'class' => 'g-recaptcha',
'data-sitekey' => $this->provider->getSiteKey(),
'data-size' => 'invisible',
]
);

return $div;
}

}
26 changes: 26 additions & 0 deletions src/InvisibleReCaptcha/InvisibleReCaptchaInputFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

namespace Pd\Forms\InvisibleReCaptcha;

class InvisibleReCaptchaInputFactory
{
/**
* @var \Contributte\ReCaptcha\ReCaptchaProvider
*/
private $reCaptchaProvider;


public function __construct(
\Contributte\ReCaptcha\ReCaptchaProvider $reCaptchaProvider

) {
$this->reCaptchaProvider = $reCaptchaProvider;
}


public function create(\Nette\Application\UI\Form $form): \Pd\Forms\InvisibleReCaptcha\InvisibleReCaptchaInput
{
return new \Pd\Forms\InvisibleReCaptcha\InvisibleReCaptchaInput($this->reCaptchaProvider, $form, '_msg_spam_detected');
}

}
4 changes: 2 additions & 2 deletions src/assets/pdForms.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @name pdForms
* @author Radek Šerý <radek.sery@peckadesign.cz>
* @version 3.0.7
* @version 3.1.0
*
* Features:
* - live validation
Expand Down Expand Up @@ -45,7 +45,7 @@

var pdForms = window.pdForms || {};

pdForms.version = '3.0.7';
pdForms.version = '3.1.0';


/**
Expand Down
159 changes: 159 additions & 0 deletions src/assets/recaptcha/nette.ajax/pdForms.recaptcha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* @name pdForms.recaptcha.nette.ajax
* @author Radek Šerý <radek.sery@peckadesign.cz>
*
* Lazyload of recaptcha scripts and lazy binding the validation after interaction with form (eg. focus into input or
* change of radio button, etc.). This version also works with forms submitted using nette.ajax.
*/
(function () {
var pdForms = window.pdForms || {};


if ('recaptcha' in pdForms) {
return;
}


window.pdFormsRecaptchaLoadCallback = function() {
pdForms.recaptcha.render();
};


function isNetteAjaxForm(form) {
if (typeof $ === 'undefined' || typeof $.nette === 'undefined') {
return false;
}

if ((' ' + form.className + ' ').indexOf(' ajax ') > -1) {
return true;
}

if (form['nette-submittedBy'] && (' ' + form['nette-submittedBy'].className + ' ').indexOf(' ajax ') > -1) {
return true;
}

return false;
}


function doSubmit(token, form) {
// Take care of $.nette.ajax
if (isNetteAjaxForm(form)) {
var initExt = $.nette.ext('init');

if (form['nette-submittedBy'] && form['nette-submittedBy'].matches(initExt.buttonSelector)) {
$(form['nette-submittedBy']).trigger('click.nette');
}
else {
$(form).submit();
}
}
else {
form.submit();
}
}

pdForms.recaptcha = {
loadApiScript: function() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://www.google.com/recaptcha/api.js?onload=pdFormsRecaptchaLoadCallback&render=explicit';

document.documentElement.append(script);
},

initForm: function(htmlId) {
var form = document.getElementById(htmlId);

if (form.length) {
form.addEventListener('focusin', pdForms.recaptcha.bind);
}
},

bind: function(e) {
if (! e.target.matches('select, input, textarea')) {
return;
}

this.removeEventListener('focusin', pdForms.recaptcha.bind);

if (typeof grecaptcha === 'undefined') {
pdForms.recaptcha.loadApiScript();
}
else {
pdForms.recaptcha.render(this);
}
},

render: function(context) {
context = context || document;

var items = context.getElementsByClassName('g-recaptcha');
var length = items.length;

if (length > 0) {
grecaptcha.ready(function () {
for (var i = 0; i < length; i++) {
(function(item) {
if (item.getAttribute('data-widget-id')) {
return;
}

var form = item.closest('form');
var widgetId = grecaptcha.render(item, {
callback: function(token) {
doSubmit(token, form);
}
});

var validateRecaptcha = function(e) {
// Form is already validated from Nette.validateForm, so if it was invalid, we can stop here
if (e.defaultPrevented) {
return false;
}

if (grecaptcha.getResponse(widgetId) === '') {
grecaptcha.execute(widgetId);

e.preventDefault();
e.stopPropagation();
return false;
}

return true;
};

form.addEventListener('submit', validateRecaptcha);

// If $.nette.ajax is loaded, we need to take care of it
if (typeof $ !== 'undefined' && $.nette) {
var initExt = $.nette.ext('init');
var selector = $(form).hasClass('ajax') ? initExt.buttonSelector.replace(/form.ajax /gi, '') : initExt.buttonSelector;

$(form)
.on('click', selector, function(e) {
// Validation is binded to submit event which doesn't occur now, we have to validate manually
if (Nette.validateForm(this.form)) {
this.form['nette-submittedBy'] = this;
validateRecaptcha.call(this, e);
}
else {
e.preventDefault();
e.stopPropagation();
}
});
}

item.setAttribute('data-widget-id', widgetId);

})(items.item(i));
}
});
}
}
};

window.pdForms = pdForms;


})();
1 change: 1 addition & 0 deletions src/assets/recaptcha/nette.ajax/pdForms.recaptcha.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a5e8bbd

Please sign in to comment.