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