1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - ApproveChanges library 4 5 If users other than the superuser are to be able to edit pages freely, they 6 must be present in a group (by default "ApprovedGroup"), and if they are to 7 be allowed to review changes, they must be present in a different group (by 8 default "PageReviewersGroup"). 9 10 @copyright: 2011, 2013 by Paul Boddie <paul@boddie.org.uk> 11 2013 by Jakub Jedelsky <jedelsky@master.cz> 12 2003-2007 MoinMoin:ThomasWaldmann, 13 2003 by Gustavo Niemeyer 14 @license: GNU GPL (v2 or later), see COPYING.txt for details. 15 """ 16 17 from MoinMoin import user 18 from MoinMoin.Page import Page 19 from MoinMoin.PageEditor import PageEditor 20 from MoinMoin.wikiutil import escape 21 import re 22 23 __version__ = "0.2" 24 25 space_pattern = re.compile("(\s+)") 26 group_member_pattern = re.compile(ur'^ \* +(?:\[\[)?(?P<member>.+?)(?:\]\])? *$', re.MULTILINE | re.UNICODE) 27 28 def have_user_specific_queue(request): 29 return getattr(request.cfg, "queued_changes_per_user", False) 30 31 def get_user_specific_queue(request): 32 return have_user_specific_queue(request) and \ 33 request.user.valid and ("%s/" % request.user.name) or \ 34 "" 35 36 def get_queued_changes_page(request): 37 return getattr(request.cfg, "queued_changes_page", "ApprovalQueue") 38 39 def get_approved_editors_group(request): 40 return getattr(request.cfg, "approved_editors_group", "ApprovedGroup") 41 42 def get_page_reviewers_group(request): 43 return getattr(request.cfg, "reviewers_group", "PageReviewersGroup") 44 45 def is_reviewer(request): 46 return request.user.valid and ( 47 has_member(request, get_page_reviewers_group(request), request.user.name) or \ 48 request.user.isSuperUser()) 49 50 def is_approved(request): 51 return request.user.valid and ( 52 user_is_approved(request, request.user.name) or \ 53 request.user.isSuperUser()) 54 55 def user_is_approved(request, username): 56 return has_member(request, get_approved_editors_group(request), username) 57 58 def is_queued_changes_page(request, pagename): 59 60 "Return whether 'pagename' is a queued changes page by testing its name." 61 62 parts = pagename.split("/") 63 return len(parts) > 1 and parts[-1] == get_queued_changes_page(request) 64 65 def get_target_page_name(page): 66 67 "Return the target page name for the given queued changes 'page'." 68 69 directive = "unapproved-user-queue" 70 body, directives = remove_directives(page.get_raw_body(), [directive]) 71 extra_parts = directives.has_key(directive) and 2 or 1 72 return "/".join(page.page_name.split("/")[:-extra_parts]) 73 74 def get_user(request, username): 75 76 "Return the user having the given 'username'." 77 78 uid = user.getUserId(request, username) 79 80 # If the user does not exist, just return None. 81 82 if not uid: 83 return None 84 85 # Otherwise, return the privileged user. 86 87 return user.User(request, uid) 88 89 def get_parent_revision_directive(request, pagename): 90 91 """ 92 Using the 'request', return a parent page revision directive for the page 93 having the given 'pagename'. 94 """ 95 96 page = Page(request, pagename) 97 return "#parent-revision %s" % page.current_rev() 98 99 def get_access_control_directive(request): 100 101 """ 102 Using the 'request', return an ACL directive for use in a page body in order 103 to prevent anyone other than reviewers from seeing it in the queue. 104 """ 105 106 return "#acl %s:read,write,delete,revert,admin All:" % ( 107 get_page_reviewers_group(request)) 108 109 def get_user_directive(request): 110 111 """ 112 Using the 'request', return a user directive for use in a page body in order 113 to record who saved the changes originally. 114 """ 115 116 if request.user.valid: 117 return "#unapproved-user %s" % request.user.name 118 else: 119 return "" 120 121 def get_user_queue_directive(request): 122 123 """ 124 Using the 'request', return a user directive for use in a page body in order 125 to record who saved the changes originally. 126 """ 127 128 if request.user.valid and have_user_specific_queue(request): 129 return "#unapproved-user-queue" 130 else: 131 return "" 132 133 def add_directives(body, directives): 134 135 "Add to the page 'body' the given 'directives'." 136 137 return "\n".join([directive for directive in directives if directive] + [body]) 138 139 def remove_directives(body, names): 140 141 """ 142 Return a new page body, copying the page 'body' provided but removing the 143 first of each directive having one of the given 'names', along with a 144 dictionary mapping directive names to values. 145 """ 146 147 new_body = [] 148 header = 1 149 found = {} 150 151 for line in body.split("\n"): 152 if header: 153 154 # Detect the end of the header. 155 156 if not line.startswith("#"): 157 header = 0 158 159 # Process the comment or directive. 160 161 else: 162 parts = space_pattern.split(line[1:]) 163 164 # Identify any directive. 165 166 directive = parts[0] 167 168 # Obtain the value of the first instance of any directive, 169 # stripping any initial space. 170 171 if directive in names and not found.has_key(directive): 172 found[directive] = "".join(parts[2:]) 173 continue 174 175 new_body.append(line) 176 177 return "\n".join(new_body), found 178 179 def add_to_group_page(request, username, groupname): 180 181 """ 182 Using the 'request', add 'username' to 'groupname', changing the group page. 183 This is not the same as adding a member to the group, but it will have the 184 same effect when the group is rescanned. 185 """ 186 187 _ = request.getText 188 189 page = PageEditor(request, groupname) 190 body = page.get_raw_body() 191 match = None 192 193 # Find the last matching span. 194 195 for match in group_member_pattern.finditer(body): 196 start, end = match.span() 197 198 # Add a group member to the body. 199 200 entry = ("\n * %s" % username) 201 202 if match: 203 body = body[:end] + entry + body[end:] 204 else: 205 body += entry 206 207 page.saveText(body, 0, comment=_("Added %s to the approved editors group.") % username) 208 209 # Utility classes and associated functions. 210 # NOTE: These are now present in MoinSupport which should be used in future. 211 212 class Form: 213 214 """ 215 A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x 216 environment. 217 """ 218 219 def __init__(self, form): 220 self.form = form 221 222 def get(self, name, default=None): 223 values = self.form.getlist(name) 224 if not values: 225 return default 226 else: 227 return values 228 229 def __getitem__(self, name): 230 return self.form.getlist(name) 231 232 class ActionSupport: 233 234 """ 235 Work around disruptive MoinMoin changes in 1.9, and also provide useful 236 convenience methods. 237 """ 238 239 def get_form(self): 240 return get_form(self.request) 241 242 def get_form(request): 243 244 "Work around disruptive MoinMoin changes in 1.9." 245 246 if hasattr(request, "values"): 247 return Form(request.values) 248 else: 249 return request.form 250 251 def escattr(s): 252 return escape(s, 1) 253 254 # More Moin 1.9 compatibility functions. 255 256 def has_member(request, groupname, username): 257 if hasattr(request.dicts, "has_member"): 258 return request.dicts.has_member(groupname, username) 259 else: 260 return username in request.groups.get(groupname, []) 261 262 # vim: tabstop=4 expandtab shiftwidth=4