Skip to content

Commit

Permalink
Merge pull request #24860 from colemanw/formbuilder-recaptcha2
Browse files Browse the repository at this point in the history
Afform - Add support for ReCaptcha v2
  • Loading branch information
colemanw authored Nov 1, 2022
2 parents 89aaf7f + 45293ec commit 1956207
Show file tree
Hide file tree
Showing 14 changed files with 187 additions and 1 deletion.
12 changes: 11 additions & 1 deletion ext/recaptcha/CRM/Utils/ReCAPTCHA.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,17 +196,27 @@ public static function checkAndAddCaptchaToForm($formName, &$form) {
}

/**
* QuickForm validate callback
*
* @param $value
* @param CRM_Core_Form $form
*
* @return mixed
*/
public static function validate($value, $form) {
return self::checkResponse($_POST['g-recaptcha-response']);
}

/**
* @param string $response
* @return bool
*/
public static function checkResponse($response) {
require_once E::path('lib/recaptcha/recaptchalib.php');

$resp = recaptcha_check_answer(CRM_Core_Config::singleton()->recaptchaPrivateKey,
$_SERVER['REMOTE_ADDR'],
$_POST['g-recaptcha-response']
$response
);
return $resp->is_valid;
}
Expand Down
66 changes: 66 additions & 0 deletions ext/recaptcha/Civi/AfformReCaptcha2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Civi;

use CRM_Recaptcha_ExtensionUtil as E;
use Civi\Afform\AHQ;
use Civi\Afform\Event\AfformValidateEvent;
use Civi\Core\Event\GenericHookEvent;
use Civi\Core\Service\AutoService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* Provides ReCaptcha2 validation element to Afform
* @internal
* @service
*/
class AfformReCaptcha2 extends AutoService implements EventSubscriberInterface {

/**
* @return array
*/
public static function getSubscribedEvents() {
return [
'civi.afform_admin.metadata' => ['onAfformGetMetadata'],
'hook_civicrm_alterAngular' => ['alterAngular'],
'civi.afform.validate' => ['onAfformValidate'],
];
}

public static function onAfformGetMetadata(GenericHookEvent $event) {
$event->elements['recaptcha2'] = [
'title' => E::ts('ReCaptcha2'),
'afform_type' => ['form'],
'directive' => 'crm-recaptcha2',
'admin_tpl' => '~/afGuiRecaptcha2/afGuiRecaptcha2.html',
'element' => [
'#tag' => 'crm-recaptcha2',
],
];
}

public static function alterAngular(GenericHookEvent $event) {
$changeSet = \Civi\Angular\ChangeSet::create('reCaptcha2')
->alterHtml(';\\.aff\\.html$;', function($doc, $path) {
// Add publicKey to recaptcha elements
foreach (pq('crm-recaptcha2') as $captcha) {
$recaptchaPublicKey = \Civi\Api4\Setting::get(FALSE)
->addSelect('recaptchaPublicKey')
->execute()->first()['value'];
pq($captcha)->attr('recaptchakey', $recaptchaPublicKey ?: 'foo');
}
});
$event->angular->add($changeSet);
}

public static function onAfformValidate(AfformValidateEvent $event) {
$layout = AHQ::makeRoot($event->getAfform()['layout']);
if (AHQ::getTags($layout, 'crm-recaptcha2')) {
$response = $event->getApiRequest()->getValues()['extra']['recaptcha2'] ?? NULL;
if (!isset($response) || !\CRM_Utils_ReCAPTCHA::checkResponse($response)) {
$event->setError(E::ts('Please go back and complete the CAPTCHA at the bottom of this form.'));
}
}
}

}
17 changes: 17 additions & 0 deletions ext/recaptcha/ang/afGuiRecaptcha2.ang.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
// Module for editing ReCaptcha2 element in the AfformAdmin GUI
return [
'js' => [
'ang/afGuiRecaptcha2.module.js',
'ang/afGuiRecaptcha2/*.js',
],
'partials' => [
'ang/afGuiRecaptcha2',
],
'css' => ['ang/css/afGuiRecaptcha2.css'],
// Ensure module is loaded on the afform_admin GUI page
'basePages' => ['civicrm/admin/afform'],
'requires' => [],
'bundles' => [],
'exports' => [],
];
3 changes: 3 additions & 0 deletions ext/recaptcha/ang/afGuiRecaptcha2.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(function(angular, $, _) {
angular.module('afGuiRecaptcha2', CRM.angRequires('afGuiRecaptcha2'));
})(angular, CRM.$, CRM._);
10 changes: 10 additions & 0 deletions ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2-menu.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<li>
<a href ng-click="$ctrl.node.recaptchatheme = ($ctrl.node.recaptchatheme === 'dark' ? 'light' : 'dark'); $event.stopPropagation();">
{{:: ts('Light') }}
<i class="crm-i fa-toggle-{{ $ctrl.node.recaptchatheme === 'dark' ? 'on' : 'off' }}"></i>
{{:: ts('Dark') }}
</a>
</li>
<li role="separator" class="divider">
</li>
<li><a href ng-click="$ctrl.deleteThis()"><span class="text-danger"><i class="crm-i fa-trash"></i> {{:: ts('Remove ReCaptcha') }}</span></a></li>
14 changes: 14 additions & 0 deletions ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="af-gui-element">
<div class="af-gui-bar">
<div class="form-inline pull-right">
<div class="btn-group pull-right" af-gui-menu>
<button type="button" class="btn btn-default btn-xs dropdown-toggle af-gui-add-element-button" data-toggle="dropdown" title="{{:: ts('Configure') }}">
<span><i class="crm-i fa-gear"></i></span>
</button>
<ul class="dropdown-menu" ng-if="menu.open" ng-include="'~/afGuiRecaptcha2/afGuiRecaptcha2-menu.html'"></ul>
</div>
</div>
</div>
<div class="af-gui-recaptcha2-placeholder" ng-class="{rc2dark: $ctrl.node.recaptchatheme === 'dark'}"></div>
</div>

