text:
"""Domain objects and functions that manage rights for various user actions."""

import logging

from constants import constants
from core.domain import activity_services
from core.domain import config_domain
from core.domain import role_services
from core.domain import subscription_services
from core.domain import user_services
from core.platform import models
import feconf
import utils

current_user_services = models.Registry.import_current_user_services()
(collection_models, exp_models,) = models.Registry.import_models([
    models.NAMES.collection, models.NAMES.exploration
])

# IMPORTANT: Ensure that all changes to how these cmds are interpreted preserve
# backward-compatibility with previous exploration snapshots in the datastore.
# Do not modify the definitions of CMD keys that already exist.
CMD_CREATE_NEW = 'create_new'
CMD_CHANGE_ROLE = 'change_role'
CMD_CHANGE_EXPLORATION_STATUS = 'change_exploration_status'
CMD_CHANGE_COLLECTION_STATUS = 'change_collection_status'
CMD_CHANGE_PRIVATE_VIEWABILITY = 'change_private_viewability'
CMD_RELEASE_OWNERSHIP = 'release_ownership'
CMD_UPDATE_FIRST_PUBLISHED_MSEC = 'update_first_published_msec'

ACTIVITY_STATUS_PRIVATE = feconf.ACTIVITY_STATUS_PRIVATE
ACTIVITY_STATUS_PUBLIC = feconf.ACTIVITY_STATUS_PUBLIC
ACTIVITY_STATUS_PUBLICIZED = feconf.ACTIVITY_STATUS_PUBLICIZED

ROLE_OWNER = 'owner'
ROLE_EDITOR = 'editor'
ROLE_VIEWER = 'viewer'
ROLE_NONE = 'none'

ROLE_ADMIN = 'admin'
ROLE_MODERATOR = 'moderator'

class ActivityRights(object):
    """Domain object for the rights/publication status of an activity (an
    exploration or a collection).
    """

    def __init__(self, exploration_id, owner_ids, editor_ids, viewer_ids,
                 community_owned=False, cloned_from=None,
                 status=ACTIVITY_STATUS_PRIVATE,
                 viewable_if_private=False, first_published_msec=None):
        self.id = exploration_id
        self.owner_ids = owner_ids
        self.editor_ids = editor_ids
        self.viewer_ids = viewer_ids
        self.community_owned = community_owned
        self.cloned_from = cloned_from
        self.status = status
        self.viewable_if_private = viewable_if_private
        self.first_published_msec = first_published_msec

    def validate(self):
        """Validates an ActivityRights object.

        Raises:
          utils.ValidationError: if any of the owners, editors and viewers
          lists overlap, or if a community-owned exploration has owners,
          editors or viewers specified.
        """
        if self.community_owned:
            if self.owner_ids or self.editor_ids or self.viewer_ids:
                raise utils.ValidationError(
                    'Community-owned explorations should have no owners, '
                    'editors or viewers specified.')

        if self.community_owned and self.status == ACTIVITY_STATUS_PRIVATE:
            raise utils.ValidationError(
                'Community-owned explorations cannot be private.')

        if self.status != ACTIVITY_STATUS_PRIVATE and self.viewer_ids:
            raise utils.ValidationError(
                'Public explorations should have no viewers specified.')

        owner_editor = set(self.owner_ids).intersection(set(self.editor_ids))
        owner_viewer = set(self.owner_ids).intersection(set(self.viewer_ids))
        editor_viewer = set(self.editor_ids).intersection(set(self.viewer_ids))
        if owner_editor:
            raise utils.ValidationError(
                'A user cannot be both an owner and an editor: %s' %
                owner_editor)
        if owner_viewer:
            raise utils.ValidationError(
                'A user cannot be both an owner and a viewer: %s' %
                owner_viewer)
        if editor_viewer:
            raise utils.ValidationError(
                'A user cannot be both an owner and an editor: %s' %
                editor_viewer)

    def to_dict(self):
        """Returns a dict suitable for use by the frontend.

        Returns:
            dict. A dict version of ActivityRights suitable for use by the
                frontend.
        """
        if self.community_owned:
            return {
                'cloned_from': self.cloned_from,
                'status': self.status,
                'community_owned': True,
                'owner_names': [],
                'editor_names': [],
                'viewer_names': [],
                'viewable_if_private': self.viewable_if_private,
            }
        else:
            return {
                'cloned_from': self.cloned_from,
                'status': self.status,
                'community_owned': False,
                'owner_names': user_services.get_human_readable_user_ids(
                    self.owner_ids),
                'editor_names': user_services.get_human_readable_user_ids(
                    self.editor_ids),
                'viewer_names': user_services.get_human_readable_user_ids(
                    self.viewer_ids),
                'viewable_if_private': self.viewable_if_private,
            }

    def is_owner(self, user_id):
        """Checks whether given user is owner of activity.

        Args:
            user_id: str or None. Id of the user.

        Returns:
            bool. Whether user is in activity owners.
        """
        return bool(user_id in self.owner_ids)

    def is_editor(self, user_id):
        """Checks whether given user is editor of activity.

        Args:
            user_id: str or None. Id of the user.

        Returns:
            bool. Whether user is in activity editors.
        """
        return bool(user_id in self.editor_ids)

    def is_viewer(self, user_id):
        """Checks whether given user is viewer of activity.

        Args:
            user_id: str or None. Id of the user.

        Returns:
            bool. Whether user is in activity viewers of.
        """
        return bool(user_id in self.viewer_ids)

    def is_published(self):
        """Checks whether activity is published.

        Args:
            activity_rights: object. Activity rights object.

        Returns:
            bool. Whether activity is published.
        """
        if self.status == ACTIVITY_STATUS_PUBLIC:
            return True
        elif self.status == ACTIVITY_STATUS_PUBLICIZED:
            return True
        else:
            return False

    def is_private(self):
        """Checks whether activity is private.

        Returns:
            bool. Whether activity is private.
        """
        return bool(self.status == ACTIVITY_STATUS_PRIVATE)

def _get_activity_rights_from_model(activity_rights_model, activity_type):
    """Constructs an ActivityRights object from the given activity rights model.

    Args:
        activity_rights_model: ActivityRightsModel. Activity rights from the
            datastore.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION

    Returns:
        ActivityRights. The rights object created from the model.
    """
    return ActivityRights(
        activity_rights_model.id,
        activity_rights_model.owner_ids,
        activity_rights_model.editor_ids,
        activity_rights_model.viewer_ids,
        community_owned=activity_rights_model.community_owned,
        cloned_from=(
            activity_rights_model.cloned_from
            if activity_type == constants.ACTIVITY_TYPE_EXPLORATION else None),
        status=activity_rights_model.status,
        viewable_if_private=activity_rights_model.viewable_if_private,
        first_published_msec=activity_rights_model.first_published_msec
    )

def _save_activity_rights(
        committer_id, activity_rights, activity_type, commit_message,
        commit_cmds):
    """Saves an ExplorationRights or CollectionRights domain object to the
    datastore.

    Args:
        committer_id: str. ID of the committer.
        activity_rights: ActivityRights. The rights object for the given
            activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION
        commit_message: str. Descriptive message for the commit.
        commit_cmds: list(dict). A list of commands describing what kind of
            commit was done.
    """
    activity_rights.validate()

    if activity_type == constants.ACTIVITY_TYPE_EXPLORATION:
        model_cls = exp_models.ExplorationRightsModel
    elif activity_type == constants.ACTIVITY_TYPE_COLLECTION:
        model_cls = collection_models.CollectionRightsModel
    model = model_cls.get(activity_rights.id, strict=False)

    model.owner_ids = activity_rights.owner_ids
    model.editor_ids = activity_rights.editor_ids
    model.viewer_ids = activity_rights.viewer_ids
    model.community_owned = activity_rights.community_owned
    model.status = activity_rights.status
    model.viewable_if_private = activity_rights.viewable_if_private
    model.first_published_msec = activity_rights.first_published_msec

    model.commit(committer_id, commit_message, commit_cmds)

def _update_exploration_summary(activity_rights):
    """Updates the exploration summary for the activity associated with the
    given rights object.

    The ID of rights object is the same as the ID of associated activity.

    Args:
        activity_rights: ActivityRights. The rights object for the given
            activity.
    """
    # TODO(msl): get rid of inline imports by refactoring code.
    from core.domain import exp_services
    exp_services.update_exploration_summary(
        activity_rights.id, None)

