Esempio n. 1
0
class SEO(DBResource):

    class_id = 'config-seo'
    class_title = MSG(u'Search Engine Optimization')
    class_description = seo_description
    class_icon16 = '/ui/ikaaro/icons/16x16/search.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/search.png'

    # Fields
    google_site_verification = Char_Field(
        title=MSG(u'Google site verification key'))
    yahoo_site_verification = Char_Field(
        title=MSG(u'Yahoo site verification key'))
    bing_site_verification = Char_Field(
        title=MSG(u'Bing site verification key'))

    # Views
    class_views = ['edit']
    edit = AutoEdit(title=MSG(u'Search engine optimization'),
                    description=seo_description,
                    fields=[
                        'google_site_verification', 'yahoo_site_verification',
                        'bing_site_verification'
                    ])

    # Configuration
    config_name = 'seo'
    config_group = 'webmaster'
Esempio n. 2
0
class ConfigVHosts(DBResource):

    class_id = 'config-vhosts'
    class_title = MSG(u'Virtual Hosts')
    class_description = MSG(u'Define the domain names for this Web Site.')
    class_icon48 = '/ui/ikaaro/icons/48x48/website.png'

    # Fields
    vhosts = Char_Field(multiple=True, title=MSG(u'Domain names'),
                        widget=MultilineWidget)

    # Views
    class_views = ['edit']
    edit = ConfigVHosts_Edit

    # Configuration
    config_name = 'vhosts'
    config_group = 'webmaster'
