From 425c6841b18d0b99f44a4d1f6cb4d5750b836a03 Mon Sep 17 00:00:00 2001
From: Benjamin Vray <bvr@odoo.com>
Date: Thu, 30 Dec 2021 11:16:24 +0000
Subject: [PATCH] [FIX] website, website_form: fix anchor link redirects
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Before this commit, links scrolling to an anchor with a special
character did not work and displayed a traceback. The issue was that to
check that the anchor is valid, we don't need to check that the anchor
is a valid url as we have been doing since these commits [1], [2]. But
we only need to check if the jQuery selector is valid to correctly
target the element to which the page must scroll.

Indeed, the anchor widget returns stuff like 'ok%C3%A9%25' when typing
'oké%' wich is not valid jQuery selector. It has to be encoded to
'#ok\\%C3\\%A9\\%25' to be valid and that's what this commit does.

We also changed the way to display a new anchor to the user in this
commit. Before, we showed the anchor unencoded in a notification and now
we show it encoded. That way, if the user copies the anchor from the
notification, it's the real anchor.

Also, this commit detect if the success URL of the redirect of a from is
the current page to perform a scroll to the anchor instead of a
redirect. To make this comparison, we needed to add the url code of the
language of the current page to the session info.

Also, before this commit, the page froze when we clicked on the "submit"
button of a form that redirected to an anchor that did not exist.

[1]: https://github.com/odoo/odoo/commit/0abfaeda96c2eaa868cc7fc5fa1926dfa90fc420
[2]: https://github.com/odoo/odoo/commit/b492bde6a121be1c15ed90ce0827fcfd72a12f5c

task-2172312

closes odoo/odoo#119720

X-original-commit: fb087dbec96f5e533d1fdd9c2b0c2e00296c83de
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
---
 addons/website/models/ir_http.py              |  1 +
 .../src/js/content/snippets.animation.js      |  8 ++--
 .../static/src/js/editor/snippets.options.js  |  3 +-
 .../static/src/snippets/s_website_form/000.js | 37 ++++++++++++++++---
 4 files changed, 37 insertions(+), 12 deletions(-)

diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 851ef10698be..581e23018330 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -451,6 +451,7 @@ class Http(models.AbstractModel):
         session_info = super(Http, self).get_frontend_session_info()
         session_info.update({
             'is_website_user': request.env.user.id == request.website.user_id.id,
+            'lang_url_code': request.lang._get_cached('url_code'),
             'geoip_country_code': request.session.get('geoip', {}).get('country_code'),
         })
         if request.env.user.has_group('website.group_website_publisher'):
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 038453cee5fb..4adc1524911e 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -12,7 +12,6 @@ var core = require('web.core');
 const dom = require('web.dom');
 var mixins = require('web.mixins');
 var publicWidget = require('web.public.widget');
-var utils = require('web.utils');
 const wUtils = require('website.utils');
 
 var qweb = core.qweb;
@@ -986,9 +985,6 @@ registry.anchorSlide = publicWidget.Widget.extend({
      * @private
      */
     _onAnimateClick: function (ev) {
-        if (this.$target[0].pathname !== window.location.pathname) {
-            return;
-        }
         var hash = this.$target[0].hash;
         if (hash === '#top' || hash === '#bottom') {
             // If the anchor targets #top or #bottom, directly call the
@@ -1003,9 +999,11 @@ registry.anchorSlide = publicWidget.Widget.extend({
             });
             return;
         }
-        if (!utils.isValidAnchor(hash)) {
+        if (!hash.length) {
             return;
         }
+        // Escape special characters to make the jQuery selector to work.
+        hash = '#' + $.escapeSelector(hash.substring(1));
         var $anchor = $(hash);
         const scrollValue = $anchor.attr('data-anchor');
         if (!$anchor.length || !scrollValue) {
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js
index 6bee39ebb374..a516ba9906c6 100644
--- a/addons/website/static/src/js/editor/snippets.options.js
+++ b/addons/website/static/src/js/editor/snippets.options.js
@@ -2544,8 +2544,7 @@ options.registry.anchor = options.Class.extend({
         this.$button = this.$el.find('we-button');
         const clipboard = new ClipboardJS(this.$button[0], {text: () => this._getAnchorLink()});
         clipboard.on('success', () => {
-            const anchor = decodeURIComponent(this._getAnchorLink());
-            const message = sprintf(Markup(_t("Anchor copied to clipboard<br>Link: %s")), anchor);
+            const message = sprintf(Markup(_t("Anchor copied to clipboard<br>Link: %s")), this._getAnchorLink());
             this.displayNotification({
               type: 'success',
               message: message,
diff --git a/addons/website/static/src/snippets/s_website_form/000.js b/addons/website/static/src/snippets/s_website_form/000.js
index 52aaa8231905..ac84e342bc3e 100644
--- a/addons/website/static/src/snippets/s_website_form/000.js
+++ b/addons/website/static/src/snippets/s_website_form/000.js
@@ -339,12 +339,39 @@ odoo.define('website.s_website_form', function (require) {
                     }
                     switch (successMode) {
                         case 'redirect': {
-                            successPage = successPage.startsWith("/#") ? successPage.slice(1) : successPage;
+                            let hashIndex = successPage.indexOf("#");
+                            if (hashIndex > 0) {
+                                // URL containing an anchor detected: extract
+                                // the anchor from the URL if the URL is the
+                                // same as the current page URL so we can scroll
+                                // directly to the element (if found) later
+                                // instead of redirecting.
+                                // Note that both currentUrlPath and successPage
+                                // can exist with or without a trailing slash
+                                // before the hash (e.g. "domain.com#footer" or
+                                // "domain.com/#footer"). Therefore, if they are
+                                // not present, we add them to be able to
+                                // compare the two variables correctly.
+                                let currentUrlPath = window.location.pathname;
+                                if (!currentUrlPath.endsWith("/")) {
+                                    currentUrlPath = currentUrlPath + "/";
+                                }
+                                if (!successPage.includes("/#")) {
+                                    successPage = successPage.replace("#", "/#");
+                                    hashIndex++;
+                                }
+                                if ([successPage, "/" + session.lang_url_code + successPage].some(link => link.startsWith(currentUrlPath + '#'))) {
+                                    successPage = successPage.substring(hashIndex);
+                                }
+                            }
                             if (successPage.charAt(0) === "#") {
-                                await dom.scrollTo($(successPage)[0], {
-                                    duration: 500,
-                                    extraOffset: 0,
-                                });
+                                const successAnchorEl = document.getElementById(successPage.substring(1));
+                                if (successAnchorEl) {
+                                    await dom.scrollTo(successAnchorEl, {
+                                        duration: 500,
+                                        extraOffset: 0,
+                                    });
+                                }
                                 break;
                             }
                             $(window.location).attr('href', successPage);
-- 
GitLab