def _update_collection_summary(activity_rights):
    """Updates the collection summary for the given activity associated with
    the given rights object.

    The ID of rights object is the same as the ID of associated activity.

    Args:
        activity_rights: ActivityRights. The rights object for the given
            activity.
    """
    from core.domain import collection_services
    collection_services.update_collection_summary(
        activity_rights.id, None)

def _update_activity_summary(activity_type, activity_rights):
    """Updates the activity summary for the given activity associated with
    the given rights object.

    The ID of rights object is the same as the ID of associated activity.

    Args:
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION
        activity_rights: ActivityRights. The rights object for the given
            activity.
    """
    if activity_type == constants.ACTIVITY_TYPE_EXPLORATION:
        _update_exploration_summary(activity_rights)
    elif activity_type == constants.ACTIVITY_TYPE_COLLECTION:
        _update_collection_summary(activity_rights)

def update_activity_first_published_msec(
        activity_type, activity_id, first_published_msec):
    """Updates the first_published_msec field for the given activity.

    The caller is responsible for ensuring that this value is not already
    set before updating it.

    Args:
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION
        activity_id: str. ID of the activity.
        first_published_msec: float. First publication time in milliseconds
            since the Epoch.
    """
    activity_rights = _get_activity_rights(activity_type, activity_id)
    commit_cmds = [{
        'cmd': CMD_UPDATE_FIRST_PUBLISHED_MSEC,
        'old_first_published_msec': activity_rights.first_published_msec,
        'new_first_published_msec': first_published_msec
    }]
    activity_rights.first_published_msec = first_published_msec
    _save_activity_rights(
        feconf.SYSTEM_COMMITTER_ID, activity_rights, activity_type,
        'set first published time in msec', commit_cmds)

def create_new_exploration_rights(exploration_id, committer_id):
    """Creates a new exploration rights object and saves it to the datastore.
    Subscribes the committer to the new exploration.

    Args:
        exploration_id: str. ID of the exploration.
        committer_id: str. ID of the committer.
    """
    exploration_rights = ActivityRights(
        exploration_id, [committer_id], [], [])
    commit_cmds = [{'cmd': CMD_CREATE_NEW}]

    exp_models.ExplorationRightsModel(
        id=exploration_rights.id,
        owner_ids=exploration_rights.owner_ids,
        editor_ids=exploration_rights.editor_ids,
        viewer_ids=exploration_rights.viewer_ids,
        community_owned=exploration_rights.community_owned,
        status=exploration_rights.status,
        viewable_if_private=exploration_rights.viewable_if_private,
        first_published_msec=exploration_rights.first_published_msec,
    ).commit(committer_id, 'Created new exploration', commit_cmds)

    subscription_services.subscribe_to_exploration(
        committer_id, exploration_id)

def get_exploration_rights(exploration_id, strict=True):
    """Retrieves the rights for this exploration from the datastore.

    Args:
        exploration_id: str. ID of the exploration.
        strict: bool. Whether to raise an error if there is no exploration
            matching the given ID.

    Returns:
        ActivityRights. The rights object for the given exploration.

    Raises:
        EntityNotFoundError. The exploration with ID exploration_id was not
            found in the datastore.
    """
    model = exp_models.ExplorationRightsModel.get(
        exploration_id, strict=strict)
    if model is None:
        return None
    return _get_activity_rights_from_model(
        model, constants.ACTIVITY_TYPE_EXPLORATION)

def is_exploration_private(exploration_id):
    """Returns whether exploration is private.

    Args:
        exploration_id: str. ID of the exploration.

    Returns:
        bool. Whether the exploration is private or not.
    """
    exploration_rights = get_exploration_rights(exploration_id)
    return exploration_rights.status == ACTIVITY_STATUS_PRIVATE

def is_exploration_public(exploration_id):
    """Returns whether exploration is public.

    Args:
        exploration_id: str. ID of the exploration.

    Returns:
        bool. Whether the exploration is public.
    """
    exploration_rights = get_exploration_rights(exploration_id)
    return exploration_rights.status == ACTIVITY_STATUS_PUBLIC

def is_exploration_cloned(exploration_id):
    """Returns whether the exploration is a clone of another exploration.

    Args:
        exploration_id: str. ID of the exploration.

    Returns:
        bool. Whether the exploration is a clone of another exploration.
    """
    exploration_rights = get_exploration_rights(exploration_id)
    return bool(exploration_rights.cloned_from)

def create_new_collection_rights(collection_id, committer_id):
    """Creates a new collection rights object and saves it to the datastore.
    Subscribes the committer to the new collection.

    Args:
        collection_id: str. ID of the collection.
        committer_id: str. ID of the committer.
    """
    collection_rights = ActivityRights(collection_id, [committer_id], [], [])
    commit_cmds = [{'cmd': CMD_CREATE_NEW}]

    collection_models.CollectionRightsModel(
        id=collection_rights.id,
        owner_ids=collection_rights.owner_ids,
        editor_ids=collection_rights.editor_ids,
        viewer_ids=collection_rights.viewer_ids,
        community_owned=collection_rights.community_owned,
        status=collection_rights.status,
        viewable_if_private=collection_rights.viewable_if_private,
        first_published_msec=collection_rights.first_published_msec
    ).commit(committer_id, 'Created new collection', commit_cmds)

    subscription_services.subscribe_to_collection(committer_id, collection_id)

def get_collection_rights(collection_id, strict=True):
    """Retrieves the rights for this collection from the datastore.

    Args:
        collection_id: str. ID of the collection.
        strict: bool. Whether to raise an error if ID is not found.

    Returns:
        ActivityRights. The rights object for the collection.

    Raises:
        EntityNotFoundError. The collection with ID collection_id is not found
            in the datastore.
    """
    model = collection_models.CollectionRightsModel.get(
        collection_id, strict=strict)
    if model is None:
        return None
    return _get_activity_rights_from_model(
        model, constants.ACTIVITY_TYPE_COLLECTION)

def get_collection_owner_names(collection_id):
    """Retrieves the owners for this collection from the datastore.

    Args:
        collection_id: str. ID of the collection.

    Returns:
        list(str). Human-readable usernames (or truncated email addresses) of
            owners for this collection.
    """
    collection_rights = get_collection_rights(collection_id)
    return user_services.get_human_readable_user_ids(
        collection_rights.owner_ids)

def is_collection_private(collection_id):
    """Returns whether the collection is private.

    Args:
        collection_id: str. ID of the collection.

    Returns:
        bool. Whether the collection is private.
    """
    collection_rights = get_collection_rights(collection_id)
    return collection_rights.status == ACTIVITY_STATUS_PRIVATE

def is_collection_public(collection_id):
    """Returns whether the collection is public.

    Args:
        collection_id: str. ID of the collection.

    Returns:
        bool. Whether the collection is public.
    """
    collection_rights = get_collection_rights(collection_id)
    return collection_rights.status == ACTIVITY_STATUS_PUBLIC

def _get_activity_rights(activity_type, activity_id):
    """Retrieves the rights object for the given activity
    based on its type.

    Args:
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION
        activity_id: str. ID of the activity.

    Returns:
        ActivityRights. The rights object associated with the given activity.

    Raises:
        Exception. activity_type provided is unknown.
    """
    if activity_type == constants.ACTIVITY_TYPE_EXPLORATION:
        return get_exploration_rights(activity_id, strict=False)
    elif activity_type == constants.ACTIVITY_TYPE_COLLECTION:
        return get_collection_rights(activity_id, strict=False)
    else:
        raise Exception(
            'Cannot get activity rights for unknown activity type: %s' % (
                activity_type))

