class IComunicatoStampa(model.Schema): arguments = schema.Tuple( title=_("arguments_label", default=u"Arguments"), description=_("arguments_help", default="Select one or more values."), value_type=schema.TextLine(), required=True, missing_value=(), ) directives.widget( "arguments", AjaxSelectFieldWidget, vocabulary="rer.ufficiostampa.vocabularies.arguments", pattern_options={"allowNewItems": "false"}, ) legislature = schema.TextLine( title=_(u"label_legislature", default=u"Legislature"), description=u"", required=True, defaultFactory=defaultLegislature, ) directives.mode(legislature="display") message_sent = schema.Bool( title=_(u"label_sent", default=u"Sent"), description=u"", required=False, default=False, ) comunicato_number = schema.TextLine(title=u"", description=u"", required=False) directives.omitted("message_sent") directives.omitted("comunicato_number")
class ILegislaturesRowSchema(model.Schema): legislature = schema.SourceText( title=_( "legislature_label", default=u"Legislature", ), description=_( "legislature_help", default=u"Insert the legislature name.", ), required=True, ) arguments = schema.List( title=_( "legislature_arguments_label", default=u"Arguments", ), description=_( "legislature_arguments_help", default=u"Insert a list of arguments related to this legislature." u" One per line.", ), required=True, value_type=schema.TextLine(), )
class UfficiostampaSettingsEditForm(controlpanel.RegistryEditForm): """ """ schema = IRerUfficiostampaSettings id = "UfficiostampaSettingsEditForm" label = _(u"Ufficio Stampa settings") description = u"" fields = field.Fields(IRerUfficiostampaSettings) fields["legislatures"].widgetFactory = JSONFieldWidget @property def can_manage_settings(self): current = api.user.get_current() return api.user.has_permission("rer.ufficiostampa: Manage Settings", user=current) def updateWidgets(self): """ """ super(UfficiostampaSettingsEditForm, self).updateWidgets() self.widgets["legislatures"].schema = ILegislaturesRowSchema if not self.can_manage_settings: fields = [ "token_secret", "token_salt", "frontend_url", "external_sender_url", "css_styles", "comunicato_number", "comunicato_year", ] for field_id in fields: self.widgets[field_id].mode = HIDDEN_MODE @button.buttonAndHandler(_(u"Save"), name="save") def handleSave(self, action): super(UfficiostampaSettingsEditForm, self).handleSave(self, action) @button.buttonAndHandler(_(u"Cancel"), name="cancel") def handleCancel(self, action): if not self.can_manage_settings: api.portal.show_message( message=_(u"Changes canceled."), type="info", request=self.request, ) self.request.response.redirect(u"{0}/channels-management".format( api.portal.get().absolute_url())) else: super(UfficiostampaSettingsEditForm, self).handleCancel(self, action)
def label(self): types_tool = api.portal.get_tool(name="portal_types") return _( "send_form_title", u"Send ${type}", mapping={"type": types_tool.getTypeInfo(self.context.portal_type).title}, )
def parse_query(self): data = json_body(self.request) if "file" not in data: raise BadRequest( _("missing_file", default=u"You need to pass a file at least.")) return data
def send(self, message, mto, site_title): portal = api.portal.get() overview_controlpanel = getMultiAdapter((portal, self.request), name="overview-controlpanel") if overview_controlpanel.mailhost_warning(): logger.error("MailHost is not configured.") return False registry = getUtility(IRegistry) encoding = registry.get("plone.email_charset", "utf-8") mailHost = api.portal.get_tool(name="MailHost") subject = translate( _( "cancel_subscription_subject_label", default=u"Manage channels subscriptions cancel for ${site}", mapping={"site": site_title}, ), context=self.request, ) try: mailHost.send( message, mto=mto, mfrom=mail_from(), subject=subject, charset=encoding, msg_type="text/html", immediate=True, ) except (SMTPException, RuntimeError) as e: logger.exception(e) return False return True
def handleCancel(self, action): return self.return_with_message( message=_( "cancel_action", default=u"Action cancelled", ), type=u"info", )
class ICancelSubscriptionsRequestForm(Interface): """ define field to manage subscriptions """ email = schema.Email( title=_(u"manage_subscriptions_email_title", default=u"Email"), description=u"", required=True, )
def add_send_error_message(self): api.portal.show_message( message=_( "error_send_mail", default=u"Error sending mail. Contact site administrator.", ), request=self.request, type="error", )
def send_external(self, data, body): frontend_url = self.get_value_from_settings(field="frontend_url") external_sender_url = self.get_value_from_settings(field="external_sender_url") channel_url = api.portal.get().absolute_url() if frontend_url: channel_url = frontend_url subscribers = self.get_subscribers(data) send_uid = self.set_history_start(data=data, subscribers=len(subscribers)) payload = { "channel_url": channel_url, "subscribers": subscribers, "subject": self.subject, "mfrom": mail_from(), "text": body, "send_uid": send_uid, } params = {"url": external_sender_url} attachments = self.get_attachments_external(data) if attachments: params["data"] = payload params["files"] = self.get_attachments_external(data) else: params["data"] = json.dumps(payload) params["headers"] = {"Content-Type": "application/json"} try: response = requests.post(**params) except (ConnectionError, Timeout) as e: logger.exception(e) self.add_send_error_message() if send_uid: self.update_history(send_id=send_uid, status="error") return if response.status_code != 200: logger.error( 'Unable to send "{message}": {reason}'.format( # noqa message=self.subject, reason=response.text, ) ) self.add_send_error_message() if send_uid: self.update_history(send_id=send_uid, status="error") return # finish status will be managed via async calls api.portal.show_message( message=_( "success_send_mail_async", default=u"Send queued with success. " u"See the status in send history.", ), request=self.request, type="info", )
def handleCancel(self, action): api.portal.show_message( message=_( "cancel_action", default=u"Action cancelled", ), type=u"info", request=self.request, ) return self.request.response.redirect(self.context.absolute_url())
def handleCancel(self, action): if not self.can_manage_settings: api.portal.show_message( message=_(u"Changes canceled."), type="info", request=self.request, ) self.request.response.redirect(u"{0}/channels-management".format( api.portal.get().absolute_url())) else: super(UfficiostampaSettingsEditForm, self).handleCancel(self, action)
def send_notify_unsubscription(self, channels, record, deleted=False): portal = api.portal.get() site_title = get_site_title() overview_controlpanel = getMultiAdapter((portal, self.request), name="overview-controlpanel") if overview_controlpanel.mailhost_warning(): logger.error("MailHost is not configured.") return False registry = getUtility(IRegistry) encoding = registry.get("plone.email_charset", "utf-8") mailHost = api.portal.get_tool(name="MailHost") subject = translate( _( "subscriptions_updated_label", default=u"Subscriptions updated for ${site}", mapping={"site": site_title}, ), context=self.request, ) message = prepare_email_message( context=api.portal.get(), template="@@cancel_subscriptions_notify_template", parameters={ "site_title": site_title, "name": "{} {}".format( record.attrs.get("surname", ""), record.attrs.get("name", ""), ), "email": record.attrs.get("email", ""), "deleted": deleted, "channels": channels, }, ) mto = mail_from() try: mailHost.send( message, mto=mto, mfrom=mto, subject=subject, charset=encoding, msg_type="text/html", immediate=True, ) except (SMTPException, RuntimeError) as e: logger.exception(e) return False return True
def handleSave(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return tool = getUtility(ISubscriptionsStore) subscription_id = data.get("uid", None) record = tool.get_record(subscription_id) if not record: msg = _( u"manage_subscriptions_inexistent_mail", default=u"Mail not found. Unable to change settings.", ) return self.return_with_message(message=msg, type="error") record_channels = record.attrs.get("channels", []) data_channels = data.get("channels", []) removed_channels = [ x for x in record_channels if x not in data_channels ] if not data_channels: # completely unsubscribed, so remove it from the db tool.delete(id=subscription_id) self.send_notify_unsubscription(channels=removed_channels, record=record, deleted=True) else: tool.update( id=subscription_id, data={"channels": data.get("channels")}, ) self.send_notify_unsubscription(channels=removed_channels, record=record, deleted=False) return self.return_with_message( message=_( "cancel_subscriptions_success", default=u"Cancel registered.", ), type=u"info", )
def check_emails(value): """Check that all values are valid email addresses""" reg_tool = api.portal.get_tool(name="portal_registration") for address in value: if not reg_tool.isValidEmail(address): raise Invalid( _( "validation_invalid_email", default="Invalid email address: ${address}", mapping={"address": address}, ) ) return True
def add(self, data): old_record = self.search(query={"email": data.get("email", "")}) if old_record: msg = translate( _( "address_already_registered", default=u"E-mail address already registered.", ), context=getRequest(), ) if six.PY2: msg = msg.encode("utf-8") raise ValueError(msg) return super(SubscriptionsStore, self).add(data=data)
def get_csv_data(self, data): if data.get("content-type", "") != "text/comma-separated-values": raise BadRequest( _( "wrong_content_type", default=u"You need to pass a csv file.", )) csv_data = data["data"] if data.get("encoding", "") == "base64": csv_data = base64.b64decode(csv_data) try: csv_data = csv_data.decode() except UnicodeDecodeError: pass csv_value = StringIO(csv_data) else: csv_value = csv_data try: dialect = csv.Sniffer().sniff(csv_data, delimiters=";,") return { "csv": csv.DictReader( csv_value, lineterminator=dialect.lineterminator, quoting=dialect.quoting, doublequote=dialect.doublequote, delimiter=dialect.delimiter, quotechar=dialect.quotechar, ) } except Exception as e: logger.exception(e) return { "error": _("error_reading_csv", default=u"Error reading csv file.") }
def handleSave(self, action): data, errors = self.extractData() if not self.get_subscribers(data=data): raise ActionExecutionError( Invalid( _( "empty_subscribers", default=u"You need to provide at least one email address or channel.", # noqa ) ) ) if errors: self.status = self.formErrorsMessage return return self.sendMessage(data=data)
class ICancelSubscriptionsForm(Interface): """ """ channels = schema.List( title=_( u"manage_subscriptions_channels_title", default=u"Deselect the channels that you do not want to be " u"subscribed anymore.", ), description=u"", required=False, defaultFactory=getSubscriptions, missing_value=(), value_type=schema.Choice(source=subscriptionsVocabulary), ) uid = schema.Int(readonly=True, defaultFactory=getUid)
class ISendForm(Interface): channels = schema.List( title=_(u"send_channels_title", default=u"Channels"), description=_( u"send_channels_description", default=u"Select which channels should receive this Comunicato. " u"All email address subscribed to this channel will receive it. ", ), required=False, missing_value=(), value_type=schema.Choice(source="rer.ufficiostampa.vocabularies.channels"), ) additional_addresses = schema.List( title=_(u"additional_addresses_title", default=u"Additional addresses"), description=_( u"additional_addresses_description", default=u"Insert a list of additional addressed that will receive " u"the mail. One per line. You can use this field also for testing " u"without sending emails to all subscribed addresses.", ), required=False, missing_value=(), value_type=schema.TextLine(), constraint=check_emails, ) notes = schema.Text( title=_(u"notes_title", default=u"Notes"), description=_( u"notes_description", default=u"Additional notes.", ), required=False, ) attachments = schema.List( title=_(u"send_attachments_title", default=u"Attachments"), description=_( u"send_attachments_description", default=u"Select which attachment you want to send via email. " u"You can only select first level Files and Images.", ), required=False, missing_value=(), value_type=schema.Choice(source="rer.ufficiostampa.vocabularies.attachments"), defaultFactory=default_attachments, )
def send_internal(self, data, body): portal = api.portal.get() overview_controlpanel = getMultiAdapter( (portal, self.request), name="overview-controlpanel" ) if overview_controlpanel.mailhost_warning(): return {"error": "MailHost is not configured."} subscribers = self.get_subscribers(data) registry = getUtility(IRegistry) encoding = registry.get("plone.email_charset", "utf-8") msg = EmailMessage() msg.set_content(body) msg["Subject"] = self.subject msg["From"] = mail_from() msg["Reply-To"] = mail_from() msg.replace_header("Content-Type", 'text/html; charset="utf-8"') self.manage_attachments(data=data, msg=msg) host = api.portal.get_tool(name="MailHost") msg["Bcc"] = ", ".join(subscribers) send_id = self.set_history_start(data=data, subscribers=len(subscribers)) try: host.send(msg, charset=encoding) except (SMTPException, RuntimeError) as e: logger.exception(e) self.add_send_error_message() self.update_history(send_id=send_id, status="error") return api.portal.show_message( message=_( "success_send_mail", default=u"Send complete.", ), request=self.request, type="info", ) if send_id: self.update_history(send_id=send_id, status="success")
def handleSave(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return email = data.get("email", None) tool = getUtility(ISubscriptionsStore) subscriptions = tool.search(query={"email": email}) if not subscriptions: msg = _( u"manage_subscriptions_request_inexistent_mail", default=u"Mail not found. Unable to send the link.", ) api.portal.show_message(message=msg, request=self.request, type=u"error") return subscription = subscriptions[0] # create CSRF token token = createToken() # sign data serializer = self.get_serializer() if not serializer: msg = _( u"manage_subscriptions_request_serializer_error", default=u"Serializer secret and salt not set in control panel." u" Unable to send the link.", ) api.portal.show_message(message=msg, request=self.request, type=u"error") return secret = serializer.dumps({ "id": subscription.intid, "email": subscription.attrs.get("email", ""), }) # send confirm email url = "{url}/cancel-subscriptions?secret={secret}&_authenticator={token}".format( # noqa url=self.context.absolute_url(), secret=secret, token=token) site_title = get_site_title() mail_text = prepare_email_message( context=api.portal.get(), template="@@cancel_subscriptions_mail_template", parameters={ "url": url, "site_title": site_title }, ) res = self.send(message=mail_text, mto=email, site_title=site_title) if not res: msg = _( u"manage_subscriptions_not_send", default=u"Unable to send manage subscriptions link. " u"Please contact site administrator.", ) msg_type = "error" else: msg = _( u"cancel_subscriptions_send_success", default=u"You will receive an email with a link to manage " u"your cancellation.", ) msg_type = "info" api.portal.show_message(message=msg, request=self.request, type=msg_type)
class CancelSubscriptionsForm(form.Form): label = _("cancel_subscriptions_request_title", u"Delete me") description = _( "manage_subscriptions_help", u"This is the list of your subscriptions.", ) ignoreContext = True fields = field.Fields(ICancelSubscriptionsForm) fields["channels"].widgetFactory = CheckBoxFieldWidget def updateWidgets(self): super(CancelSubscriptionsForm, self).updateWidgets() self.widgets["uid"].mode = HIDDEN_MODE def render(self): data = decode_token() if data.get("error", ""): return self.return_with_message(message=data["error"], type=u"error") return super(CancelSubscriptionsForm, self).render() def return_with_message(self, message, type): api.portal.show_message( message=message, request=self.request, type=type, ) return self.request.response.redirect(api.portal.get().absolute_url()) @button.buttonAndHandler(_(u"save_button", default="Save")) def handleSave(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return tool = getUtility(ISubscriptionsStore) subscription_id = data.get("uid", None) record = tool.get_record(subscription_id) if not record: msg = _( u"manage_subscriptions_inexistent_mail", default=u"Mail not found. Unable to change settings.", ) return self.return_with_message(message=msg, type="error") record_channels = record.attrs.get("channels", []) data_channels = data.get("channels", []) removed_channels = [ x for x in record_channels if x not in data_channels ] if not data_channels: # completely unsubscribed, so remove it from the db tool.delete(id=subscription_id) self.send_notify_unsubscription(channels=removed_channels, record=record, deleted=True) else: tool.update( id=subscription_id, data={"channels": data.get("channels")}, ) self.send_notify_unsubscription(channels=removed_channels, record=record, deleted=False) return self.return_with_message( message=_( "cancel_subscriptions_success", default=u"Cancel registered.", ), type=u"info", ) @button.buttonAndHandler(_(u"cancel_button", default="Cancel"), name="cancel") def handleCancel(self, action): return self.return_with_message( message=_( "cancel_action", default=u"Action cancelled", ), type=u"info", ) def send_notify_unsubscription(self, channels, record, deleted=False): portal = api.portal.get() site_title = get_site_title() overview_controlpanel = getMultiAdapter((portal, self.request), name="overview-controlpanel") if overview_controlpanel.mailhost_warning(): logger.error("MailHost is not configured.") return False registry = getUtility(IRegistry) encoding = registry.get("plone.email_charset", "utf-8") mailHost = api.portal.get_tool(name="MailHost") subject = translate( _( "subscriptions_updated_label", default=u"Subscriptions updated for ${site}", mapping={"site": site_title}, ), context=self.request, ) message = prepare_email_message( context=api.portal.get(), template="@@cancel_subscriptions_notify_template", parameters={ "site_title": site_title, "name": "{} {}".format( record.attrs.get("surname", ""), record.attrs.get("name", ""), ), "email": record.attrs.get("email", ""), "deleted": deleted, "channels": channels, }, ) mto = mail_from() try: mailHost.send( message, mto=mto, mfrom=mto, subject=subject, charset=encoding, msg_type="text/html", immediate=True, ) except (SMTPException, RuntimeError) as e: logger.exception(e) return False return True
class CancelSubscriptionsRequestForm(form.Form): label = _("cancel_subscriptions_request_title", u"Delete me") description = _( "cancel_subscriptions_request_help", u"If you want to cancel your subscriptions, please insert your email " u"address. You will receive an email with the link. " u"That link will expire in 24 hours.", ) ignoreContext = True fields = field.Fields(ICancelSubscriptionsRequestForm) def updateWidgets(self): super(CancelSubscriptionsRequestForm, self).updateWidgets() if self.request.get("email", None): self.widgets["email"].value = self.request.get("email") @button.buttonAndHandler(_(u"send_button", default="Send")) def handleSave(self, action): data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return email = data.get("email", None) tool = getUtility(ISubscriptionsStore) subscriptions = tool.search(query={"email": email}) if not subscriptions: msg = _( u"manage_subscriptions_request_inexistent_mail", default=u"Mail not found. Unable to send the link.", ) api.portal.show_message(message=msg, request=self.request, type=u"error") return subscription = subscriptions[0] # create CSRF token token = createToken() # sign data serializer = self.get_serializer() if not serializer: msg = _( u"manage_subscriptions_request_serializer_error", default=u"Serializer secret and salt not set in control panel." u" Unable to send the link.", ) api.portal.show_message(message=msg, request=self.request, type=u"error") return secret = serializer.dumps({ "id": subscription.intid, "email": subscription.attrs.get("email", ""), }) # send confirm email url = "{url}/cancel-subscriptions?secret={secret}&_authenticator={token}".format( # noqa url=self.context.absolute_url(), secret=secret, token=token) site_title = get_site_title() mail_text = prepare_email_message( context=api.portal.get(), template="@@cancel_subscriptions_mail_template", parameters={ "url": url, "site_title": site_title }, ) res = self.send(message=mail_text, mto=email, site_title=site_title) if not res: msg = _( u"manage_subscriptions_not_send", default=u"Unable to send manage subscriptions link. " u"Please contact site administrator.", ) msg_type = "error" else: msg = _( u"cancel_subscriptions_send_success", default=u"You will receive an email with a link to manage " u"your cancellation.", ) msg_type = "info" api.portal.show_message(message=msg, request=self.request, type=msg_type) def get_serializer(self): try: token_secret = api.portal.get_registry_record( "token_secret", interface=IRerUfficiostampaSettings) token_salt = api.portal.get_registry_record( "token_salt", interface=IRerUfficiostampaSettings) except (KeyError, InvalidParameterError): return None if not token_secret or not token_salt: None return URLSafeTimedSerializer(token_secret, token_salt) def send(self, message, mto, site_title): portal = api.portal.get() overview_controlpanel = getMultiAdapter((portal, self.request), name="overview-controlpanel") if overview_controlpanel.mailhost_warning(): logger.error("MailHost is not configured.") return False registry = getUtility(IRegistry) encoding = registry.get("plone.email_charset", "utf-8") mailHost = api.portal.get_tool(name="MailHost") subject = translate( _( "cancel_subscription_subject_label", default=u"Manage channels subscriptions cancel for ${site}", mapping={"site": site_title}, ), context=self.request, ) try: mailHost.send( message, mto=mto, mfrom=mail_from(), subject=subject, charset=encoding, msg_type="text/html", immediate=True, ) except (SMTPException, RuntimeError) as e: logger.exception(e) return False return True
def decode_token(): request = getRequest() secret = request.form.get("secret", "") if not secret: return { "error": _( "unsubscribe_confirm_secret_null", default= u"Unable to manage subscriptions. Token not present.", # noqa ) } try: token_secret = api.portal.get_registry_record( "token_secret", interface=IRerUfficiostampaSettings) token_salt = api.portal.get_registry_record( "token_salt", interface=IRerUfficiostampaSettings) except (KeyError, InvalidParameterError): return { "error": _( "unsubscribe_confirm_secret_token_settings_error", default= u"Unable to manage subscriptions. Token keys not set in control panel.", # noqa ) } if not token_secret or not token_salt: return { "error": _( "unsubscribe_confirm_secret_token_settings_error", default= u"Unable to manage subscriptions. Token keys not set in control panel.", # noqa ) } serializer = URLSafeTimedSerializer(token_secret, token_salt) try: data = serializer.loads(secret, max_age=86400) except SignatureExpired: return { "error": _( "unsubscribe_confirm_secret_expired", default=u"Unable to manage subscriptions. Token expired.", ) } except BadSignature: return { "error": _( "unsubscribe_confirm_secret_invalid", default=u"Unable to manage subscriptions. Invalid token.", ) } record_id = data.get("id", "") email = data.get("email", "") if not record_id or not email: return { "error": _( "unsubscribe_confirm_invalid_parameters", default=u"Unable to manage subscriptions. Invalid parameters.", ) } tool = getUtility(ISubscriptionsStore) record = tool.get_record(record_id) if not record: return { "error": _( "unsubscribe_confirm_invalid_id", default=u"Unable to manage subscriptions. Invalid id.", ) } if record.attrs.get("email", "") != email: return { "error": _( "unsubscribe_confirm_invalid_email", default=u"Unable to manage subscriptions. Invalid email.", ) } return {"data": record}
def reply(self): alsoProvides(self.request, IDisableCSRFProtection) query = self.parse_query() tool = getUtility(ISubscriptionsStore) clear = query.get("clear", False) overwrite = query.get("overwrite", False) if clear: tool.clear() csv_data = self.get_csv_data(data=query["file"]) if csv_data.get("error", "") or not csv_data.get("csv", None): self.request.response.setStatus(500) return dict(error=dict( type="InternalServerError", message=csv_data.get("error", ""), )) res = { "skipped": [], "imported": 0, } i = 1 for row in csv_data.get("csv", []): i += 1 email = row.get("email", "") row["channels"] = row["channels"].split(",") if not email: msg = translate( _( "skip_no_email", default=u"[${row}] - row without email", mapping={"row": i}, ), context=self.request, ) logger.warning("[SKIP] - {}".format(msg)) res["skipped"].append(msg) continue records = tool.search(query={"email": email}) if not records: # add it record_id = tool.add(data=row) if not record_id: msg = translate( _( "skip_unable_to_add", default=u"[${row}] - unable to add", mapping={"row": i}, ), context=self.request, ) logger.warning("[SKIP] - {}".format(msg)) res["skipped"].append(msg) continue res["imported"] += 1 else: if len(records) != 1: msg = translate( _( "skip_duplicate_multiple", default= u'[${row}] - Multiple values for "${email}"', # noqa mapping={ "row": i, "email": email }, ), context=self.request, ) logger.warning("[SKIP] - {}".format(msg)) res["skipped"].append(msg) continue record = records[0] if not overwrite: msg = translate( _( "skip_duplicate", default= u'[${row}] - "${email}" already in database', # noqa mapping={ "row": i, "email": email }, ), context=self.request, ) if six.PY2: msg = msg.encode("utf-8") logger.warning("[SKIP] - {}".format(msg)) res["skipped"].append(msg) continue else: tool.update(id=record.intid, data=row) res["imported"] += 1 return res
def getSearchFields(): request = getRequest() portal = api.portal.get() return [ { "id": "SearchableText", "label": translate( _("comunicati_search_text_label", default=u"Search text"), context=request, ), "help": "", "type": "text", }, { "id": "portal_type", "label": translate( _("label_portal_type", default="Type"), context=request, ), "help": "", "type": "checkbox", "options": getTypesValues(), "default": getTypesDefault(), "hidden": api.user.is_anonymous(), }, { "id": "created", "label": translate( _("comunicati_search_created_label", default=u"Date"), context=request, ), "help": "", "type": "date", }, { "id": "legislature", "label": translate( _("label_legislature", default="Legislature"), context=request, ), "help": "", "type": "select", "multivalued": True, "options": getVocabularyTermsForForm( context=portal, vocab_name="rer.ufficiostampa.vocabularies.legislatures", ), }, { "id": "arguments", "label": translate( _("legislature_arguments_label", default="Arguments"), context=request, ), "help": "", "type": "select", "multivalued": True, "options": getVocabularyTermsForForm( context=portal, vocab_name="rer.ufficiostampa.vocabularies.all_arguments", ), }, ]
class IRerUfficiostampaSettings(model.Schema): legislatures = schema.SourceText( title=_( "legislatures_label", default=u"List of legislatures", ), description=_( "legislatures_help", default=u"This is a list of all legislatures. The last one is the" u" one used to fill fields in a new Comunicato.", ), required=True, ) subscription_channels = schema.List( title=_(u"subscription_channels_label", default=u"Subscription Channels"), description=_( u"subscription_channels_description", default=u"List of available subscription channels." u"One per line." u"These channels will be used for users subscriptions " u"and for select to which channel send a Comunicato.", ), required=True, default=[], missing_value=[], value_type=schema.TextLine(), ) token_secret = schema.TextLine( title=_("token_secret_label", default=u"Token secret"), description=_( "token_secret_help", default=u"Insert the secret key for token generation.", ), required=True, ) token_salt = schema.TextLine( title=_("token_salt_label", default=u"Token salt"), description=_( "token_salt_help", default=u"Insert the salt for token generation. This, in " u"conjunction with the secret, will generate unique tokens for " u"subscriptions management links.", ), required=True, ) frontend_url = schema.TextLine( title=_("frontend_url_label", default=u"Frontend URL"), description=_( "frontend_url_help", default=u"If the frontend site is published with a different URL " u"than the backend, insert it here. All links in emails will be " u"converted with that URL.", ), required=False, ) external_sender_url = schema.TextLine( title=_("external_sender_url_label", default=u"External sender URL"), description=_( "external_sender_url_help", default=u"If you want to send emails with an external tool " u"(rer.newsletterdispatcher.flask), insert the url of the service " u"here. If empty, all emails will be sent from Plone.", ), required=False, ) css_styles = schema.SourceText( title=_( "css_styles_label", default=u"Styles", ), description=_( "css_styles_help", default=u"Insert a list of CSS styles for received emails.", ), required=True, ) comunicato_number = schema.Int( title=_( "comunicato_number_label", default=u"Comunicato number", ), description=_( "comunicato_number_help", default=u"The number of last sent Comunicato. You don't have to " "edit this. It's automatically updated when a Comunicato is published.", # noqa ), required=True, default=0, ) comunicato_year = schema.Int( title=_( "comunicato_year_label", default=u"Comunicato year", ), description=_( "comunicato_year_help", default=u"You don't have to edit this. It's automatically updated" u" on every new year.", ), required=True, default=2021, )
class SendForm(form.Form): description = _( "send_form_help", u"Send this Comunicato or Invito to a list of recipients.", ) ignoreContext = True fields = field.Fields(ISendForm) fields["channels"].widgetFactory = CheckBoxFieldWidget fields["attachments"].widgetFactory = CheckBoxFieldWidget @property def label(self): types_tool = api.portal.get_tool(name="portal_types") return _( "send_form_title", u"Send ${type}", mapping={"type": types_tool.getTypeInfo(self.context.portal_type).title}, ) @button.buttonAndHandler(_(u"send_button", default="Send")) def handleSave(self, action): data, errors = self.extractData() if not self.get_subscribers(data=data): raise ActionExecutionError( Invalid( _( "empty_subscribers", default=u"You need to provide at least one email address or channel.", # noqa ) ) ) if errors: self.status = self.formErrorsMessage return return self.sendMessage(data=data) @button.buttonAndHandler(_(u"cancel_button", default="Cancel"), name="cancel") def handleCancel(self, action): api.portal.show_message( message=_( "cancel_action", default=u"Action cancelled", ), type=u"info", request=self.request, ) return self.request.response.redirect(self.context.absolute_url()) def sendMessage(self, data): external_sender_url = self.get_value_from_settings(field="external_sender_url") body = prepare_email_message( context=self.context, template="@@send_mail_template", parameters={ "notes": data.get("notes", ""), "site_title": get_site_title(), "date": DateTime(), "folders": self.get_folders_attachments(), }, ) if external_sender_url: self.send_external(data=data, body=body) else: self.send_internal(data=data, body=body) return self.request.response.redirect(self.context.absolute_url()) def get_value_from_settings(self, field): try: return api.portal.get_registry_record( field, interface=IRerUfficiostampaSettings ) except (KeyError, InvalidParameterError): return None return None def set_history_start(self, data, subscribers): # if it's a preview, do not store infos if not data.get("channels", []): return "" # mark as sent self.context.message_sent = True tool = getUtility(ISendHistoryStore) intid = tool.add( { "type": self.type_name, "title": self.context.Title(), "number": getattr(self.context, "comunicato_number", ""), "url": self.context.absolute_url(), "recipients": subscribers, "channels": data.get("channels", []), "status": "sending", } ) return intid def update_history(self, send_id, status): tool = getUtility(ISendHistoryStore) res = tool.update( id=send_id, data={"completed_date": datetime.now(), "status": status}, ) if res and "error" in res: logger.error( 'Unable to update history with id "{}": {}'.format( send_id, res["error"] ) ) @property @memoize def type_name(self): types_tool = api.portal.get_tool(name="portal_types") return types_tool.getTypeInfo(self.context.portal_type).title @property @memoize def subject(self): value = u"{type}: {title}".format( type=self.context.portal_type == "ComunicatoStampa" and "Comunicato Regione" # noqa or "Invito Regione", # noqa title=self.context.title, ) if six.PY2 and HAS_FTYFY: return fix_text(value) return value def get_subscribers(self, data): channels = data.get("channels", []) subscribers = set() tool = getUtility(ISubscriptionsStore) for channel in channels: records = tool.search(query={"channels": channel}) subscribers.update([x.attrs.get("email", "") for x in records]) subscribers.update(data.get("additional_addresses", [])) return sorted(list(subscribers)) def get_folders_attachments(self): if self.context.portal_type == "InvitoStampa": return [] return self.context.listFolderContents( contentFilter={"portal_type": ["Folder"]} ) def get_attachments(self, data): attachments = [] for item_id in data.get("attachments", []): item = self.context.get(item_id, None) if not item: continue field = item.portal_type == "Image" and item.image or item.file attachments.append( { "data": field.data, "filename": field.filename, "content_type": item.content_type(), } ) return attachments def get_attachments_external(self, data): attachments = [] for item_id in data.get("attachments", []): item = self.context.get(item_id, None) if not item: continue field = item.portal_type == "Image" and item.image or item.file attachments.append( ( field.filename, (field.filename, field.open(), item.content_type()), ) ) return attachments def manage_attachments(self, data, msg): attachments = self.get_attachments(data=data) for attachment in attachments: msg.add_attachment( attachment["data"], maintype=attachment["content_type"], subtype=attachment["content_type"], filename=attachment["filename"], ) def add_send_error_message(self): api.portal.show_message( message=_( "error_send_mail", default=u"Error sending mail. Contact site administrator.", ), request=self.request, type="error", ) # main methods def send_internal(self, data, body): portal = api.portal.get() overview_controlpanel = getMultiAdapter( (portal, self.request), name="overview-controlpanel" ) if overview_controlpanel.mailhost_warning(): return {"error": "MailHost is not configured."} subscribers = self.get_subscribers(data) registry = getUtility(IRegistry) encoding = registry.get("plone.email_charset", "utf-8") msg = EmailMessage() msg.set_content(body) msg["Subject"] = self.subject msg["From"] = mail_from() msg["Reply-To"] = mail_from() msg.replace_header("Content-Type", 'text/html; charset="utf-8"') self.manage_attachments(data=data, msg=msg) host = api.portal.get_tool(name="MailHost") msg["Bcc"] = ", ".join(subscribers) send_id = self.set_history_start(data=data, subscribers=len(subscribers)) try: host.send(msg, charset=encoding) except (SMTPException, RuntimeError) as e: logger.exception(e) self.add_send_error_message() self.update_history(send_id=send_id, status="error") return api.portal.show_message( message=_( "success_send_mail", default=u"Send complete.", ), request=self.request, type="info", ) if send_id: self.update_history(send_id=send_id, status="success") def send_external(self, data, body): frontend_url = self.get_value_from_settings(field="frontend_url") external_sender_url = self.get_value_from_settings(field="external_sender_url") channel_url = api.portal.get().absolute_url() if frontend_url: channel_url = frontend_url subscribers = self.get_subscribers(data) send_uid = self.set_history_start(data=data, subscribers=len(subscribers)) payload = { "channel_url": channel_url, "subscribers": subscribers, "subject": self.subject, "mfrom": mail_from(), "text": body, "send_uid": send_uid, } params = {"url": external_sender_url} attachments = self.get_attachments_external(data) if attachments: params["data"] = payload params["files"] = self.get_attachments_external(data) else: params["data"] = json.dumps(payload) params["headers"] = {"Content-Type": "application/json"} try: response = requests.post(**params) except (ConnectionError, Timeout) as e: logger.exception(e) self.add_send_error_message() if send_uid: self.update_history(send_id=send_uid, status="error") return if response.status_code != 200: logger.error( 'Unable to send "{message}": {reason}'.format( # noqa message=self.subject, reason=response.text, ) ) self.add_send_error_message() if send_uid: self.update_history(send_id=send_uid, status="error") return # finish status will be managed via async calls api.portal.show_message( message=_( "success_send_mail_async", default=u"Send queued with success. " u"See the status in send history.", ), request=self.request, type="info", )