1+ from BTrees .OOBTree import OOBTree
2+ from DateTime import DateTime
3+ from DateTime .interfaces import DateTimeError
14from plone .app .redirector .interfaces import IRedirectionStorage
5+ from plone .restapi .batching import HypermediaBatch
26from plone .restapi .bbb import IPloneSiteRoot
37from plone .restapi .interfaces import IExpandableElement
48from plone .restapi .serializer .converters import datetimelike_to_iso
59from plone .restapi .services import Service
10+ from plone .restapi .utils import deroot_path
11+ from plone .restapi .utils import is_falsy
12+ from plone .restapi .utils import is_truthy
613from Products .CMFPlone .controlpanel .browser .redirects import RedirectsControlPanel
14+ from zExceptions import BadRequest
15+ from zExceptions import HTTPNotAcceptable as NotAcceptable
716from zope .component import adapter
817from zope .component import getUtility
9- from zope .component .hooks import getSite
1018from zope .interface import implementer
1119from zope .interface import Interface
1220
@@ -22,40 +30,60 @@ def __init__(self, context, request):
2230 self .context = context
2331 self .request = request
2432
25- def reply_item (self ):
33+ def reply (self , query , manual , start , end ):
2634 storage = getUtility (IRedirectionStorage )
35+ portal_path = "/" .join (self .context .getPhysicalPath ()[:2 ])
2736 context_path = "/" .join (self .context .getPhysicalPath ())
28- redirects = storage .redirects (context_path )
29- aliases = [deroot_path (alias ) for alias in redirects ]
30- self .request .response .setStatus (200 )
31- self .request .response .setHeader ("Content-Type" , "application/json" )
32- return [{"path" : alias } for alias in aliases ], len (aliases )
33-
34- def reply_root (self ):
35- """
36- redirect-to - target
37- path - path
38- redirect - full path with root
39- """
40- batch = RedirectsControlPanel (self .context , self .request ).redirects ()
41- redirects = [entry for entry in batch ]
42-
43- for redirect in redirects :
44- del redirect ["redirect" ]
45- redirect ["datetime" ] = datetimelike_to_iso (redirect ["datetime" ])
46- self .request .response .setStatus (200 )
4737
48- self .request .form ["b_start" ] = "0"
49- self .request .form ["b_size" ] = "1000000"
50- self .request .__annotations__ .pop ("plone.memoize" )
38+ if not IPloneSiteRoot .providedBy (self .context ):
39+ tree = OOBTree ()
40+ rds = storage .redirects (context_path )
41+ for rd in rds :
42+ rd_full = storage .get_full (rd )
43+ tree [rd ] = rd_full
44+ storage = tree
45+ else :
46+ storage = storage ._paths
47+
48+ if query and query .startswith ("/" ):
49+ min_k = f"{ portal_path } /{ query .strip ('/' )} "
50+ max_k = min_k [:- 1 ] + chr (ord (min_k [- 1 ]) + 1 )
51+ redirects = storage .items (min = min_k , max = max_k , excludemax = True )
52+ elif query :
53+ redirects = [path for path in storage .items () if query in path ]
54+ else :
55+ redirects = storage .items ()
56+
57+ aliases = []
58+ for path , info in redirects :
59+ if manual != "" :
60+ if info [2 ] != manual :
61+ continue
62+ if start and info [1 ]:
63+ if info [1 ] < start :
64+ continue
65+ if end and info [1 ]:
66+ if info [1 ] >= end :
67+ continue
68+
69+ redirect = {
70+ "path" : deroot_path (path ),
71+ "redirect-to" : deroot_path (info [0 ]),
72+ "datetime" : datetimelike_to_iso (info [1 ]),
73+ "manual" : info [2 ],
74+ }
75+ aliases .append (redirect )
76+
77+ batch = HypermediaBatch (self .request , aliases )
5178
52- newbatch = RedirectsControlPanel (self .context , self .request ).redirects ()
53- items_total = len ([item for item in newbatch ])
79+ self .request .response .setStatus (200 )
5480 self .request .response .setHeader ("Content-Type" , "application/json" )
55-
56- return redirects , items_total
81+ return [i for i in batch ], batch .items_total , batch .links
5782
5883 def reply_root_csv (self ):
84+ if not IPloneSiteRoot .providedBy (self .context ):
85+ raise NotAcceptable ("CSV reply is only available from site root." )
86+
5987 batch = RedirectsControlPanel (self .context , self .request ).redirects ()
6088 redirects = [entry for entry in batch ]
6189
@@ -80,19 +108,50 @@ def reply_root_csv(self):
80108 return content
81109
82110 def __call__ (self , expand = False ):
111+ data = self .request .form
112+
113+ query = data .get ("query" , data .get ("q" , None ))
114+ manual = data .get ("manual" , "" )
115+ start = data .get ("start" , None )
116+ end = data .get ("end" , None )
117+
118+ if query and not isinstance (query , str ):
119+ raise BadRequest ('Parameter "query" must be a string.' )
120+
121+ if manual :
122+ if is_truthy (manual ):
123+ manual = True
124+ elif is_falsy (manual ):
125+ manual = False
126+ else :
127+ raise BadRequest ('Parameter "manual" must be a boolean.' )
128+
129+ if start :
130+ try :
131+ start = DateTime (start )
132+ except DateTimeError as e :
133+ raise BadRequest (str (e ))
134+
135+ if end :
136+ try :
137+ end = DateTime (end )
138+ except DateTimeError as e :
139+ raise BadRequest (str (e ))
140+
83141 result = {"aliases" : {"@id" : f"{ self .context .absolute_url ()} /@aliases" }}
84142 if not expand :
85143 return result
86- if IPloneSiteRoot .providedBy (self .context ):
87- if self .request .getHeader ("Accept" ) == "text/csv" :
88- result ["aliases" ]["items" ] = self .reply_root_csv ()
89- return result
90- else :
91- items , items_total = self .reply_root ()
144+ if self .request .getHeader ("Accept" ) == "text/csv" :
145+ result ["aliases" ]["items" ] = self .reply_root_csv ()
146+ return result
92147 else :
93- items , items_total = self .reply_item ()
148+ items , items_total , batching = self .reply (query , manual , start , end )
149+
94150 result ["aliases" ]["items" ] = items
95151 result ["aliases" ]["items_total" ] = items_total
152+ if batching :
153+ result ["aliases" ]["links" ] = batching
154+
96155 return result
97156
98157
@@ -115,12 +174,3 @@ def render(self):
115174 return json .dumps (
116175 content , indent = 2 , sort_keys = True , separators = (", " , ": " )
117176 )
118-
119-
120- def deroot_path (path ):
121- """Remove the portal root from alias"""
122- portal = getSite ()
123- root_path = "/" .join (portal .getPhysicalPath ())
124- if not path .startswith ("/" ):
125- path = "/%s" % path
126- return path .replace (root_path , "" , 1 )
0 commit comments