diff --git a/src/iosanita/contenttypes/adapters/__init__.py b/src/iosanita/contenttypes/adapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/iosanita/contenttypes/adapters/configure.zcml b/src/iosanita/contenttypes/adapters/configure.zcml new file mode 100644 index 0000000..3ebe30e --- /dev/null +++ b/src/iosanita/contenttypes/adapters/configure.zcml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/src/iosanita/contenttypes/adapters/query.py b/src/iosanita/contenttypes/adapters/query.py new file mode 100644 index 0000000..25be442 --- /dev/null +++ b/src/iosanita/contenttypes/adapters/query.py @@ -0,0 +1,26 @@ +from iosanita.contenttypes.interfaces import IIosanitaContenttypesLayer +from plone.restapi.interfaces import IZCatalogCompatibleQuery +from plone.restapi.search.query import ZCatalogCompatibleQueryAdapter as BaseAdapter +from zope.component import adapter +from zope.interface import implementer +from zope.interface import Interface +from plone import api + + +@implementer(IZCatalogCompatibleQuery) +@adapter(Interface, IIosanitaContenttypesLayer) +class ZCatalogCompatibleQueryAdapter(BaseAdapter): + """ """ + + def __call__(self, query): + """ + Do not show excluded from search items when anonymous are performing + some catalog searches + """ + query = super().__call__(query=query) + + if api.user.is_anonymous(): + # For the anonymous user, only content that is not "excluded from the search" is found + query["exclude_from_search"] = False + + return query diff --git a/src/iosanita/contenttypes/behaviors/configure.zcml b/src/iosanita/contenttypes/behaviors/configure.zcml index 2dcbd68..c0198cf 100644 --- a/src/iosanita/contenttypes/behaviors/configure.zcml +++ b/src/iosanita/contenttypes/behaviors/configure.zcml @@ -85,14 +85,6 @@ provides=".contatti.IContattiUnitaOrganizzativa" marker=".contatti.IContattiUnitaOrganizzativa" /> - - + + diff --git a/src/iosanita/contenttypes/behaviors/contatti.py b/src/iosanita/contenttypes/behaviors/contatti.py index d9e825c..6827966 100644 --- a/src/iosanita/contenttypes/behaviors/contatti.py +++ b/src/iosanita/contenttypes/behaviors/contatti.py @@ -13,6 +13,8 @@ from iosanita.contenttypes import _ from iosanita.contenttypes.interfaces.persona import IPersona +from iosanita.contenttypes.interfaces.step import IStep + from iosanita.contenttypes.interfaces.unita_organizzativa import IUnitaOrganizzativa @@ -61,29 +63,48 @@ class IContattiUnitaOrganizzativa(model.Schema): @provider(IFormFieldProvider) -class IContattiStep(model.Schema): +class IContattiEvent(model.Schema): contact_info = RelationList( title=_( "contact_info_label", - default="Contatti", + default="Punti di contatto", ), description=_( - "contatti_step_contact_info_help", - default="I contatti per questo step.", + "contact_info_help", + default="Relazione con i punti di contatto dell'evento.", ), + required=True, + default=[], + value_type=RelationChoice( + title=_("Punti di contatto"), + vocabulary="plone.app.vocabularies.Catalog", + ), + ) + form.widget( + "contact_info", + RelatedItemsFieldWidget, + vocabulary="plone.app.vocabularies.Catalog", + pattern_options={ + "selectableTypes": ["PuntoDiContatto"], + }, + ) + model.fieldset( + "contatti", + label=_("contatti_label", default="Contatti"), + fields=["contact_info"], ) @provider(IFormFieldProvider) -class IContattiEvent(model.Schema): +class IContattiPersona(model.Schema): contact_info = RelationList( title=_( "contatti_event_contact_info_label", default="Punti di contatto", ), description=_( - "contatti_event_contact_info_help", - default="Relazione con i punti di contatto dell'evento.", + "contact_info_help", + default="Punti di contatto della persona.", ), required=True, default=[], @@ -92,6 +113,7 @@ class IContattiEvent(model.Schema): vocabulary="plone.app.vocabularies.Catalog", ), ) + form.widget( "contact_info", RelatedItemsFieldWidget, @@ -108,7 +130,7 @@ class IContattiEvent(model.Schema): @provider(IFormFieldProvider) -class IContattiPersona(model.Schema): +class IContattiStep(model.Schema): contact_info = RelationList( title=_( "contatti_persona_contact_info_label", @@ -116,7 +138,7 @@ class IContattiPersona(model.Schema): ), description=_( "contact_info_help", - default="Punti di contatto della persona.", + default="Punti di contatto per questo passo.", ), required=True, default=[], @@ -175,3 +197,12 @@ class ContattiPersona(object): def __init__(self, context): self.context = context + + +@implementer(IContattiStep) +@adapter(IStep) +class ContattiStep(object): + """ """ + + def __init__(self, context): + self.context = context diff --git a/src/iosanita/contenttypes/behaviors/exclude_from_search.py b/src/iosanita/contenttypes/behaviors/exclude_from_search.py new file mode 100644 index 0000000..fc73272 --- /dev/null +++ b/src/iosanita/contenttypes/behaviors/exclude_from_search.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from iosanita.contenttypes import _ +from plone.autoform.interfaces import IFormFieldProvider +from plone.dexterity.interfaces import IDexterityContent +from plone.supermodel import model +from zope import schema +from zope.component import adapter +from zope.interface import implementer +from zope.interface import provider + + +@provider(IFormFieldProvider) +class IExcludeFromSearch(model.Schema): + """ """ + + exclude_from_search = schema.Bool( + title=_("exclude_from_search_label", default="Escludi dalla ricerca"), + description=_( + "help_exclude_from_search", + default="Se selezionato, questo contenuto non verrà mostrato nelle ricerche del sito per gli utenti anonimi.", + ), + required=False, + default=False, + ) + model.fieldset( + "settings", + fields=["exclude_from_search"], + ) + + +@implementer(IExcludeFromSearch) +@adapter(IDexterityContent) +class ExcludeFromSearch(object): + """ """ + + def __init__(self, context): + self.context = context diff --git a/src/iosanita/contenttypes/configure.zcml b/src/iosanita/contenttypes/configure.zcml index e26658b..464b258 100644 --- a/src/iosanita/contenttypes/configure.zcml +++ b/src/iosanita/contenttypes/configure.zcml @@ -10,10 +10,10 @@ - + - + diff --git a/src/iosanita/contenttypes/content/punto_di_contatto.py b/src/iosanita/contenttypes/content/punto_di_contatto.py new file mode 100644 index 0000000..1505a3c --- /dev/null +++ b/src/iosanita/contenttypes/content/punto_di_contatto.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from iosanita.contenttypes.interfaces.punto_di_contatto import IPuntoDiContatto +from plone.dexterity.content import Container +from zope.interface import implementer + + +@implementer(IPuntoDiContatto) +class PuntoDiContatto(Container): + """ """ diff --git a/src/iosanita/contenttypes/controlpanels/__init__.py b/src/iosanita/contenttypes/controlpanels/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/iosanita/contenttypes/controlpanels/configure.zcml b/src/iosanita/contenttypes/controlpanels/configure.zcml new file mode 100644 index 0000000..a25901c --- /dev/null +++ b/src/iosanita/contenttypes/controlpanels/configure.zcml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/iosanita/contenttypes/controlpanels/settings.py b/src/iosanita/contenttypes/controlpanels/settings.py new file mode 100644 index 0000000..9935b93 --- /dev/null +++ b/src/iosanita/contenttypes/controlpanels/settings.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +from iosanita.contenttypes import _ +from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper +from plone.app.registry.browser.controlpanel import RegistryEditForm +from plone.restapi.controlpanels.interfaces import IControlpanel +from zope.interface import Interface +from zope.schema import Bool +from zope.schema import List +from zope.schema import SourceText +from zope.schema import TextLine + + +class IoSanitaSettingsControlpanel(IControlpanel): + """ """ + + +class IIoSanitaSettings(Interface): + + lead_image_dimension = List( + title=_( + "lead_image_dimension_label", + default="Dimensioni lead image", + ), + description=_( + "lead_image_dimension_help", + default="Se un content-type deve avere una dimensione della " + "leadimage particolare, indicarle qui. " + "Inserire le dimensioni nella forma di esempio " + "PortalType|900x900", + ), + required=True, + default=[ + "News Item|1920x600", + "Servizio|1920x600", + "UnitaOrganizzativa|1920x600", + "Persona|180x100", + ], + value_type=TextLine(), + ) + + search_sections = SourceText( + title=_("search_sections_label", default="Sezioni ricerca"), + description=_( + "search_sections_help", + default="Inserire una lista di sezioni per la ricerca.", + ), + default="", + required=False, + ) + + show_modified_default = Bool( + title=_("show_modified_default_label", default="Mostra la data di modifica"), + description=_( + "show_modified_default_help", + default="Questo è il valore di default per decidere se mostrare " + "o meno la data di modifica nei contenuti che hanno la behavior " + "abilitata. E' poi possibile sovrascrivere il default nei singoli " + 'contenuti (nel tab "Impostazioni").', + ), + default=True, + required=False, + ) + show_dynamic_folders_in_footer = Bool( + title=_("show_dynamic_folders_in_footer_label", default="Footer dinamico"), + description=_( + "show_dynamic_folders_in_footer_help", + default="Se selezionato, il footer verrà popolato automaticamente " + "con i contenuti di primo livello non esclusi dalla navigazione.", + ), + default=True, + required=False, + ) + + +class IoSanitaControlPanelForm(RegistryEditForm): + schema = IIoSanitaSettings + id = "io-sanita-control-panel" + label = _("Impostazioni Io sanita") + + +class IoSanitaControlPanelView(ControlPanelFormWrapper): + """ """ + + form = IoSanitaControlPanelForm diff --git a/src/iosanita/contenttypes/events/common.py b/src/iosanita/contenttypes/events/common.py new file mode 100644 index 0000000..536a8e3 --- /dev/null +++ b/src/iosanita/contenttypes/events/common.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +from iosanita.contenttypes.interfaces import IIosanitaContenttypesLayer +from iosanita.contenttypes.utils import create_default_blocks +from plone import api +from Products.CMFPlone.interfaces import ISelectableConstrainTypes + + +SUBFOLDERS_MAPPING = { + "Bando": { + "content": [ + {"id": "documenti", "title": "Documenti", "type": "Bando Folder Deepening"}, + { + "id": "comunicazioni", + "title": "Comunicazioni", + "type": "Bando Folder Deepening", + }, + {"id": "esiti", "title": "Esiti", "type": "Bando Folder Deepening"}, + ], + }, + "Documento": { + "content": [ + { + "id": "multimedia", + "title": "Multimedia", + "type": "Document", + "allowed_types": ("Image",), + }, + ], + }, + "Event": { + "content": [ + { + "id": "immagini", + "title": "Immagini", + "allowed_types": ("Image", "Link"), + "publish": True, + }, + { + "id": "video", + "title": "Video", + "allowed_types": ("Link",), + "publish": True, + }, + { + "id": "sponsor_evento", + "title": "Sponsor Evento", + "allowed_types": ("Link",), + "publish": True, + }, + { + "id": "documenti", + "title": "Allegati", + "allowed_types": ("File",), + "publish": True, + }, + ], + }, + "Incarico": { + "content": [ + {"id": "compensi-file", "title": "Compensi", "allowed": ("File",)}, + { + "id": "importi-di-viaggio-e-o-servizi", + "title": "Importi di viaggio e/o servizi", + "allowed_types": ("File",), + }, + ], + "allowed_types": [], + }, + "Venue": { + "content": [ + { + "id": "multimedia", + "title": "Multimedia", + "type": "Folder", + "allowed_types": ( + "Image", + "Link", + ), + "publish": True, + } + ], + }, + "News Item": { + "content": [ + { + "id": "multimedia", + "title": "Multimedia", + "allowed_types": ( + "Image", + "Link", + ), + }, + { + "id": "documenti-allegati", + "title": "Documenti allegati", + "allowed_types": ( + "File", + "Image", + ), + }, + ], + }, + "Persona": { + "content": [ + { + "id": "foto-e-attivita-politica", + "title": "Foto e attività politica", + "allowed_types": ("Image",), + }, + { + "id": "curriculum-vitae", + "title": "Curriculum vitae", + "allowed_types": ("File",), + }, + { + "id": "situazione-patrimoniale", + "title": "Situazione patrimoniale", + "allowed_types": ("File",), + }, + { + "id": "dichiarazione-dei-redditi", + "title": "Dichiarazione dei redditi", + "allowed_types": ("File",), + }, + { + "id": "spese-elettorali", + "title": "Spese elettorali", + "allowed_types": ("File",), + }, + { + "id": "variazione-situazione-patrimoniale", + "title": "Variazione situazione patrimoniale", + "allowed_types": ("File",), + }, + { + "id": "altre-cariche", + "title": "Altre cariche", + "allowed_types": ("File",), + }, + {"id": "incarichi", "title": "Incarichi", "allowed_types": ("Incarico",)}, + { + "id": "altri-documenti", + "title": "Altri documenti", + "allowed_types": ("File", "Image", "Link"), + }, + ], + "allowed_types": [], + }, + "Pratica": { + "content": [ + { + "id": "allegati", + "title": "Allegati", + "type": "Folder", + "allowed_types": ("File",), + } + ], + }, + "Servizio": { + "content": [ + { + "id": "modulistica", + "title": "Modulistica", + "allowed_types": ("Link",), + }, + {"id": "allegati", "title": "Allegati", "allowed_types": ("File", "Link")}, + ], + }, + "UnitaOrganizzativa": { + "content": [ + {"id": "allegati", "title": "Allegati", "allowed_types": ("File",)}, + ], + }, + "Step": { + "content": [ + {"id": "documenti", "title": "Documenti", "allowed_types": ("File",)}, + ], + }, +} + + +def onModify(context, event): + for description in event.descriptions: + if "IBasic.title" in getattr( + description, "attributes", [] + ) or "IDublinCore.title" in getattr(description, "attributes", []): + for child in context.listFolderContents(): + child.reindexObject(idxs=["parent"]) + + +def createSubfolders(context, event): + """ + Create subfolders structure based on a portal_type mapping + """ + if not IIosanitaContenttypesLayer.providedBy(context.REQUEST): + return + + subfolders_mapping = SUBFOLDERS_MAPPING.get(context.portal_type, []) + if not subfolders_mapping: + return + + for mapping in subfolders_mapping.get("content", {}): + if mapping["id"] not in context.keys(): + portal_type = mapping.get("type", "Document") + child = api.content.create( + container=context, + type=portal_type, + title=mapping["title"], + id=mapping["id"], + ) + if portal_type == "Document": + create_default_blocks(context=child) + + if portal_type in ["Folder", "Document"]: + child.exclude_from_search = True + child.reindexObject(idxs=["exclude_from_search"]) + # select constraints + if mapping.get("allowed_types", ()): + constraints_child = ISelectableConstrainTypes(child) + constraints_child.setConstrainTypesMode(1) + constraints_child.setLocallyAllowedTypes(mapping["allowed_types"]) + + if mapping.get("publish", False): + with api.env.adopt_roles(["Reviewer"]): + api.content.transition(obj=child, transition="publish") + + allowed_types = subfolders_mapping.get("allowed_types", None) + if allowed_types is not None and not isinstance(allowed_types, list): + raise ValueError("Subfolder map is not well formed") + + if isinstance(allowed_types, list): + constraints_context = ISelectableConstrainTypes(context) + constraints_context.setConstrainTypesMode(1) + constraints_context.setLocallyAllowedTypes(allowed_types) diff --git a/src/iosanita/contenttypes/events/configure.zcml b/src/iosanita/contenttypes/events/configure.zcml index d276036..163feec 100644 --- a/src/iosanita/contenttypes/events/configure.zcml +++ b/src/iosanita/contenttypes/events/configure.zcml @@ -7,6 +7,16 @@ + + + diff --git a/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_pdc.cfg b/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_pdc.cfg new file mode 100644 index 0000000..799a947 --- /dev/null +++ b/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_pdc.cfg @@ -0,0 +1,11 @@ +[taxonomy] +name = tipologia_pdc +title = Tipo di punto di contatto +description = Il sistema di gestione contenuti basato su React +default_language = it +field_title = Tipo di punto di contatto +field_description = Seleziona la tipologia del punto di contatto +field_prefix = +taxonomy_fieldset = contatti +is_single_select = false +is_required = true \ No newline at end of file diff --git a/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_punti_di_contatto.xml b/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_pdc.xml similarity index 96% rename from src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_punti_di_contatto.xml rename to src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_pdc.xml index 13f595b..f2d8cf8 100644 --- a/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_punti_di_contatto.xml +++ b/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_pdc.xml @@ -9,7 +9,7 @@ Tipologia punti di contatto - tipologia_punti_di_contatto + tipologia_pdc email diff --git a/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_punti_di_contatto.cfg b/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_punti_di_contatto.cfg deleted file mode 100644 index 3a6eb08..0000000 --- a/src/iosanita/contenttypes/profiles/behaviors/taxonomies/tipologia_punti_di_contatto.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[taxonomy] -name = tipologia_punti_di_contatto -title = Tipologia punti di contatto -description = Il sistema di gestione contenuti basato su React -default_language = it -field_title = Punti di contatto -field_description = Contatti degli organizzatori o della struttura organizzatrice dell''evento. -field_prefix = -taxonomy_fieldset = contatti -is_single_select = false -is_required = true diff --git a/src/iosanita/contenttypes/profiles/default/registry/settings.xml b/src/iosanita/contenttypes/profiles/default/registry/settings.xml new file mode 100644 index 0000000..2e64b24 --- /dev/null +++ b/src/iosanita/contenttypes/profiles/default/registry/settings.xml @@ -0,0 +1,16 @@ + + + + + + + + gallery 250:65536 + + + diff --git a/src/iosanita/contenttypes/profiles/default/rolemap.xml b/src/iosanita/contenttypes/profiles/default/rolemap.xml index cc2b989..a6f2dbb 100644 --- a/src/iosanita/contenttypes/profiles/default/rolemap.xml +++ b/src/iosanita/contenttypes/profiles/default/rolemap.xml @@ -54,5 +54,14 @@ + + + + + + + diff --git a/src/iosanita/contenttypes/profiles/default/types.xml b/src/iosanita/contenttypes/profiles/default/types.xml index d40b0ec..0f1598d 100644 --- a/src/iosanita/contenttypes/profiles/default/types.xml +++ b/src/iosanita/contenttypes/profiles/default/types.xml @@ -15,4 +15,7 @@ + diff --git a/src/iosanita/contenttypes/profiles/default/types/Document.xml b/src/iosanita/contenttypes/profiles/default/types/Document.xml new file mode 100644 index 0000000..70057d7 --- /dev/null +++ b/src/iosanita/contenttypes/profiles/default/types/Document.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/iosanita/contenttypes/profiles/default/types/Event.xml b/src/iosanita/contenttypes/profiles/default/types/Event.xml index 27482ac..51c9000 100644 --- a/src/iosanita/contenttypes/profiles/default/types/Event.xml +++ b/src/iosanita/contenttypes/profiles/default/types/Event.xml @@ -26,7 +26,6 @@ - diff --git a/src/iosanita/contenttypes/profiles/default/types/PuntoDiContatto.xml b/src/iosanita/contenttypes/profiles/default/types/PuntoDiContatto.xml new file mode 100644 index 0000000..3ddecf8 --- /dev/null +++ b/src/iosanita/contenttypes/profiles/default/types/PuntoDiContatto.xml @@ -0,0 +1,105 @@ + + + + + Punto di Contatto + + + False + PuntoDiContatto + + + + + True + False + + + + + iosanita.contenttypes.AddPuntoDiContatto + iosanita.contenttypes.content.punto_di_contatto.PuntoDiContatto + + + iosanita.contenttypes.interfaces.punto_di_contatto.IPuntoDiContatto + + + + + + + + + + + + + + + + + + + + + + string:${folder_url}/++add++PuntoDiContatto + view + False + view + + + + + + + + + + + + + + + + + + + diff --git a/src/iosanita/contenttypes/profiles/default/types/Step.xml b/src/iosanita/contenttypes/profiles/default/types/Step.xml index beb1331..57d49ef 100644 --- a/src/iosanita/contenttypes/profiles/default/types/Step.xml +++ b/src/iosanita/contenttypes/profiles/default/types/Step.xml @@ -24,6 +24,7 @@ + @@ -50,7 +51,7 @@ - + diff --git a/src/iosanita/contenttypes/utils.py b/src/iosanita/contenttypes/utils.py new file mode 100644 index 0000000..f86235a --- /dev/null +++ b/src/iosanita/contenttypes/utils.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +from iosanita.contenttypes.controlpanels.settings import IIoSanitaSettings +from plone import api +from plone.restapi.behaviors import IBlocks +from uuid import uuid4 +from zope.interface import implementer + +import json +import logging +import six + + +HAVE_REST_API_PRE_961 = False + +try: + # plone 6.0.11 with last plone.restapi>9.6.0 + from plone.restapi.indexers import get_blocks_text + from plone.restapi.indexers import text_strip + +except ImportError: + # plone 6.0.10.1 with plone.restapi<9.6.1 + HAVE_REST_API_PRE_961 = True + from plone.restapi.indexers import SearchableText_blocks + + +logger = logging.getLogger(__name__) + + +def get_settings_for_language(field): + values = api.portal.get_registry_record( + field, interface=IIoSanitaSettings, default=[] + ) + if not values: + return [] + if not isinstance(values, six.text_type): + return values + try: + json_data = json.loads(values) + except Exception as e: + logger.exception(e) + return values + lang = api.portal.get_current_language() + return json_data.get(lang, []) + + +def create_default_blocks(context): + title_uuid = str(uuid4()) + context.blocks = {title_uuid: {"@type": "title"}} + context.blocks_layout = {"items": [title_uuid]} + + +def text_in_block(blocks): + @implementer(IBlocks) + class FakeObject(object): + """ + We use a fake object to use SearchableText Indexer + """ + + def Subject(self): + return "" + + def __init__(self, blocks, blocks_layout): + self.blocks = blocks + self.blocks_layout = blocks_layout + self.id = "" + self.title = "" + self.description = "" + + if not blocks: + return None + + fakeObj = FakeObject(blocks.get("blocks", ""), blocks.get("blocks_layout", "")) + + if HAVE_REST_API_PRE_961: + return SearchableText_blocks(fakeObj)() + else: + blocks_text = get_blocks_text(fakeObj) + return text_strip(blocks_text)