##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""This module implements a simple item mix-in for objects that have a
very simple (e.g. one-screen) management interface, like documents,
Aqueduct database adapters, etc.

This module can also be used as a simple template for implementing new
item types.

$Id: SimpleItem.py 40468 2005-12-02 13:45:58Z chrisw $
"""

import warnings
import marshal, re, sys, time

import AccessControl.Role, AccessControl.Owned, App.Common
import Globals, App.Management, Acquisition, App.Undo
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl import getSecurityManager, Unauthorized
from AccessControl.Permissions import view as View
from AccessControl.ZopeSecurityPolicy import getRoles
from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
from ComputedAttribute import ComputedAttribute
from DocumentTemplate.html_quote import html_quote
from DocumentTemplate.ustr import ustr
from ExtensionClass import Base
from webdav.Resource import Resource
from zExceptions import Redirect
from zExceptions.ExceptionFormatter import format_exception
from zope.interface import implements

import ZDOM
from CopySupport import CopySource
from interfaces import IItem
from interfaces import IItemWithName
from interfaces import ISimpleItem
from Traversable import Traversable

HTML=Globals.HTML

import logging
logger = logging.getLogger()

class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
           ZDOM.Element,
           AccessControl.Owned.Owned,
           App.Undo.UndoSupport,
           ):
    """A common base class for simple, non-container objects."""

    implements(IItem)

    security = ClassSecurityInfo()

    isPrincipiaFolderish=0
    isTopLevelPrincipiaApplicationObject=0

    def manage_afterAdd(self, item, container):
        pass
    manage_afterAdd.__five_method__ = True

    def manage_beforeDelete(self, item, container):
        pass
    manage_beforeDelete.__five_method__ = True

    def manage_afterClone(self, item):
        pass
    manage_afterClone.__five_method__ = True

    # Direct use of the 'id' attribute is deprecated - use getId()
    id=''

    security.declarePublic('getId')
    def getId(self):
        """Return the id of the object as a string.

        This method should be used in preference to accessing an id attribute
        of an object directly. The getId method is public.
        """
        name=getattr(self, 'id', None)
        if callable(name):
            return name()
        if name is not None:
            return name
        if hasattr(self, '__name__'):
            return self.__name__
        raise AttributeError, 'This object has no id'

    # Alias id to __name__, which will make tracebacks a good bit nicer:
    __name__=ComputedAttribute(lambda self: self.getId())

    # Name, relative to SOFTWARE_URL of icon used to display item
    # in folder listings.
    icon=''

    # Meta type used for selecting all objects of a given type.
    meta_type='simple item'

    # Default title.
    title=''

    # Default propertysheet info:
    __propsets__=()

    manage_options=(
        App.Undo.UndoSupport.manage_options
        +AccessControl.Owned.Owned.manage_options
        +({'label': 'Interfaces',
           'action': 'manage_interfaces'},)
        )

    # Attributes that must be acquired
    REQUEST=Acquisition.Acquired

    # Allow (reluctantly) access to unprotected attributes
    __allow_access_to_unprotected_subobjects__=1

    def title_or_id(self):
        """Return the title if it is not blank and the id otherwise.
        """
        title=self.title
        if callable(title):
            title=title()
        if title: return title
        return self.getId()

    def title_and_id(self):
        """Return the title if it is not blank and the id otherwise.

        If the title is not blank, then the id is included in parens.
        """
        title=self.title
        if callable(title):
            title=title()
        id = self.getId()
        return title and ("%s (%s)" % (title,id)) or id

    def this(self):
        # Handy way to talk to ourselves in document templates.
        return self

    def tpURL(self):
        # My URL as used by tree tag
        return self.getId()

    def tpValues(self):
        # My sub-objects as used by the tree tag
        return ()

    _manage_editedDialog=Globals.DTMLFile('dtml/editedDialog', globals())
    def manage_editedDialog(self, REQUEST, **args):
        return apply(self._manage_editedDialog,(self, REQUEST), args)

    def raise_standardErrorMessage(
        self, client=None, REQUEST={},
        error_type=None, error_value=None, tb=None,
        error_tb=None, error_message='',
        tagSearch=re.compile(r'[a-zA-Z]>').search,
        error_log_url=''):

        try:
            if error_type  is None: error_type =sys.exc_info()[0]
            if error_value is None: error_value=sys.exc_info()[1]

            # allow for a few different traceback options
            if tb is None and error_tb is None:
                tb=sys.exc_info()[2]
            if type(tb) is not type('') and (error_tb is None):
                error_tb = pretty_tb(error_type, error_value, tb)
            elif type(tb) is type('') and not error_tb:
                error_tb = tb

            # turn error_type into a string
            if hasattr(error_type, '__name__'):
                error_type=error_type.__name__

            if hasattr(self, '_v_eek'):
                # Stop if there is recursion.
                raise error_type, error_value, tb
            self._v_eek=1

            if str(error_type).lower() in ('redirect',):
                raise error_type, error_value, tb

            if not error_message:
                try:
                    s = ustr(error_value)
                except:
                    s = error_value
                try:
                    match = tagSearch(s)
                except TypeError:
                    match = None
                if match is not None:
                    error_message=error_value

            if client is None: client=self
            if not REQUEST: REQUEST=self.aq_acquire('REQUEST')

            try:
                if hasattr(client, 'standard_error_message'):
                    s=getattr(client, 'standard_error_message')
                else:
                    client = client.aq_parent
                    s=getattr(client, 'standard_error_message')
                kwargs = {'error_type': error_type,
                          'error_value': error_value,
                          'error_tb': error_tb,
                          'error_traceback': error_tb,
                          'error_message': error_message,
                          'error_log_url': error_log_url}

                if getattr(aq_base(s),'isDocTemp',0):
                    v = s(client, REQUEST, **kwargs)
                elif callable(s):
                    v = s(**kwargs)
                else:
                    v = HTML.__call__(s, client, REQUEST, **kwargs)
            except:
                logger.error(
                    'Exception while rendering an error message',
                    exc_info=True
                    )
                try:
                    strv = str(error_value)
                except:
                    strv = ('<unprintable %s object>' % 
                            str(type(error_value).__name__))
                v = strv + (
                    (" (Also, the following error occurred while attempting "
                     "to render the standard error message, please see the "
                     "event log for full details: %s)")%(
                    html_quote(sys.exc_info()[1]),
                    ))
            raise error_type, v, tb
        finally:
            if hasattr(self, '_v_eek'): del self._v_eek
            tb=None

    def manage(self, URL1):
        """
        """
        raise Redirect, "%s/manage_main" % URL1

    # This keeps simple items from acquiring their parents
    # objectValues, etc., when used in simple tree tags.
    def objectValues(self, spec=None):
        return ()
    objectIds=objectItems=objectValues

    # FTP support methods

    def manage_FTPstat(self,REQUEST):
        """Psuedo stat, used by FTP for directory listings.
        """
        from AccessControl.User import nobody
        mode=0100000

        if (hasattr(aq_base(self),'manage_FTPget')):
            try:
                if getSecurityManager().validate(
                    None, self, 'manage_FTPget', self.manage_FTPget):
                    mode=mode | 0440
            except Unauthorized:
                pass

            if nobody.allowed(
                self.manage_FTPget,
                getRoles(self, 'manage_FTPget', self.manage_FTPget, ()),
                ):
                mode=mode | 0004

        # check write permissions
        if hasattr(aq_base(self),'PUT'):
            try:
                if getSecurityManager().validate(None, self, 'PUT', self.PUT):
                    mode=mode | 0220
            except Unauthorized:
                pass

            if nobody.allowed(
                self.PUT,
                getRoles(self, 'PUT', self.PUT, ()),
                ):
                mode=mode | 0002

        # get size
        if hasattr(aq_base(self), 'get_size'):
            size=self.get_size()
        elif hasattr(aq_base(self),'manage_FTPget'):
            size=len(self.manage_FTPget())
        else:
            size=0
        # get modification time
        if hasattr(aq_base(self), 'bobobase_modification_time'):
            mtime=self.bobobase_modification_time().timeTime()
        else:
            mtime=time.time()
        # get owner and group
        owner=group='Zope'
        if hasattr(aq_base(self), 'get_local_roles'):
            for user, roles in self.get_local_roles():
                if 'Owner' in roles:
                    owner=user
                    break
        return marshal.dumps((mode,0,0,1,owner,group,size,mtime,mtime,mtime))

    def manage_FTPlist(self,REQUEST):
        """Directory listing for FTP.

        In the case of non-Foldoid objects, the listing should contain one
        object, the object itself.
        """
        # check to see if we are being acquiring or not
        ob=self
        while 1:
            if App.Common.is_acquired(ob):
                raise ValueError('FTP List not supported on acquired objects')
            if not hasattr(ob,'aq_parent'):
                break
            ob=ob.aq_parent

        stat=marshal.loads(self.manage_FTPstat(REQUEST))
        id = self.getId()
        return marshal.dumps((id,stat))

    def __len__(self):
        return 1

    def __repr__(self):
        """Show the physical path of the object and its context if available.
        """
        try:
            path = '/'.join(self.getPhysicalPath())
        except:
            return Base.__repr__(self)
        context_path = None
        context = aq_parent(self)
        container = aq_parent(aq_inner(self))
        if aq_base(context) is not aq_base(container):
            try:
                context_path = '/'.join(context.getPhysicalPath())
            except:
                context_path = None
        res = '<%s' % self.__class__.__name__
        res += ' at %s' % path
        if context_path:
            res += ' used for %s' % context_path
        res += '>'
        return res

InitializeClass(Item)


class Item_w__name__(Item):

    """Mixin class to support common name/id functions"""

    implements(IItemWithName)

    def getId(self):
        """Return the id of the object as a string.
        """
        return self.__name__

    def title_or_id(self):
        """Return the title if it is not blank and the id otherwise.
        """
        return self.title or self.__name__

    def title_and_id(self):
        """Return the title if it is not blank and the id otherwise.

        If the title is not blank, then the id is included in parens.
        """
        t=self.title
        return t and ("%s (%s)" % (t,self.__name__)) or self.__name__

    def _setId(self, id):
        self.__name__=id

    def getPhysicalPath(self):
        """Get the physical path of the object.

        Returns a path (an immutable sequence of strings) that can be used to
        access this object again later, for example in a copy/paste operation.
        getPhysicalRoot() and getPhysicalPath() are designed to operate
        together.
        """
        path = (self.__name__,)

        p = aq_parent(aq_inner(self))
        if p is not None:
            path = p.getPhysicalPath() + path

        return path


def pretty_tb(t, v, tb, as_html=1):
    tb = format_exception(t, v, tb, as_html=as_html)
    tb = '\n'.join(tb)
    return tb


class SimpleItem(Item, Globals.Persistent,
                 Acquisition.Implicit,
                 AccessControl.Role.RoleManager,
                 ):

    # Blue-plate special, Zope Masala
    """Mix-in class combining the most common set of basic mix-ins
    """

    implements(ISimpleItem)

    security = ClassSecurityInfo()
    security.setPermissionDefault(View, ('Manager',))

    manage_options=Item.manage_options+(
        {'label':'Security',
         'action':'manage_access',
         'help':('OFSP', 'Security.stx')},
        )

InitializeClass(SimpleItem)
