|
+
diff --git a/addons/event/models/res_config_settings.py b/addons/event/models/res_config_settings.py
index 5105365bdc6..38907e18fb1 100644
--- a/addons/event/models/res_config_settings.py
+++ b/addons/event/models/res_config_settings.py
@@ -1,12 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, fields, models
+import base64
+import binascii
+from odoo import _, api, exceptions, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
+ def _default_use_google_maps_static_api(self):
+ api_key = self.env['ir.config_parameter'].sudo().get_param('google_maps.signed_static_api_key')
+ api_secret = self.env['ir.config_parameter'].sudo().get_param('google_maps.signed_static_api_secret')
+ return bool(api_key and api_secret)
+
+ google_maps_static_api_key = fields.Char("Google Maps API key", compute="_compute_maps_static_api_key",
+ readonly=False, store=True, config_parameter='google_maps.signed_static_api_key')
+ google_maps_static_api_secret = fields.Char("Google Maps API secret", compute="_compute_maps_static_api_secret",
+ readonly=False, store=True, config_parameter='google_maps.signed_static_api_secret')
module_event_sale = fields.Boolean("Tickets")
module_website_event_meet = fields.Boolean("Discussion Rooms")
module_website_event_track = fields.Boolean("Tracks and Agenda")
@@ -17,6 +28,21 @@ class ResConfigSettings(models.TransientModel):
module_event_barcode = fields.Boolean("Barcode")
module_website_event_sale = fields.Boolean("Online Ticketing")
module_event_booth = fields.Boolean("Booth Management")
+ use_google_maps_static_api = fields.Boolean("Google Maps static API", default=_default_use_google_maps_static_api)
+
+ @api.depends('use_google_maps_static_api')
+ def _compute_maps_static_api_key(self):
+ """Clear API key on disabling google maps."""
+ for config in self:
+ if not config.use_google_maps_static_api:
+ config.google_maps_static_api_key = ''
+
+ @api.depends('use_google_maps_static_api')
+ def _compute_maps_static_api_secret(self):
+ """Clear API secret on disabling google maps."""
+ for config in self:
+ if not config.use_google_maps_static_api:
+ config.google_maps_static_api_secret = ''
@api.onchange('module_website_event_track')
def _onchange_module_website_event_track(self):
@@ -27,3 +53,23 @@ class ResConfigSettings(models.TransientModel):
if not config.module_website_event_track:
config.module_website_event_track_live = False
config.module_website_event_track_quiz = False
+
+ def _check_google_maps_static_api_secret(self):
+ for config in self:
+ if config.google_maps_static_api_secret:
+ try:
+ base64.urlsafe_b64decode(config.google_maps_static_api_secret)
+ except binascii.Error:
+ raise exceptions.UserError(_("Please enter a valid base64 secret"))
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ configs = super().create(vals_list)
+ configs._check_google_maps_static_api_secret()
+ return configs
+
+ def write(self, vals):
+ configs = super().write(vals)
+ if vals.get('google_maps_static_api_secret'):
+ configs._check_google_maps_static_api_secret()
+ return configs
diff --git a/addons/event/models/res_partner.py b/addons/event/models/res_partner.py
index f82b58ae502..58a59efc063 100644
--- a/addons/event/models/res_partner.py
+++ b/addons/event/models/res_partner.py
@@ -1,7 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import fields, models
+import base64
+import binascii
+import hmac
+
+import requests
+import werkzeug.urls
+
+from odoo import api, fields, models
class ResPartner(models.Model):
@@ -9,14 +16,71 @@ class ResPartner(models.Model):
event_count = fields.Integer(
'# Events', compute='_compute_event_count', groups='event.group_event_registration_desk')
+ static_map_url = fields.Char(compute="_compute_static_map_url")
+ static_map_url_is_valid = fields.Boolean(compute="_compute_static_map_url_is_valid")
def _compute_event_count(self):
self.event_count = 0
for partner in self:
partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)])
+ @api.depends('zip', 'city', 'country_id', 'street')
+ def _compute_static_map_url(self):
+ for partner in self:
+ partner.static_map_url = partner._google_map_signed_img(zoom=13, width=598, height=200)
+
+ def _compute_static_map_url_is_valid(self):
+ """Compute whether the link is valid.
+
+ This should only remain valid for a relatively short time.
+ Here, for the duration it is in cache.
+ """
+ session = requests.Session()
+ for partner in self:
+ url = self.static_map_url
+ if not url:
+ partner.static_map_url_is_valid = False
+ continue
+
+ is_valid = False
+ # If the response isn't strictly successful, assume invalid url
+ try:
+ res = session.get(url, timeout=2)
+ if res.ok and not res.headers.get('X-Staticmap-API-Warning'):
+ is_valid = True
+ except requests.exceptions.RequestException:
+ pass
+
+ partner.static_map_url_is_valid = is_valid
+
def action_event_view(self):
action = self.env["ir.actions.actions"]._for_xml_id("event.action_event_view")
action['context'] = {}
action['domain'] = [('registration_ids.partner_id', 'child_of', self.ids)]
return action
+
+ def _google_map_signed_img(self, zoom=13, width=298, height=298):
+ """Create a signed static image URL for the location of this partner."""
+ GOOGLE_MAPS_STATIC_API_KEY = self.env['ir.config_parameter'].sudo().get_param('google_maps.signed_static_api_key')
+ GOOGLE_MAPS_STATIC_API_SECRET = self.env['ir.config_parameter'].sudo().get_param('google_maps.signed_static_api_secret')
+ if not GOOGLE_MAPS_STATIC_API_KEY or not GOOGLE_MAPS_STATIC_API_SECRET:
+ return None
+ # generate signature as per https://developers.google.com/maps/documentation/maps-static/digital-signature#server-side-signing
+ location_string = f"{self.street}, {self.city} {self.zip}, {self.country_id and self.country_id.display_name or ''}"
+ params = {
+ 'center': location_string,
+ 'markers': f'size:mid|{location_string}',
+ 'size': f"{width}x{height}",
+ 'zoom': zoom,
+ 'sensor': "false",
+ 'key': GOOGLE_MAPS_STATIC_API_KEY,
+ }
+ unsigned_path = '/maps/api/staticmap?' + werkzeug.urls.url_encode(params)
+ try:
+ api_secret_bytes = base64.urlsafe_b64decode(GOOGLE_MAPS_STATIC_API_SECRET + "====")
+ except binascii.Error:
+ return None
+ url_signature_bytes = hmac.digest(api_secret_bytes, unsigned_path.encode(), 'sha1')
+ params['signature'] = base64.urlsafe_b64encode(url_signature_bytes)
+
+ return 'https://maps.googleapis.com/maps/api/staticmap?' + werkzeug.urls.url_encode(params)
diff --git a/addons/event/views/res_config_settings_views.xml b/addons/event/views/res_config_settings_views.xml
index f26b6c7796c..5b734a72bca 100644
--- a/addons/event/views/res_config_settings_views.xml
+++ b/addons/event/views/res_config_settings_views.xml
@@ -52,6 +52,25 @@
+
+
+
+
+
+
+
diff --git a/addons/mail/views/res_config_settings_views.xml b/addons/mail/views/res_config_settings_views.xml
index 291c3c95cb7..b805040ed73 100644
--- a/addons/mail/views/res_config_settings_views.xml
+++ b/addons/mail/views/res_config_settings_views.xml
@@ -42,7 +42,10 @@
Save this page and come back here to set up the feature.
-
+
diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py
index 5271c302adc..0f6a8c86b30 100644
--- a/odoo/tools/mail.py
+++ b/odoo/tools/mail.py
@@ -223,16 +223,6 @@ def html_sanitize(src, silent=True, sanitize_tags=True, sanitize_attributes=Fals
cleaner = _Cleaner(**kwargs)
cleaned = cleaner.clean_html(src)
assert isinstance(cleaned, str)
- # MAKO compatibility: $, { and } inside quotes are escaped, preventing correct mako execution
- cleaned = cleaned.replace(u'%24', u'$')
- cleaned = cleaned.replace(u'%7B', u'{')
- cleaned = cleaned.replace(u'%7D', u'}')
- cleaned = cleaned.replace(u'%20', u' ')
- cleaned = cleaned.replace(u'%5B', u'[')
- cleaned = cleaned.replace(u'%5D', u']')
- cleaned = cleaned.replace(u'%7C', u'|')
- cleaned = cleaned.replace(u'<%', u'<%')
- cleaned = cleaned.replace(u'%>', u'%>')
# html considerations so real html content match database value
cleaned = cleaned.replace(u'\xa0', u' ')
except etree.ParserError as e:
|