# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - ApproveChanges library

    This library relies on the existence of a user (by default
    "ApprovalQueueUser") who has sufficient privileges to write pages with ACLs
    to an approval queue (ACL permissions "write,admin").

    If users other than the superuser are to be able to edit pages freely, they
    must be present in a group (by default "ApprovedGroup"), and if they are to
    be allowed to review changes, they must be present in a different group (by
    default "PageReviewersGroup").

    @copyright: 2011 by Paul Boddie <paul@boddie.org.uk>
                2003-2007 MoinMoin:ThomasWaldmann,
                2003 by Gustavo Niemeyer
    @license: GNU GPL (v2 or later), see COPYING.txt for details.
"""

from MoinMoin import user
from MoinMoin.Page import Page
from MoinMoin.PageEditor import PageEditor
from MoinMoin.wikiutil import escape
import re

__version__ = "0.1"

space_pattern = re.compile("(\s+)")
group_member_pattern = re.compile(ur'^ \* +(?:\[\[)?(?P<member>.+?)(?:\]\])? *$', re.MULTILINE | re.UNICODE)

def get_queued_changes_page(request):
    return getattr(request.cfg, "queued_changes_page", "ApprovalQueue")

def get_approved_editors_group(request):
    return getattr(request.cfg, "approved_editors_group", "ApprovedGroup")

def get_page_reviewers_group(request):
    return getattr(request.cfg, "reviewers_group", "PageReviewersGroup")

def get_queued_changes_user(request):
    return getattr(request.cfg, "queued_changes_user", "ApprovalQueueUser")

def is_reviewer(request):
    return request.user.valid and (
        has_member(request, get_approved_editors_group(request), request.user.name) or \
        request.user.isSuperUser())

def is_approved(request):
    return request.user.valid and (
        user_is_approved(request, request.user.name) or \
        request.user.isSuperUser())

def user_is_approved(request, username):
    return has_member(request, get_approved_editors_group(request), username)

def is_queued_changes_user(request):
    return request.user.valid and request.user.name == get_queued_changes_user(request)

def is_queued_changes_page(request, pagename):

    "Return whether 'pagename' is a queued changes page by testing its name."

    parts = pagename.split("/")
    return len(parts) > 1 and parts[-1] == get_queued_changes_page(request)

def get_target_page_name(pagename):

    "Return the target page name for the given queued changes 'pagename'."

    return "/".join(pagename.split("/")[:-1])

def get_user_for_saving(request):

    "Return a user that can save pages with ACLs."

    username = get_queued_changes_user(request)

    # If the user does not exist, just return the existing user.

    return get_user(request, username) or request.user

def get_user(request, username):

    "Return the user having the given 'username'."

    uid = user.getUserId(request, username)

    # If the user does not exist, just return None.

    if not uid:
        return None

    # Otherwise, return the privileged user.

    return user.User(request, uid)

def get_parent_revision_directive(request, pagename):

    """
    Using the 'request', return a parent page revision directive for the page
    having the given 'pagename'.
    """

    page = Page(request, pagename)
    return "#parent-revision %s" % page.current_rev()

def get_access_control_directive(request):

    """
    Using the 'request', return an ACL directive for use in a page body in order
    to prevent anyone other than reviewers from seeing it in the queue.
    """

    return "#acl %s:read,write,delete,revert,admin All:" % (
        get_page_reviewers_group(request))

def get_user_directive(request):

    """
    Using the 'request', return a user directive for use in a page body in order
    to record who saved the changes originally.
    """

    if request.user.valid:
        return "#unapproved-user %s" % request.user.name
    else:
        return ""

def add_directives(body, directives):

    "Add to the page 'body' the given 'directives'."

    return "\n".join([directive for directive in directives if directive] + [body])

def remove_directives(body, names):

    """
    Return a new page body, copying the page 'body' provided but removing the
    first of each directive having one of the given 'names', along with a
    dictionary mapping directive names to values.
    """

    new_body = []
    header = 1
    found = {}

    for line in body.split("\n"):
        if header:

            # Detect the end of the header.

            if not line.startswith("#"):
                header = 0

            # Process the comment or directive.

            else:
                parts = space_pattern.split(line[1:])

                # Identify any directive.

                directive = parts[0]

                # Obtain the value of the first instance of any directive,
                # stripping any initial space.

                if directive in names and not found.has_key(directive):
                    found[directive] = "".join(parts[2:])
                    continue

        new_body.append(line)

    return "\n".join(new_body), found

def add_to_group_page(request, username, groupname):

    """
    Using the 'request', add 'username' to 'groupname', changing the group page.
    This is not the same as adding a member to the group, but it will have the
    same effect when the group is rescanned.
    """

    _ = request.getText

    page = PageEditor(request, groupname)
    body = page.get_raw_body()
    match = None

    # Find the last matching span.

    for match in group_member_pattern.finditer(body):
        start, end = match.span()

    # Add a group member to the body.

    entry = ("\n * %s" % username)

    if match:
        body = body[:end] + entry + body[end:]
    else:
        body += entry

    page.saveText(body, 0, comment=_("Added %s to the approved editors group.") % username)

# Utility classes and associated functions.
# NOTE: These are a subset of EventAggregatorSupport.

class Form:

    """
    A wrapper preserving MoinMoin 1.8.x (and earlier) behaviour in a 1.9.x
    environment.
    """

    def __init__(self, form):
        self.form = form

    def get(self, name, default=None):
        values = self.form.getlist(name)
        if not values:
            return default
        else:
            return values

    def __getitem__(self, name):
        return self.form.getlist(name)

class ActionSupport:

    """
    Work around disruptive MoinMoin changes in 1.9, and also provide useful
    convenience methods.
    """

    def get_form(self):
        return get_form(self.request)

def get_form(request):

    "Work around disruptive MoinMoin changes in 1.9."

    if hasattr(request, "values"):
        return Form(request.values)
    else:
        return request.form

def escattr(s):
    return escape(s, 1)

# More Moin 1.9 compatibility functions.

def has_member(request, groupname, username):
    if hasattr(request.dicts, "has_member"):
        return request.dicts.has_member(groupname, username)
    else:
        return username in request.dicts.get(groupname, [])

# vim: tabstop=4 expandtab shiftwidth=4