Esempio n. 3
0
class Root(Folder):

    class_id = 'iKaaro'
    class_version = '20180428'
    class_title = MSG(u'iKaaro')
    class_icon16 = '/ui/ikaaro/icons/16x16/root.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/root.png'
    class_skin = 'aruni'

    abspath = Path('/')

    # Config
    context_cls = CMSContext

    # Fields
    website_languages = Char_Field(multiple=True, default=['en'])

    def init_resource(self, email, password):
        super(Root, self).init_resource()
        # Configuration
        title = {'en': u'Configuration'}
        self.make_resource('config', Configuration, title=title)
        # First user
        user = self.make_user(email, password)
        user.set_value('groups', ['/config/groups/admins'])

    def make_resource(self, name, cls, **kw):
        if name == 'ui':
            raise ValueError, 'cannot add a resource with the name "ui"'
        return super(Root, self).make_resource(name, cls, **kw)

    ########################################################################
    # Override itools.web.root.Root
    ########################################################################
    def get_user_title(self, userid):
        if not userid:
            return None

        # Userid (abspath) or username
        if userid[0] != '/':
            userid = '/users/%s' % userid

        # Get user
        user = self.get_resource(userid, soft=True)
        if user is None:
            username = userid.rsplit('/', 1)[-1]
            log_warning('unkwnown user %s' % username, domain='ikaaro')
            return unicode(username)

        # Ok
        return user.get_title()

    ########################################################################
    # Publish
    ########################################################################
    def internal_server_error(self, context):
        # Send email (TODO Move this to the itools.log system)
        self.alert_on_internal_server_error(context)

        # Ok
        namespace = {'traceback': traceback.format_exc()}
        handler = context.get_template(
            '/ui/ikaaro/root/internal_server_error.xml')
        return stl(handler, namespace, mode='html')

    def alert_on_internal_server_error(self, context):
        # TODO Move this to the itools.log system
        # Get email address
        email = context.server.config.get_value('log-email')
        # We send an email with the traceback
        if email:
            headers = u'\n'.join(
                [u'%s => %s' % (x, y) for x, y in context.get_headers()])
            subject = MSG(u'Internal server error').gettext()
            text = u'%s\n\n%s\n\n%s' % (context.uri, traceback.format_exc(),
                                        headers)
            self.send_email(email, subject, text=text)

    ########################################################################
    # Traverse
    ########################################################################
    def _get_names(self):
        return [x for x in super(Root, self)._get_names() if x]

    ########################################################################
    # Start / Stop API
    ########################################################################
    def launch_at_start(self, context):
        """Method called at instance start"""
        self.update_to_078()

    def launch_at_stop(self, context):
        """Method called at instance stop"""
        pass

    ########################################################################
    # API
    ########################################################################
    def get_default_language(self):
        return self.get_value('website_languages')[0]

    def get_default_edit_languages(self):
        return [self.get_default_language()]

    def before_traverse(self,
                        context,
                        min=Decimal('0.000001'),
                        zero=Decimal('0.0')):
        # Set the language cookie if specified by the query.
        # NOTE We do it this way, instead of through a specific action,
        # to avoid redirections.
        language = context.get_form_value('language')
        if language is not None and language != '':
            context.set_cookie('language', language)

        # The default language (give a minimum weight)
        accept = context.accept_language
        default = self.get_default_language()
        if accept.get(default, zero) < min:
            accept.set(default, min)
        # User Profile (2.0)
        user = context.user
        if user is not None:
            language = user.get_value('user_language')
            if language is not None:
                accept.set(language, 2.0)
        # Cookie (2.5)
        language = context.get_cookie('language')
        if language is not None and language != '':
            accept.set(language, 2.5)

    def get_skin(self, context):
        # Open in fancybox ?
        if (getattr(context.view, 'can_be_open_in_fancybox', False)
                and 'fancybox' in context.uri.query):
            return skin_registry['fancybox']
        # Back-Office
        hostname = context.uri.authority
        if hostname[:3] in ['bo.', 'bo-']:
            return skin_registry['aruni']
        # Fron-Office
        return skin_registry[self.class_skin]

    def after_traverse(self, context):
        body = context.entity
        is_str = type(body) is str
        is_xml = is_xml_stream(body)
        if not is_str and not is_xml:
            return

        # If there is not a content type, just serialize the content
        if context.content_type:
            if is_xml:
                context.entity = stream_to_str_as_html(body)
            return

        # Standard page, wrap the content into the general template
        if is_str:
            body = XMLParser(body, doctype=xhtml_doctype)
        context.entity = self.get_skin(context).template(body)
        context.content_type = 'text/html; charset=UTF-8'

    def get_available_languages(self):
        """Returns the language codes for the user interface.
        """
        source = itools_source_language
        target = itools_target_languages
        # A package based on itools
        cls = self.__class__
        if cls is not Root:
            exec('import %s as pkg' % cls.__module__.split('.', 1)[0])
            config = Path(pkg.__path__[0]).resolve_name('setup.conf')
            config = ConfigFile(str(config))
            source = config.get_value('source_language', default=source)
            target = config.get_value('target_languages', default=target)

        target = target.split()
        if source in target:
            target.remove(source)

        target.insert(0, source)
        return target

    def get_version_of_packages(self, context):
        # Python, itools & ikaaro
        packages = ['sys', 'itools', 'ikaaro']
        config = context.server.config
        packages.extend(config.get_value('modules'))
        # Try packages we frequently use
        packages.extend([
            'gio', 'xapian', 'pywin32', 'PIL.Image', 'docutils', 'reportlab',
            'xlrd', 'lpod'
        ])
        # Mapping from package to version attribute
        package2version = {
            'gio': 'pygio_version',
            'xapian': 'version_string',
            'PIL.Image': 'VERSION',
            'reportlab': 'Version',
            'sys': 'version_info',
            'xlrd': '__VERSION__'
        }

        # Namespace
        versions = {}
        for name in packages:
            attribute = package2version.get(name, '__version__')

            # Exception: PIL
            if name == 'PIL.Image':
                name = 'PIL'
                try:
                    package = __import__('Image', fromlist=['PIL'])
                except ImportError:
                    continue
            # XXX Skip stuff like 'ikaaro.blog', etc.
            elif '.' in name:
                continue
            # Common case
            else:
                try:
                    package = __import__(name)
                except ImportError:
                    continue

            # Version
            try:
                version = getattr(package, attribute)
            except AttributeError:
                version = None
            else:
                if hasattr(version, '__call__'):
                    version = version()
                if type(version) is tuple or name == 'sys':
                    version = '.'.join([str(v) for v in version])
            # Ok
            versions[name] = version

        # Insert first the platform
        versions['os'] = {
            'linux2': u'GNU/Linux',
            'darwin': u'Mac OS X',
            'win32': u'Windows'
        }.get(sys.platform, sys.platform)
        return versions

    ########################################################################
    # Email
    def send_email(self,
                   to_addr,
                   subject,
                   reply_to=None,
                   text=None,
                   html=None,
                   encoding='utf-8',
                   subject_with_host=True,
                   return_receipt=False,
                   attachment=None):
        # 1. Check input data
        if type(subject) is unicode:
            subject = subject.encode(encoding)
        elif isinstance(subject, MSG):
            subject = subject.gettext()
        else:
            raise TypeError, 'unexpected subject of type %s' % type(subject)

        if len(subject.splitlines()) > 1:
            raise ValueError, 'the subject cannot have more than one line'
        if text and not isinstance(text, unicode):
            raise TypeError, 'the text must be a Unicode string'
        if html and not isinstance(html, unicode):
            raise TypeError, 'the html must be a Unicode string'

        # 2. Local variables
        context = get_context()
        server = context.server
        mail = self.get_resource('/config/mail')

        # 3. Start the message
        message = MIMEMultipart('related')
        message['Date'] = formatdate(localtime=True)

        # 4. From
        from_addr = mail.get_value('emails_from_addr').strip()
        if from_addr:
            # FIXME Parse the address and use Header
            message['From'] = from_addr.encode(encoding)
        else:
            message['From'] = server.smtp_from

        # 5. To
        if isinstance(to_addr, tuple):
            real_name, address = to_addr
            to_addr = '%s <%s>' % (Header(real_name, encoding), address)
        message['To'] = to_addr

        # 6. Subject
        if subject_with_host is True and context.uri:
            subject = '[%s] %s' % (context.uri.authority, subject)
        message['Subject'] = Header(subject, encoding)

        # 7. Reply-To
        if reply_to:
            message['Reply-To'] = reply_to
        elif mail.get_value('emails_reply_to'):
            user = context.user
            if user:
                user_title = Header(user.get_title(), encoding)
                user_email = user.get_value('email')
                message['Reply-To'] = '%s <%s>' % (user_title, user_email)

        # Return Receipt
        if return_receipt and reply_to:
            message['Disposition-Notification-To'] = reply_to  # Standard
            message['Return-Receipt-To'] = reply_to  # Outlook 2000

        # 8. Body
        signature = mail.get_value('emails_signature')
        if signature:
            signature = signature.strip()
            if not signature.startswith('--'):
                signature = '-- \n%s' % signature
            text += '\n\n%s' % signature

        # Create MIMEText
        if html:
            html = html.encode(encoding)
            message_html = MIMEText(html, 'html', _charset=encoding)
        if text:
            text = text.encode(encoding)
            message_text = MIMEText(text, _charset=encoding)
        # Attach MIMETEXT to message
        if text and html:
            message_alternative = MIMEMultipart('alternative')
            message.attach(message_alternative)
            message_alternative.attach(message_text)
            message_alternative.attach(message_html)
        elif html:
            message.attach(message_html)
        elif text:
            message.attach(message_text)
        # Attach attachment
        if attachment:
            subtype = attachment.get_mimetype()
            data = attachment.to_str()
            if subtype[:6] == 'image/':
                subtype = subtype[6:]
                mime_cls = MIMEImage
            else:
                mime_cls = MIMEApplication
            message_attachment = mime_cls(data, subtype)
            message_attachment.add_header('Content-Disposition',
                                          'attachment',
                                          filename=attachment.name)
            message.attach(message_attachment)

        # 6. Send email
        server.send_email(message)

    #######################################################################
    # Access control
    #######################################################################
    def make_user(self, loginname=None, password=None):
        """
        Create a new user into the database.
        :param loginname: The user login (not mandatory)
        :param password: The user password (not mandatory)
        :return: the user or None if a user with the same login already exist
        """
        # Check if the user with the same login exist
        if self.get_user_from_login(loginname) is not None:
            return
        # Create the user
        users = self.get_resource('/users')
        cls = self.database.get_resource_class('user')
        user = users.make_resource(None, cls)

        # Set login name and paswword
        if loginname is not None:
            user.set_property(user.login_name_property, loginname)
        if password is not None:
            user.set_value('password', password)

        # Return the user
        return user

    def is_allowed_to_register(self, user, resource):
        if user:
            return False
        return self.get_resource('config/register').get_value('is_open')

    def is_admin(self, user, resource):
        if user is None:
            return False

        return '/config/groups/admins' in user.get_value('groups')

    def has_permission(self, user, permission, resource, class_id=None):
        if resource is None:
            return False
        access = self.get_resource('config/access')
        return access.has_permission(user, permission, resource, class_id)

    def is_allowed_to_view(self, user, resource):
        return self.has_permission(user, 'view', resource)

    def is_allowed_to_edit(self, user, resource):
        return self.has_permission(user, 'edit', resource)

    def is_allowed_to_add(self, user, resource):
        return self.has_permission(user, 'add', resource)

    def is_allowed_to_share(self, user, resource):
        return self.has_permission(user, 'share', resource)

    # By default all other change operations (add, remove, copy, etc.)
    # are equivalent to "edit".
    def is_allowed_to_put(self, user, resource):
        return self.has_permission(user, 'edit', resource)

    def is_allowed_to_remove(self, user, resource):
        return self.has_permission(user, 'edit', resource)

    def is_allowed_to_copy(self, user, resource):
        return self.has_permission(user, 'edit', resource)

    def is_allowed_to_move(self, user, resource):
        return self.has_permission(user, 'edit', resource)

    def get_user(self, name):
        return self.get_resource('users/%s' % name, soft=True)

    def get_user_from_login(self, username):
        """Return the user identified by its unique e-mail or username, or
        return None.
        """
        # Search the user by username (login name)
        database = self.database
        results = database.search(parent_paths='/users', username=username)

        n = len(results)
        if n == 0:
            return None
        if n > 1:
            error = 'There are %s users in the database identified as "%s"'
            raise ValueError, error % (n, username)

        # Get the user
        brain = results.get_documents()[0]
        return self.get_user(brain.name)

    update_20170106_title = MSG(u'Add uuid to all resources')

    def update_20170106(self):
        i = 0
        context = get_context()
        context.set_mtime = False
        for resource in self.traverse_resources():
            if not resource.get_value('uuid'):
                resource.set_uuid()
                i += 1
            if i and i % 100 == 0:
                context.database.save_changes()

    def update_to_078(self):
        """
        Migrate database from 077 to 078
        It's move static files into database static
        """
        from itools.fs.lfs import lfs, LocalFolder
        from itools.fs import lfs
        import os
        import shutil
        context = get_context()
        database_path = context.database.path + '/database'
        # Create database static
        database_static_path = context.database.path + '/database_static'
        if not lfs.exists(database_static_path):
            lfs.make_folder(database_static_path)
        # Check if migration has already be done
        f = LocalFolder(database_static_path)
        items = list(f.traverse())
        if len(items) > 2:
            return
        # Move static files into database static
        f = LocalFolder(database_path)
        for p in f.traverse():
            db_path = p.replace(database_path, '')
            if db_path.startswith('/.git'):
                continue
            if f.is_file(p) and not p.endswith('.metadata'):
                new_path = p.replace(database_path, database_static_path)
                print('Move {0} {1}'.format(p, new_path))
                source = lfs._resolve_path(p)
                target = lfs._resolve_path(new_path)
                # Create folder
                parent_path = os.path.dirname(target)
                if not os.path.exists(parent_path):
                    os.makedirs(parent_path)
                # Use shutil to be compatible with symbolic links
                shutil.move(source, target)
        worktree = context.database.backend.worktree
        worktree._call(['git', 'add', '-u'])
        worktree._call(
            ['git', 'commit', '-m', 'Move static files in database_static'])

    #######################################################################
    # Views
    #######################################################################
    register = RegisterForm()
    terms_of_service = TermsOfService_View()
    # Public views
    contact = ContactForm()
    powered_by = PoweredBy()
    # Web views (401/403/404/405...)
    forbidden = ForbiddenView()
    unauthorized = LoginView()
    not_found = NotFoundView()
    method_not_allowed = NotAllowedView()
    unavailable = UnavailableView()
    # Special
    upload_stats = UploadStatsView()
    update_instance = UpdateInstanceView()
    update_docs = UpdateDocs()
    _ctrl = CtrlView()
