diff --git a/src/moin/app.py b/src/moin/app.py index fede0bde7..af9dd11e6 100644 --- a/src/moin/app.py +++ b/src/moin/app.py @@ -30,7 +30,7 @@ from moin.utils import monkeypatch # noqa from moin.utils.clock import Clock from moin import auth, user, config -from moin.constants.misc import ANON +from moin.constants.misc import ANON, VALID_ITEMLINK_VIEWS from moin.i18n import i18n_init from moin.themes import setup_jinja_env, themed_error from moin.storage.middleware import protecting, indexing, routing @@ -156,6 +156,8 @@ class ItemNameConverter(PathConverter): from moin.apps.serve import serve app.register_blueprint(serve, url_prefix="/+serve") + + app.view_endpoints = get_endpoints(app) clock.stop("create_app register") clock.start("create_app flask-cache") # 'SimpleCache' caching uses a dict and is not thread safe according to the docs. @@ -191,6 +193,16 @@ class ItemNameConverter(PathConverter): return app +def get_endpoints(app): + """Get dict with views and related endpoints allowed as itemlink""" + view_endpoints = {} + for rule in app.url_map.iter_rules(): + view = rule.rule.split("/")[1] + if view in VALID_ITEMLINK_VIEWS and rule.rule == f"/{view}/": + view_endpoints[view] = rule.endpoint + return view_endpoints + + def destroy_app(app): deinit_backends(app) diff --git a/src/moin/constants/misc.py b/src/moin/constants/misc.py index 08627bbac..8c852411f 100644 --- a/src/moin/constants/misc.py +++ b/src/moin/constants/misc.py @@ -1,4 +1,5 @@ # Copyright: 2011 MoinMoin:ThomasWaldmann +# Copyright: 2024 MoinMoin:UlrichB # License: GNU GPL v2 (or any later version), see LICENSE.txt for details. """ @@ -69,3 +70,6 @@ NO_LOCK = 0 # false, someone else holds lock for current item LOCKED = 1 # true, current user has obtained or renewed lock LOCK = "lock" + +# Valid views allowed for itemlinks +VALID_ITEMLINK_VIEWS = ["+meta", "+history", "+download", "+highlight"] diff --git a/src/moin/converters/link.py b/src/moin/converters/link.py index 919a1b9f8..c4b414cfe 100644 --- a/src/moin/converters/link.py +++ b/src/moin/converters/link.py @@ -1,4 +1,5 @@ # Copyright: 2008 MoinMoin:BastianBlank +# Copyright: 2024 MoinMoin:UlrichB # License: GNU GPL v2 (or any later version), see LICENSE.txt for details. """ @@ -8,8 +9,10 @@ special wiki links. """ +from flask import current_app as app from flask import g as flaskg +from moin.constants.misc import VALID_ITEMLINK_VIEWS from moin.utils.interwiki import is_known_wiki, url_for_item from moin.utils.iri import Iri from moin.utils.mime import type_moin_document @@ -182,18 +185,28 @@ def handle_wiki_links(self, elem, input, to_tag=ConverterBase._tag_xlink_href): elem.set(to_tag, link) def handle_wikilocal_links(self, elem, input, page, to_tag=ConverterBase._tag_xlink_href): + view_name = "" if input.path: - # this can be a relative path, make it absolute: - path = input.path + item_name = str(input.path) + # Remove view from item_name before searching + if item_name.startswith("+"): + view_name = item_name.split("/")[0] + if view_name in VALID_ITEMLINK_VIEWS: + item_name = item_name.split(f"{view_name}/")[1] if page: - path = self.absolute_path(path, page.path) - item_name = str(path) + # this can be a relative path, make it absolute: + item_name = str(self.absolute_path(Iri(path=item_name).path, page.path)) if not flaskg.storage.has_item(item_name): # XXX these index accesses slow down the link converter quite a bit elem.set(moin_page.class_, "moin-nonexistent") else: item_name = str(page.path[1:]) if page else "" endpoint, rev, query = self._get_do_rev(input.query) + + if view_name in app.view_endpoints.keys(): + # Other views will be shown with class moin-nonexistent as non-existent links + endpoint = app.view_endpoints[view_name] + url = url_for_item(item_name, rev=rev, endpoint=endpoint) if not page: url = url[1:] diff --git a/src/moin/templates/utils.html b/src/moin/templates/utils.html index b2b61ab75..d9f94da1b 100644 --- a/src/moin/templates/utils.html +++ b/src/moin/templates/utils.html @@ -153,7 +153,7 @@
  • Item Links:  {%- if meta['itemlinks'] -%} {%- for item in meta['itemlinks']|sort -%} - {{ item }} + {{ item }} {%- if not loop.last %}, {% endif -%} {%- endfor -%} {%- else -%} diff --git a/src/moin/themes/__init__.py b/src/moin/themes/__init__.py index 83b785317..83a29e207 100644 --- a/src/moin/themes/__init__.py +++ b/src/moin/themes/__init__.py @@ -1,7 +1,7 @@ # Copyright: 2003-2010 MoinMoin:ThomasWaldmann # Copyright: 2008 MoinMoin:RadomirDopieralski # Copyright: 2010 MoinMoin:DiogenesAugusto -# Copyright: 2023 MoinMoin project +# Copyright: 2023-2024 MoinMoin project # License: GNU GPL v2 (or any later version), see LICENSE.txt for details. """ @@ -26,6 +26,7 @@ from moin import wikiutil, user from moin.constants.keys import USERID, ADDRESS, HOSTNAME, REVID, ITEMID, NAME_EXACT, ASSIGNED_TO, NAME, NAMESPACE from moin.constants.contenttypes import CONTENTTYPES_MAP, CONTENTTYPE_MARKUP, CONTENTTYPE_TEXT, CONTENTTYPE_MOIN_19 +from moin.constants.misc import VALID_ITEMLINK_VIEWS from moin.constants.namespaces import NAMESPACE_DEFAULT, NAMESPACE_USERS, NAMESPACE_ALL from moin.constants.rights import SUPERUSER from moin.search import SearchForm @@ -591,6 +592,20 @@ def item_exists(self, itemname): """ return self.storage.has_item(itemname) + def itemlink_exists(self, itemlink): + """ + Check whether the item pointed to by the given itemlink exists or not + + :rtype: boolean + :returns: whether item pointed to by the link exists or not + """ + item_name = itemlink + if itemlink.startswith("+"): + view_name = itemlink.split("/")[0] + if view_name in VALID_ITEMLINK_VIEWS: + item_name = itemlink.split(f"{view_name}/")[1] + return self.storage.has_item(item_name) + def variables_css(self): """ Check whether this theme has a variables.css file