imio.smartweb.core 1.2.32__py3-none-any.whl → 1.2.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. imio/smartweb/core/browser/configure.zcml +16 -0
  2. imio/smartweb/core/browser/sitemap.py +135 -0
  3. imio/smartweb/core/browser/static/smartweb-view-compiled.js +1 -1
  4. imio/smartweb/core/browser/static/src/view.js +11 -0
  5. imio/smartweb/core/browser/utils.py +1 -2
  6. imio/smartweb/core/browser/vocabulary.py +35 -0
  7. imio/smartweb/core/contents/sections/contact/utils.py +0 -4
  8. imio/smartweb/core/contents/sections/macros.pt +13 -9
  9. imio/smartweb/core/contents/sections/text/view.pt +4 -3
  10. imio/smartweb/core/profiles/default/diff_tool.xml +8 -0
  11. imio/smartweb/core/profiles/default/metadata.xml +1 -1
  12. imio/smartweb/core/profiles/default/repositorytool.xml +1 -0
  13. imio/smartweb/core/profiles/default/types/imio.smartweb.SectionHTML.xml +0 -1
  14. imio/smartweb/core/profiles/validation/contentrules.xml +102 -0
  15. imio/smartweb/core/profiles.zcml +8 -0
  16. imio/smartweb/core/tests/test_sections.py +18 -0
  17. imio/smartweb/core/tests/test_sitemap.py +155 -0
  18. imio/smartweb/core/upgrades/configure.zcml +18 -0
  19. imio/smartweb/core/upgrades/profiles/1052_to_1053/diff_tool.xml +8 -0
  20. imio/smartweb/core/upgrades/profiles/1052_to_1053/repositorytool.xml +9 -0
  21. imio/smartweb/core/upgrades/profiles/1052_to_1053/types/imio.smartweb.SectionHTML.xml +14 -0
  22. imio/smartweb/core/viewlets/ogptags.py +1 -2
  23. imio/smartweb/core/vocabularies.py +2 -0
  24. imio/smartweb/core/webcomponents/build/js/392.smartweb-webcomponents-compiled.js +1 -1
  25. imio/smartweb/core/webcomponents/src/components/Filters/DateFilter.jsx +1 -0
  26. {imio.smartweb.core-1.2.32.dist-info → imio.smartweb.core-1.2.34.dist-info}/METADATA +35 -3
  27. {imio.smartweb.core-1.2.32.dist-info → imio.smartweb.core-1.2.34.dist-info}/RECORD +33 -25
  28. {imio.smartweb.core-1.2.32.dist-info → imio.smartweb.core-1.2.34.dist-info}/WHEEL +1 -1
  29. /imio.smartweb.core-1.2.32-py3.8-nspkg.pth → /imio.smartweb.core-1.2.34-py3.10-nspkg.pth +0 -0
  30. {imio.smartweb.core-1.2.32.dist-info → imio.smartweb.core-1.2.34.dist-info}/LICENSE.GPL +0 -0
  31. {imio.smartweb.core-1.2.32.dist-info → imio.smartweb.core-1.2.34.dist-info}/LICENSE.rst +0 -0
  32. {imio.smartweb.core-1.2.32.dist-info → imio.smartweb.core-1.2.34.dist-info}/namespace_packages.txt +0 -0
  33. {imio.smartweb.core-1.2.32.dist-info → imio.smartweb.core-1.2.34.dist-info}/top_level.txt +0 -0
@@ -88,6 +88,14 @@
88
88
  layer="imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
89
89
  />
90
90
 
91
+ <browser:page
92
+ name="sitemap.xml.gz"
93
+ for="plone.base.interfaces.INavigationRoot"
94
+ class=".sitemap.CustomSiteMapView"
95
+ permission="zope2.Public"
96
+ layer="imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
97
+ />
98
+
91
99
  <browser:page
92
100
  name="events_view"
93
101
  for="*"
@@ -112,6 +120,14 @@
112
120
  layer="imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
113
121
  />
114
122
 
123
+ <browser:page
124
+ name="getVocabulary"
125
+ for="*"
126
+ class=".vocabulary.SmartwebVocabularyView"
127
+ permission="zope2.View"
128
+ layer="imio.smartweb.core.interfaces.IImioSmartwebCoreLayer"
129
+ />
130
+
115
131
  <z3c:widgetTemplate
116
132
  mode="input"
117
133
  widget="plone.app.z3cform.interfaces.ILinkWidget"