Esempio n. 4
0
class DBResource(Resource):

    class_version = '20071215'
    class_description = None
    class_icon16 = 'icons/16x16/resource.png'
    class_icon48 = 'icons/48x48/resource.png'
    class_views = []
    context_menus = []

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

    def __eq__(self, resource):
        if not isinstance(resource, DBResource):
            error = "cannot compare DBResource and %s" % type(resource)
            raise TypeError, error

        return self.abspath == resource.abspath

    def __ne__(self, node):
        return not self.__eq__(node)

    #######################################################################
    # API / Tree
    #######################################################################
    @property
    def database(self):
        return self.metadata.database

    @lazy
    def parent(self):
        abspath = self.abspath
        if len(abspath) == 0:
            return None
        return self.get_resource(abspath[:-1])

    @property
    def name(self):
        return self.abspath.get_name()

    def get_root(self):
        return self.get_resource('/')

    def get_pathto(self, resource):
        return self.abspath.get_pathto(resource.abspath)

    #######################################################################
    # API / Folderish
    #######################################################################
    __fixed_handlers__ = []  # Resources that cannot be removed

    @property
    def handler(self):
        cls = FolderHandler
        key = self.metadata.key[:-9]
        handler = self.database.get_handler(key, cls=cls, soft=True)
        if handler is None:
            handler = cls()
            self.database.push_phantom(key, handler)

        return handler

    def get_handlers(self):
        """Return all the handlers attached to this resource, except the
        metadata.
        """
        handlers = [self.handler]
        # Fields
        for name, field in self.get_fields():
            if issubclass(field, File_Field):
                value = field.get_value(self, name)
                if value is not None:
                    handlers.append(value)

        # Ok
        return handlers

    def _get_names(self):
        folder = self.handler
        return [
            x[:-9] for x in folder.get_handler_names() if x[-9:] == '.metadata'
        ]

    def get_names(self, path='.'):
        resource = self.get_resource(path)
        return resource._get_names()

    def get_resource(self, path, soft=False):
        if type(path) is not Path:
            path = Path(path)

        # 1. Get the metadata
        if path.is_absolute():
            abspath = path
        else:
            abspath = self.abspath.resolve2(path)

        return self.database.get_resource(abspath, soft=soft)

    def get_resources(self, path='.'):
        here = self.get_resource(path)
        for name in here._get_names():
            yield here.get_resource(name)

    def make_resource_name(self):
        max_id = -1
        for name in self.get_names():
            # Mixing explicit and automatically generated names is allowed
            try:
                id = int(name)
            except ValueError:
                continue
            if id > max_id:
                max_id = id

        return str(max_id + 1)

    def make_resource(self, name, cls, soft=False, **kw):
        # Automatic name
        if name is None:
            name = self.make_resource_name()

        # Make a resource somewhere else
        if '/' in name:
            path = dirname(name)
            name = basename(name)
            resource = self.get_resource(path)
            resource.make_resource(name, cls, soft=soft, **kw)
            return

        # Soft
        if soft is True:
            resource = self.get_resource(name, soft=True)
            if resource:
                return resource

        # Make the metadata
        metadata = Metadata(cls=cls)
        self.handler.set_handler('%s.metadata' % name, metadata)
        metadata.set_property('mtime', get_context().timestamp)
        # Initialize
        resource = self.get_resource(name)
        self.database.add_resource(resource)
        resource.init_resource(**kw)
        # Ok
        return resource

    def del_resource(self, name, soft=False, ref_action='restrict'):
        """ref_action allows to specify which action is done before deleting
        the resource.
        ref_action can take 2 values:
        - 'restrict' (default value): do an integrity check
        - 'force': do nothing
        """
        database = self.database
        resource = self.get_resource(name, soft=soft)
        if soft and resource is None:
            return

        # Referential action
        if ref_action == 'restrict':
            # Check referencial-integrity (FIXME Check sub-resources too)
            path = str(resource.abspath)
            query_base_path = get_base_path_query(path)
            query = AndQuery(PhraseQuery('links', path),
                             NotQuery(query_base_path))
            results = database.search(query)
            # A resource may have been updated in the same transaction,
            # so not yet reindexed: we need to check that the resource
            # really links.
            for referrer in results.get_resources():
                if path in referrer.get_links():
                    err = 'cannot delete, resource "%s" is referenced'
                    raise ConsistencyError, err % path
        elif ref_action == 'force':
            # Do not check referencial-integrity
            pass
        else:
            raise ValueError, 'Incorrect ref_action "%s"' % ref_action

        # Events, remove
        path = str(resource.abspath)
        database.remove_resource(resource)
        # Remove
        fs = database.fs
        for handler in resource.get_handlers():
            # Skip empty folders and phantoms
            if fs.exists(handler.key):
                database.del_handler(handler.key)
        self.handler.del_handler('%s.metadata' % name)
        # Clear cookie
        context = get_context()
        cut, paths = context.get_cookie('ikaaro_cp', datatype=CopyCookie)
        if path in paths:
            context.del_cookie('ikaaro_cp')

    def copy_resource(self, source, target):
        raise NotImplementedError

    def move_resource(self, source, target):
        raise NotImplementedError

    def traverse_resources(self):
        yield self
        for name in self._get_names():
            resource = self.get_resource(name)
            for x in resource.traverse_resources():
                yield x

    #######################################################################
    # API / Views
    #######################################################################
    def get_default_view_name(self):
        views = self.class_views
        if not views:
            return None
        context = get_context()
        for view_name in views:
            view = getattr(self, view_name, None)
            if context.is_access_allowed(self, view):
                return view_name
        return views[0]

    def get_view(self, name, query=None):
        # To define a default view, override this
        if name is None:
            name = self.get_default_view_name()
            if name is None:
                return None

        # Explicit view, defined by name
        view = getattr(self, name, None)
        if is_prototype(view, BaseView):
            context = get_context()
            view = view(resource=self, context=context)  # bind
            return view

        return None

    def get_context_menus(self):
        return self.context_menus

    ########################################################################
    # Properties
    ########################################################################
    def get_value(self, name, language=None):
        field = self.get_field(name)
        if field is None:
            return None
        return field.get_value(self, name, language)

    def set_value(self, name, value, language=None, **kw):
        field = self.get_field(name)
        if field.multilingual and language is None:
            raise ValueError, 'Field %s is multilingual' % name
        if field is None:
            raise ValueError, 'Field %s do not exist' % name
        return field.set_value(self, name, value, language, **kw)

    def get_value_title(self, name, language=None):
        field = self.get_field(name)
        if field is None:
            return None
        return field.get_value_title(self, name, language)

    def get_brain_value(self, name):
        brain = get_context().database.search(
            PhraseQuery('abspath', str(self.abspath))).get_documents()[0]
        return getattr(brain, name, None)

    def get_html_field_body_stream(self, name, language=None):
        """Utility method, returns the stream for the given html field.
        """
        # 1. Check it is an html-file field
        field = self.get_field(name)
        if not is_prototype(field, HTMLFile_Field):
            raise ValueError, 'expected html-file field'

        # 2. Get the handler
        handler = field.get_value(self, name, language)
        if not handler:
            handler = field.class_handler()

        # 3. Get the body
        body = handler.get_body()
        if not body:
            raise ValueError, 'html file does not have a body'
        return body.get_content_elements()

    def get_property(self, name, language=None):
        property = self.metadata.get_property(name, language=language)
        if property:
            return property

        field = self.get_field(name)
        if field is None:
            return None

        default = field.get_default()
        if field.multiple:
            return [Property(x) for x in default]
        return Property(default)

    # XXX Backwards compatibility
    set_property = set_value

    def get_page_title(self):
        return self.get_title()

    def init_resource(self, **kw):
        """Return a Metadata object with sensible default values.
        """
        # Ownership
        owner = self.get_field('owner')
        if owner:
            user = get_context().user
            if user:
                self.set_value('owner', str(user.abspath))

        # Keyword parameters
        for name, value in kw.items():
            field = self.get_field(name)
            if field is None:
                raise ValueError, 'undefined field "%s"' % name
            if type(value) is dict:
                for lang in value:
                    field._set_value(self, name, value[lang], lang)
            else:
                field._set_value(self, name, value)

    def load_handlers(self):
        self.get_handlers()

    ########################################################################
    # Fields
    ########################################################################
    mtime = Datetime_Field(indexed=True, stored=True, readonly=True)
    last_author = Char_Field(indexed=False, stored=True, readonly=True)
    title = Text_Field(indexed=True, stored=True, title=MSG(u'Title'))
    description = Textarea_Field(indexed=True,
                                 title=MSG(u'Description'),
                                 hidden_by_default=True)
    subject = Text_Field(indexed=True,
                         title=MSG(u'Keywords'),
                         hidden_by_default=True)
    share = Share_Field

    @property
    def is_content(self):
        return self.parent.is_content

    def has_property(self, name, language=None):
        return self.metadata.has_property(name, language=language)

    def del_property(self, name):
        if self.has_property(name):
            self.database.change_resource(self)
            self.metadata.del_property(name)

    ########################################################################
    # Versioning
    ########################################################################
    def get_files_to_archive(self, content=False):
        metadata = self.metadata.key
        if content is True:
            folder = self.handler.key
            return [metadata, folder]
        return [metadata]

    def get_revisions(self,
                      n=None,
                      content=False,
                      author_pattern=None,
                      grep_pattern=None):
        if self.parent is None and content is True:
            files = None
        else:
            files = self.get_files_to_archive(content)

        worktree = self.database.worktree
        return worktree.git_log(files, n, author_pattern, grep_pattern)

    def get_owner(self):
        return self.get_value('owner')

    def get_share(self):
        return self.get_value('share')

    ########################################################################
    # Indexing
    ########################################################################
    def to_text(self):
        """This function must return:
           1) An unicode text.
            or
           2) A dict in a multilingual context:
              {'fr': u'....',
               'en': u'....' ....}
        """
        raise NotImplementedError

    def get_catalog_values(self):
        values = {}

        # Step 1. Automatically index fields
        languages = self.get_root().get_value('website_languages')
        for name, field in self.get_fields():
            if not field.indexed and not field.stored:
                continue

            if field.multilingual:
                value = {}
                for language in languages:
                    value[language] = field.get_value(self, name, language)
                values[name] = value
            else:
                values[name] = field.get_value(self, name)

        # Step 2. Index non-metadata properties
        # Path related fields
        abspath = self.abspath
        values['abspath'] = str(abspath)
        n = len(abspath)
        values['abspath_depth'] = n
        if n:
            values['parent_paths'] = [str(abspath[:i]) for i in range(n)]

        values['name'] = self.name
        values['is_content'] = self.is_content

        # Class related fields
        values['format'] = self.metadata.format
        values['base_classes'] = []
        for cls in self.__class__.__mro__:
            class_id = getattr(cls, 'class_id', None)
            if class_id:
                values['base_classes'].append(class_id)

        # Links to other resources
        values['owner'] = self.get_owner()
        values['share'] = self.get_share()
        values['links'] = list(self.get_links())
        values['onchange_reindex'] = self.get_onchange_reindex()

        # Full text
        context = get_context()
        try:
            server = context.server
        except AttributeError:
            server = None
        if server is not None and server.index_text:
            try:
                values['text'] = self.to_text()
            except NotImplementedError:
                pass
            except Exception:
                log = 'Indexation failed: %s' % abspath
                log_warning(log, domain='ikaaro')

        # Time events
        reminder, payload = self.next_time_event()
        values['next_time_event'] = reminder
        values['next_time_event_payload'] = dumps(payload)

        # Ok
        return values

    def get_onchange_reindex(self):
        return None

    #######################################################################
    # Time events
    #######################################################################
    def next_time_event(self):
        return None, None

    def time_event(self, payload):
        raise NotImplementedError

    #######################################################################
    # API
    #######################################################################
    def rename_handlers(self, new_name):
        """Consider we want to rename this resource to the given 'new_name',
        return the old a new names for all the attached handlers (except the
        metadata).

        This method is required by the "move_resource" method.
        """
        langs = self.get_resource('/').get_value('website_languages')

        aux = [(self.name, new_name)]
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field and issubclass(field, File_Field):
                old = '%s.%s' % (self.name, field_name)
                new = '%s.%s' % (new_name, field_name)
                if field.multilingual:
                    for language in langs:
                        aux.append(('%s.%s' % (old, language),
                                    '%s.%s' % (new, language)))
                else:
                    aux.append((old, new))

        return aux

    def _on_move_resource(self, source):
        """This method updates the links from/to other resources.  It is
        called when the resource has been moved and/or renamed.

        This method is called by 'Database._before_commit', the 'source'
        parameter is the place the resource has been moved from.
        """
        # (1) Update links to other resources
        self.update_incoming_links(Path(source))

        # (2) Update resources that link to me
        database = self.database
        target = self.abspath
        query = PhraseQuery('links', source)
        results = database.search(query).get_documents()
        for result in results:
            path = result.abspath
            path = database.resources_old2new.get(path, path)
            resource = self.get_resource(path)
            resource.update_links(source, target)

    def get_links(self):
        # Automatically from the fields
        languages = self.get_resource('/').get_value('website_languages')
        links = set()
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.get_links(links, self, field_name, languages)

        # Support for dynamic models
        class_id = self.metadata.format
        if class_id[0] == '/':
            links.add(class_id)

        # Ok
        return links

    def update_links(self, source, target):
        """The resource identified by 'source' is going to be moved to
        'target'.  Update our links to it.

        The parameters 'source' and 'target' are absolute 'Path' objects.
        """
        base = str(self.abspath)
        old_base = self.database.resources_new2old.get(base, base)
        old_base = Path(old_base)
        new_base = Path(base)
        languages = self.get_resource('/').get_value('website_languages')

        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.update_links(self, field_name, source, target, languages,
                                   old_base, new_base)

        self.database.change_resource(self)

    def update_incoming_links(self, source):
        """Update the relative links coming out from this resource after it
        was moved, so they are not broken. The old path is in parameter. The
        new path is "self.abspath".
        """
        languages = self.get_resource('/').get_value('website_languages')
        for field_name in self.fields:
            field = self.get_field(field_name)
            if field:
                field.update_incoming_links(self, field_name, source,
                                            languages)

    ########################################################################
    # Upgrade
    ########################################################################
    def get_next_versions(self):
        cls_version = self.class_version
        obj_version = self.metadata.version
        # Set zero version if the resource does not have a version
        if obj_version is None:
            obj_version = '00000000'

        # Get all the version numbers
        versions = []
        for cls in self.__class__.mro():
            for name in cls.__dict__.keys():
                if not name.startswith('update_'):
                    continue
                kk, version = name.split('_', 1)
                if len(version) != 8:
                    continue
                try:
                    int(version)
                except ValueError:
                    continue
                if version > obj_version and version <= cls_version:
                    versions.append(version)

        versions.sort()
        return versions

    def update(self, version):
        # Action
        getattr(self, 'update_%s' % version)()

        # If the action removes the resource, we are done
        metadata = self.metadata
        if metadata.key is None:
            return

        # Update version
        metadata.set_changed()
        metadata.version = version

    #######################################################################
    # Icons
    #######################################################################
    @classmethod
    def get_class_icon(cls, size=16):
        icon = getattr(cls, 'class_icon%s' % size, None)
        if icon is None:
            return None
        return '/ui/%s' % icon

    @classmethod
    def get_resource_icon(cls, size=16):
        icon = getattr(cls, 'icon%s' % size, None)
        if icon is None:
            return cls.get_class_icon(size)
        return ';icon%s' % size

    def get_method_icon(self, view, size='16x16', **kw):
        icon = getattr(view, 'icon', None)
        if icon is None:
            return None
        if callable(icon):
            icon = icon(self, **kw)
        return '/ui/icons/%s/%s' % (size, icon)

    #######################################################################
    # User interface
    #######################################################################
    def get_views(self):
        context = get_context()
        for name in self.class_views:
            view_name = name.split('?')[0]
            view = self.get_view(view_name)
            if context.is_access_allowed(self, view):
                yield name, view

    def get_title(self, language=None):
        title = self.get_value('title', language=language)
        if title:
            return title
        # Fallback to the resource's name
        return unicode(self.name)

    def get_edit_languages(self, context):
        root = self.get_root()
        site_languages = root.get_value('website_languages')
        default = root.get_default_edit_languages()

        # Can not use context.query[] because edit_language is not necessarily
        # defined
        datatype = String(multiple=True, default=default)
        edit_languages = context.get_query_value('edit_language', datatype)
        edit_languages = [x for x in edit_languages if x in site_languages]

        return edit_languages if edit_languages else default

    #######################################################################
    # Cut & Paste Resources
    #######################################################################
    def can_paste(self, source):
        """Is the source resource can be pasted into myself.
        Question is "can I handle this type of resource?"
        """
        raise NotImplementedError

    def can_paste_into(self, target):
        """Can I be pasted into the given target.
        Question is "Is this container compatible with myself?"
        """
        # No restriction by default. Functional modules will want to keep
        # their specific resources for them.
        return True

    # Views
    new_instance = AutoAdd(fields=['title', 'location'])
    edit = AutoEdit(fields=['title', 'description', 'subject', 'share'])
    remove = DBResource_Remove
    get_file = DBResource_GetFile
    get_image = DBResource_GetImage
    # Login/Logout
    login = LoginView
    logout = LogoutView
    # Popups
    add_image = DBResource_AddImage
    add_link = DBResource_AddLink
    add_media = DBResource_AddMedia
    # Commit log
    commit_log = DBResource_CommitLog
    changes = DBResource_Changes
    # Links
    backlinks = DBResource_Backlinks
    links = DBResource_Links
    # External editor
    http_put = Put_View
    http_delete = Delete_View
    # Rest (web services)
    rest_login = Rest_Login
    rest_query = Rest_Query
    rest_create = Rest_Create
    rest_read = Rest_Read
    rest_update = Rest_Update
    rest_delete = Rest_Delete
    rest_schema = Rest_Schema
