From 31d5dc75d021176bd6e9986e6aad6feb70b869b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Mon, 27 Jul 2020 19:09:18 +0200
Subject: [PATCH] publik: make it possible to create publik menu from pages
 (#45741)

---
 combo/apps/publik/views.py | 77 +++++++++++++++++++++++++++++++-------
 combo/data/models.py       |  8 ++--
 tests/test_publik.py       | 77 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 141 insertions(+), 21 deletions(-)

diff --git a/combo/apps/publik/views.py b/combo/apps/publik/views.py
index ca3dd4a4..9695822a 100644
--- a/combo/apps/publik/views.py
+++ b/combo/apps/publik/views.py
@@ -17,35 +17,86 @@
 import json
 
 from django.conf import settings
+from django.contrib.auth.decorators import login_required
 from django.http import Http404, HttpResponse
 
+from combo.data.models import Page
+
+
+def menuitem_url(request, page, subpage=None):
+    if subpage and subpage.redirect_url:
+        return subpage.get_redirect_url()
+    elif subpage:
+        return request.build_absolute_uri('/'.join(['', page.slug, subpage.slug, '']))
+    if page.redirect_url:
+        return page.get_redirect_url()
+    return request.build_absolute_uri('/'.join(['', page.slug, '']))
+
+
+@login_required
 def services_js(request, *args, **kwargs):
     if not getattr(settings, 'KNOWN_SERVICES', None):
         raise Http404()
     services = []
     portal_agent_url = None
     portal_agent_title = None
-    for service_id, services_dict in settings.KNOWN_SERVICES.items():
-        for service_slug, service in services_dict.items():
-            if service.get('secondary') and service_id != 'authentic':
+    menu_items = []
+
+    if getattr(settings, 'PUBLIK_EXPLICIT_MENU', False):
+        pages = Page.objects.filter(
+                parent_id__isnull=True,
+                exclude_from_navigation=False)
+        second_level_pages = Page.objects.filter(
+                parent_id__in=[x.id for x in pages],
+                exclude_from_navigation=False)
+        for page in pages:
+            if not page.is_visible(request.user, ignore_superuser=True):
                 continue
-            if service.get('is-portal-agent'):
-                portal_agent_url = service['url']
-                portal_agent_title = service['title']
-            services.append({
-                'title': service['title'],
-                'slug': service_slug,
-                'service_id': service_id,
-                'uniq': bool(len([x for x in services_dict.values() if not x.get('secondary')]) == 1),
-                'url': service['url'],
-                'backoffice_menu_url': service['backoffice-menu-url'],
+            menu_items.append(
+                {
+                    'label': page.title,
+                    'url': menuitem_url(request, page),
+                    'slug': page.slug if page.slug != 'index' else 'home',
+                    'icon': request.build_absolute_uri(page.picture.url) if page.picture else None,
                 })
+            for subpage in second_level_pages:
+                if subpage.parent_id != page.id:
+                    continue
+                if not subpage.is_visible(request.user, ignore_superuser=True):
+                    continue
+                menu_items.append(
+                    {
+                        'label': subpage.title,
+                        'sub': True,
+                        'url': menuitem_url(request, page, subpage),
+                        'slug': subpage.slug,
+                        'icon': request.build_absolute_uri(subpage.picture.url) if subpage.picture else None,
+                    })
+    else:
+        for service_id, services_dict in settings.KNOWN_SERVICES.items():
+            for service_slug, service in services_dict.items():
+                if service.get('secondary') and service_id != 'authentic':
+                    continue
+                if service.get('is-portal-agent'):
+                    portal_agent_url = service['url']
+                    portal_agent_title = service['title']
+                services.append({
+                    'title': service['title'],
+                    'slug': service_slug,
+                    'service_id': service_id,
+                    'uniq': bool(len([x for x in services_dict.values() if not x.get('secondary')]) == 1),
+                    'url': service['url'],
+                    'backoffice_menu_url': service['backoffice-menu-url'],
+                    })
+
     response_vars = {
             'PUBLIK_ENVIRONMENT_LABEL': settings.TEMPLATE_VARS.get('environment_label'),
             'PUBLIK_PORTAL_AGENT_URL': portal_agent_url,
             'PUBLIK_PORTAL_AGENT_TITLE': portal_agent_title,
             'PUBLIK_PORTAL_AGENT_EXTRA_CSS': settings.TEMPLATE_VARS.get(
                 'portal_agent_extra_css'),
+            'PUBLIK_MENU_ITEMS': menu_items,
+            'PUBLIK_EXPLICIT_MENU': getattr(settings, 'PUBLIK_EXPLICIT_MENU', False),
             'COMBO_KNOWN_SERVICES': services,
     }
     response_body = '\n'.join(
diff --git a/combo/data/models.py b/combo/data/models.py
index 85eb1d89..0861d8bf 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -71,14 +71,14 @@ class PostException(Exception):
     pass
 
 
-def element_is_visible(element, user=None):
+def element_is_visible(element, user=None, ignore_superuser=False):
     if element.public:
         if getattr(element, 'restricted_to_unlogged', None) is True:
             return (user is None or user.is_anonymous)
         return True
     if user is None or user.is_anonymous:
         return False
-    if user.is_superuser:
+    if user.is_superuser and not ignore_superuser:
         return True
     page_groups = element.groups.all()
     if not page_groups:
@@ -361,8 +361,8 @@ class Page(models.Model):
         groupnames = ', '.join([x.name for x in groups]) if groups else _('logged users')
         return _('Private (%s)') % groupnames
 
-    def is_visible(self, user=None):
-        return element_is_visible(self, user=user)
+    def is_visible(self, user=None, ignore_superuser=False):
+        return element_is_visible(self, user=user, ignore_superuser=ignore_superuser)
 
     def get_cells(self):
         return CellBase.get_cells(page=self)
diff --git a/tests/test_publik.py b/tests/test_publik.py
index a187429a..ab563dfd 100644
--- a/tests/test_publik.py
+++ b/tests/test_publik.py
@@ -1,11 +1,33 @@
+import json
+import re
+
 import pytest
 from webtest import TestApp
 
 from django.conf import settings
+from django.contrib.auth.models import User, Group
+
+from combo.data.models import Page
+
+from . import test_manager
 
 pytestmark = pytest.mark.django_db
 
 
+def login(app):
+    test_manager.login(app, username='normal-user', password='normal-user')
+
+
+@pytest.fixture
+def normal_user():
+    try:
+        user = User.objects.get(username='normal-user')
+        user.groups = []
+    except User.DoesNotExist:
+        user = User.objects.create_user(username='normal-user', email='', password='normal-user')
+    return user
+
+
 class KnownServices(object):
     def __init__(self, known_services):
         self.known_services = known_services
@@ -18,18 +40,25 @@ class KnownServices(object):
         settings.KNOWN_SERVICES = self.orig_value
 
 
-def test_services_js_no_services(app):
+def test_services_js_no_services(app, normal_user):
     with KnownServices({}):
-        resp = app.get('/__services.js', status=404)
+        app.get('/__services.js', status=302)
+        login(app)
+        app.get('/__services.js', status=404)
+
 
-def test_services_js_multiple_wcs(app):
+def test_services_js_multiple_wcs(app, normal_user):
+    login(app)
     resp = app.get('/__services.js', status=200)
     assert resp.content_type == 'text/javascript'
     assert 'var PUBLIK_ENVIRONMENT_LABEL = null' in resp.text
     assert 'var PUBLIK_PORTAL_AGENT_URL = null' in resp.text
     assert '"uniq": false' in resp.text
 
-def test_services_js_portal_agent(app):
+
+def test_services_js_portal_agent(app, normal_user):
+    app.get('/__services.js', status=302)  # authentication required
+    login(app)
     known_services = {
         'combo': {
             'portal-agent': {
@@ -50,3 +79,43 @@ def test_services_js_portal_agent(app):
         assert resp.content_type == 'text/javascript'
         assert 'var PUBLIK_PORTAL_AGENT_URL = "http://example.net";' in resp.text
         assert 'var PUBLIK_PORTAL_AGENT_TITLE = "Portal Agent"' in resp.text
+
+
+def test_services_js_explicit_menu(app, normal_user, settings):
+    settings.PUBLIK_EXPLICIT_MENU = True
+
+    group1 = Group.objects.create(name='plop1')
+    group2 = Group.objects.create(name='plop2')
+
+    index_page = Page.objects.create(title='Home', slug='index',
+            template_name='standard', public=False,
+            exclude_from_navigation=False)
+    index_page.groups.set([group1])
+    other_page = Page.objects.create(title='Page', slug='page',
+            template_name='standard', public=False,
+            exclude_from_navigation=False)
+    other_page.groups.set([group1])
+    sub_page = Page.objects.create(title='Subpage', slug='subpage',
+            template_name='standard', parent=other_page, public=False,
+            exclude_from_navigation=False)
+    sub_page.groups.set([group2])
+    last_page = Page.objects.create(title='Last page', slug='lastpage',
+            template_name='standard', public=False,
+            exclude_from_navigation=False)
+    last_page.groups.set([group1])
+
+    def get_menu_var(resp):
+        return json.loads(re.findall('var PUBLIK_MENU_ITEMS = (.*);', resp.text)[0])
+
+    login(app)
+    resp = app.get('/__services.js', status=200)
+    assert [x['slug'] for x in get_menu_var(resp)] == []
+
+    normal_user.groups.set([group1])
+    resp = app.get('/__services.js', status=200)
+    assert [x['slug'] for x in get_menu_var(resp)] == ['home', 'page', 'lastpage']
+    assert len(get_menu_var(resp)) == 3
+
+    normal_user.groups.set([group1, group2])
+    resp = app.get('/__services.js', status=200)
+    assert [x['slug'] for x in get_menu_var(resp)] == ['home', 'page', 'subpage', 'lastpage']
-- 
2.28.0