@@ -0,0 +1,135 @@
1
+ from BTrees.OOBTree import OOBTree
2
+ from imio.smartweb.core.config import DIRECTORY_URL
3
+ from imio.smartweb.core.config import EVENTS_URL
4
+ from imio.smartweb.core.config import NEWS_URL
5
+ from imio.smartweb.core.utils import get_json
6
+ from plone import api
7
+ from plone.base.interfaces import IPloneSiteRoot
8
+ from plone.app.layout.sitemap.sitemap import SiteMapView
9
+ from plone.registry.interfaces import IRegistry
10
+ from Products.CMFCore.utils import getToolByName
11
+ from Products.CMFPlone.utils import normalizeString
12
+ from zope.component import getUtility
13
+
14
+
15
+ class CustomSiteMapView(SiteMapView):
16
+
17
+ def objects(self):
18
+ """Returns the data to create the sitemap."""
19
+
20
+ friendlytypes = [
21
+ "Link",
22
+ "imio.smartweb.Folder",
23
+ "LIF",
24
+ "LRF",
25
+ "imio.smartweb.Page",
26
+ "imio.smartweb.Procedure",
27
+ "File",
28
+ "Collection",
29
+ "imio.smartweb.PortalPage",
30
+ "Image",
31
+ "imio.smartweb.CirkwiView",
32
+ ]
33
+
34
+ catalog = getToolByName(self.context, "portal_catalog")
35
+ query = {}
36
+ utils = getToolByName(self.context, "plone_utils")
37
+ query["portal_type"] = utils.getUserFriendlyTypes(friendlytypes)
38
+ registry = getUtility(IRegistry)
39
+ typesUseViewActionInListings = frozenset(
40
+ registry.get("plone.types_use_view_action_in_listings", [])
41
+ )
42
+
43
+ is_plone_site_root = IPloneSiteRoot.providedBy(self.context)
44
+ if not is_plone_site_root:
45
+ query["path"] = "/".join(self.context.getPhysicalPath())
46
+
47
+ query["is_default_page"] = True
48
+ default_page_modified = OOBTree()
49
+ for item in catalog.searchResults(query):
50
+ key = item.getURL().rsplit("/", 1)[0]
51
+ value = (item.modified.micros(), item.modified.ISO8601())
52
+ default_page_modified[key] = value
53
+
54
+ # The plone site root is not catalogued.
55
+ if is_plone_site_root:
56
+ loc = self.context.absolute_url()
57
+ date = self.context.modified()
58
+ # Comparison must be on GMT value
59
+ modified = (date.micros(), date.ISO8601())
60
+ default_modified = default_page_modified.get(loc, None)
61
+ if default_modified is not None:
62
+ modified = max(modified, default_modified)
63
+ lastmod = modified[1]
64
+ yield {
65
+ "loc": loc,
66
+ "lastmod": lastmod,
67
+ # 'changefreq': 'always',
68
+ # hourly/daily/weekly/monthly/yearly/never
69
+ # 'prioriy': 0.5, # 0.0 to 1.0
70
+ }
71
+
72
+ query["is_default_page"] = False
73
+ for item in catalog.searchResults(query):
74
+ loc = item.getURL()
75
+ date = item.modified
76
+ # Comparison must be on GMT value
77
+ modified = (date.micros(), date.ISO8601())
78
+ default_modified = default_page_modified.get(loc, None)
79
+ if default_modified is not None:
80
+ modified = max(modified, default_modified)
81
+ lastmod = modified[1]
82
+ if item.portal_type in typesUseViewActionInListings:
83
+ loc += "/view"
84
+ yield {
85
+ "loc": loc,
86
+ "lastmod": lastmod,
87
+ }
88
+
89
+ auth_sources = [
90
+ {"directory": {"path": DIRECTORY_URL, "type": "imio.directory.Contact"}},
91
+ {"news": {"path": NEWS_URL, "type": "imio.news.NewsItem"}},
92
+ {"events": {"path": EVENTS_URL, "type": "imio.events.Event"}},
93
+ ]
94
+ for auth_source in auth_sources:
95
+ for auth_source_key, auth_source_value in auth_source.items():
96
+ entity_uid = api.portal.get_registry_record(
97
+ f"smartweb.{auth_source_key}_entity_uid", default=None
98
+ )
99
+ auth_source_uid = api.portal.get_registry_record(
100
+ f"smartweb.default_{auth_source_key}_view", default=None
101
+ )
102
+ if entity_uid is None or auth_source_uid is None:
103
+ continue
104
+ brains = api.content.find(UID=auth_source_uid)
105
+ obj = brains[0].getObject()
106
+ auth_source_view_url = obj.absolute_url()
107
+ endpoint = "@search"
108
+ if auth_source_key == "directory":
109
+ selected_container = f"selected_entities={entity_uid}"
110
+ if auth_source_key == "news":
111
+ selected_container = (
112
+ f"selected_news_folders={obj.selected_news_folder}"
113
+ )
114
+ if auth_source_key == "events":
115
+ selected_container = f"selected_agendas={obj.selected_agenda}"
116
+ endpoint = "@events"
117
+ params = [
118
+ selected_container,
119
+ f"portal_type={auth_source_value.get('type')}",
120
+ "fullobjects=0",
121
+ "sort_on=sortable_title",
122
+ ]
123
+ url = f"{auth_source_value.get('path')}/{endpoint}?{'&'.join(params)}"
124
+ results = get_json(url)
125
+ if results is None:
126
+ continue
127
+ for item in results.get("items"):
128
+ item_id = normalizeString(item.get("title"))
129
+ item_uid = item.get("id")
130
+ loc = f"{auth_source_view_url}/{item_id}?u={item_uid}"
131
+ lastmod = item.get("modified")
132
+ yield {
133
+ "loc": loc,
134
+ "lastmod": lastmod,
135
+ }
@@ -1 +1 @@
1
- (()=>{"use strict";jQuery(document).ready((function(e){e(".opening_informations").click((function(s){e(this).siblings(".table_schedule").toggleClass("table_schedule--active"),s.preventDefault()})),e(document).click((function(s){e(".table_schedule").hasClass("table_schedule--active")&&(e(".table_schedule").is(s.target)||e(".opening_informations").is(s.target)||e(".table_schedule td").is(s.target)||e(".table_schedule").toggleClass("table_schedule--active"))}))})),$(document).ready((function(){const e=$("#portal-globalnav"),s=$("#subsite-navigation");function t(){e.removeClass("activated"),s.removeClass("activated"),document.body.classList.remove("submenu-open-nav-overflow"),document.documentElement.classList.remove("submenu-open-nav-overflow"),$(".show-nav").removeClass("show-nav"),$(".mask-menu").removeClass("in")}$(".close-nav").click((function(){t()})),$(document).mouseup((a=>{e.is(a.target)||0!==e.has(a.target).length||s.is(a.target)||0!==s.has(a.target).length||t()})),$("li.has_subtree > a").click((function(){return $(this).closest(e).length>0&&(!$("#portal-globalnav .show-nav").length>0&&($(e).toggleClass("activated"),$(".mask-menu").toggleClass("in")),$(this).parent().toggleClass("show-nav"),$(this).parent().find(".show-nav").toggleClass("show-nav"),$(this).parent().siblings(".show-nav").toggleClass("show-nav"),$(this).parent().find(".activated").toggleClass(".activated"),!$("#portal-globalnav .show-nav").length>0&&t()),$(this).closest(s).length>0&&(!$("#subsite-navigation .show-nav").length>0&&($(s).toggleClass("activated"),document.body.classList.add("submenu-open-nav-overflow"),document.documentElement.classList.add("submenu-open-nav-overflow")),$(this).parent().toggleClass("show-nav"),$(this).parent().find(".show-nav").toggleClass("show-nav"),$(this).parent().siblings(".show-nav").toggleClass("show-nav"),$(this).parent().find(".activated").toggleClass(".activated"),!$("#subsite-navigation .show-nav").length>0&&t()),!1})),$(".prev-nav").click((function(){$(this).closest(".show-nav").toggleClass("show-nav")}))}))})();
1
+ (()=>{"use strict";jQuery(document).ready((function(e){e(".opening_informations").click((function(t){e(this).siblings(".table_schedule").toggleClass("table_schedule--active"),t.preventDefault()})),e(document).click((function(t){e(".table_schedule").hasClass("table_schedule--active")&&(e(".table_schedule").is(t.target)||e(".opening_informations").is(t.target)||e(".table_schedule td").is(t.target)||e(".table_schedule").toggleClass("table_schedule--active"))}))})),$(document).ready((function(){const e=$("#portal-globalnav"),t=$("#subsite-navigation");function s(){e.removeClass("activated"),t.removeClass("activated"),document.body.classList.remove("submenu-open-nav-overflow"),document.documentElement.classList.remove("submenu-open-nav-overflow"),$(".show-nav").removeClass("show-nav"),$(".mask-menu").removeClass("in")}$(".close-nav").click((function(){s()})),$(document).mouseup((a=>{e.is(a.target)||0!==e.has(a.target).length||t.is(a.target)||0!==t.has(a.target).length||s()})),$("li.has_subtree > a").click((function(){return $(this).closest(e).length>0&&(!$("#portal-globalnav .show-nav").length>0&&($(e).toggleClass("activated"),$(".mask-menu").toggleClass("in")),$(this).parent().toggleClass("show-nav"),$(this).parent().find(".show-nav").toggleClass("show-nav"),$(this).parent().siblings(".show-nav").toggleClass("show-nav"),$(this).parent().find(".activated").toggleClass(".activated"),!$("#portal-globalnav .show-nav").length>0&&s()),$(this).closest(t).length>0&&(!$("#subsite-navigation .show-nav").length>0&&($(t).toggleClass("activated"),document.body.classList.add("submenu-open-nav-overflow"),document.documentElement.classList.add("submenu-open-nav-overflow")),$(this).parent().toggleClass("show-nav"),$(this).parent().find(".show-nav").toggleClass("show-nav"),$(this).parent().siblings(".show-nav").toggleClass("show-nav"),$(this).parent().find(".activated").toggleClass(".activated"),!$("#subsite-navigation .show-nav").length>0&&s()),!1})),$(".prev-nav").click((function(){$(this).closest(".show-nav").toggleClass("show-nav")})),e.on("focusout",(function(t){setTimeout((function(){e.has(document.activeElement).length||s()}),0)}))}))})();
@@ -83,6 +83,17 @@ $(document).ready(function () {
83
83
  $(this).closest(".show-nav").toggleClass("show-nav")
84
84
  });