Esempio n. 5
0
class User(DBResource):

    class_id = 'user'
    class_version = '20081217'
    class_title = MSG(u'User')
    class_icon16 = '/ui/ikaaro/icons/16x16/user.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/user.png'
    class_views = [
        'profile', 'edit_account', 'edit_preferences', 'edit_password',
        'edit_groups'
    ]

    # Fields
    firstname = Text_Field(multilingual=False,
                           indexed=True,
                           stored=True,
                           title=MSG(u'First Name'))
    lastname = Text_Field(multilingual=False,
                          indexed=True,
                          stored=True,
                          title=MSG(u'Last Name'))
    email = UserEmail_Field
    password = Password_Field(multiple=True)
    avatar = File_Field(title=MSG(u'Avatar'))
    user_language = Char_Field
    user_timezone = Char_Field
    user_state = UserState_Field
    groups = UserGroups_Field
    username = Char_Field(indexed=True, stored=True)  # Backwards compatibility

    # Remove some fields
    title = None
    description = None
    subject = None
    text = None

    ########################################################################
    # Indexing
    ########################################################################
    def get_catalog_values(self):
        values = super(User, self).get_catalog_values()

        # email domain
        email = self.get_value('email')
        if email and '@' in email:
            values['email_domain'] = email.split('@', 1)[1]

        # username (overrides default)
        values['username'] = self.get_login_name()

        # groups
        values['groups'] = self.get_value('groups')

        return values

    ########################################################################
    # API / Authentication
    ########################################################################
    def get_user_id(self):
        # Used by itools.web
        return str(self.name)

    def get_password(self):
        password = self.get_property('password')
        return password[-1] if password else None

    def get_auth_token(self):
        # Used by itools.web
        password = self.get_password()
        return password.value if password else None

    def authenticate(self, password):
        my_password = self.get_password()
        if my_password is None:
            return False
        algo = my_password.get_parameter('algo', 'sha1')
        salt = my_password.get_parameter('salt', '')

        password_hashed, salt = get_secure_hash(password, algo, salt)
        return password_hashed == my_password.value

    def _login(self, password, context):
        # We call this method '_login' to avoid a name clash with the login
        # view.

        if not self.authenticate(password):
            error = MSG_LOGIN_WRONG_NAME_OR_PASSWORD
        elif self.get_value('user_state') == 'inactive':
            error = ERROR(
                u'Your account has been canceled, contact the administrator '
                u' if you want to get access again.')
        else:
            error = None
            context.login(self)

        # To activate this feature set the lastlog field
        lastlog = self.get_field('lastlog')
        if lastlog:
            success = error is None
            self.set_value('lastlog', context.timestamp, success=success)

        # Ok
        return error

    def update_pending_key(self):
        state = self.get_property('user_state')
        if state.value == 'pending':
            # TODO Implement expiration
            return state.get_parameter('key')

        key = generate_password(30)
        self.set_value('user_state', 'pending', key=key)
        return key

    ########################################################################
    # API
    ########################################################################
    def get_owner(self):
        return str(self.abspath)

    def get_title(self, language=None):
        firstname = self.get_value('firstname')
        lastname = self.get_value('lastname')
        if firstname:
            if lastname:
                return '%s %s' % (firstname, lastname)
            return firstname
        if lastname:
            return lastname
        return self.get_login_name().decode('utf-8')

    login_name_property = 'email'

    def get_login_name(self):
        return self.get_value(self.login_name_property)

    def get_timezone(self):
        return self.get_value('user_timezone')

    #######################################################################
    # Views
    #######################################################################
    resend_confirmation = User_ResendConfirmation
    confirm_registration = User_ConfirmRegistration
    change_password_forgotten = User_ChangePasswordForgotten
    profile = User_Profile
    edit_account = User_EditAccount
    edit_preferences = User_EditPreferences
    edit_password = User_EditPassword
    edit_groups = AutoEdit(access='is_admin',
                           fields=['groups'],
                           title=MSG(u'Edit groups'))
