diff options
| author | fja-odoo <fja@odoo.com> | 2020-04-24 10:50:28 +0000 |
|---|---|---|
| committer | fja-odoo <fja@odoo.com> | 2020-06-05 10:58:50 +0000 |
| commit | 541148613451f4c14a299672d91c4397d8a9f6de (patch) | |
| tree | c798cd359c3960d4ae86ebc9847666a3a30bf4a6 /addons/website_form | |
| parent | fea1706f275e88a0d28dd2776c6aa0dd62c1bf3d (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')
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 --> |
