summaryrefslogtreecommitdiff
path: root/addons/website_form
diff options
context:
space:
mode:
authorfja-odoo <fja@odoo.com>2020-04-24 10:50:28 +0000
committerfja-odoo <fja@odoo.com>2020-06-05 10:58:50 +0000
commit541148613451f4c14a299672d91c4397d8a9f6de (patch)
treec798cd359c3960d4ae86ebc9847666a3a30bf4a6 /addons/website_form
parentfea1706f275e88a0d28dd2776c6aa0dd62c1bf3d (diff)
[IMP] google_recaptcha, *: integrate recaptchaV3
* = base_setup, website_form, website_sale, website_crm, crm_iap_lead_website, website_hr_recruitment, website_mass_mailing Integrate reCaptchaV3 on website_form submit and website_mass_mailing subscription. You can now use ReCaptchaV3 to add reCaptcha verification in any module using google_recaptcha. Also added a better error management on the form with custom messages. task-2217980 closes odoo/odoo#48466 Related: odoo/enterprise#9649 Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
Diffstat (limited to 'addons/website_form')
-rw-r--r--addons/website_form/__manifest__.py2
-rw-r--r--addons/website_form/controllers/main.py19
-rw-r--r--addons/website_form/static/src/snippets/s_website_form/000.js34
-rw-r--r--addons/website_form/static/src/snippets/s_website_form/001.scss10
-rw-r--r--addons/website_form/static/src/snippets/s_website_form/options.js31
-rw-r--r--addons/website_form/static/src/xml/website_form.xml10
-rw-r--r--addons/website_form/static/src/xml/website_form_editor.xml9
-rw-r--r--addons/website_form/views/snippets/s_website_form.xml2
8 files changed, 89 insertions, 28 deletions
diff --git a/addons/website_form/__manifest__.py b/addons/website_form/__manifest__.py
index f1d0c0647df..afb17e1a5a0 100644
--- a/addons/website_form/__manifest__.py
+++ b/addons/website_form/__manifest__.py
@@ -7,7 +7,7 @@
This module adds a new building block in the website builder in order to build new forms from scratch in any website page.
""",
'version': '1.0',
- 'depends': ['website', 'mail'],
+ 'depends': ['website', 'mail', 'google_recaptcha'],
'data': [
'data/mail_mail_data.xml',
'views/assets.xml',
diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py
index 218c8189306..0a1cb0f24e8 100644
--- a/addons/website_form/controllers/main.py
+++ b/addons/website_form/controllers/main.py
@@ -8,11 +8,11 @@ import pytz
from datetime import datetime
from psycopg2 import IntegrityError
-from odoo import http, SUPERUSER_ID
+from odoo import http, SUPERUSER_ID, _
from odoo.http import request
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.translate import _
-from odoo.exceptions import ValidationError
+from odoo.exceptions import ValidationError, UserError
from odoo.addons.base.models.ir_qweb_fields import nl2br
@@ -21,9 +21,22 @@ class WebsiteForm(http.Controller):
# Check and insert values from the form on the model <model>
@http.route('/website_form/<string:model_name>', type='http', auth="public", methods=['POST'], website=True)
def website_form(self, model_name, **kwargs):
+ try:
+ if request.env['ir.http']._verify_request_recaptcha_token('website_form'):
+ return self._handle_website_form(model_name, **kwargs)
+ error = _("Suspicious activity detected by Google reCaptcha.")
+ except (ValidationError, UserError) as e:
+ error = e.args[0]
+ return json.dumps({
+ 'error': error,
+ })
+
+ def _handle_website_form(self, model_name, **kwargs):
model_record = request.env['ir.model'].sudo().search([('model', '=', model_name), ('website_form_access', '=', True)])
if not model_record:
- return json.dumps(False)
+ return json.dumps({
+ 'error': _("The form's specified model does not exist")
+ })
try:
data = self.extract_data(model_record, request.params)
diff --git a/addons/website_form/static/src/snippets/s_website_form/000.js b/addons/website_form/static/src/snippets/s_website_form/000.js
index 4d08906c6b5..b06a9b62a74 100644
--- a/addons/website_form/static/src/snippets/s_website_form/000.js
+++ b/addons/website_form/static/src/snippets/s_website_form/000.js
@@ -3,6 +3,7 @@ odoo.define('website_form.s_website_form', function (require) {
var core = require('web.core');
var time = require('web.time');
+ const {ReCaptcha} = require('google_recaptcha.ReCaptchaV3');
var ajax = require('web.ajax');
var publicWidget = require('web.public.widget');
@@ -21,11 +22,19 @@ odoo.define('website_form.s_website_form', function (require) {
*/
init: function () {
this._super(...arguments);
+ this._recaptcha = new ReCaptcha();
this.__started = new Promise(resolve => this.__startResolve = resolve);
},
-
+ willStart: function () {
+ const res = this._super(...arguments);
+ if (!this.$target[0].classList.contains('s_website_form_no_recaptcha')) {
+ this._recaptcha.loadLibs();
+ }
+ return res;
+ },
start: function () {
var self = this;
+
// Initialize datetimepickers
var datepickers_options = {
minDate: moment({y: 1900}),
@@ -86,7 +95,7 @@ odoo.define('website_form.s_website_form', function (require) {
this.$target.parent().find('.s_website_form_end_message').addClass('d-none');
},
- send: function (e) {
+ send: async function (e) {
e.preventDefault(); // Prevent the default submit behavior
// Prevent users from crazy clicking
this.$target.find('.s_website_form_send, .o_website_form_send').addClass('disabled'); // !compatibility
@@ -95,7 +104,7 @@ odoo.define('website_form.s_website_form', function (require) {
self.$target.find('#s_website_form_result, #o_website_form_result').empty(); // !compatibility
if (!self.check_error_fields({})) {
- self.update_status('invalid');
+ self.update_status('error', _t("Please fill in the form correctly."));
return false;
}
@@ -132,6 +141,13 @@ odoo.define('website_form.s_website_form', function (require) {
}
});
+ const tokenObj = await this._recaptcha.getToken('website_form');
+ if (tokenObj.token) {
+ form_values['recaptcha_token_response'] = tokenObj.token;
+ } else if (tokenObj.error) {
+ self.update_status('error', tokenObj.error);
+ return false;
+ }
// Post form and handle result
ajax.post(this.$target.attr('action') + (this.$target.data('force_action') || this.$target.data('model_name')), form_values)
.then(function (result_data) {
@@ -140,7 +156,7 @@ odoo.define('website_form.s_website_form', function (require) {
result_data = JSON.parse(result_data);
if (!result_data.id) {
// Failure, the server didn't return the created record ID
- self.update_status('error');
+ self.update_status('error', result_data.error ? result_data.error : false);
if (result_data.error_fields) {
// If the server return a list of bad fields, show these fields for users
self.check_error_fields(result_data.error_fields);
@@ -265,17 +281,23 @@ odoo.define('website_form.s_website_form', function (require) {
return value;
},
- update_status: function (status) {
+ update_status: function (status, message) {
if (status !== 'success') { // Restore send button behavior if result is an error
this.$target.find('.s_website_form_send, .o_website_form_send').removeClass('disabled'); // !compatibility
}
var $result = this.$('#s_website_form_result, #o_website_form_result'); // !compatibility
+ if (status === 'error' && !message) {
+ message = _t("An error has occured, the form has not been sent.");
+ }
+
// Note: we still need to wait that the widget is properly started
// before any qweb rendering which depends on xmlDependencies
// because the event handlers are binded before the call to
// willStart for public widgets...
- this.__started.then(() => $result.replaceWith(qweb.render(`website_form.status_${status}`)));
+ this.__started.then(() => $result.replaceWith(qweb.render(`website_form.status_${status}`, {
+ message: message,
+ })));
},
});
});
diff --git a/addons/website_form/static/src/snippets/s_website_form/001.scss b/addons/website_form/static/src/snippets/s_website_form/001.scss
index acfaae6f5a1..375a4bb5fe0 100644
--- a/addons/website_form/static/src/snippets/s_website_form/001.scss
+++ b/addons/website_form/static/src/snippets/s_website_form/001.scss
@@ -43,15 +43,15 @@
padding-bottom: 0.5rem;
}
- .s_website_form_submit {
+ .s_website_form_submit, .s_website_form_recaptcha {
.s_website_form_label {
float: left;
height: 1px;
}
- &.s_website_form_no_submit_label {
- .s_website_form_label {
- display: none;
- }
+ }
+ .s_website_form_no_submit_label {
+ .s_website_form_label {
+ display: none;
}
}
}
diff --git a/addons/website_form/static/src/snippets/s_website_form/options.js b/addons/website_form/static/src/snippets/s_website_form/options.js
index f21f3e11ef2..582791f381f 100644
--- a/addons/website_form/static/src/snippets/s_website_form/options.js
+++ b/addons/website_form/static/src/snippets/s_website_form/options.js
@@ -9,7 +9,10 @@ const qweb = core.qweb;
const _t = core._t;
const FormEditor = options.Class.extend({
- xmlDependencies: ['/website_form/static/src/xml/website_form_editor.xml'],
+ xmlDependencies: [
+ '/website_form/static/src/xml/website_form_editor.xml',
+ '/google_recaptcha/static/src/xml/recaptcha.xml',
+ ],
//----------------------------------------------------------------------
// Private
@@ -340,8 +343,8 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
const proms = [this._super(...arguments)];
// Disable text edition
this.$target.attr('contentEditable', false);
- // Make button editable
- this.$target.find('.s_website_form_send').attr('contentEditable', true);
+ // Make button and recaptcha editable
+ this.$target.find('.s_website_form_send, .s_website_form_recaptcha').attr('contentEditable', true);
// Get potential message
this.$message = this.$target.parent().find('.s_website_form_end_message');
this.showEndMessage = false;
@@ -479,6 +482,22 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
this.$target[0].dataset.mark = value.trim();
this._setLabelsMark();
},
+ /**
+ * Toggle the recaptcha legal terms
+ */
+ toggleRecaptchaLegal: function (previewMode, value, params) {
+ const recaptchaLegalEl = this.$target[0].querySelector('.s_website_form_recaptcha');
+ if (recaptchaLegalEl) {
+ recaptchaLegalEl.remove();
+ } else {
+ const template = document.createElement('template');
+ const labelWidth = this.$target[0].querySelector('.s_website_form_label').style.width;
+ template.innerHTML = qweb.render("webite_form.s_website_form_recaptcha_legal", {labelWidth: labelWidth});
+ const legal = template.content.firstElementChild;
+ legal.setAttribute('contentEditable', true);
+ this.$target.find('.s_website_form_submit').before(legal);
+ }
+ },
//--------------------------------------------------------------------------
// Private
@@ -503,6 +522,8 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
return this.$target[0].dataset.successMode;
case 'setMark':
return this._getMark();
+ case 'toggleRecaptchaLegal':
+ return !this.$target[0].querySelector('.s_website_form_recaptcha') || '';
}
return this._super(...arguments);
},
@@ -643,7 +664,7 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
await formInfo.formFields.forEach(async field => {
field.formatInfo = formatInfo;
await this._fetchFieldRecords(field);
- this.$target.find('.s_website_form_submit').before(this._renderField(field));
+ this.$target.find('.s_website_form_submit, .s_website_form_recaptcha').first().before(this._renderField(field));
});
}
},
@@ -1219,7 +1240,7 @@ options.registry.AddFieldForm = FormEditor.extend({
const field = this._getCustomField('char', 'Custom Text');
field.formatInfo = this._getDefaultFormat();
const htmlField = this._renderField(field);
- this.$target.find('.s_website_form_submit').before(htmlField);
+ this.$target.find('.s_website_form_submit, .s_website_form_recaptcha').first().before(htmlField);
this.trigger_up('activate_snippet', {
$snippet: $(htmlField),
});
diff --git a/addons/website_form/static/src/xml/website_form.xml b/addons/website_form/static/src/xml/website_form.xml
index 5403f763e24..9c1390c74df 100644
--- a/addons/website_form/static/src/xml/website_form.xml
+++ b/addons/website_form/static/src/xml/website_form.xml
@@ -8,17 +8,11 @@
</span>
</t>
- <!-- Missing field status -->
- <t t-name="website_form.status_invalid">
- <span id="s_website_form_result" class="text-danger ml8">
- <i class="fa fa-close mr4" role="img" aria-label="Error" title="Error"/>Please fill in the form correctly.
- </span>
- </t>
-
<!-- Error status -->
<t t-name="website_form.status_error">
<span id="s_website_form_result" class="text-danger ml8">
- <i class="fa fa-close mr4" role="img" aria-label="Error" title="Error"/>An error has occured, the form has not been sent.
+ <i class="fa fa-close mr4" role="img" aria-label="Error" title="Error"/>
+ <t t-esc="message"/>
</span>
</t>
</templates>
diff --git a/addons/website_form/static/src/xml/website_form_editor.xml b/addons/website_form/static/src/xml/website_form_editor.xml
index 6db59f01a22..13d4ae1aeb5 100644
--- a/addons/website_form/static/src/xml/website_form_editor.xml
+++ b/addons/website_form/static/src/xml/website_form_editor.xml
@@ -21,6 +21,15 @@
</div>
</t>
+ <t t-name="webite_form.s_website_form_recaptcha_legal">
+ <div class="col-12 s_website_form_recaptcha" data-name="Recaptcha Legal">
+ <div t-attf-style="width: #{labelWidth or '200px'}" class="s_website_form_label"/>
+ <div class="col-sm">
+ <t t-call="google_recaptcha.recaptcha_legal_terms"/>
+ </div>
+ </div>
+ </t>
+
<!-- Generic Field Layout -->
<!-- Changes made here needs to be reflected in the different Form view (Contact Us, Jobs, ...) -->
<t t-name="website_form.field">
diff --git a/addons/website_form/views/snippets/s_website_form.xml b/addons/website_form/views/snippets/s_website_form.xml
index 16d10107cf4..8683db21832 100644
--- a/addons/website_form/views/snippets/s_website_form.xml
+++ b/addons/website_form/views/snippets/s_website_form.xml
@@ -39,6 +39,8 @@
<we-button class="fa fa-fw fa-eye align-self-end toggle-edit-message" title="Edit Message" data-name="message_opt" data-dependencies="show_message_opt"/>
</we-row>
<we-urlpicker string="URL" data-select-data-attribute="/contactus-thank-you" data-attribute-name="successPage" data-name="url_opt" data-dependencies="show_redirect_opt"/>
+ <t t-set="recaptcha_public_key" t-value="request.env['ir.config_parameter'].sudo().get_param('recaptcha_public_key')"/>
+ <we-checkbox t-if="recaptcha_public_key" string="Show reCaptcha Policy" data-toggle-recaptcha-legal="" data-no-preview="true"/>
</div>
<!-- Add Field Form -->