19 changes: 19 additions & 0 deletions ext/recaptcha/ang/crmRecaptcha2.ang.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
// Module for recaptcha2 Afform element
return [
'js' => [
'ang/crmRecaptcha2.module.js',
'ang/crmRecaptcha2/*.js',
],
'partials' => [
'ang/crmRecaptcha2',
],
'css' => [],
'basePages' => [],
'requires' => [],
'bundles' => [],
'exports' => [
// This triggers Afform to automatically require this module on forms using recaptcha2
'crm-recaptcha2' => 'E',
],
];
3 changes: 3 additions & 0 deletions ext/recaptcha/ang/crmRecaptcha2.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(function(angular, $, _) {
angular.module('crmRecaptcha2', CRM.angRequires('crmRecaptcha2'));
})(angular, CRM.$, CRM._);
28 changes: 28 additions & 0 deletions ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
(function(angular, $, _) {
angular.module('crmRecaptcha2').component('crmRecaptcha2', {
templateUrl: '~/crmRecaptcha2/crmRecaptcha2.html',
bindings: {
recaptchakey: '@',
recaptchatheme: '@',
},
require: {
afForm: '^^',
},
controller: function($scope, $element) {
var ctrl = this;

this.$onInit = function() {
this.recaptchatheme = this.recaptchatheme || 'light';

// Global callback because recaptcha can't directly call angular functions
window.crmRecaptcha2Change = function(response) {
$scope.$apply(function() {
// Add response to form data
var extra = ctrl.afForm.getData('extra');
extra.recaptcha2 = response;
});
};
};
}
});
})(angular, CRM.$, CRM._);
2 changes: 2 additions & 0 deletions ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="{{:: $ctrl.recaptchakey }}" data-theme="{{:: $ctrl.recaptchatheme }}" data-callback="crmRecaptcha2Change"></div>
12 changes: 12 additions & 0 deletions ext/recaptcha/ang/css/afGuiRecaptcha2.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
div.af-gui-recaptcha2-placeholder {
width: 305px;
height: 76px;
opacity: .85;
background-image: url('rc2-light.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
div.af-gui-recaptcha2-placeholder.rc2dark {
background-image: url('rc2-dark.png');
}
Binary file added ext/recaptcha/ang/css/rc2-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ext/recaptcha/ang/css/rc2-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions ext/recaptcha/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
<mixins>
<mixin>menu-xml@1.0.0</mixin>
<mixin>setting-php@1.0.0</mixin>
<mixin>ang-php@1.0.0</mixin>
<mixin>scan-classes@1.0.0</mixin>
</mixins>
<civix>
<namespace>CRM/Recaptcha</namespace>
Expand Down

0 comments on commit 1956207

Please sign in to comment.