85
85
 
86
+
87
+ // ici on ferme le menu au focusout
88
+ menu.on('focusout', function(e) {
89
+ // Utilisez setTimeout pour s'assurer que document.activeElement est mis à jour
90
+ setTimeout(function() {
91
+ if (!menu.has(document.activeElement).length) {
92
+ closeNav();
93
+ }
94
+ }, 0);
95
+ });
96
+
86
97
  // close nav fonction
87
98
  function closeNav() {
88
99
  menu.removeClass('activated');
@@ -1,5 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
- from Products.Five.browser import BrowserView
3
2
  from imio.smartweb.core.contents import IPages
4
3
  from imio.smartweb.core.contents.pages.procedure.utils import sign_url
5
4
  from imio.smartweb.core.utils import get_plausible_vars
@@ -7,12 +6,12 @@ from imio.smartweb.locales import SmartwebMessageFactory as _
7
6
  from plone import api
8
7
  from plone.api.portal import get_registry_record
9
8
  from plone.formwidget.geolocation.vocabularies import _ as _geo
9
+ from Products.Five.browser import BrowserView
10
10
  from zope.component import getMultiAdapter
11
11
  from zope.i18n import translate
12
12
 
13
13
  import json
14
14
  import requests
15
- import os
16
15
 
17
16
 
18
17
  class UtilsView(BrowserView):
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ from plone.app.content.browser.vocabulary import VocabularyView
3
+ from plone.app.content.browser.vocabulary import VocabLookupException
4
+ from plone.app.content.utils import json_dumps
5
+
6
+
7
+ class SmartwebVocabularyView(VocabularyView):
8
+
9
+ def __call__(self):
10
+ form = self.request.form
11
+ name = form.get("name")
12
+ if name != "imio.smartweb.vocabulary.RemoteContacts":
13
+ return super(SmartwebVocabularyView, self).__call__()
14
+
15
+ self.request.response.setHeader(
16
+ "Content-Type", "application/json; charset=utf-8"
17
+ )
18
+
19
+ try:
20
+ vocabulary = self.get_vocabulary()
21
+ except VocabLookupException as e:
22
+ return json_dumps({"error": e.args[0]})
23
+
24
+ query = form.get("query")
25
+ items = []
26
+ if not query:
27
+ items = vocabulary
28
+ else:
29
+ for term in vocabulary:
30
+ if query.lower() in term.title.lower():
31
+ items.append(term)
32
+
33
+ results = [{"id": item.value, "text": item.title} for item in items]
34
+
35
+ return json_dumps({"results": results, "total": len(results)})
@@ -1,8 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- from datetime import date, datetime
4
- from datetime import timedelta
5
- from decimal import Decimal
6
3
  from imio.smartweb.common.contact_utils import ContactProperties as ContactSchedule
7
4
  from imio.smartweb.common.utils import rich_description
8
5
  from imio.smartweb.core.utils import batch_results
@@ -11,7 +8,6 @@ from imio.smartweb.core.utils import hash_md5
11
8
  from imio.smartweb.locales import SmartwebMessageFactory as _
12
9
  from plone import api
13
10
  from zope.i18n import translate
14
- from zope.i18nmessageid import MessageFactory
15
11
 
16
12
  import json
17
13
 
@@ -56,15 +56,19 @@
56
56
  <tal:dates define="modified context/ModificationDate">
57
57
  <span class="documentModified"
58
58
  tal:condition="modified">
59
- <span i18n:translate="">
60
- Modified
61
- </span>
62
- <span class="pat-display-time"
63
- data-pat-display-time="from-now: true"
64
- tal:attributes="datetime modified"
65
- tal:content="modified">
66
- Modified
67
- </span>
59
+ <a class="documentModified"
60
+ tal:attributes="href string:${context/absolute_url}/@@historyview"
61
+ tal:omit-tag="python:context.portal_type != 'imio.smartweb.SectionText'">
62
+ <span i18n:translate="">
63
+ Modified
64
+ </span>
65
+ <span class="pat-display-time"
66
+ data-pat-display-time="from-now: true"
67
+ tal:attributes="datetime modified"
68
+ tal:content="modified">
69
+ Modified
70
+ </span>
71
+ </a>
68
72
  </span>
69
73
  </tal:dates>
70
74
 
@@ -10,10 +10,11 @@
10
10
  <metal:main fill-slot="content-core">
11
11
  <metal:content-core define-macro="content-core">
12
12
  <metal:macro use-macro="context/@@sections_macros/section_edition" />
13
- <div tal:define="image context/image | nothing;"
14
- class="container section-container section-text"
13
+ <div tal:define="image context/image | nothing;
14
+ collapse_klass_container python: 'section-text-collapsed' if context.collapsible_section else '';"
15
15
  id=""
16
- tal:attributes="id string:container-section-${context/id}">
16
+ tal:attributes="class string:container section-container section-text ${collapse_klass_container};
17
+ id string:container-section-${context/id};">
17
18
  <metal:macro use-macro="context/@@sections_macros/section_title" />
18
19
  <div tal:define="collapse_klass python: 'collapse' if context.collapsible_section else '';
19
20
  klass string:body-section figure-${context/alignment} figure-${context/image_size} ${collapse_klass};"
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <object>
3
+ <difftypes>
4
+ <type portal_type="imio.smartweb.SectionText">
5
+ <field name="any" difftype="Compound Diff for Dexterity types"/>
6
+ </type>
7
+ </difftypes>
8
+ </object>
@@ -1,6 +1,6 @@
1
1
  <?xml version='1.0' encoding='UTF-8'?>
2
2
  <metadata>
3
- <version>1052</version>
3
+ <version>1053</version>
4
4
  <dependencies>
5
5
  <dependency>profile-plone.app.dexterity:default</dependency>
6
6
  <dependency>profile-plone.app.imagecropping:default</dependency>
@@ -2,6 +2,7 @@
2
2
  <repositorytool>
3
3
  <policymap>
4
4
  <type name="imio.smartweb.SectionText">
5
+ <policy name="at_edit_autoversion" />
5
6
  <policy name="version_on_revert"/>
6
7
  </type>
7
8
  </policymap>
@@ -32,7 +32,6 @@
32
32
  <element value="plone.namefromtitle"/>
33
33
  <element value="plone.locking"/>
34
34
  <element value="plone.shortname"/>
35
- <element value="plone.versioning" />
36
35
  </property>
37
36
 
38
37
  </object>
@@ -0,0 +1,102 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <contentrules>
3
+ <rule name="rule-1" title="Notification de contenu à valider"
4
+ cascading="False" description="" enabled="True"
5
+ event="Products.CMFCore.interfaces.IActionSucceededEvent"
6
+ stop-after="False">
7
+ <conditions>
8
+ <condition type="plone.conditions.PortalType">
9
+ <property name="check_types">
10
+ <element>imio.smartweb.Page</element>
11
+ <element>imio.smartweb.PortalPage</element>
12
+ <element>imio.smartweb.Procedure</element>
13
+ </property>
14
+ </condition>
15
+ <condition type="plone.conditions.WorkflowState">
16
+ <property name="wf_states">
17
+ <element>pending</element>
18
+ </property>
19
+ </condition>
20
+ </conditions>
21
+ <actions>
22
+ <action type="plone.actions.Mail">
23
+ <property name="subject">Nouveau contenu sur le site</property>
24
+ <property name="source"/>
25
+ <property name="recipients">${reviewer_emails}</property>
26
+ <property name="exclude_actor">False</property>
27
+ <property name="message">Un nouveau contenu a été soumis à validation. Cliquez sur le lien pour aller le valider ou le refuser : ${absolute_url}</property>
28
+ </action>
29
+ <action type="plone.actions.Logger">
30
+ <property name="targetLogger">imio.smartweb.core</property>
31
+ <property name="loggingLevel">20</property>
32
+ <property
33
+ name="message">Content to review notification sent for &amp;c.</property>
34
+ </action>
35
+ </actions>
36
+ </rule>
37
+ <rule name="rule-2" title="Notification de modification de section de contenu publié"
38
+ cascading="False" description="" enabled="True"
39
+ event="zope.lifecycleevent.interfaces.IObjectModifiedEvent"
40
+ stop-after="False">
41
+ <conditions>
42
+ <condition type="plone.conditions.PortalType">
43
+ <property name="check_types">
44
+ <element>imio.smartweb.SectionText</element>
45
+ </property>
46
+ </condition>
47
+ <condition type="plone.conditions.TalesExpression">
48
+ <property
49
+ name="tales_expression">python: hasattr(here, &quot;aq_parent&quot;) and portal.portal_workflow.getInfoFor(here.aq_parent, &quot;review_state&quot;, &quot;&quot;) == &quot;published&quot;</property>
50
+ </condition>
51
+ </conditions>
52
+ <actions>
53
+ <action type="plone.actions.Mail">
54
+ <property name="subject">Contenu publié modifié sur le site</property>
55
+ <property name="source"/>
56
+ <property name="recipients">${reviewer_emails}</property>
57
+ <property name="exclude_actor">False</property>
58
+ <property name="message">Une section texte d&#x27;un contenu déjà publié a été modifiée. Cliquez sur le lien pour consulter la modification effectuée : ${absolute_url}/@@historyview</property>
59
+ </action>
60
+ <action type="plone.actions.Logger">
61
+ <property name="targetLogger">imio.smartweb.core</property>
62
+ <property name="loggingLevel">20</property>
63
+ <property
64
+ name="message">Published content change notification sent for &amp;c (user: &amp;u).</property>
65
+ </action>
66
+ </actions>
67
+ </rule>
68
+ <rule name="rule-3" title="Notification d'ajout de section à un contenu publié"
69
+ cascading="False" description="" enabled="True"
70
+ event="zope.lifecycleevent.interfaces.IObjectAddedEvent"
71
+ stop-after="False">
72
+ <conditions>
73
+ <condition type="plone.conditions.PortalType">
74
+ <property name="check_types">
75
+ <element>imio.smartweb.SectionText</element>
76
+ </property>
77
+ </condition>
78
+ <condition type="plone.conditions.TalesExpression">
79
+ <property
80
+ name="tales_expression">python: hasattr(here, &quot;aq_parent&quot;) and portal.portal_workflow.getInfoFor(here.aq_parent, &quot;review_state&quot;, &quot;&quot;) == &quot;published&quot;</property>
81
+ </condition>
82
+ </conditions>
83
+ <actions>
84
+ <action type="plone.actions.Mail">
85
+ <property name="subject">Contenu publié modifié sur le site</property>
86
+ <property name="source"/>
87
+ <property name="recipients">${reviewer_emails}</property>
88
+ <property name="exclude_actor">False</property>
89
+ <property name="message">Une section texte a été ajoutée à un contenu déjà publié. Cliquez sur le lien pour consulter la section texte ajoutée : ${absolute_url}</property>
90
+ </action>
91
+ <action type="plone.actions.Logger">
92
+ <property name="targetLogger">imio.smartweb.core</property>
93
+ <property name="loggingLevel">20</property>
94
+ <property
95
+ name="message">Published content add notification sent for &amp;c (user: &amp;u).</property>
96
+ </action>
97
+ </actions>
98
+ </rule>
99
+ <assignment name="rule-1" bubbles="True" enabled="True" location=""/>
100
+ <assignment name="rule-2" bubbles="True" enabled="True" location=""/>
101
+ <assignment name="rule-3" bubbles="True" enabled="True" location=""/>
102
+ </contentrules>
@@ -45,6 +45,14 @@
45
45
  provides="Products.GenericSetup.interfaces.EXTENSION"
46
46
  />
47
47
 
48
+ <genericsetup:registerProfile
49
+ name="validation"
50
+ title="imio.smartweb.core validation"
51
+ directory="profiles/validation"
52
+ description="Installs smartweb validation content rules"
53
+ provides="Products.GenericSetup.interfaces.EXTENSION"
54
+ />
55
+
48
56
  <genericsetup:registerProfile
49
57
  name="last-compilation"
50
58
  title="imio.smartweb.core last compilation dates"
@@ -588,3 +588,21 @@ class TestSections(ImioSmartwebTestCase):
588
588
  self.assertEqual(
589
589
  view.get_class(section), "sectiontext my-css col-sm-3 with-background"
590
590
  )
591
+
592
+ def test_sections_history(self):
593
+ api.content.transition(self.page, "publish")
594
+ section_types = get_sections_types()
595
+ page_view = queryMultiAdapter((self.page, self.request), name="full_view")()
596
+ count_historyview_link = page_view.count("@@historyview")
597
+ self.assertEqual(count_historyview_link, 1)
598
+ for section_type in section_types:
599
+ api.content.create(
600
+ container=self.page,
601
+ type=section_type,
602
+ title="Title of my {}".format(section_type),
603
+ )
604
+ page_view = queryMultiAdapter((self.page, self.request), name="full_view")()
605
+ section_text = api.content.find(portal_type="imio.smartweb.SectionText")[0]
606
+ self.assertIn(f"{section_text.getURL()}/@@historyview", page_view)
607
+ # One more history view link on SectionText
608
+ self.assertEqual(page_view.count("@@historyview"), count_historyview_link + 1)
@@ -0,0 +1,155 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from freezegun import freeze_time
4
+ from gzip import GzipFile
5
+ from io import BytesIO
6
+ from imio.smartweb.core.testing import IMIO_SMARTWEB_CORE_FUNCTIONAL_TESTING
7
+ from imio.smartweb.core.testing import ImioSmartwebTestCase
8
+ from imio.smartweb.core.tests.utils import get_json
9
+ from imio.smartweb.core.tests.utils import make_named_image
10
+ from plone import api
11
+ from plone.app.testing import setRoles
12
+ from plone.app.testing import TEST_USER_ID
13
+ from plone.app.textfield.value import RichTextValue
14
+ from plone.base.utils import safe_text
15
+ from plone.namedfile.file import NamedBlobImage
16
+ from zope.component import getMultiAdapter
17
+
18
+ import json
19
+ import requests_mock
20
+
21
+
22
+ class TestPage(ImioSmartwebTestCase):
23
+ layer = IMIO_SMARTWEB_CORE_FUNCTIONAL_TESTING
24
+
25
+ @freeze_time("2024-02-02 8:00:00")
26
+ def setUp(self):
27
+ self.request = self.layer["request"]
28
+ self.portal = self.layer["portal"]
29
+ setRoles(self.portal, TEST_USER_ID, ["Manager"])
30
+ api.portal.set_registry_record("plone.enable_sitemap", True)
31
+
32
+ self.default_page = api.content.create(
33
+ container=self.portal,
34
+ type="imio.smartweb.PortalPage",
35
+ title="Portal page",
36
+ id="portal-page",
37
+ )
38
+ self.portal.setDefaultPage("portal-page")
39
+
40
+ self.folder = api.content.create(
41
+ container=self.portal,
42
+ type="imio.smartweb.Folder",
43
+ title="Folder",
44
+ id="folder",
45
+ )
46
+ self.page = api.content.create(
47
+ container=self.folder,
48
+ type="imio.smartweb.Page",
49
+ title="Page 1",
50
+ id="page1",
51
+ )
52
+ self.section_text = api.content.create(
53
+ container=self.page,
54
+ type="imio.smartweb.SectionText",
55
+ title="Section text",
56
+ )
57
+ self.section_text.text = RichTextValue(
58
+ "<p>Kamoulox</p>", "text/html", "text/html"
59
+ )
60
+
61
+ self.rest_directory = api.content.create(
62
+ container=self.portal,
63
+ type="imio.smartweb.DirectoryView",
64
+ title="directory view",
65
+ )
66
+ self.rest_agenda = api.content.create(
67
+ container=self.portal,
68
+ type="imio.smartweb.EventsView",
69
+ title="agenda view",
70
+ )
71
+ self.rest_agenda.selected_agenda = "64f4cbee9a394a018a951f6d94452914"
72
+ self.rest_news = api.content.create(
73
+ container=self.portal,
74
+ type="imio.smartweb.NewsView",
75
+ title="news view",
76
+ )
77
+ self.rest_news.selected_news_folder = "64f4cbee9a394a018a951f6d94452914"
78
+ api.content.transition(self.rest_directory, "publish")
79
+ api.content.transition(self.rest_agenda, "publish")
80
+ api.content.transition(self.rest_news, "publish")
81
+
82
+ # 'http://localhost:8080/Plone/@search?selected_entities=396907b3b1b04a97896b12cc792c77f8&portal_type=imio.directory.Contact&fullobjects=0&sort_on=sortable_title'
83
+ @freeze_time("2024-02-02 10:00:00")
84
+ @requests_mock.Mocker()
85
+ def test_sitemap(self, m):
86
+ sitemap = getMultiAdapter(
87
+ (self.portal, self.portal.REQUEST), name="sitemap.xml.gz"
88
+ )
89
+ xml = self.uncompress(sitemap())
90
+ self.assertIn("<lastmod >2024-02-02T08:00:00+00:00</lastmod>", xml)
91
+ self.assertIn("<loc>http://nohost/plone/folder</loc>", xml)
92
+ self.assertIn("http://nohost/plone/folder/page1", xml)
93
+ self.assertNotIn(
94
+ "<loc>http://nohost/plone/folder/page1/gallery/image/view</loc>", xml
95
+ )
96
+
97
+ # Gallery and image created 2024-02-02 10:00:00
98
+ gallery = api.content.create(
99
+ container=self.page,
100
+ type="imio.smartweb.SectionGallery",
101
+ title="Gallery",
102
+ )
103
+ image = api.content.create(
104
+ container=gallery,
105
+ type="Image",
106
+ title="Image",
107
+ )
108
+ image.image = NamedBlobImage(**make_named_image())
109
+ sitemap = getMultiAdapter(
110
+ (self.portal, self.portal.REQUEST), name="sitemap.xml.gz"
111
+ )
112
+ xml = self.uncompress(sitemap())
113
+ self.assertIn(
114
+ "<loc>http://nohost/plone/folder/page1/gallery/image/view</loc>", xml
115
+ )
116
+ # Gallery and image created 2024-02-02 10:00:00
117
+ self.assertIn(
118
+ "<loc>http://nohost/plone/folder/page1/gallery/image/view</loc>\n <lastmod >2024-02-02T10:00:00+00:00</lastmod>",
119
+ xml,
120
+ )
121
+
122
+ rest_views = {
123
+ "directory": self.rest_directory,
124
+ "events": self.rest_agenda,
125
+ "news": self.rest_news,
126
+ }
127
+ for k, v in rest_views.items():
128
+ api.portal.set_registry_record(f"smartweb.default_{k}_view", v.UID())
129
+
130
+ self.json_contacts = get_json("resources/json_contacts_raw_mock.json")
131
+ self.json_news = get_json("resources/json_rest_news.json")
132
+ self.json_events = get_json("resources/json_rest_events.json")
133
+
134
+ contact_search_url = "http://localhost:8080/Plone/@search?selected_entities=396907b3b1b04a97896b12cc792c77f8&portal_type=imio.directory.Contact&fullobjects=0&sort_on=sortable_title"
135
+ news_search_url = f"http://localhost:8080/Plone/@search?selected_news_folders={self.rest_news.selected_news_folder}&portal_type=imio.news.NewsItem&fullobjects=0&sort_on=sortable_title"
136
+ events_search_url = f"http://localhost:8080/Plone/@events?selected_agendas={self.rest_agenda.selected_agenda}&portal_type=imio.events.Event&fullobjects=0&sort_on=sortable_title"
137
+
138
+ m.get(contact_search_url, text=json.dumps(self.json_contacts))
139
+ m.get(news_search_url, text=json.dumps(self.json_news))
140
+ m.get(events_search_url, text=json.dumps(self.json_events))
141
+
142
+ sitemap = getMultiAdapter(
143
+ (self.portal, self.portal.REQUEST), name="sitemap.xml.gz"
144
+ )
145
+ xml = self.uncompress(sitemap())
146
+ self.assertIn("<loc>http://nohost/plone/directory-view/contact1-title", xml)
147
+ self.assertIn("<loc>http://nohost/plone/news-view/", xml)
148
+ self.assertIn("<loc>http://nohost/plone/agenda-view/", xml)
149
+
150
+ def uncompress(self, sitemapdata):
151
+ sio = BytesIO(sitemapdata)
152
+ unzipped = GzipFile(fileobj=sio)
153
+ xml = unzipped.read()
154
+ unzipped.close()
155
+ return safe_text(xml)