Esempio n. 6
0
class File(DBResource):

    class_id = 'file'
    class_version = '20090122'
    class_title = MSG(u'File')
    class_description = MSG(
        u'Upload office documents, images, media files, etc.')
    class_icon16 = '/ui/ikaaro/icons/16x16/file.png'
    class_icon48 = '/ui/ikaaro/icons/48x48/file.png'
    class_views = [
        'view', 'edit', 'externaledit', 'remove', 'subscribe', 'links',
        'backlinks', 'commit_log'
    ]

    # Fields
    data = File_Field(required=True, class_handler=FileHandler)
    filename = Char_Field()
    owner = Owner_Field()

    def get_all_extensions(self):
        format = self.metadata.format
        # FIXME This is a hack, compression encodings are not yet properly
        # supported (to do for the next major version).
        if format == 'application/x-gzip':
            extensions = ['gz', 'tgz']
        elif format == 'application/x-bzip2':
            extensions = ['bz2', 'tbz2']
        else:
            cls = self.data.class_handler
            extensions = [x[1:] for x in guess_all_extensions(format)]
            if cls.class_extension in extensions:
                extensions.remove(cls.class_extension)
            extensions.insert(0, cls.class_extension)
        return extensions

    #######################################################################
    # Versioning & Indexing
    #######################################################################
    def to_text(self):
        data = self.get_value('data')
        return data and data.to_text() or u''

    def get_files_to_archive(self, content=False):
        # Handlers
        files = [x.key for x in self.get_handlers()]
        # Metadata
        metadata = self.metadata.key
        files.append(metadata)
        return files

    #######################################################################
    # User Interface
    #######################################################################
    def get_content_type(self):
        return self.get_value('data').get_mimetype()

    # Views
    new_instance = File_NewInstance()
    download = File_Download()
    view = File_View()
    edit = File_Edit()
    externaledit = File_ExternalEdit_View()
    external_edit = File_ExternalEdit()