-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathOutlineSupport.py
1080 lines (952 loc) · 41.4 KB
/
OutlineSupport.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# zwiki page hierarchy functionality
# based on original code by Ken Manheimer
#
# The PageOutlineSupport mixin manages the wiki's page hierarchy, making use
# of the more generic Outline class. It is broken into several smaller
# mixins to help keep things organized.
#
# glossary:
# primary parent: our designated parent page, or the first if more than one
# siblings: pages which share a parent with us
# context: our parents and their parents up to the top, and maybe our siblings
# children: pages for which we are a parent
# offspring: all our children and their children to the bottom
# subtopics: end-user name for offspring or children
# nesting: nested list structure representing a piece of hierarchy
# outline: an Outline object, encapsulatng a nesting and other info
# wiki outline, page hierarchy: the hierarchy of pages within a wiki
#
# todo:
# evaluate experimental separate outline structure
# -cache wiki nesting structure on folder
# -encapsulate it as an Outline object
# -make code work with an Outline object
# -move generic operations there
# -remove getWikiParentInfo
# -add mutators to help with syncing
# -add
# -delete
# -replace
# -reparent
# -add tests and keep it synced in parallel with parents attributes
# -keep it synced during rename
# -keep it synced during create
# -keep it synced during delete
# -keep it synced during reparent
# -keep it synced during ZMI operations
# -check mutators for persistence compatibility ?
# -refactor PageOutlineSupport
# try dropping parents property/index/metadata
# upgrade process
# code cleanup/renaming
# simplify Outline implementation
# remove Outline/nesting split ?
# refactor/simplify OutlineRendering
from __future__ import nested_scopes
import re
from types import *
from urllib import quote, unquote
from AccessControl import ClassSecurityInfo
import Acquisition
from App.Common import absattr
from Globals import InitializeClass, REPLACEABLE
import Persistence
from OFS.SimpleItem import SimpleItem
import Permissions
from Utils import flatten, BLATHER, safe_hasattr, base_hasattr, isunicode
from Defaults import PAGE_METATYPE
from Regexps import bracketedexpr
import Outline
from i18n import _
def deepappend(nesting, page):
"""
Append a page to the very bottom of a nesting.
"""
if type(nesting[-1]) is type([]):
nesting[-1] = deepappend(nesting[-1], page)
else:
if len(nesting) is 1: nesting.append(page)
else: nesting[-1] = [nesting[-1],page]
return nesting
class PersistentOutline(Outline.Outline, SimpleItem):
"""
I am a persistent version of Outline.
XXX do my mutators need to do more of this sort of thing ?:
# do an attr assignment in case we ever cache this as a persistent object
if childmap.has_key(parent): pchildren = childmap[parent]
else: pchildren = []
if name not in pchildren: pchildren.append(name)
childmap[parent] = pchildren
or:
self._p_changed = 1...
"""
meta_type = 'Zwiki Outline Cache'
# always create with this id:
id = 'outline'
# these objects get auto-generated by ZWikiPage's manage_afterAdd
# causing problems for the ATCT migrator in plone 2.1rc<=3. To avoid
# it, allow them to be overwritten without complaint. Maybe drop this
# later though, the outline cache can contain valuable ordering
# information ?
__replaceable__ = REPLACEABLE
InitializeClass(PersistentOutline)
class ParentsProperty:
"""
I provide the parents property, the old way to store page hierarchy.
For now we keep this property up to date, in sync with the wiki
outline, but we'll try removing it completely at some point. (?)
Although it's a simple and relatively robust design, it doesn't scale
so well: generating the hierarchy structure from the parents
properties each time is expensive, and although modifying
decentralized parents properties seems better than a shared outline
object from the perspective of avoiding conflict errors, you still
need to update the central catalog so there may be no great advantage.
"""
security = ClassSecurityInfo()
parents = []
_properties=(
{'id':'parents', 'type': 'lines', 'mode': 'w'},
)
def ensureParentsPropertyIsList(self):
"""
Ensure our parents property is a list, returning true if changed.
Zope lines properties switched from tuple to list a while back,
and lingering tuples cause ongoing breakage. Called by upgrade,
ensureValidParents and accessors for maximum robustness.
"""
if type(self.parents) != ListType:
BLATHER("converting %s's parents property to a list" % self.pageName())
self.setParents(self.parents)
return True
else:
return False
# I'd call this parents, but too many upgrade hassles ?
def getParents(self):
"""
Robust accessor returning a copy of our parents list.
"""
# to ease upgrades, ensure unicode
return [self.tounicode(p) for p in list(self.parents)]
def setParents(self,parents):
parents = list(parents)
parents.sort()
self.parents = parents
def addParent(self,parent):
if parent:
# we sometimes start page names with space as a subtopic
# ordering hack..
#parent = string.strip(parent)
if parent and not parent in self.parents:
self.ensureParentsPropertyIsList()
self.parents.append(parent)
self._p_changed = 1
def removeParent(self,parent):
self.ensureParentsPropertyIsList()
try:
self.parents.remove(parent)
self._p_changed = 1
except ValueError:
BLATHER("failed to remove %s from %s's parents (%s)" \
% (parent,self.getId(),self.parents))
def ensureValidParents(self):
"""
Ensure that this page's parents are all valid, and reindex if needed.
"""
parents = self.getParents()
# convert to exact page names, filtering out any which don't exist
cleanedupparents = map(lambda x:absattr(x.Title),
filter(lambda x:x,
map(lambda x:self.pageWithName(x),
parents)))
# make sure we're not parented under ourself
if self.pageName() in cleanedupparents:
cleanedupparents.remove(self.pageName())
# sort
cleanedupparents.sort()
# if changed, save and reindex
if cleanedupparents != parents:
BLATHER("adjusting %s's parents from %s to %s" %
(self.pageName(), parents, cleanedupparents))
self.setParents(cleanedupparents)
self.index_object() #XXX only need to update parents index & metadata
InitializeClass(ParentsProperty)
class ShowSubtopicsProperty:
"""
I determine when to display subtopics on a page.
"""
security = ClassSecurityInfo()
def subtopicsEnabled(self,**kw):
"""
Decide in a complicated way if this page should display it's subtopics.
First, if the folder has a show_subtopics property (can acquire)
and it's false, we will never show subtopics. Otherwise look for
a show_subtopics property
- in REQUEST
- on the current page (don't acquire)
- on our primary ancestor pages, all the way to the top,
and return the first one we find. Otherwise return true.
"""
prop = 'show_subtopics'
# disabled by a folder property ?
if not getattr(self.folder(),prop,1): return 0
# specified by a request var ?
if safe_hasattr(self,'REQUEST') and self.REQUEST.has_key(prop):
return self.REQUEST.get(prop) and 1
# specified on this page ?
if base_hasattr(self,prop): return getattr(self,prop) and 1
# specified on a parent or elder ancestor ?
for a in self.ancestorsAsList2():
p = self.pageWithName(a)
if base_hasattr(p,prop): return getattr(p,prop) and 1
# not specified, use default
return 1
def subtopicsPropertyStatus(self):
"""
Get the status of the show_subtopics property on this page.
no property: -1 ("default")
true property: 1 ("always")
false property: 0 ("never")
"""
if not safe_hasattr(self.aq_base,'show_subtopics'): return -1
else: return self.show_subtopics and 1
def setSubtopicsPropertyStatus(self,status,REQUEST=None):
"""
Set, clear or remove this page's show_subtopics property.
Same values as getSubtopicsStatus.
"""
props = map(lambda x:x['id'], self._properties)
if status == -1:
if 'show_subtopics' in props:
self.manage_delProperties(ids=['show_subtopics'],
REQUEST=REQUEST)
elif status:
if not 'show_subtopics' in props:
self.manage_addProperty('show_subtopics',1,'boolean',
REQUEST=REQUEST)
else:
self.manage_changeProperties(show_subtopics=1,
REQUEST=REQUEST)
else:
if not 'show_subtopics' in props:
self.manage_addProperty('show_subtopics',0,'boolean',
REQUEST=REQUEST)
else:
self.manage_changeProperties(show_subtopics=0,
REQUEST=REQUEST)
InitializeClass(ShowSubtopicsProperty)
class OutlineManager:
"""
I manage and query a cached outline object for the wiki.
When first accessed I create a PersistentOutline based on the parents
properties and store it in the wiki folder. This can be queried more
efficiently.
old:
KM: We could make this a persistent object and minimize recomputes.
Put it in a standard place in the wiki's folder, or have the pages in
a folder share an instance, but use a single persistent one which need
not recompute all the relationship maps every time - just needs to
compare all pages parents settings with the last noticed parents
settings, and adjust the children, roots, and parents maps just for
those that changed.
"""
security = ClassSecurityInfo()
# mutators
def wikiOutline(self):
"""
Get the outline cache which holds details of the wiki's page hierarchy.
"""
self.ensureWikiOutline()
return self.folder().outline
def ensureWikiOutline(self):
"""
Ensure this wiki has an outline cache, for fast page
hierarchy. This gets called before any use of the outline
cache, so that it is always present and current. If it's
missing or if it's one of the early types (which get lost
during a folder rename or when moving pages to a new folder)
or it it's a pre-0.61 non-unicode outline, rebuild it.
"""
o = safe_hasattr(self.folder().aq_base,'outline') and self.folder().outline or None
if not o:
self.rebuildWikiOutline()
security.declareProtected(Permissions.View, 'updateWikiOutline')
def updateWikiOutline(self):
"""
Regenerate the wiki folder's cached outline object.
The wiki's outline object (a PersistentOutline) is a
representation of the page hierarchy, containing the same
information as in the pages' parents properties but in a form
easier to query. This method either generates a new one from the
parents properties, or updates an old one trying to preserve the
order of subtopics. Complications.
This checks and corrects any invalid parents information. It also
loads all page objects from the ZODB, which is probably ok as this
is not done frequently.
"""
BLATHER('updating outline data for wiki',self.folder().getId())
oldchildmap = {}
# backwards compatibility
# there have been three kinds of outline cache: folder attribute,
# non-SimpleItem-based PersistentOutline object, and
# SimpleItem-based PersistentOutline object
# a pre-0.39 outline is just an attribute, delete (but save childmap)
if (safe_hasattr(self.folder().aq_base,'outline') and
not 'outline' in self.folder().objectIds()):
oldchildmap = self.folder().outline.childmap()
del self.folder().outline
# if there's no outline object, make one
if not safe_hasattr(self.folder().aq_base,'outline'):
self.folder()._setObject('outline', PersistentOutline())
self.folder().outline.setChildmap(oldchildmap)
# regenerate the parentmap
parentmap = {}
for p in self.pageObjects():
p.ensureValidParents() # poor caching
parentmap[p.pageName()] = p.getParents()
self.folder().outline.setParentmap(parentmap)
# update the childmap (without losing subtopics order) and nesting
self.folder().outline.update()
# easier-to-type alias
updatecontents = updateWikiOutline
security.declareProtected(Permissions.View, 'rebuildWikiOutline')
def rebuildWikiOutline(self):
"""Regenerate the wiki folder's cached outline object, throwing away
the old data. Will reset subtopic order to alphabetic.
"""
BLATHER('purging outline data for wiki',self.folder().getId())
if 'outline' in self.folder().objectIds(): self.folder()._delObject('outline')
self.updateWikiOutline()
security.declareProtected(Permissions.Reparent, 'reparent')
def reparent(self, parents=[], REQUEST=None, pagename=None):
"""
Move this page under the named parent pages in the wiki outline.
parent page names may be passed in several ways:
- in the parents argument (a list or string of page names)
- in the pagename argument (a single name)
(this last is to support the page management form).
Page names may be ids, or fuzzy, even partial. Any which do not
resolve to an existing page or are duplicates will be ignored.
"""
if not self.checkSufficientId(REQUEST):
return self.denied(
_("Sorry, this wiki doesn't allow anonymous reparenting. Please configure a username in options first."))
oldparents = self.getParents()
# clean the arguments carefully to avoid parenting anomalies
# page mgmt form must use pagename field:
pagename = pagename and self.tounicode(pagename)
if pagename:
parents = [pagename]
# or parents might be a string
elif type(parents) != ListType:
parents = [parents]
# empty strings are common, remove before calling pageWithFuzzyName
parents = filter(lambda x:x, parents)
# to unicode
parents = map(self.tounicode, parents)
# look up the page (brain) corresponding to each (fuzzy) parent name
parents = map(lambda x:self.pageWithFuzzyName(x,allow_partial=1),
parents)
# strip out Nones (pages not found)
parents = filter(lambda x:x, parents)
# convert back to proper page names
parents = map(lambda x:absattr(x.Title), parents)
# remove any duplicates
uniqueparents = []
for p in parents:
if not p in uniqueparents: uniqueparents.append(p)
# finally - update our parents property, the outline cache, and catalog
self.setParents(uniqueparents)
self.wikiOutline().reparent(self.pageName(),uniqueparents)
self.index_object()
# send mail if appropriate
self.sendMailToEditSubscribers(
'%s was reparented from %s to %s.' % (
self.pageName(), oldparents, uniqueparents),
REQUEST=REQUEST,
subject='(reparented)')
if REQUEST is not None: REQUEST.RESPONSE.redirect(REQUEST['URL1'])
security.declareProtected(Permissions.Reparent, 'reorder')
def reorder(self, child=None, REQUEST=None):
"""
Move the named child page one place to the left among this page's children.
If a child is not specified, sort all children alphabetically.
Ordering is only stored in the wiki outline cache, and will be
lost if the outline is completely regenerated.
When invoked from the web, redirects to /backlinks.
"""
if child and not child in self.childrenAsList(): return
self.wikiOutline().reorder(self.pageName(),child)
if REQUEST is not None: REQUEST.RESPONSE.redirect(REQUEST['URL1']+'/backlinks')
# queries
def primaryParentName(self):
"""
Get the name of this page's primary (alphabetically first) parent.
"""
return self.wikiOutline().firstParent(self.pageName())
def primaryParent(self):
"""
Return this page's primary parent page.
"""
p = self.primaryParentName()
if p: return self.pageWithName(p)
else: return None
def primaryParentUrl(self):
"""
Get the URL of this page's primary parent.
"""
p = self.primaryParent()
if p: return p.pageUrl()
else: return None
def upUrl(self):
"""
Get the URL of the primary parent if any, otherwise the default
page, otherwise the first page in the folder
"""
return self.primaryParentUrl() or self.defaultPageUrl()
security.declareProtected(Permissions.View, 'firstPage')
def firstPage(self):
"""
Get the name of the first page in the hierarchy.
"""
return self.wikiOutline().first()
security.declareProtected(Permissions.View, 'firstPageUrl')
def firstPageUrl(self):
"""
Get the URL of the first page in the hierarchy.
"""
p = self.pageWithName(self.firstPage())
if p: return p.pageUrl()
else: return None
security.declareProtected(Permissions.View, 'lastPage')
def lastPage(self):
"""
Get the name of the last page in the hierarchy.
"""
return self.wikiOutline().last()
security.declareProtected(Permissions.View, 'lastPageUrl')
def lastPageUrl(self):
"""
Get the URL of the last page in the hierarchy.
"""
p = self.pageWithName(self.lastPage())
if p: return p.pageUrl()
else: return None
security.declareProtected(Permissions.View, 'nextPage')
def nextPage(self):
"""
Get the name of the next page in the hierarchy.
XXX nextPageName ?
"""
return self.wikiOutline().next(self.pageName())
security.declareProtected(Permissions.View, 'nextPageUrl')
def nextPageUrl(self):
"""
Get the URL of the next page in the hierarchy.
"""
p = self.pageWithName(self.nextPage())
if p: return p.pageUrl()
else: return None
security.declareProtected(Permissions.View, 'previousPage')
def previousPage(self):
"""
Get the name of the previous page in the hierarchy.
"""
return self.wikiOutline().previous(self.pageName())
security.declareProtected(Permissions.View, 'previousPageUrl')
def previousPageUrl(self):
"""
Get the URL of the previous page in the hierarchy.
"""
p = self.pageWithName(self.previousPage())
if p: return p.pageUrl()
else: return None
security.declareProtected(Permissions.View, 'ancestorsAsList')
def ancestorsAsList(self, REQUEST=None):
"""
Return the names of all my ancestor pages as a flat list, eldest first.
If there are multiple lines of ancestry, return only the first.
"""
try: return flatten(self.ancestorsNesting())[:-1]
except: return [] # XXX temp, get rid of
security.declareProtected(Permissions.View, 'ancestorsAsList2')
def ancestorsAsList2(self, REQUEST=None):
"""
A more robust variant of ancestorsAsList (but, in reverse
order). This is a temporary workaround to help
subtopicsEnabled because Outline.ancestorsNesting returns
nothing in the case of One<->Two circular parents (issue
#965).
"""
ps = []
p = self.primaryParent()
while p and not p.pageName() in ps and not p.pageName()==self.pageName():
ps.append(p.pageName())
p = p.primaryParent()
return ps
security.declareProtected(Permissions.View, 'siblingsAsList')
def siblingsAsList(self,include_me=False,sort_alpha=True):
"""
Return the names of other pages sharing my first parent.
Siblings by my other parents are ignored.
Optionally include the current page in the list.
Optionally suppress alphabetical sorting of result.
"""
return self.wikiOutline().siblings(self.pageName(), \
include_me=include_me,sort_alpha=sort_alpha)
security.declareProtected(Permissions.View, 'childrenAsList')
def childrenAsList(self):
"""
Return the list of names of my immediate children, if any.
"""
return self.wikiOutline().children(self.pageName())
security.declareProtected(Permissions.View, 'childrenIdsAsList')
def childrenIdsAsList(self, REQUEST=None):
"""
Return all my children's page ids as a flat list.
"""
return map(lambda x:absattr(self.pageWithNameOrId(x).id),
self.childrenAsList())
security.declareProtected(Permissions.View, 'offspringAsList')
def offspringAsList(self, REQUEST=None):
"""
Return my offsprings' page names as a flat list, excluding my name.
"""
list = flatten(self.offspringNesting())
list.remove(self.pageName())
return list
security.declareProtected(Permissions.View, 'offspringIdsAsList')
def offspringIdsAsList(self, REQUEST=None):
"""
Return my offsprings' page ids as a flat list.
"""
return map(lambda x:absattr(self.pageWithNameOrId(x).id),
self.offspringAsList())
# queries returning nestings (lists-of-lists) - a low-level
# representation of all or part of the wiki outline, which can be
# processed by certain render methods. See also nestingAsRenderList's
# docstring.
def ancestorsNesting(self):
"""
Return a nesting representing this page's ancestors.
"""
return self.wikiOutline().ancestors(self.pageName())
def ancestorsAndSiblingsNesting(self):
"""
Return a nesting representing this page's ancestors and siblings.
"""
return self.wikiOutline().ancestorsAndSiblings(self.pageName())
def ancestorsAndChildrenNesting(self):
"""
Return a nesting representing this page's ancestors and children.
"""
return self.wikiOutline().ancestorsAndChildren(self.pageName())
def childrenNesting(self):
"""
Return a nesting representing this page's children.
"""
return self.wikiOutline().children(self.pageName())
def offspringNesting(self,depth=None):
"""
Return a nesting representing this page's descendants.
"""
return self.wikiOutline().offspring([self.pageName()],depth=depth)
InitializeClass(OutlineManager)
class OutlineRendering:
"""
I present various parts of the wiki outline as HTML.
Some code cleanup here would be nice.
"""
security = ClassSecurityInfo()
security.declareProtected(Permissions.View, 'contents')
def contents(self, REQUEST=None, here=None):
"""
Show the entire page hierarchy, using the contentspage template.
Includes all the branches in the wiki - from the possibly multiple
roots - and all singletons, ie those without parents or children.
The page named by here, or the current page, will be highlighted
with "you are here".
"""
nesting = self.wikiOutline().nesting()
singletons = []
combos = []
baseurl = self.wiki_url()
for i in nesting:
if type(i) == StringType:
#try:
# # XXX poor caching ?
# linktitle = self.folder()[i].linkTitle()
#except:
linktitle = ''
singletons.append(\
'<a href="%s/%s" name="%s" title="%s">%s</a>'\
% (baseurl, self.canonicalIdFrom(i), quote(i),
linktitle,
self.formatWikiname(i)))
else:
combos.append(i)
# this view is often invoked via a default page, not the current one,
# to reduce bot traffic (see contentsUrl). Try to figure out the
# current page for "you are here".
here = unquote(here or self.referringPageName() or '')
return self.contentspage(
self.renderNesting(combos,here=here),
singletons,
REQUEST=REQUEST)
def referringPageId(self,REQUEST=None):
"""
If the referrer was a page in this wiki, return its id, or None.
"""
if not REQUEST: REQUEST = self.REQUEST
if not REQUEST: return None
referrer = REQUEST.get('HTTP_REFERER', None)
if not referrer: return None
m = re.match(r'^'+re.escape(self.wikiUrl())+r'/?([^?#/]*)', referrer)
if m: return m.group(1)
else: return None
def referringPageName(self,REQUEST=None):
"""
If the referrer was a page in this wiki, return its name, or None.
"""
id = self.referringPageId(REQUEST=REQUEST)
if id:
p = self.pageWithId(id)
if p: return p.pageName()
return None
security.declareProtected(Permissions.View, 'context')
def context(self, REQUEST=None, with_siblings=0, enlarge_current=0):
"""
Return HTML showing this page's ancestors and siblings.
XXX how can we use a page template for this ? macro ?
"""
# get the nesting structure
here = self.pageName()
if with_siblings:
nesting = self.ancestorsAndSiblingsNesting()
else:
# why does the above require a nesting and not this one ?
# nesting = self.get_ancestors()
#nesting = WikiNesting(self.folder()).get_ancestors(here,self)
nesting = self.ancestorsNesting()
# XXX looks like cruft
if (len(nesting) == 0 or
(len(nesting) == 1 and len(nesting[0]) == 1)) and not enlarge_current:
return " "
# format and link it
# backwards compatibility: in case of an old editform template
# which shows context, include the new page name at the bottom (unlinked)
if REQUEST and REQUEST.has_key('page') and self.tounicode(REQUEST['page']) != here:
here = self.tounicode(REQUEST['page'])
nesting = deepappend(nesting, here)
suppress_hyperlink=1
else:
suppress_hyperlink=0
hierarchy = self.renderNesting(
nesting, here,
enlarge_current=enlarge_current,
suppress_hyperlink=suppress_hyperlink)
# special case: if parent seems to be missing, reset
# XXX will not match ? clean up
if hierarchy == '<ul>\n</ul>':
self.setParents([])
self.index_object()
hierarchy = self.renderNesting(
nesting, here,
enlarge_current=enlarge_current,
suppress_hyperlink=suppress_hyperlink)
# if a SiteMap page exists, point the contents link there
contentsurl = self.contentsUrl()
contentslink = \
'<a href="%s" title="show wiki contents">%s contents</a>' \
% (contentsurl, self.folder().title)
#with a contents link
#return '<small><ul>%s\n%s\n</ul></small>' % (contentslink,hierarchy)
#XXX temp
if self.usingPloneSkin():
return '%s\n' % (hierarchy)
else:
return '<small>%s\n</small>' % (hierarchy)
security.declareProtected(Permissions.View, 'contextX')
def contextX(self, REQUEST=None, with_siblings=0):
"""
Return this page's context information as a skin-renderable structure.
Like context, but allows a skin template to control the rendering.
See nestingAsRenderList.
"""
if with_siblings:
nesting = self.ancestorsAndChildrenNesting()
else:
nesting = self.ancestorsNesting()
# backwards compatibility: in case of an old editform template
# which shows context, include the new page name at the bottom (unlinked)
here = self.pageName()
if REQUEST.has_key('page') and self.tounicode(REQUEST['page']) is not here:
here = self.tounicode(REQUEST['page'])
nesting = deepappend(nesting, here)
suppress_hyperlink=1
else:
suppress_hyperlink=0
# convert to a render list
renderlist = self.nestingAsRenderList(nesting, here)
# special case: if parent seems to be missing, reset XXX
if len(renderlist) == 2 :
self.setParents([])
self.index_object()
renderlist = self.nestingAsRenderList(nesting, here)
return {'contentsUrl':self.contentsUrl(), 'hierarchy':renderlist}
security.declareProtected(Permissions.View, 'children')
def children(self):
"""
Return HTML showing my immediate children, if any.
"""
children = self.childrenAsList()
if children:
return self.renderNesting(children)
else:
return ''
security.declareProtected(Permissions.View, 'offspring')
def offspring(self, REQUEST=None, info=None, exclude_self=0, depth=None):
"""
Return HTML displaying all my offspring.
"""
here = self.pageName()
return self.renderNesting(
self.offspringNesting(depth=depth),here,suppress_current=exclude_self)
security.declareProtected(Permissions.View, 'subtopics')
def subtopics(self, REQUEST=None, **kw):
"""
Render my subtopics as a HTML fragment, using a template.
Allows the template to be selected by subtopics_style folder
property. If there is no property or the specified template
does not exist, uses the built-in subtopics_outline.
Some overlap with the show_subtopics property.
Pass any arguments, like max depth, through to the template.
"""
DEFAULTSTYLE = 'outline'
style = getattr(self,'subtopics_style',None)
if not (style and self.hasSkinTemplate('subtopics_'+style)):
style = DEFAULTSTYLE
return self.getSkinTemplate('subtopics_'+style)(self,REQUEST,**kw)
security.declareProtected(Permissions.View, 'navlinks')
def navlinks(self):
"""
Return HTML for my next/previous/up links.
XXX not used ?
"""
none = 'none'
t = ''
prev, next = self.previousPage(), self.nextPage()
if prev: prev = self.renderLink('['+prev+']',access_key='P')
else: prev = none
if next: next = self.renderLink('['+next+']',access_key='N')
else: next = none
t += '<span class="accesskey">n</span>ext: %s <span class="accesskey">p</span>revious: %s' \
% (next,prev) # Info style!
t += ' <span class="accesskey">u</span>p: %s' \
% ((self.parents and self.renderLink('['+self.primaryParentName()+']',
access_key='U'))
or none)
#if contents:
# contentsurl = self.contentsUrl()
# contentslink = \
# '<a href="%s" title="show wiki contents">contents</a>'\
# % (contentsurl)
# t += ' contents: %s' % contentslink
return t
security.declareProtected(Permissions.View, 'renderNesting')
def renderNesting(self, nesting, here=None, enlarge_current=0,
suppress_hyperlink=0, suppress_current=0,
did=None, got=None, indent=''):
"""
Format a nesting structure as HTML unordered lists of wiki links.
- nesting is the nesting to be formatted
- here is the name of the page to highlight with "you are here", if any
- if enlarge_current is true, here will be enlarged instead
- if suppress_hyperlink is true, here will not be linked
(backwards compatibility for old editforms)
- if suppress_current is true, here will not be shown at all
- did, got & indent are for recursion, callers should not use
Do we need all this complicated code, can't we do it in the skin
template ? I think so, except for two issues: for very large
nestings the python version might be perceptibly quicker; and,
it's easier to recurse with python. See also nestingAsRenderList.
"""
# XXX oh yeah.. kludgorama
def renderContentsLink(page):
"""Render a link to page, suitable for contents or context."""
# quicker than renderLinkToPage, since we know it exists:
wikiurl = self.wiki_url()
def quicklink(page):
id = self.canonicalIdFrom(page)
return u'<a href="%s/%s" name="%s">%s</a>' \
% (wikiurl,id,id,self.formatWikiname(page))
if here and page == here:
if enlarge_current:
# just assume we are in the page header, and link to
# backlinks as well as enlarging
#return '<big><big><big><big><strong>%s</strong></big></big></big></big>' % \
return u'<h1 style="display:inline;">%s</h1>' % \
('<a href="%s/%s/backlinks" title="%s" name="%s">%s</a>' % \
(wikiurl,
self.canonicalIdFrom(page),
_("which pages link to this one ?"),
page,
self.formatWikiname(page)))
else:
# just highlight "here"
return u'%s <span id="youarehere"><-- %s.</span>' % \
((suppress_hyperlink and page) # another special case
or quicklink(page),
_("You are here"))
else:
#return self.renderLinkToPage(page,name=page)
return quicklink(page)
#XXX cleanup
if suppress_current and nesting[0] == here: # a single childless page
return ''
if did is None: did = []
if got is None:
got = ['<ul class="outline expandable">']
recursing = 0
else:
recursing = 1
for n in nesting:
if type(n) == ListType:
if not (n[0]==here and suppress_current): #XXX temp
got.append('%s <li>%s' % (indent,renderContentsLink(n[0])))
if len(n) > 1:
if not (n[0]==here and suppress_current): #XXX temp
got.append('<ul class="outline expandable">')
for i in n[1:]:
if type(i) == ListType:
got = self.renderNesting(
[i],
here=here,
enlarge_current=enlarge_current,
suppress_hyperlink=suppress_hyperlink,
suppress_current=suppress_current,
did=did,
got=got,
indent=indent+' ')
else:
got.append('%s <li>%s</li>' % (indent,renderContentsLink(i)))
if not (n[0]==here and suppress_current): #XXX temp
got.append("</ul>")
else:
got[-1] += ' ...' # a parent whose children were omitted
if not (n[0]==here and suppress_current):
got.append('%s </li>' % indent)
else:
got.append('%s <li>%s</li>' % (indent,renderContentsLink(n)))
if recursing:
return got
else:
got.append("</ul>")
return "\n".join(got)
security.declareProtected(Permissions.View, 'nestingAsRenderList')
def nestingAsRenderList(self, nesting, here=None, suppress_current=0,
did=None, got=None, indent=''):
"""
Convert a nesting structure to a skin-renderable intermediate form.
This was renderNestingX in Dan McMullen's skin refactoring
(http://zwiki.org/WikipageX). The intermediate form is a flat list
which a skin template can render without needing recursion.
Example: a chunk of page hierarchy like::
AnnoyingQuote
AnnoyingQuoteArchive
AngryDenial
AnnoyingQuoteDiscussion
which is stored as a nesting like::
[['AnnoyingQuote', ['AnnoyingQuoteArchive', 'AngryDenial'], 'AnnoyingQuoteDiscussion']]
gets converted to a render list like::
[
{'type': '+'},
{'href': 'zwikib/AnnoyingQuote', 'type': '=', 'page': 'AnnoyingQuote', 'name': 'AnnoyingQuote'},
{'type': '+'},
{'href': 'zwikib/AnnoyingQuoteArchive', 'type': '=', 'page': 'AnnoyingQuoteArchive', 'name': 'AnnoyingQuoteArchive'},
{'type': '+'},
{'href': 'zwikib/AngryDenial', 'type': '=', 'page': 'AngryDenial', 'name': 'AngryDenial'},
{'type': '-'},
{'href': 'zwikib/AnnoyingQuoteDiscussion', 'type': '=', 'page': 'AnnoyingQuoteDiscussion', 'name': 'AnnoyingQuoteDiscussion'},
{'type': '-'},
{'type': '-'}
]
which can be rendered by some TAL like::
<span tal:define="ctx python: here.contextX(request)">
<a href="contentsUrl" title="show wiki contents" accesskey="c"
tal:attributes="href ctx/contentsUrl"
tal:content="python: container.title + ' contents'">contents</a>
<span tal:repeat="ii ctx/hierarchy" tal:omit-tag="">
<span tal:condition="python: '+' in ii['type']"
tal:replace="structure string:<ul style="margin: 0;">">ul</span>