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