From 45293ec80ea76c441c4a134fb32b38c933cce3d9 Mon Sep 17 00:00:00 2001 From: Kurund Jalmi Date: Fri, 7 Oct 2022 20:09:57 +0100 Subject: [PATCH] Afform - Add support for ReCaptcha v2 Fixes dev/core#3173 Co-authored-by: Coleman Watts --- ext/recaptcha/CRM/Utils/ReCAPTCHA.php | 12 +++- ext/recaptcha/Civi/AfformReCaptcha2.php | 66 ++++++++++++++++++ ext/recaptcha/ang/afGuiRecaptcha2.ang.php | 17 +++++ ext/recaptcha/ang/afGuiRecaptcha2.module.js | 3 + .../afGuiRecaptcha2/afGuiRecaptcha2-menu.html | 10 +++ .../ang/afGuiRecaptcha2/afGuiRecaptcha2.html | 14 ++++ ext/recaptcha/ang/crmRecaptcha2.ang.php | 19 +++++ ext/recaptcha/ang/crmRecaptcha2.module.js | 3 + .../crmRecaptcha2/crmRecaptcha2.component.js | 28 ++++++++ .../ang/crmRecaptcha2/crmRecaptcha2.html | 2 + ext/recaptcha/ang/css/afGuiRecaptcha2.css | 12 ++++ ext/recaptcha/ang/css/rc2-dark.png | Bin 0 -> 4989 bytes ext/recaptcha/ang/css/rc2-light.png | Bin 0 -> 5010 bytes ext/recaptcha/info.xml | 2 + 14 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 ext/recaptcha/Civi/AfformReCaptcha2.php create mode 100644 ext/recaptcha/ang/afGuiRecaptcha2.ang.php create mode 100644 ext/recaptcha/ang/afGuiRecaptcha2.module.js create mode 100644 ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2-menu.html create mode 100644 ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2.html create mode 100644 ext/recaptcha/ang/crmRecaptcha2.ang.php create mode 100644 ext/recaptcha/ang/crmRecaptcha2.module.js create mode 100644 ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.component.js create mode 100644 ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.html create mode 100644 ext/recaptcha/ang/css/afGuiRecaptcha2.css create mode 100644 ext/recaptcha/ang/css/rc2-dark.png create mode 100644 ext/recaptcha/ang/css/rc2-light.png diff --git a/ext/recaptcha/CRM/Utils/ReCAPTCHA.php b/ext/recaptcha/CRM/Utils/ReCAPTCHA.php index f631733ad9..e4283cb4d8 100644 --- a/ext/recaptcha/CRM/Utils/ReCAPTCHA.php +++ b/ext/recaptcha/CRM/Utils/ReCAPTCHA.php @@ -196,17 +196,27 @@ class CRM_Utils_ReCAPTCHA { } /** + * 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; } diff --git a/ext/recaptcha/Civi/AfformReCaptcha2.php b/ext/recaptcha/Civi/AfformReCaptcha2.php new file mode 100644 index 0000000000..81b341047b --- /dev/null +++ b/ext/recaptcha/Civi/AfformReCaptcha2.php @@ -0,0 +1,66 @@ + ['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.')); + } + } + } + +} diff --git a/ext/recaptcha/ang/afGuiRecaptcha2.ang.php b/ext/recaptcha/ang/afGuiRecaptcha2.ang.php new file mode 100644 index 0000000000..f1d15aad5c --- /dev/null +++ b/ext/recaptcha/ang/afGuiRecaptcha2.ang.php @@ -0,0 +1,17 @@ + [ + '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' => [], +]; diff --git a/ext/recaptcha/ang/afGuiRecaptcha2.module.js b/ext/recaptcha/ang/afGuiRecaptcha2.module.js new file mode 100644 index 0000000000..c6b913a7cd --- /dev/null +++ b/ext/recaptcha/ang/afGuiRecaptcha2.module.js @@ -0,0 +1,3 @@ +(function(angular, $, _) { + angular.module('afGuiRecaptcha2', CRM.angRequires('afGuiRecaptcha2')); +})(angular, CRM.$, CRM._); diff --git a/ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2-menu.html b/ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2-menu.html new file mode 100644 index 0000000000..56de591caf --- /dev/null +++ b/ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2-menu.html @@ -0,0 +1,10 @@ +
  • + + {{:: ts('Light') }} + + {{:: ts('Dark') }} + +
  • + +
  • {{:: ts('Remove ReCaptcha') }}
  • diff --git a/ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2.html b/ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2.html new file mode 100644 index 0000000000..19d09ec4f6 --- /dev/null +++ b/ext/recaptcha/ang/afGuiRecaptcha2/afGuiRecaptcha2.html @@ -0,0 +1,14 @@ +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + diff --git a/ext/recaptcha/ang/crmRecaptcha2.ang.php b/ext/recaptcha/ang/crmRecaptcha2.ang.php new file mode 100644 index 0000000000..741be11a38 --- /dev/null +++ b/ext/recaptcha/ang/crmRecaptcha2.ang.php @@ -0,0 +1,19 @@ + [ + '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', + ], +]; diff --git a/ext/recaptcha/ang/crmRecaptcha2.module.js b/ext/recaptcha/ang/crmRecaptcha2.module.js new file mode 100644 index 0000000000..e663e497de --- /dev/null +++ b/ext/recaptcha/ang/crmRecaptcha2.module.js @@ -0,0 +1,3 @@ +(function(angular, $, _) { + angular.module('crmRecaptcha2', CRM.angRequires('crmRecaptcha2')); +})(angular, CRM.$, CRM._); diff --git a/ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.component.js b/ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.component.js new file mode 100644 index 0000000000..d8b49b06fc --- /dev/null +++ b/ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.component.js @@ -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._); diff --git a/ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.html b/ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.html new file mode 100644 index 0000000000..fc6a59e722 --- /dev/null +++ b/ext/recaptcha/ang/crmRecaptcha2/crmRecaptcha2.html @@ -0,0 +1,2 @@ + +
    diff --git a/ext/recaptcha/ang/css/afGuiRecaptcha2.css b/ext/recaptcha/ang/css/afGuiRecaptcha2.css new file mode 100644 index 0000000000..38da4139f8 --- /dev/null +++ b/ext/recaptcha/ang/css/afGuiRecaptcha2.css @@ -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'); +} diff --git a/ext/recaptcha/ang/css/rc2-dark.png b/ext/recaptcha/ang/css/rc2-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..595ee361f04c3758bf395d9e6829dff17e5295dd GIT binary patch literal 4989 zcmV-@6N2oCP)iN_l2=(;l(T zTxXpzY@IP`ojzinK5U&fVx2Ni>D{z*@;+sBAS|rrYFLwiO3*iJd&CW z4?gcQm%7X~PJhW6-0JGt)7G=osjqgrZ*g_FvGsf!5CoBAQ50tdFN_U5kz9Nb1e9D{ zPb_N0bB*|-Mhs@Q5zE%$pGbPTy?d3@T;()IRI{L%CD|w|{%vYtr)q9+9(@I4N^^;O zzu?{lss}+3ZVE|(dCaNHj$QgnJU5eARF7x5dO$bPtgW65S5G)K-4+Dztah41PGi05 zuPA0sHY&1SmCem^V4JF~RN9Yel_`z{|Fbz~Q&%^t2SJecz~FB|@XW;EF zxt4cg+189^>(OTG39fRPpq^o;ISNfjF=|ZTD4R8AR3yE^{M%I{uXgru`%ncY>rBXj z-40h2)q^0&dzl|Nvp+YUgvyhhf+Z`Ft%G`^&7CXD)f36pBiV!D^mJQbrPExannN!C zsMEhrH4Cy)WoA_}D$J-+UzzG#6r-ee1}UZTtmCM6J@x-kaQ2~k-UWgnT}O2%)6XQA zz8KF;*?p(psyuIcCz@$Qb9U=VO}YFlTML(et!l34#YZoKeI&EWVCvPTeV8_)EV!I# z>mA-Q_3ss3VN_2G1VIR)vf389<4^s|UXNv_;AlAp-r&sdWa?4AaLq)rjW9oE zez+|#yna*>j(&Eq20{R-h*9>zKZQnFVGP zxp#au>Z=m1O1vczyyGp4o+35(Ve8=_2to)?`+R?7;OY31m!i3u)`n$!CvMjqs65GK zRwO+Y$-c;E?uEXA@xyH#f-1J3LXW;*3D%fVkc@(?=b5iWb?{Dwc+13B1@(xa9&Zs^ z52}ZQAPAuhrgnb5f5mIj%oJa*z&LOBotbFvU~ExiUVo}1cxhAb?`pjq~~*crLd1mCyrY0%dQ?OCp588ys!NGhTU$ zl2)KRMevU3tB6`z)T+Go7~kw;=1?fKYuB!yd}+X!!%t8IK_nT*yyP9p6)#8Ald%kM zI$Sq7K4G^?taxp~NsB-$l&O>{V4pVq+};PC3#VoT@}8}Y!K;prbC|J-Y8!}_C*A__ zmF#V+B0#?Rt>;Vh&O7WwDS}|@;f2L>*2BlHvBK$zC_i21H(4$3EVGPje+JeeghFf> zw~u*#_dSmhNqP5IF|M-C6C^!Pe7yA#ttfi!XytFm!qv2c^`+7Leu^Ln$U^NfuGpJg z_Ifxq8OhAR&IQ+4;nZ{ll5Nb1-^R59<)}sLaL464^PTDeg~3#2_sjZv!cTcx-XYvO zMf-ZIEG*0uW1mAs^-zi+h&tEbef!@dnMP<}EHpSCeqby-INmof2G0a<{VP>FiOqXX zh~D{R^B)zwW2~pX^1J&Ec&8+IN}{*q@Z?2(uR{${R1bn6&SQ@2OVxYtf2QZYr+V*u zs_)(>VfNhnL?|_BUiVv36hGu%OnR5U<{T%R8>n8TdWrffL@N=kOnhaLUvE{3w=8(d z4tIeX2l&E;>Ol~sQ##46%QBM{#+W2Y zvd#N1$ds;_qCHH#qtr7@J!{FrQQ{sUo)K~3sOVlx+@s<#qXOjUA);+@sJ#@`gCIye zp7`}|f4_aluHAd~?b@@KXE)DId+yk=W4q1n-MhDL-FnhVCs9g4L-ZX8B0cPI;jqJx zIP|cO%3+5)Ac7#GdJqHw^~By_-wax#)}lE6QM!}aXs~GT3A51wg~19H7jZS(2f;$9 z)>y>svdb4RsF3$I(H5fC8T#7Ws>Fwyc9Y%gvdb>J*=0BHpB#F6pN2O@s?PB49|<@2 zo_ps;xIfN0_w?Xke=z88Z~sOAcBH2e0@uNKfNzUqb?G=()AE1>)@N?DYG6oMPh6iAbSGNlAG<*OMdxT)-NLh{N1L1928{2N@rQ zo?nk1-5HKc(bN8K*CE{?ACGgt+v2X7mqO2%FJHiyty;_-LJ!;@!!W=dPB2dlxD7&q7et+Dd(4#c%UJpI#avd~z_;Bs? z>C>{0Ba`LKnKNr^YrDI9{aWyqZr{HBL$<5X)9RSE+0`ltxf#w=XuE@9QI4U}aNu+` z&2YsKaG(x%QES47l~fB_rXOduX_?1T=lRrx80PDGQ2@r zkl`p39Z^GA6-APzlApvi|1AD%sXwsP;@Zx=7hm*p2OTv%CI!4PnRMZhVd4`{Wtw4~5; z;^<)!dI&Hg41!Q(WqsE35~ngk2Oa8$;Ed2C&5e?xYIE{hHwRS*&VJzgVVt8~CJyMM z8>Zz1!?J>gV_Q9RedAyRSv56@e9r?{O1z{l?3&QSJMbNNGvH2;5iqNt74_VqPupkv zGK(Yr+B~GB7lysGtOiBs`@tX`)r0sR5XrP`KSoC(Jli5_D@}o}UAy-A^XJXYO|Bl4 z>BH@}Z~O1wz1!X0efjd`pSAv|_2R_~3bL>Q=F%cy(9fceLeG%H2+b5VYa$#+>M$a7 zP_i(0`O!$e`(u}ON!5?IN(y~f!70Y05O0CRq@C!4+9AO zROYi+Yqu6&$36;zJ9#Pe05mPR17IeNdy{sl=b@cYuwGRudgE$q|ozI&;!!284&{=7_3ZEO z0~0rH+?bo2yL$C1xI?}VL5ESod?CrKk=w+kH*ek`QT^)Gt4o(IAtj{HGfU{<=2t#M z=1oQqMLI;b^yC4L$=^Q3=utOZpFVxU6Lo{9PMrdMu3WhS z4+y~&hf(sgu|aJPwGh4uA=cM7E?>Tk1y!zRhR{O+7awF^IVJl^=;`pDzBAw&96#Xd z$N?9neP$@&Qi7X-{ zlx99!t!>4eVP~4s@RUetu9nL>gvYE25!wM`-2#YF~q_vIuWk{I+TG%atAph zOb2(cjzZ6@p@%Qm>4w~=d+aiGyDbS3<2()nS1Q?&fIv6kVI)$l0uhTQq37swJskv| z7f@|L78u$$m+N5=#aUU85?lCkeJBEKnTiF9$ke684;D_Mz%$#^%^xMA&$)Bw9zTBk z{P}Y**a-z2pa8;x&d`a{E$R$`g}vE6 zVJr(U56u685(0jT>b=i7B{%O)&RJuO)|$NYs{8DqTnDW#nAYm-5Y z(K#nekL}`*HAoLYfc}3YJg3%Lh3JqTMF2>TJ^-W#0O=t;07#EM0Hg;1=~-)il*tyy zF94*clp<+R?T&15+!^rvvU3KUmg7(W{x2m=n6RNJiY}#AQFIjr6s0JNQWQlHf`cH) zpWUX%w0q~(o4f3rap#>6Zj;=!fqqU-n&jQ0t>1C54W(q!1uquobsL`*EMNUe=rC3> zi$taT8=y~ExJBAb3a~~^^-&kao$;y?*KLoAl1C?@!Yw)fc=h~z2!dc~Z#>LRDj4HQ zIT~+*~0kEJ)%}lcQsaB>cJ8c zx5leloW^)m4@*aI+ohOLwoA@`pdS3+Tz48flX_kZg1~7@op2URQmqc=QZgp;(YURa zEa?FiaEENP(~ac$Z0(e>x9VZC7__%oyi<>?wKq6jd*prfuO&S{R)PpgRuI0UY-hk@CP%O9(M~Uis zN%ar}sRy;-A5e9CL_K%;6#TnFZLn;zZY{;lgzMsei+V)4p?T1dn67?J^$-N92SbR* z$<IrcUu9+dC z!Wifk)k6@Z9z+!V3JyQW=?igH8u##B4`y3(7G20zd(*}LRXw<>rropO^@ITj#hsu9 zmx7aeehCCYka}Jpf*|z}1VNB`2!bH>5ClPxdI*9b^$-L>ka`G$AoUOg!Hdm%=d5+k z!Pe*Ev2ihT#y;Y)`E2Zt-*NjdY)wq+`BV_39+=TCJnc11kEY>*MIVT$l^Ij^X`>#3 zAobt|2csIvi`9ZMp!z@mTKC2;`D}2D0xwsyyGazoW?)B8rNB)VqXG+HmNw;ar}4Z( zmd$3X#PjN5X}f3u3u-JxbPc2UTY*}YvCiqS>1GAi*{uuxSb|-wo+?S|d1VNSdcr%e znwW*gH8k>k)^pAxhQA_jyCN7R?zHaW&}`NkH<@bH##p_VMl{cp>S0N*j@v}C+4PWp zJ%=G{WtURmNvw7$!d11wl82$fVMSTZYnL!m&r3m2uE#<^FI~=0;Y8OJFJ?WaJR9|# zb3DR*Q#;1Nn0`qHz11Q*;G7*wQf-p`a-*Jbv8b^gRfsTHG6b`*BlIxesRxNWrN&Wj z!4kLJt4A3;1_R;GsAq@8S=CM&wnmbAelG}8PjF8NMM{38a6pCETog0Fn6kYMCtNIP zHx$5evMEa9`3YA#0!$cH)omf3QID+Gtq=WqxrPbYZ>1iBAobw8y_N#QjAj96K`)E3 z$FV!292Hm#W^L2Gh>i$)dR9FM&$>0-2J~FV?5U`SXZ;2itTjtR2c#0jCx^wY!tm$k zK#S`HL9l!VtNM)p(@uR!0+l=mL)SIqP!bGL}cdkncn*t z<2=u+p3JPZw$?U)cWbTHS~K&N%yAr7JrUuZj{^Hd#5~Wv_kG{5dP*tVCjpdFrfFK& zb&T=VdI0sb*3<*22T%{dAJqe>2S7c5dH~b|s0TnjK=0f9VCO8H+_ttj{HMeq8G?^7 zCdrFtyUaZF1|BoF0fX#ihAvu?y;vin0BD*JLLF<4-&!|w zOQU*Aopf^w-CU1uN})&+MN%k~L}3n>6i&(^Zf;q1%aY1Y3!Ao7tg}{aU0AiL=KigJ zSh4r)g)vAyp8@~?WJS&A^DWt9wsTD3Cw_4*BsIFHg(f=-lF}7AxDb zkbQhnrLd?fyRdS^#I85HH~k{A>Cwi6g~Dt}XT|rmbqc8GT>t=pO@U-}Zf@>hz2_oz zSHmeelq~Cpc*hqf)Bc!*r#-9Z7FIM{T-ZL{R%XD=3T8HIac8aWH8it^cCW9#ck`+9 zqore#!^$#oP+j-YCw9LC>UkFc04NF>pFBGLK)ZZX!_`QNNC%f6SNx697nghqgj3I= zie@X*Vqv;1Os^T+hsjdTtclIijEF~y-SG3!rk&4T#s{32h)_4vv?SDdKFn~=P5*%O^x=x(wwt!Adn%Jy2=J~J~!yF{AJ zP>e{qGQ>X2`kw~3Jh_|LXK>BwMU@%4@|o=BbD*Ag0RX_6=kxcstC5B)VNSxX336{n zo;SVYiy=RUd{{;#_23&lZKUCHWAF4^*g>-^jqPJ(M4HJMoh17hoNIp(-teE~zQr{o z23OX=ZWA}21@*iQ0Kns|Tz=+}_L*?~jSwdh>5wAQAro;@zBg*#8%G|jp307e?k?;d z3)^pX50+BTAni(1*gj6NG-3e>dIy%y+68vO?9Lk59pWZX&zk@Mcn4FzAs24A9^z1t z)SF=kk8v^v6Oun6`(jebKBVDt+}!%BP}@(vT^44*;?7vzL_HK15R=BnF*>k!GJ3mc zbO;7#MrRjHOxD0`htxv=06PaYpgmmJ>iQ~tQZ`io=5C0x<-8x{Z$e7(c#SGKVLE6b1oTAdB-MT=`663GBFvPdV~$29#sVZh&*__^z!sW zEroFX_2Anv2l*0YBEZT1=={pQBd@;tYA%;+6pr|!1%L8tC?SpK@^8wkD9iHb=;)vS zKSDVNO)j2t3>)l22KzAfj?N+I?7Yq{lGNk+X5VI>oSfXXYu7Vh8u;M&6e<9efXMUs zQ&W-VYmt;3ATdYw#ier3735H;>Bir>PK}R`OR|a-b$WWbNj&OVJsn6W6S=8(-Yt@4 za^?Q!Z)<60jfugLXGm`w(mT>RY@br4)9O1wh-{H;d)DY2G`iA82aY_$ zI$Npi{I{dwD%(N((kLGvp9lbeSI6_e>zNEUXaORfd0%wS8=c2RIxkL%@x&BJdDq7l z`Fy@-(;56Q{vF;STMUdGUs5d^oN3CHE_+8$yfeJO#_N{zv`f^xw)5*jJpcfx>amNt zTM`q#xa^D0cvcrYEApOI(|E!gLy^_5?b?5|SmHf*h!B>I@`jMgYlqjKTwE;}odV?~ z-qG9Bq)5ZX>ukK<&eLoLs|QjKQ4atBPaV(IMv-@QzGlTl^`m1o{~yI;?TT?vboTD# zYg1EG>IdFgQh{i#Irgl6QE$gV&MATcX~^FIdh~&Wype6VyZ00|3B| z!;?2PTnnup^*;Ji?f)*-{^w%tzb|~H8s+nOpIAelg@tX;E_HcJJF6Zq-r$%-<2bdE9U93W@L$ul+y#NzfA0F-uJ zM?@in{Kft8yw0~_ocdw&(vKh3_hHoHvb?gb9>XwFimHpVGwr!iHS=aT? zt)JST9RJwrF$^QelvEV}Gt-XuzRu3MGKW2N_CW!lysEz z6(t>|N;OGv{UCb1R&8_uz3)Yx3(XgnUzmXzx5gU{Jxj2&Har-^j8U%|lJU3T#d zKfm|(Nh*qBe}5l&AEuW7xfFVolvKd=$JyB#SOdyXBo2=N(EIoAXR{efnOEB&g&vW+ zni0&K)&ES3#MZP?TCbG;JLow*J*AIBkvr1&fy;v;=C!dBJ1`IZFH51PPDh$%Mperj zF?Dq_{FUfg7HOPJ7R?T!N0b?sT#3~-OL;wg^nCpI@zbYIWDE3t?m)QJ<>lpSlTzq$ zbSqx0ewmtiU}~D1iycA_2WQG<+9uU?ExjHo$spJd4i3)G&za@H@dUZRf z(4QM&8V=l ztlP8Y#L+Z6&L^R3>6)hLmK#p%rm3XCbi(HYqo(w9%?yh&^+%=#86!WP_hBODS>RfR z2ALx-&g-Td=C>9pXR=%kCE5kphQ<{H*<8F#gD4FhEI^VBi;u<}YuMvx%GJyMc0A1j z$7HjYl>OKz2~V!mt!cU~kT4o$Nx4P67X zxI;n_C35vCklA7aV_QpzX_LU|#-^l?>~A>!Ur#$qzCBz$8S8Ch5MyXKHrvJ+&w~YF z$(av>6-@Gi)K=O8-Q3)qoSYCkQj$K>j@awNhYw|0!UD=19v&hG1is*sK^8ehp#q5X zxw^VqZBz<9c&}^8lfZ1>`iKO9p^B{M|IZUWre6p-gUuP_TW$ujkw`Gmdu_l4C&Mu< zV5+yKp?4%F(b-6 z82Fw(1(wLiI@p8eAIG+@^2o6!6=gZ}CwP`8zFas^S$r>Qd_f#CMn`qSm8_E*hs8f-7G1`acJzIvw&&bw0#;^f1z~ym(TS z^Qx+f$o?VpOo!;PejYuBLeEz3a2+2XgBPq<#&@9-9Fjgmk7jrxFCmP@5&q6>why8S zAylqsr_eJDxOgM>UxOaGrk@SC!ePM0WY7A5%PYhHmZ=K3wpQ2I*B}KQ6Bg;}DQ&f|Erl9FKnL#6Y&HW^=0`_34nJZF=jff~QYhQzNrwev{9M7#vWOp%)7BG~Q%`5CvmQN=U_GQPDde-r{_doFxukG0 zmDPxr8)g!VY|J_w8O{!r6e=P3!uJz`BdF+sUo-2rz~IpUF0erGcV4}E^(Q-Lz#+R1 zhT;GJ8!Cj5BZN{)8A_c}D0R!wdrPmOloCP+1#UhWle4pD_wKT5&DrJ^Q|s zeLmAW;T6Ys1^~NsrQ%tDiM*MNKN4;(S*H5bN&He|iBO?F@BXl7ojpUeo2N&t!%2&n zqfcYV3+$cD^x3AzQih(~af32gxwx-U4*=kE@oOtHQ4Op0-fdGYGuf>EW~HfMK^Zbg zB&mn-`r_zk2-EN>W=fIj9GK$7Am# zlSMAF*wNjkFD1!@>v}TRsD@W5wg_o1YH|-@N6l0>rmU!*n2PLuP{wF)#(1h8zt)1~ zNime><>FpZPr9!fJ4KbD9snMSG;A3+=gB1Jf{AJ}-rJ>?EFX>ET9FiRj~NtGgKfs`TS@AIdH{G>^^g{_C+KnC zS`mBvJ@IxOwc#%(&Wg$2M?Hy;jC$;pNApBO#_4LP2Y|;^PYfaM2UpMLdX`GiJTuL< z@a!p6_nqpYIU90TN;gtei+@c$X4KhJXha9~JSYI_A@^t|PUqdziuc$wlew;Ng-n_a za{Js!CdfQAzjNmdk*UGFOFhKiYsO1jKs&ryIvqv#vU*66Y0WHP&nX6idL9n|^$>P$ z3&G+CIqhsyresgP>xr{1i$4;kSDEbg_o^qZswwuy?|P^pO-K>Zg5~U>o-YRg0QCR> zpdJ7KfO-G`P!9kA>Hz=%s0RQ5^#A|>)C0g{;^sWd7~`BVCh~T%PYX*~UHoUfcBv!} zWC0fbMLz6M&$j|VJ>qH?x3)^$ZANg`AD@UFwX9cH*-9qaNz>de}V`7E~DAqi7?!kNhW8v=`pl7G*``PZ_O~H=H+DLNvyn_>FS(| za`Du_lB{UXRncu8>Uk&t=6bjwRM{==CY&Q(xw%sH>MnIKkBdjR=Q<#n2-#bs^6{z| z=R7GX7-(DDlzsS6R|p}i$-bl>+KkRbga@IRTQh=sRE(px;I`4kvw>tjxlmLHRW`%4 zSq!)Uc)+M9@mxK0QR;fEHANVHYsf5~LXTV~^(2COw65!rYmQgBQ;(L-=2J7?qMm@? zFX#TGL_U~5OFaNUJ>(t=MX0m;OC>aQb30c;1aXA2>ws(Pa}vPyWK)vFJJjRXT8LAO zs>Xh{Z&8mKi@c@!ZYpR1_D87)0H`PS?dzdnejiG~xKL#&c`-YhgQ00=E@aWKA$fJg zVBWdHk$z%TLADK zW+o!NcXyA7`+8KhwZ?tE1mHWZHC4S$CNuBr8Dprbi2MP-r-_KFjxpSQ?_E{*^|-s4 z_1^zK4FG!YW_J5dW*!cQ`+Cm3)^hhbr+p3p%sJhCt@ZrQ`8oIXR8?k1#Qrn@h=|Or cs@r$YPEM437Sy#xW&i*H07*qoM6N<$f<8aEhyVZp literal 0 HcmV?d00001 diff --git a/ext/recaptcha/info.xml b/ext/recaptcha/info.xml index bf847ddb81..ea93a067d5 100644 --- a/ext/recaptcha/info.xml +++ b/ext/recaptcha/info.xml @@ -28,6 +28,8 @@ menu-xml@1.0.0 setting-php@1.0.0 + ang-php@1.0.0 + scan-classes@1.0.0 CRM/Recaptcha -- 2.25.1