class Actor(object):
    """Domain object for a user with various rights."""

    def __init__(self, user_id):
        # Note that this may be None.
        self.user_id = user_id
        # Value of None is a placeholder. This property gets initialized
        # when the first call to `is_admin()` is made.
        self._is_admin = None
        # Value of None is a placeholder. This property gets initialized
        # when the first call to `is_moderator()` is made.
        self._is_moderator = None

    def is_admin(self):
        """Returns whether this user is an administrator.

        Returns:
            bool. Whether this user is an administrator.
        """
        if self._is_admin is None:
            self._is_admin = self.user_id in config_domain.ADMIN_IDS.value
        return self._is_admin

    def is_moderator(self):
        """Returns whether this user is a moderator.

        Returns:
            bool. Whether this user is a moderator.
        """
        if self._is_moderator is None:
            self._is_moderator = (
                self.is_admin() or
                self.user_id in config_domain.MODERATOR_IDS.value)
        return self._is_moderator

    def _is_owner(self, rights_object):
        return (
            rights_object.community_owned or
            self.user_id in rights_object.owner_ids)

    def _has_editing_rights(self, rights_object):
        return (rights_object.community_owned or
                self.user_id in rights_object.editor_ids or
                self.user_id in rights_object.owner_ids)

    def _has_viewing_rights(self, rights_object):
        return (rights_object.status != ACTIVITY_STATUS_PRIVATE or
                self.user_id in rights_object.viewer_ids or
                self.user_id in rights_object.editor_ids or
                self.user_id in rights_object.owner_ids)

    def _can_play(self, rights_object):
        if rights_object.status == ACTIVITY_STATUS_PRIVATE:
            return (self._has_viewing_rights(rights_object)
                    or rights_object.viewable_if_private
                    or self.is_moderator())
        else:
            return True

    def _can_edit(self, rights_object):
        return (
            self._has_editing_rights(rights_object) or self.is_admin() or (
                self.is_moderator() and (
                    rights_object.status != ACTIVITY_STATUS_PRIVATE)
            )
        )

    def _can_delete(self, rights_object):
        is_deleting_own_private_object = (
            rights_object.status == ACTIVITY_STATUS_PRIVATE and
            self._is_owner(rights_object))

        is_mod_deleting_public_object = (
            rights_object.status == ACTIVITY_STATUS_PUBLIC and
            self.is_moderator())

        return (
            is_deleting_own_private_object or is_mod_deleting_public_object)

    def is_owner(self, activity_type, activity_id):
        """Returns whether this user is an owner of the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user is an owner of the given activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False
        return self._is_owner(activity_rights)

    def has_editing_rights(self, activity_type, activity_id):
        """Returns whether this user has editing rights for the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user is in the owner/editor list for the given
                activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False
        return self._has_editing_rights(activity_rights)

    def has_viewing_rights(self, activity_type, activity_id):
        """Returns whether this user has viewing rights for the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has the right to view the given activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False
        return self._has_viewing_rights(activity_rights)

    def can_play(self, activity_type, activity_id):
        """Returns whether this user has rights to play the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has the right to play the given activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False
        return self._can_play(activity_rights)

    def can_view(self, activity_type, activity_id):
        """Returns whether this user has rights to view the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has the right to view the given activity.
        """
        return self.can_play(activity_type, activity_id)

    # TODO(sll): Add a check here for whether a user is banned or not,
    # rather than having this check in the controller.
    def can_edit(self, activity_type, activity_id):
        """Returns whether this user has rights to edit the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has the right to edit the given activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False
        return self._can_edit(activity_rights)

    def can_delete(self, activity_type, activity_id):
        """Returns whether this user has rights to delete the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has the right to delete the given activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False
        return self._can_delete(activity_rights)

    def can_change_private_viewability(
            self, activity_type, activity_id):
        """Returns whether this user is allowed to change the viewability of
        the given private activity.

        The caller is responsible for ensuring that the given activity is
        private.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user is allowed to change the viewability of
                the given private activity.
        """

        return self.can_publish(activity_type, activity_id)

    def can_publish(self, activity_type, activity_id):
        """Returns whether this user is allowed to publish the given activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user is allowed to publish the given activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False

        if activity_rights.status != ACTIVITY_STATUS_PRIVATE:
            return False
        if activity_rights.cloned_from:
            return False

        return self.is_owner(activity_type, activity_id) or self.is_admin()

    def can_unpublish(self, activity_type, activity_id):
        """Returns whether this user is allowed to unpublish the given
        activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user is allowed to unpublish the given
                activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False

        if activity_rights.status != ACTIVITY_STATUS_PUBLIC:
            return False
        if activity_rights.community_owned:
            return False

        return self.is_moderator()

    def can_modify_roles(self, activity_type, activity_id):
        """Returns whether this user has the right to modify roles for the given
        activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has the right to modify roles for the given
                activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False

        if activity_rights.community_owned or activity_rights.cloned_from:
            return False
        return self.is_admin() or self.is_owner(activity_type, activity_id)

    def can_release_ownership(self, activity_type, activity_id):
        """Returns whether this user is allowed to release ownership of the
        given activity to the community.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user is allowed to release ownership of the
                given activity to the community.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False

        if activity_rights.status == ACTIVITY_STATUS_PRIVATE:
            return False
        return self.can_modify_roles(activity_type, activity_id)

    def can_publicize(self, activity_type, activity_id):
        """Returns whether this user is allowed to publicize the given
        activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has a right to publicize the given activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False

        if activity_rights.status != ACTIVITY_STATUS_PUBLIC:
            return False
        return self.is_moderator()

    def can_unpublicize(self, activity_type, activity_id):
        """Returns whether this user is allowed to unpublicize the given
        activity.

        Args:
            activity_type: str. The type of activity. Possible values:
                constants.ACTIVITY_TYPE_EXPLORATION
                constants.ACTIVITY_TYPE_COLLECTION
            activity_id: str. ID of the activity.

        Returns:
            bool. Whether the user has a right to unpublicize the given
                activity.
        """
        activity_rights = _get_activity_rights(activity_type, activity_id)
        if activity_rights is None:
            return False

        if activity_rights.status != ACTIVITY_STATUS_PUBLICIZED:
            return False
        return self.is_moderator()

def _assign_role(
        committer_id, assignee_id, new_role, activity_id, activity_type):
    """Assigns a new role to the user.

    Args:
        committer_id: str. ID of the user who is performing the action.
        activity_rights: ExplorationRightsModel|CollectionRightsModel. The
            storage object for the rights of the given activity.
        assignee_id: str. ID of the user whose role is being changed.
        new_role: str. The name of the new role: One of
            ROLE_OWNER
            ROLE_EDITOR
            ROLE_VIEWER
        activity_id: str. ID of the activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION

    Raises:
        Exception. The committer does not have rights to modify a role.
        Exception. The user already owns the activity.
        Exception. The user can already edit the activity.
        Exception. The activity is already publicly editable.
        Exception. The user can already view the activity.
        Exception. The activity is already publicly viewable.
        Exception. The role is invalid.
    """
    if not Actor(committer_id).can_modify_roles(activity_type, activity_id):
        logging.error(
            'User %s tried to allow user %s to be a(n) %s of activity %s '
            'but was refused permission.' % (
                committer_id, assignee_id, new_role, activity_id))
        raise Exception(
            'UnauthorizedUserException: Could not assign new role.')

    activity_rights = _get_activity_rights(activity_type, activity_id)

    assignee_username = user_services.get_username(assignee_id)
    old_role = ROLE_NONE

    if new_role == ROLE_OWNER:
        if Actor(assignee_id)._is_owner(activity_rights):  # pylint: disable=protected-access
            raise Exception('This user already owns this %s.' % activity_type)

        activity_rights.owner_ids.append(assignee_id)

        if assignee_id in activity_rights.viewer_ids:
            activity_rights.viewer_ids.remove(assignee_id)
            old_role = ROLE_VIEWER
        if assignee_id in activity_rights.editor_ids:
            activity_rights.editor_ids.remove(assignee_id)
            old_role = ROLE_EDITOR

    elif new_role == ROLE_EDITOR:
        if Actor(assignee_id)._has_editing_rights(activity_rights):  # pylint: disable=protected-access
            raise Exception(
                'This user already can edit this %s.' % activity_type)

        if activity_rights.community_owned:
            raise Exception(
                'Community-owned %ss can be edited by anyone.' % activity_type)

        activity_rights.editor_ids.append(assignee_id)

        if assignee_id in activity_rights.viewer_ids:
            activity_rights.viewer_ids.remove(assignee_id)
            old_role = ROLE_VIEWER

    elif new_role == ROLE_VIEWER:
        if Actor(assignee_id)._has_viewing_rights(activity_rights):  # pylint: disable=protected-access
            raise Exception(
                'This user already can view this %s.' % activity_type)

        if activity_rights.status != ACTIVITY_STATUS_PRIVATE:
            raise Exception(
                'Public %ss can be viewed by anyone.' % activity_type)

        activity_rights.viewer_ids.append(assignee_id)

    else:
        raise Exception('Invalid role: %s' % new_role)

    commit_message = 'Changed role of %s from %s to %s' % (
        assignee_username, old_role, new_role)
    commit_cmds = [{
        'cmd': CMD_CHANGE_ROLE,
        'assignee_id': assignee_id,
        'old_role': old_role,
        'new_role': new_role
    }]

    _save_activity_rights(committer_id, activity_rights, activity_type,
                          commit_message, commit_cmds)
    _update_activity_summary(activity_type, activity_rights)

def _release_ownership_of_activity(committer_id, activity_id, activity_type):
    """Releases ownership of the given activity to the community.

    Args:
        committer_id: str. ID of the user who is performing the action.
        activity_id: str. ID of the activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION

    Raise:
        Exception. The committer does not have release rights.
    """
    if not Actor(committer_id).can_release_ownership(
            activity_type, activity_id):
        logging.error(
            'User %s tried to release ownership of %s %s but was '
            'refused permission.' % (committer_id, activity_type, activity_id))
        raise Exception(
            'The ownership of this %s cannot be released.' % activity_type)

    activity_rights = _get_activity_rights(activity_type, activity_id)
    activity_rights.community_owned = True
    activity_rights.owner_ids = []
    activity_rights.editor_ids = []
    activity_rights.viewer_ids = []
    commit_cmds = [{
        'cmd': CMD_RELEASE_OWNERSHIP,
    }]

    _save_activity_rights(
        committer_id, activity_rights, activity_type,
        '%s ownership released to the community.' % activity_type, commit_cmds)
    _update_activity_summary(activity_type, activity_rights)

def _change_activity_status(
        committer_id, activity_id, activity_type, new_status, commit_message):
    """Changes the status of the given activity.

    Args:
        committer_id: str. ID of the user who is performing the update action.
        activity_id: str. ID of the activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION
        new_status: str. The new status of the activity.
        commit_message: str. The human-written commit message for this change.
    """
    activity_rights = _get_activity_rights(activity_type, activity_id)
    old_status = activity_rights.status
    activity_rights.status = new_status
    if activity_type == constants.ACTIVITY_TYPE_EXPLORATION:
        cmd_type = CMD_CHANGE_EXPLORATION_STATUS
    elif activity_type == constants.ACTIVITY_TYPE_COLLECTION:
        cmd_type = CMD_CHANGE_COLLECTION_STATUS
    commit_cmds = [{
        'cmd': cmd_type,
        'old_status': old_status,
        'new_status': new_status
    }]

    if new_status != ACTIVITY_STATUS_PRIVATE:
        activity_rights.viewer_ids = []
        if activity_rights.first_published_msec is None:
            activity_rights.first_published_msec = (
                utils.get_current_time_in_millisecs())

    _save_activity_rights(
        committer_id, activity_rights, activity_type, commit_message,
        commit_cmds)
    _update_activity_summary(activity_type, activity_rights)

def _publish_activity(committer_id, activity_id, activity_type):
    """Publishes the given activity.

    Args:
        committer_id: str. ID of the committer.
        activity_id: str. ID of the activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION

    Raises:
        Exception. The committer does not have rights to publish the
            activity.
    """
    if not Actor(committer_id).can_publish(activity_type, activity_id):
        logging.error(
            'User %s tried to publish %s %s but was refused '
            'permission.' % (committer_id, activity_type, activity_id))
        raise Exception('This %s cannot be published.' % activity_type)

    _change_activity_status(
        committer_id, activity_id, activity_type, ACTIVITY_STATUS_PUBLIC,
        '%s published.' % activity_type)

def _unpublish_activity(committer_id, activity_id, activity_type):
    """Unpublishes the given activity.

    Args:
        committer_id: str. ID of the committer.
        activity_id: str. ID of the activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION

    Raises:
        Exception. The committer does not have rights to unpublish the
            activity.
    """
    if not Actor(committer_id).can_unpublish(activity_type, activity_id):
        logging.error(
            'User %s tried to unpublish %s %s but was refused '
            'permission.' % (committer_id, activity_type, activity_id))
        raise Exception('This %s cannot be unpublished.' % activity_type)

    _change_activity_status(
        committer_id, activity_id, activity_type, ACTIVITY_STATUS_PRIVATE,
        '%s unpublished.' % activity_type)

    activity_services.remove_featured_activity(activity_type, activity_id)

def _publicize_activity(committer_id, activity_id, activity_type):
    """Publicizes the given activity.

    Args:
        committer_id: str. ID of the committer.
        activity_id: str. ID of the activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION

    Raises:
        Exception. The committer does not have rights to publicize the activity.
    """
    if not Actor(committer_id).can_publicize(activity_type, activity_id):
        logging.error(
            'User %s tried to publicize %s %s but was refused '
            'permission.' % (committer_id, activity_type, activity_id))
        raise Exception('This %s cannot be marked as "featured".' % (
            activity_type))

    _change_activity_status(
        committer_id, activity_id, activity_type, ACTIVITY_STATUS_PUBLICIZED,
        '%s publicized.' % activity_type)

def _unpublicize_activity(committer_id, activity_id, activity_type):
    """Unpublicizes the given activity.

    Args:
        committer_id: str. ID of the committer.
        activity_id: str. ID of the activity.
        activity_type: str. The type of activity. Possible values:
            constants.ACTIVITY_TYPE_EXPLORATION
            constants.ACTIVITY_TYPE_COLLECTION

    Raises:
        Exception. The committer does not have rights to unpublicize the
            activity.
    """
    if not Actor(committer_id).can_unpublicize(activity_type, activity_id):
        logging.error(
            'User %s tried to unpublicize exploration %s but was refused '
            'permission.' % (committer_id, activity_id))
        raise Exception('This exploration cannot be unmarked as "featured".')

    _change_activity_status(
        committer_id, activity_id, activity_type, ACTIVITY_STATUS_PUBLIC,
        'Exploration unpublicized.')

# Rights functions for activities.
def assign_role_for_exploration(
        committer_id, exploration_id, assignee_id, new_role):
    """Assigns a user to the given role and subscribes the assignee to future
    exploration updates.

    The caller should ensure that assignee_id corresponds to a valid user in
    the system.

    Args:
        committer_id: str. ID of the committer.
        exploration_id: str. ID of the exploration.
        assignee_id: str. ID of the user whose role is being changed.
        new_role: str. The name of the new role: One of
            ROLE_OWNER
            ROLE_EDITOR

    Raises:
        Exception. This could potentially throw an exception from
            _assign_role.
    """
    _assign_role(
        committer_id, assignee_id, new_role, exploration_id,
        constants.ACTIVITY_TYPE_EXPLORATION)
    if new_role in [ROLE_OWNER, ROLE_EDITOR]:
        subscription_services.subscribe_to_exploration(
            assignee_id, exploration_id)

def release_ownership_of_exploration(committer_id, exploration_id):
    """Releases ownership of the given exploration to the community.

    Args:
        committer_id: str. ID of the committer.
        exploration_id: str. ID of the exploration.

    Raises:
        Exception. This could potentially throw an exception from
            _release_ownership_of_activity.
    """
    _release_ownership_of_activity(
        committer_id, exploration_id, constants.ACTIVITY_TYPE_EXPLORATION)

def set_private_viewability_of_exploration(
        committer_id, exploration_id, viewable_if_private):
    """Sets the viewable_if_private attribute for the given exploration's rights
    object.

    If viewable_if_private is True, this allows a private exploration
    to be viewed by anyone with the link.

    Args:
        committer_id: str. ID of the committer.
        exploration_id: str. ID of the exploration.
        viewable_if_private: bool. Whether the exploration should be made
            viewable (by anyone with the link).

    Raises:
        Exception. The committer does not have the permission to perform change
            action.
        Exception. If the viewable_if_private property is already as desired.
    """
    if not Actor(committer_id).can_change_private_viewability(
            constants.ACTIVITY_TYPE_EXPLORATION, exploration_id):
        logging.error(
            'User %s tried to change private viewability of exploration %s '
            'but was refused permission.' % (committer_id, exploration_id))
        raise Exception(
            'The viewability status of this exploration cannot be changed.')

    exploration_rights = get_exploration_rights(exploration_id)
    old_viewable_if_private = exploration_rights.viewable_if_private
    if old_viewable_if_private == viewable_if_private:
        raise Exception(
            'Trying to change viewability status of this exploration to %s, '
            'but that is already the current value.' % viewable_if_private)

    exploration_rights.viewable_if_private = viewable_if_private
    commit_cmds = [{
        'cmd': CMD_CHANGE_PRIVATE_VIEWABILITY,
        'old_viewable_if_private': old_viewable_if_private,
        'new_viewable_if_private': viewable_if_private,
    }]
    commit_message = (
        'Made exploration viewable to anyone with the link.'
        if viewable_if_private else
        'Made exploration viewable only to invited playtesters.')

    _save_activity_rights(
        committer_id, exploration_rights, constants.ACTIVITY_TYPE_EXPLORATION,
        commit_message, commit_cmds)
    _update_exploration_summary(exploration_rights)

def publish_exploration(committer_id, exploration_id):
    """Publishes the given exploration.

    It is the responsibility of the caller to check that the exploration is
    valid prior to publication.

    Args:
        committer_id: str. ID of the committer.
        exploration_id: str. ID of the exploration.

    Raises:
        Exception. This could potentially throw an exception from
            _publish_activity.
    """
    _publish_activity(
        committer_id, exploration_id, constants.ACTIVITY_TYPE_EXPLORATION)

def unpublish_exploration(committer_id, exploration_id):
    """Unpublishes the given exploration.

    Args:
        committer_id: str. ID of the committer.
        exploration_id: str. ID of the exploration.

    Raises:
        Exception. This could potentially throw an exception from
            _unpublish_activity.
    """
    _unpublish_activity(
        committer_id, exploration_id, constants.ACTIVITY_TYPE_EXPLORATION)

def publicize_exploration(committer_id, exploration_id):
    """Publicizes the given exploration.

    It is the responsibility of the caller to check that the exploration is
    valid prior to publicizing it.

    Args:
        committer_id: str. ID of the committer.
        exploration_id: str. ID of the exploration.

    Raises:
        Exception. This could potentially throw an exception from
            _publicize_activity.
    """
    _publicize_activity(
        committer_id, exploration_id, constants.ACTIVITY_TYPE_EXPLORATION)

def unpublicize_exploration(committer_id, exploration_id):
    """Unpublicizes the given exploration.

    Args:
        committer_id: str. ID of the committer.
        exploration_id: str. ID of the exploration.

    Raises:
        Exception. This could potentially throw an exception from
            _unpublicize_activity.
    """
    _unpublicize_activity(
        committer_id, exploration_id, constants.ACTIVITY_TYPE_EXPLORATION)

# Rights functions for collections.
def assign_role_for_collection(
        committer_id, collection_id, assignee_id, new_role):
    """Assign the given user to the given role and subscribes the assignee
    to future collection updates.

    The caller should ensure that assignee_id corresponds to a valid user in
    the system.

    Args:
        committer_id: str. ID of the committer.
        collection_id: str. ID of the collection.
        assignee_id: str. ID of the user whose role is being changed.
        new_role: str. The name of the new role: One of
            ROLE_OWNER
            ROLE_EDITOR

    Raises:
        Exception. This could potentially throw an exception from
            _assign_role.
    """
    _assign_role(
        committer_id, assignee_id, new_role, collection_id,
        constants.ACTIVITY_TYPE_COLLECTION)
    if new_role in [ROLE_OWNER, ROLE_EDITOR]:
        subscription_services.subscribe_to_collection(
            assignee_id, collection_id)

def release_ownership_of_collection(committer_id, collection_id):
    """Releases ownership of the given collection to the community.

    Args:
        committer_id: str. ID of the committer.
        collection_id: str. ID of the collection.

    Raises:
        Exception. This could potentially throw an exception from
            _release_ownership_of_activity.
    """
    _release_ownership_of_activity(
        committer_id, collection_id, constants.ACTIVITY_TYPE_COLLECTION)

def publish_collection(committer_id, collection_id):
    """Publishes the given collection.

    It is the responsibility of the caller to check that the collection is
    valid prior to publication.

    Args:
        committer_id: str. ID of the committer.
        collection_id: str. ID of the collection.

    Raises:
        Exception. This could potentially throw an exception from
            _publish_activity.
    """
    _publish_activity(
        committer_id, collection_id, constants.ACTIVITY_TYPE_COLLECTION)

def unpublish_collection(committer_id, collection_id):
    """Unpublishes the given collection.

    Args:
        committer_id: str. ID of the committer.
        collection_id: str. ID of the collection.

    Raises:
        Exception. This could potentially throw an exception from
            _unpublish_activity.

    """
    _unpublish_activity(
        committer_id, collection_id, constants.ACTIVITY_TYPE_COLLECTION)

def publicize_collection(committer_id, collection_id):
    """Publicizes the given collection.

    It is the responsibility of the caller to check that the collection is
    valid prior to publicizing it.

    Args:
        committer_id: str. ID of the committer.
        collection_id: str. ID of the collection.

    Raises:
        Exception. This could potentially throw an exception from
            _publicize_activity.
    """
    _publicize_activity(
        committer_id, collection_id, constants.ACTIVITY_TYPE_COLLECTION)

def unpublicize_collection(committer_id, collection_id):
    """Unpublicizes the given collection.

    Args:
        committer_id: str. ID of the committer.
        collection_id: str. ID of the collection.

    Raises:
        Exception. This could potentially throw an exception from
            _unpublicize_activity.
    """
    _unpublicize_activity(
        committer_id, collection_id, constants.ACTIVITY_TYPE_COLLECTION)

def check_can_access_activity(
        user_id, user_actions, activity_type, activity_rights):
    """Checks whether the user can access given activity.

    Args:
        user_id: str or None. Id of the given user.
        user_actions: list(str). List of actions given user can perform.
        activity_type: str. Signifies whether activity is exploration or
            collection.
        activity_rights: rights_object or None. Rights object of the given
            activity.

    Returns:
        bool. Whether the given activity can be accessed.
    """
    action_play_public = (
        role_services.ACTION_PLAY_ANY_PUBLIC_EXPLORATION
        if activity_type == constants.ACTIVITY_TYPE_EXPLORATION
        else role_services.ACTION_PLAY_ANY_PUBLIC_COLLECTION)

    action_play_private = (
        role_services.ACTION_PLAY_ANY_PRIVATE_EXPLORATION
        if activity_type == constants.ACTIVITY_TYPE_EXPLORATION
        else role_services.ACTION_PLAY_ANY_PRIVATE_COLLECTION)

    if activity_rights is None:
        return False
    elif activity_rights.is_published():
        return bool(action_play_public in user_actions)
    elif activity_rights.is_private():
        return bool(
            (action_play_private in user_actions) or
            activity_rights.is_viewer(user_id) or
            activity_rights.is_owner(user_id) or
            activity_rights.is_editor(user_id) or
            activity_rights.viewable_if_private)

def check_can_edit_activity(
        user_id, user_actions, activity_type, activity_rights):
    """Checks whether the user can edit given activity.

    Args:
        user_id: str or None. Id of the given user.
        user_actions: list(str). List of actions the user can perform.
        activity_type: str. Signifies whether activity is exploration or
            collection.
        activity_rights: rights_object or None. Rights object of the given
            activity.

    Returns:
        bool. Whether the given user can edit this activity.
    """
    action_edit_any_activity = (
        role_services.ACTION_EDIT_ANY_EXPLORATION
        if activity_type == constants.ACTIVITY_TYPE_EXPLORATION
        else role_services.ACTION_EDIT_ANY_COLLECTION)
    action_edit_any_public_activity = (
        role_services.ACTION_EDIT_ANY_PUBLIC_EXPLORATION
        if activity_type == constants.ACTIVITY_TYPE_EXPLORATION
        else role_services.ACTION_EDIT_ANY_PUBLIC_COLLECTION)
    action_edit_owned_activity = (
        role_services.ACTION_EDIT_OWNED_EXPLORATION
        if activity_type == constants.ACTIVITY_TYPE_EXPLORATION
        else role_services.ACTION_EDIT_OWNED_COLLECTION)

    if action_edit_owned_activity in user_actions:
        if (activity_rights.is_owner(user_id) or
                activity_rights.is_editor(user_id)):
            return True
    else:
        return False

    if (activity_rights.community_owned or
            (action_edit_any_activity in user_actions)):
        return True

    if (activity_rights.is_published() and
            (action_edit_any_public_activity in user_actions)):
        return True

    return False

def check_can_unpublish_collection(user_actions, collection_rights):
    """Checks whether the user can unpublish given collection.

    Args:
        user_actions: list(str). List of actions the user can perform.
        collection_rights: rights_object or None. Rights object of given
            collection.

    Returns:
        bool. Whether the user can unpublish given collection.
    """
    if collection_rights is None:
        return False

    if (collection_rights.is_published() and
            role_services.ACTION_UNPUBLISH_PUBLIC_COLLECTION in user_actions):
        return True

    return False

def check_can_delete_exploration(user_id, user_actions, exploration_rights):
    """Checks whether the user can delete given exploration.

    Args:
        user_id: str or None. Id of the user.
        user_actions: list(str). List of actions the user can perform.
        exploration_rights: rights_object or None. Rights object of given
            exploration.

    Returns:
        bool. Whether the user can delete given exploration.
    """
    if exploration_rights is None:
        return False

    if (exploration_rights.is_private() and
            (role_services.ACTION_DELETE_OWNED_PRIVATE_EXPLORATION in (
                user_actions)) and
            exploration_rights.is_owner(user_id)):
        return True
    elif (exploration_rights.is_published() and
          role_services.ACTION_DELETE_ANY_PUBLIC_EXPLORATION in (
              user_actions)):
        return True
    return False

def check_can_modify_exploration_roles(
        user_id, user_actions, exploration_rights):
    """Checks whether the user can modify roles for given exploration.

    Args:
        user_id: str or None. Id of the user.
        user_actions: list(str). List of actions the user can perform.
        exploration_rights: rights_object or None. Rights Object of given
            exploration.

    Returns:
        bool. Whether the user can modify roles for given exploration.
    """
    if exploration_rights is None:
        return False

    if (exploration_rights.community_owned or
            exploration_rights.cloned_from):
        return False

    if (role_services.ACTION_MODIFY_ROLES_FOR_ANY_EXPLORATION in
            user_actions):
        return True
    if (role_services.ACTION_MODIFY_ROLES_FOR_OWNED_EXPLORATION in
            user_actions):
        if exploration_rights.is_owner(user_id):
            return True
    return False

def check_can_release_ownership(user_id, user_actions, exploration_rights):
    """Checks whether the user can release ownership for given exploration.

    Args:
        user_id: str or None. Id of the user.
        user_actions: list(str). List of actions the user can perform.
        exploration_rights: rights_object or None. Rights Object of given
            exploration.

    Returns:
        bool. Whether the user can release ownership for given exploration.
    """
    if exploration_rights is None:
        return False

    if exploration_rights.is_private():
        return False

    return check_can_modify_exploration_roles(
        user_id, user_actions, exploration_rights)

def check_can_publish_exploration(user_id, user_actions, exploration_rights):
    """Checks whether the user can publish given exploration.

    Args:
        user_id: str or None. Id of the user.
        user_actions: list(str). List of actions the user can perform.
        exploration_rights: rights_object or None. Rights Object of given
            exploration.

    Returns:
        bool. Whether the user can publish given exploration.
    """
    if exploration_rights is None:
        return False

    if exploration_rights.cloned_from:
        return False

    if role_services.ACTION_PUBLISH_ANY_EXPLORATION in user_actions:
        return True

    if exploration_rights.is_private():
        if role_services.ACTION_PUBLISH_OWNED_EXPLORATION in user_actions:
            if exploration_rights.is_owner(user_id):
                return True

    return False

def check_can_publicize_exploration(user_actions, exploration_rights):
    """Checks whether the user can publicize given exploration.

    Args:
        user_actions: list(str). List of actions the user can perform.
        exploration_rights: rights_object or None. Rights object of given
            exploration.

    Returns:
        bool. Whether the user can publicize given exploration.
    """
    if exploration_rights is None:
        return False

    if exploration_rights.status == ACTIVITY_STATUS_PUBLIC:
        if role_services.ACTION_PUBLICIZE_EXPLORATION in user_actions:
            return True

def check_can_unpublicize_exploration(user_actions, exploration_rights):
    """Checks whether the user can unpublicize given exploration.

    Args:
        user_actions: list(str). List of actions the user can perform.
        exploration_rights: rights_object or None. Rights object of given
            exploration.

    Returns:
        bool. Whether the user can unpublicize given exploration.
    """
    if exploration_rights is None:
        return False

    if exploration_rights.status == ACTIVITY_STATUS_PUBLICIZED:
        if role_services.ACTION_UNPUBLICIZE_EXPLORATION in user_actions:
            return True

def check_can_unpublish_exploration(user_actions, exploration_rights):
    """Checks whether the user can unpublish given exploration.

    Args:
        user_actions: list(str). List of actions the user can perform.
        exploration_rights: rights_object or None. Rights object of given
            exploration.

    Returns:
        bool. Whether the user can unpublish given exploration.
    """
    if exploration_rights is None:
        return False

    if exploration_rights.community_owned:
        return False

    if exploration_rights.status == ACTIVITY_STATUS_PUBLIC:
        if role_services.ACTION_UNPUBLISH_PUBLIC_EXPLORATION in user_actions:
            return True
    return False


text:
using _10.Create_Custom_Class_Attribute.Entities.Interfaces;
using _10.Create_Custom_Class_Attribute.Factories;
using System.Linq;

namespace _10.Create_Custom_Class_Attribute.Core.Command
{
    public class AddCommand : Command
    {
        public AddCommand(Db database, string[] tokens) : base(database, tokens)
        {
        }

        public override void Ecexute()
        {
            // Input tokens
            string name = this.Tokens[0];
            int targetSocket = int.Parse(this.Tokens[1]);
            string gemLevelOfQuality = this.Tokens[2].Split().ToArray()[0];
            string gemType = this.Tokens[2].Split().ToArray()[1];

            IGem gem = GemFactory.GetGem(gemType, gemLevelOfQuality); // Get new gem
            IWeapon weapon = this.Database.Weapons.FirstOrDefault(n => n.Name == name); // Search for weapon

            if (gem != null && weapon != null)
            {
                // Both gem and weapon are not null
                weapon.AddGem(targetSocket, gem);
            }
        }
    }
}

redpajama_set_name: RedPajamaGithub


text:
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Collections.Generic;

// Test derived from dotnet/corefx src\System.Numerics.Vectors\src\System\Numerics\Matrix4x4.cs, op_Multiply().
// This was an ARM32-specific bug for addressing local variables as floats. ARM32 floating-point instructions
// have a different offset range than integer instructions. If the local variable itself is a struct, but the
// instruction generated is a float local field, then we were computing the offset as integer, but the actual
// instruction was float. In certain frame layouts, the range will be out of range in this case, but we will
// not have allocated a "reserved" register which is used for generating large offsets.
//
// The key functions in the JIT that are related are Compiler::compRsvdRegCheck() and Compiler::lvaFrameAddress().

public class Test
{
    public struct BigStruct
    {
        public float float1;
        public float float2;
        public float float3;
        public float float4;
        public float float5;
        public float float6;
        public float float7;
        public float float8;
        public float float9;
        public float float10;
        public float float11;
        public float float12;
        public float float13;
        public float float14;
        public float float15;
        public float float16;
        public float float17;
        public float float18;
        public float float19;
        public float float20;
        public float float21;
        public float float22;
        public float float23;
        public float float24;
        public float float25;
        public float float26;
        public float float27;
        public float float28;
        public float float29;
        public float float30;
        public float float31;
        public float float32;
        public float float33;
        public float float34;
        public float float35;
        public float float36;
        public float float37;
        public float float38;
        public float float39;
        public float float40;
        public float float41;
        public float float42;
        public float float43;
        public float float44;
        public float float45;
        public float float46;
        public float float47;
        public float float48;
        public float float49;
        public float float50;
        public float float51;
        public float float52;
        public float float53;
        public float float54;
        public float float55;
        public float float56;
        public float float57;
        public float float58;
        public float float59;
        public float float60;
        public float float61;
        public float float62;
        public float float63;
        public float float64;
        public float float65;
        public float float66;
        public float float67;
        public float float68;
        public float float69;
        public float float70;
        public float float71;
        public float float72;
        public float float73;
        public float float74;
        public float float75;
        public float float76;
        public float float77;
        public float float78;
        public float float79;
        public float float80;
        public float float81;
        public float float82;
        public float float83;
        public float float84;
        public float float85;
        public float float86;
        public float float87;
        public float float88;
        public float float89;
        public float float90;
        public float float91;
        public float float92;
        public float float93;
        public float float94;
        public float float95;
        public float float96;
        public float float97;
        public float float98;
        public float float99;
        public float float100;
        public float float101;
        public float float102;
        public float float103;
        public float float104;
        public float float105;
        public float float106;
        public float float107;
        public float float108;
        public float float109;
        public float float110;
        public float float111;
        public float float112;
        public float float113;
        public float float114;
        public float float115;
        public float float116;
        public float float117;
        public float float118;
        public float float119;
        public float float120;
        public float float121;
        public float float122;
        public float float123;
        public float float124;
        public float float125;
        public float float126;
        public float float127;
        public float float128;
        public float float129;
        public float float130;
        public float float131;
        public float float132;
        public float float133;
        public float float134;
        public float float135;
        public float float136;
        public float float137;
        public float float138;
        public float float139;
        public float float140;
        public float float141;
        public float float142;
        public float float143;
        public float float144;
        public float float145;
        public float float146;
        public float float147;
        public float float148;
        public float float149;
        public float float150;
        public float float151;
        public float float152;
        public float float153;
        public float float154;
        public float float155;
        public float float156;
        public float float157;
        public float float158;
        public float float159;
        public float float160;
        public float float161;
        public float float162;
        public float float163;
        public float float164;
        public float float165;
        public float float166;
        public float float167;
        public float float168;
        public float float169;
        public float float170;
        public float float171;
        public float float172;
        public float float173;
        public float float174;
        public float float175;
        public float float176;
        public float float177;
        public float float178;
        public float float179;
        public float float180;
        public float float181;
        public float float182;
        public float float183;
        public float float184;
        public float float185;
        public float float186;
        public float float187;
        public float float188;
        public float float189;
        public float float190;
        public float float191;
        public float float192;
        public float float193;
        public float float194;
        public float float195;
        public float float196;
        public float float197;
        public float float198;
        public float float199;
        public float float200;
        public float float201;
        public float float202;
        public float float203;
        public float float204;
        public float float205;
        public float float206;
        public float float207;
        public float float208;
        public float float209;
        public float float210;
        public float float211;
        public float float212;
        public float float213;
        public float float214;
        public float float215;
        public float float216;
        public float float217;
        public float float218;
        public float float219;
        public float float220;
        public float float221;
        public float float222;
        public float float223;
        public float float224;
        public float float225;
        public float float226;
        public float float227;
        public float float228;
        public float float229;
        public float float230;
        public float float231;
        public float float232;
        public float float233;
        public float float234;
        public float float235;
        public float float236;
        public float float237;
        public float float238;
        public float float239;
        public float float240;
        public float float241;
        public float float242;
        public float float243;
        public float float244;
        public float float245;
        public float float246;
        public float float247;
        public float float248;
        public float float249;
        public float float250;
        public float float251;
        public float float252;
        public float float253;
        public float float254;
        public float float255;
    }

    public struct Matrix4x4
    {
        public float M11;
        public float M12;
        public float M13;
        public float M14;

        public float M21;
        public float M22;
        public float M23;
        public float M24;

        public float M31;
        public float M32;
        public float M33;
        public float M34;

        public float M41;
        public float M42;
        public float M43;
        public float M44;

        /// <summary>
        /// Constructs a Matrix4x4 from the given components.
        /// </summary>
        public Matrix4x4(float m11, float m12, float m13, float m14,
                         float m21, float m22, float m23, float m24,
                         float m31, float m32, float m33, float m34,
                         float m41, float m42, float m43, float m44)
        {
            this.M11 = m11;
            this.M12 = m12;
            this.M13 = m13;
            this.M14 = m14;

            this.M21 = m21;
            this.M22 = m22;
            this.M23 = m23;
            this.M24 = m24;

            this.M31 = m31;
            this.M32 = m32;
            this.M33 = m33;
            this.M34 = m34;

            this.M41 = m41;
            this.M42 = m42;
            this.M43 = m43;
            this.M44 = m44;
        }

        /// <summary>
        /// Returns a boolean indicating whether the given two matrices are equal.
        /// </summary>
        /// <param name="value1">The first matrix to compare.</param>
        /// <param name="value2">The second matrix to compare.</param>
        /// <returns>True if the given matrices are equal; False otherwise.</returns>
        public static bool Equals(Matrix4x4 value1, Matrix4x4 value2)
        {
            return (value1.M11 == value2.M11 && value1.M22 == value2.M22 && value1.M33 == value2.M33 && value1.M44 == value2.M44 && // Check diagonal element first for early out.
                    value1.M12 == value2.M12 && value1.M13 == value2.M13 && value1.M14 == value2.M14 && value1.M21 == value2.M21 &&
                    value1.M23 == value2.M23 && value1.M24 == value2.M24 && value1.M31 == value2.M31 && value1.M32 == value2.M32 &&
                    value1.M34 == value2.M34 && value1.M41 == value2.M41 && value1.M42 == value2.M42 && value1.M43 == value2.M43);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void AddHelper(ref BigStruct b)
        {
            b.float1 += 1.0F;
            b.float255 += 2.0F;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static Matrix4x4 AddTest(Matrix4x4 value1, Matrix4x4 value2)
        {
            BigStruct b = new BigStruct();

            b.float1   = value1.M11 + value2.M11;
            b.float255 = value1.M12 + value2.M12;

            AddHelper(ref b);

            Matrix4x4 m;

            m = value1;
            m.M11 = b.float1 + b.float255;
            m.M12 = b.float1 - b.float255;

            return m;
        }

        /// <summary>
        /// Returns a String representing this matrix instance.
        /// </summary>
        /// <returns>The string representation.</returns>
        public override string ToString()
        {
            return string.Format("{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} }}",
                                 M11, M12, M13, M14,
                                 M21, M22, M23, M24,
                                 M31, M32, M33, M34,
                                 M41, M42, M43, M44);
        }

        public static int Main()
        {
            Matrix4x4 m1 = new Matrix4x4(1.0F,2.0F,3.0F,4.0F,
                                         5.0F,6.0F,7.0F,8.0F,
                                         9.0F,10.0F,11.0F,12.0F,
                                         13.0F,14.0F,15.0F,16.0F);
            Matrix4x4 m2 = new Matrix4x4(13.0F,14.0F,15.0F,16.0F,
                                         9.0F,10.0F,11.0F,12.0F,
                                         5.0F,6.0F,7.0F,8.0F,
                                         1.0F,2.0F,3.0F,4.0F);

            Matrix4x4 m3 = AddTest(m1,m2);

            Matrix4x4 mresult = new Matrix4x4(33.0F,-3.0F,3.0F,4.0F,
                                              5.0F,6.0F,7.0F,8.0F,
                                              9.0F,10.0F,11.0F,12.0F,
                                              13.0F,14.0F,15.0F,16.0F);
            if (Equals(m3,mresult))
            {
                Console.WriteLine("PASS");
                return 100;
            }
            else
            {
                Console.WriteLine("FAIL: matrices don't match");
                Console.WriteLine(" m3      = {0}", m3.ToString());
                Console.WriteLine(" mresult = {0}", mresult.ToString());
                return 1;
            }
        }

    }
}


text:
/*
Plugin: Viewer.Carousel
    Viewer plugin for automatic cycling through elements.
    Inspired on bootstrap"s implementation, relying on css3 transitions;
    rewritten for mootools.

Credit:
    bootstrap-carousel.js v2.2.2 http://twitter.github.com/bootstrap/javascript.html#carousel

Depends on:
    Viewer

DOM structure:

    (start code)
    div.carousel
        a.xxx
        a.xxx
    (end)

    becomes
    (start code)
    div.carousel[.slide]

      ol.carousel-indicators
        li.active[data-target="#myCarousel"][data-slide-to="0"]
        li[data-target="#myCarousel"][data-slide-to="1"]
        li[data-target="#myCarousel"][data-slide-to="2"]

      <!-- Carousel items -->
      div.carousel-inner
        div.item.active
          img | object | iframe
        div.item
          img | object | iframe
        ...

      <!-- Carousel nav -->
      a.controls.prev[href="#myCarousel"][data-slide="prev"] <
      a.controls.next[href="#myCarousel"][data-slide="next"] >
    (end)

Example:
>    new Carousel(container, [el1, el2], {container:..., ...} );

*/
Viewer.Carousel = new Class({

    Binds: ["build", "cycle", "stop", "next", "prev", "slid"],
    Implements: [Events, Options],

    options: {
        cycle: 1e4, //=> when set, the carousel automatically cycles through all items
        width: 400, // Default width of the carousel (in pixels)
        height: 300 // Default height of the carousel (in pixels)
    },

    initialize: function(elements, options){

        var self = this,
            t = "transitionend";

        options = self.setOptions(options).options;
        //console.log("CAROUSEL Options: ", options);

        self.css3 = Element.Events[t] ? t : null;
        self.element = options.container;

        Viewer.preloads( $$(elements), options, self.build );

    },

    build: function(elements, width, height){

        var self = this,
            items = [], indicators = [],
            cycle = self.options.cycle,
            NOP = function(){};

        $$(elements).each( function(el){

            items.push("div.item", [
                el, { styles: {
                    //add padding to center the item inside its container, but still fill 100% of the available space
                    padding: Number(height - el.height).limit(0, height) / 2 + "px " +
                             Number(width - el.width).limit(0, width) / 2 + "px"
                }},
                "div.carousel-caption", {
                    html: (el.title || el.alt || el.textContent)
                }
            ]);
            indicators.push("li");
        });
        items[0] += ".active";
        indicators[0] += ".active";

        self.element.empty()
            .set({
                "class": "carousel",
                //maxHeight,maxHeight will auto-scale the images if they are too big to fit
                styles: { maxWidth: width, maxHeight: height },
                events: {
                    "click:relay(li)": function(){ self.to(this.getAllPrevious().length); },
                    mouseenter: cycle ? self.stop : NOP,
                    mouseleave: cycle ? self.cycle : NOP
                }
            })
            .adopt([
                "div.carousel-progress", /*ffs {"transitionDuration":self.cycle/1e3+"s"},*/
                "ol.carousel-indicators",
                    indicators,
                "div.carousel-inner",
                    items,
                "a.controls.prev[html=<]", {events: { click: self.prev }},
                "a.controls.next[html=>]", {events: { click: self.next }}
                ].slick()
            );

        //self.cycle();  only start cycling after a first mouseenter , next()

    },

    get: function( selector ){
        return this.element.getElements(selector);
    },

    /*
    Function: cycle
        Cycle through the carousel items.
        - invoked on initialization
        - invoked on mouseleave
        - invokde at end of the sliding operations
    */
    cycle: function(/*event*/){

        var self = this,
            cycle = self.options.cycle;

        if( cycle && !self.sliding ){
            self.stop(); // make sure to first clear the tid
            self.tid = self.next.delay( cycle );
            self.element.addClass("sliding");
        }

    },

    /*
    Function: stop
        Stop the autocycle mechanism.
        - invoked on mouseenter
        - invoked at start of the sliding operation
    */
    stop: function( /*event*/ ){

        //console.log("stop ", this.tid, this.sliding, arguments);
        clearTimeout( this.tid );
        this.tid = null;
        this.element.removeClass("sliding");

    },

    /*
    Function: to
        Slide directly to a specific carousel item.
        Not yet used.
    */
    to: function( pos ){

        var self = this,
            items = self.get(".item"),
            item = items[pos],
            active = items.indexOf( items.filter(".active")[0] );

        if ( !item ){ return; }

        if( self.sliding ){

            //console.log("concurrency betweeen slide() and pos() - wf "slid" event to occur");
            self.element.addEvent("slid", self.to.pass(pos) );

        } else if ( !item.match(".active") ){

            self.slide( pos > active ? "next" : "prev", item );

        }

    },

    next: function(){
        if( !this.sliding ){ this.slide("next"); }
    },

    prev: function () {
        if( !this.sliding ){ this.slide("prev"); }
    },

    /*
    Function:slide
        Move the carousel to the next item.
        It fully relies on css3 transition.
        If not supported, no animation-effects are applied (lazy me)

    stable =>
    >    .active  => left:0;
    >    .next    => left:100%;
    >    .prev    => left:-100%;

    slide-type = next => Slide to left:
    >    item.active.left   => left:-100%;
    >    item.next.left     => left:0;

    slide-type = prev => Slide to right:
    >    item.active.right  => left:100%;
    >    item.prev.right    => left:0;

    Arguments
        - type : "next","prev"
        - next : element to be shown at end of the "slide" ( array )

    */
    slide: function(type, next){

        var self = this,
            active = self.get(".item.active")[0],
            gonext = (type == "next"),
            css3 = self.css3,
            slid = self.slid;

        self.sliding = true;
        self.stop();

        next = next ||
            active[ gonext ? "getNext" : "getPrevious" ]() ||
            active.getParent()[ gonext ? "getFirst" : "getLast" ]();

        if( next.match(".active") ){ return; }

        next.addClass( type ); //.next or .prev

        self.fireEvent("slide");

        if( css3 ){

            //console.log("transition: "+css3)
            next.offsetWidth; // force reflow -- is this really needed ?
            $$(active, next).addClass( gonext ? "left" : "right" );
            self.element.addEvent( css3, slid );

        } else {

            slid();

        }

    },

    slid: function(){

        var self = this,
            items = self.get(".item"),
            newActive = self.get(".item.next,.item.prev")[0];

        if( newActive ){

            items.set("class", "item");  //wipe out .active, .next, .prev, .left, .right
            newActive.addClass("active");

            self.get("li").set("class", "")[items.indexOf(newActive)].addClass("active");

            self.sliding = false;
            self.cycle();
            self.fireEvent("slid", 0/*dummy*/, 1/*delay 1ms*/);

        }

    }

});

/*
Extension : css3 native events
    Extend mootools to support css3 native events
    (needed by Viewer.Carousel)

Credits:
    Inspired by  https://github.com/amadeus/CSSEvents/

Example:
>    $(element).addEvent("transitionend",function(event){ ...})

*/
!(function(css3){

var B = Browser,
    NativeEvents = Element.NativeEvents,
    pfx = B.cssprefix = (B.safari || B.chrome || B.platform.ios) ? "webkit" : (B.opera) ? "o" : (B.ie) ? "ms" : "";

    for ( style in css3 ){

        var eventType = css3[style],
            type = eventType.toLowerCase(),
            aType = pfx ? pfx + eventType : type,
            aTest = pfx ? pfx + style.capitalize() : style;

        if( document.createElement("div").style[ aTest ] != null ){

            NativeEvents[type] = NativeEvents[aType] = 2;
            Element.Events[type] = { base: aType };

        }

        //console.log(Element.NativeEvents, Element.Events);

    }

})({transition: "TransitionEnd"});
//})({transition:"TransitionStart",transition:"TransitionEnd",animation:"AnimationStart",animation:"AnimationIteration",animation:"AnimationEnd");


text:
<?php

declare (strict_types=1);
namespace RectorPrefix20210615\Symplify\Astral\NodeTraverser;

use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Expression;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\NodeVisitorAbstract;
final class SimpleCallableNodeTraverser
{
    /**
     * @param Node|Node[]|null $nodes
     */
    public function traverseNodesWithCallable($nodes, callable $callable) : void
    {
        if ($nodes === null) {
            return;
        }
        if ($nodes === []) {
            return;
        }
        if (!\is_array($nodes)) {
            $nodes = [$nodes];
        }
        $nodeTraverser = new \PhpParser\NodeTraverser();
        $callableNodeVisitor = $this->createNodeVisitor($callable);
        $nodeTraverser->addVisitor($callableNodeVisitor);
        $nodeTraverser->traverse($nodes);
    }
    private function createNodeVisitor(callable $callable) : \PhpParser\NodeVisitor
    {
        return new class($callable) extends \PhpParser\NodeVisitorAbstract
        {
            /**
             * @var callable
             */
            private $callable;
            public function __construct(callable $callable)
            {
                $this->callable = $callable;
            }
            /**
             * @return int|Node|null
             */
            public function enterNode(\PhpParser\Node $node)
            {
                $originalNode = $node;
                $callable = $this->callable;
                /** @var int|Node|null $newNode */
                $newNode = $callable($node);
                if ($originalNode instanceof \PhpParser\Node\Stmt && $newNode instanceof \PhpParser\Node\Expr) {
                    return new \PhpParser\Node\Stmt\Expression($newNode);
                }
                return $newNode;
            }
        };
    }
}