1 # -*- coding: iso-8859-1 -*- 2 """ 3 MoinMoin - ApproveChanges 4 5 Permit the approval of an edited page queued by the page editor when an 6 unprivileged user attempted to save a page. Since queued changes are placed 7 in a subpage, this action just moves the queued page content into the 8 existing page when approving the changes. 9 10 @copyright: 2011, 2013 Paul Boddie <paul@boddie.org.uk> 11 @copyright: 2006 MoinMoin:ThomasWaldmann 12 @license: GNU GPL (v2 or later), see COPYING.txt for details. 13 """ 14 15 Dependencies = ['pages'] 16 17 from MoinMoin.action import ActionBase 18 from MoinMoin.Page import Page 19 from MoinMoin.PageEditor import PageEditor, conflict_markers 20 from MoinMoin.user import User 21 from MoinMoin.util import diff3 22 from MoinSupport import getPagesForSearch 23 from ApproveChangesSupport import * 24 25 # Action class and supporting functions. 26 27 class ApproveChanges(ActionBase): 28 29 "An action which approves a queued page version." 30 31 queued_content_directives = ["acl", "parent-revision", "unapproved-user", "unapproved-user-queue"] 32 33 def __init__(self, pagename, request): 34 ActionBase.__init__(self, pagename, request) 35 _ = self._ 36 self.form_trigger = "execute" 37 self.form_trigger_label = _("Execute the indicated changes") 38 39 def get_revision(self): 40 request = self.request 41 form = get_form(request) 42 43 # Get the revision or None. 44 45 rev = form.get("rev") 46 if rev is None: 47 return self.page.current_rev() 48 else: 49 return int(rev[0]) 50 51 def get_body(self, rev): 52 request = self.request 53 page = Page(request, self.page.page_name, rev=rev) 54 return page.get_raw_body() 55 56 def get_queued_edits(self, request, username): 57 return getPagesForSearch("title:regex:%s/%s$" % (username, get_queued_changes_page(request)), request) 58 59 def get_form_html(self, buttons_html): 60 _ = self._ 61 request = self.request 62 fmt = request.formatter 63 form = get_form(request) 64 65 if not is_queued_changes_page(request, self.pagename): 66 return fmt.paragraph(1) + fmt.text(_("This page does not show queued changes.")) + fmt.paragraph(0) 67 68 rev = self.get_revision() 69 70 # Get information about the queued changes. 71 72 body = self.get_body(rev) 73 _body, directives = remove_directives(body, self.queued_content_directives) 74 75 # Get the target page's parent revision for the queued changes. 76 77 target_page_name = get_target_page_name(self.page) 78 target_page = PageEditor(request, target_page_name) 79 80 current_rev = target_page.current_rev() 81 parent_rev = int(directives.get("parent-revision", current_rev)) 82 83 # Get the user who submitted the changes. 84 85 username = directives.get("unapproved-user") 86 87 # Get other edit details, if requested. 88 89 if form.get("show-edits") and username: 90 queued_edits = self.get_queued_edits(request, username) 91 else: 92 queued_edits = [] 93 94 d = { 95 "approval_label" : escape(_("Make %s an approved user") % username), 96 "approve_edit_label" : escape(_("Approve the displayed page version")), 97 "block_label" : escape(_("Block user %s by disabling their account") % username), 98 "buttons_html" : buttons_html, 99 "discard_edit_label" : escape(_("Discard the displayed page version")), 100 "editaction_label" : escape(_("Edit action")), 101 "nothing_label" : escape(_("Take no action for now")), 102 "remove_all_label" : escape(_("Remove all queued edits by this user")), 103 "rev" : escattr(rev), 104 "notice" : escape(_("The affected page has been edited since the queued changes were made.")), 105 "show_edits_label" : escattr(_("Show all queued edits by this user")), 106 "showing_edits_label" : escape(_("Queued edits by this user")), 107 "useraction_label" : escape(_("User action")), 108 } 109 110 # Make sure that the radio buttons are selected. 111 112 for value in "nothing", "approve", "block": 113 d["useraction_%s" % value] = form.get("useraction", ["nothing"])[0] == value and "checked='checked'" or "" 114 115 for value in "approve", "discard", "remove_all": 116 d["editaction_%s" % value] = form.get("editaction", ["approve"])[0] == value and "checked='checked'" or "" 117 118 # Prepare the output HTML. 119 120 html = ''' 121 <table>''' 122 123 if parent_rev != current_rev: 124 html += ''' 125 <tr> 126 <td colspan="2"><strong>%(notice)s</strong></td> 127 </tr>''' % d 128 129 # Actions to be taken with the edit. 130 131 html += ''' 132 <tr> 133 <td class="label" rowspan="3"><label>%(editaction_label)s</label></td> 134 <td><input name="editaction" type="radio" value="approve" %(editaction_approve)s /> <label>%(approve_edit_label)s</label></td> 135 </tr> 136 <tr> 137 <td><input name="editaction" type="radio" value="discard" %(editaction_discard)s /> <label>%(discard_edit_label)s</label></td> 138 </tr> 139 <tr> 140 <td><input name="editaction" type="radio" value="remove_all" %(editaction_remove_all)s /> <label>%(remove_all_label)s</label></td> 141 </tr>''' % d 142 143 # User information and actions. 144 145 if username and not user_is_approved(request, username): 146 if not queued_edits: 147 html += ''' 148 <tr> 149 <td></td> 150 <td><input name="show-edits" type="submit" value="%(show_edits_label)s" /></td> 151 </tr>''' % d 152 else: 153 html += ''' 154 <tr> 155 <td class="label"><label>%(showing_edits_label)s</label></td> 156 <td><ul>''' % d 157 158 for queued_page in queued_edits: 159 html += ''' 160 <li>%s</li>''' % escape(queued_page.page_name) 161 162 html += ''' 163 </ul></td> 164 </tr>''' 165 166 # Actions to be taken with the user. 167 168 html += ''' 169 <tr> 170 <td class="label" rowspan="3"><label>%(useraction_label)s</label></td> 171 <td><input name="useraction" type="radio" value="nothing" %(useraction_nothing)s /> <label>%(nothing_label)s</label></td> 172 </tr> 173 <tr> 174 <td><input name="useraction" type="radio" value="approve" %(useraction_approve)s /> <label>%(approval_label)s</label></td> 175 </tr> 176 <tr> 177 <td><input name="useraction" type="radio" value="block" %(useraction_block)s /> <label>%(block_label)s</label></td> 178 </tr>''' % d 179 180 html += ''' 181 <tr> 182 <td></td> 183 <td class="buttons"> 184 %(buttons_html)s 185 </td> 186 </tr> 187 </table>''' % d 188 189 if rev: 190 html += ''' 191 <input name="rev" type="hidden" value="%(rev)s" />''' % d 192 193 return html 194 195 def do_action(self): 196 197 "Approve the page and move it into place." 198 199 _ = self._ 200 request = self.request 201 form = get_form(request) 202 203 # Make sure that only suitably privileged users can perform this action. 204 205 queued_changes_page = get_queued_changes_page(request) 206 207 if not is_reviewer(request): 208 return 0, _("Only page reviewers can perform this action.") 209 210 # Edit the target page, using this page's content. 211 # The current page must be a queued page version. 212 213 if not is_queued_changes_page(request, self.pagename): 214 return 0, _("This page is not queued for approval.") 215 216 # First, the displayed revision of the queued changes page must be 217 # retrieved. 218 219 rev = self.get_revision() 220 body = self.get_body(rev) 221 222 # Remove any introduced directives. 223 224 body, directives = remove_directives(body, self.queued_content_directives) 225 226 # Get the target page's parent revision for the queued changes. 227 228 target_page_name = get_target_page_name(self.page) 229 target_page = PageEditor(request, target_page_name) 230 231 current_rev = target_page.current_rev() 232 parent_rev = int(directives.get("parent-revision", current_rev)) 233 234 # Get the user who submitted the changes. 235 236 username = directives.get("unapproved-user") 237 useraction = form.get("useraction", [""])[0] 238 editaction = form.get("editaction", [""])[0] 239 240 # Approve the user if requested, regardless of what happens below. 241 242 if username: 243 if useraction == "approve": 244 add_to_group_page(request, username, get_approved_editors_group(request)) 245 246 # Disable the user if requested. 247 248 elif useraction == "block": 249 user = User(request, None, username) 250 251 # From the MoinMoin.script.account.disable module. 252 253 if not user.disabled: 254 user.disabled = 1 255 user.name = "%s-%s" % (user.name, user.id) 256 if user.email: 257 user.email = "%s-%s" % (user.email, user.id) 258 user.subscribed_pages = "" # avoid using email 259 user.save() 260 261 # Remove all edits if requested. 262 263 if editaction == "remove_all": 264 for queued_page in self.get_queued_edits(request, username): 265 queued_page = PageEditor(request, queued_page.page_name) 266 queued_page.deletePage(_("Changes to page discarded.")) 267 268 # Redirect to the target page. 269 270 request.http_redirect(target_page.url(request)) 271 return 1, None 272 273 # Discard the edit if requested. 274 275 if editaction == "discard": 276 277 # NOTE: The page could be deleted completely or certain revisions 278 # NOTE: purged. 279 # NOTE: (to-do/proper-queued-page-deletion.txt) 280 281 current_page = PageEditor(request, self.pagename) 282 current_page.deletePage(_("Changes to page discarded.")) 283 284 # Redirect to the target page. 285 286 request.http_redirect(target_page.url(request)) 287 return 1, None 288 289 # Where the parent revision differs from the current revision of the 290 # page, attempt to merge the changes. 291 292 conflict = False 293 294 if parent_rev != current_rev: 295 296 # The body of the parent revision of the target page, along with the 297 # body of the current revision must be acquired. 298 299 parent_body = Page(request, target_page_name, rev=parent_rev).get_raw_body() 300 current_body = target_page.get_raw_body() 301 302 # The parent, current and queued texts must then be merged. 303 304 body = diff3.text_merge(parent_body, current_body, body, True, *conflict_markers) 305 306 # Look for conflict markers and redirect to edit mode on the 307 # resulting page if they are present. 308 309 for marker in conflict_markers: 310 if body.find(marker) != -1: 311 conflict = True 312 break 313 314 # Delete the queued changes page. 315 # NOTE: The page could be deleted completely or certain revisions 316 # NOTE: purged. 317 # NOTE: (to-do/proper-queued-page-deletion.txt) 318 319 current_page = PageEditor(request, self.pagename) 320 current_page.deletePage(_("Changes to page approved.")) 321 322 # Prepare a comment. 323 324 comment = username and \ 325 _("Changes to page by %s approved from queue revision %d.") % (username, rev) or \ 326 _("Changes to page approved from queue revision %d.") % rev 327 328 # Save the target page, but only if there is no conflict. 329 330 if not conflict: 331 332 # Switch user if a specific user was recorded. 333 334 if username: 335 new_user = get_user(request, username) 336 else: 337 new_user = None 338 339 if new_user: 340 user = request.user 341 request.user = new_user 342 343 # Save the page. 344 345 try: 346 try: 347 target_page.saveText(body, 0, comment=comment) 348 except PageEditor.Unchanged: 349 pass 350 351 # Restore the user. 352 353 finally: 354 if new_user: 355 request.user = user 356 357 # Redirect to the target page. 358 359 request.http_redirect(target_page.url(request)) 360 361 # Otherwise, send the page editor. 362 # NOTE: Replacing the revision in the request to prevent Moin from 363 # NOTE: attempting to use the queued changes page's revision. 364 # NOTE: Replacing the action and page in the request to avoid issues 365 # NOTE: with editing tickets. 366 367 else: 368 request.rev = current_rev 369 request.action = "edit" 370 request.page = target_page 371 target_page.sendEditor(preview=body, comment=comment, staytop=True) 372 373 return 1, None 374 375 def render_success(self, msg, msgtype): 376 377 """ 378 Render neither 'msg' nor 'msgtype' since redirection should occur 379 instead. 380 NOTE: msgtype is optional because MoinMoin 1.5.x does not support it. 381 """ 382 383 pass 384 385 # Action function. 386 387 def execute(pagename, request): 388 ApproveChanges(pagename, request).render() 389 390 # vim: tabstop=4 expandtab shiftwidth=4