Пример #1
0
class GitConnector(Component):
    implements(IRepositoryConnector, IWikiSyntaxProvider, IPropertyRenderer)

    _persistent_cache = BoolOption('git', 'persistent_cache', 'false',
                       "Enable persistent caching of commit tree")

    _cached_repository = BoolOption('git', 'cached_repository', 'false',
                    "Wrap `GitRepository` in `CachedRepository`")

    _shortrev_len = IntOption('git', 'shortrev_len', 7,
                  "Length rev sha sums should be tried to be abbreviated to"
                  " (must be >= 4 and <= 40)")

    _git_bin = PathOption('git', 'git_bin', '/usr/bin/git',
                  "Path to git executable (relative to trac project folder!)")

    def __init__(self):
        self._version = None

        try:
            self._version = PyGIT.Storage.git_version(git_bin=self._git_bin)
        except PyGIT.GitError, e:
            self.log.error("GitError: %s", e)

        if self._version:
            self.log.info("detected GIT version %s", self._version['v_str'])
            self.env.systeminfo.append(('GIT', self._version['v_str']))
            if not self._version['v_compatible']:
                self.log.error("GIT version %s installed not compatible "
                               "(need >= %s)" , self._version['v_str'],
                               self._version['v_min_str'])
Пример #2
0
class BittenNotify(Component):
    """Sends notifications on build status by mail."""
    implements(IBuildListener, ITemplateProvider)

    notify_on_failure = BoolOption('notification', 'notify_on_failed_build',
                                   'true', """Notify if bitten build fails.""")

    notify_on_success = BoolOption('notification',
                                   'notify_on_successful_build', 'false',
                                   """Notify if bitten build succeeds.""")

    def __init__(self):
        self.log.debug('Initializing BittenNotify plugin')

    def notify(self, build=None):
        self.log.info('BittenNotify invoked for build %r', build)
        self.log.debug('build status: %s', build.status)
        if not self._should_notify(build):
            return
        self.log.info('Sending notification for build %r', build)
        try:
            email = BuildNotifyEmail(self.env)
            email.notify(build)
        except Exception, e:
            self.log.exception(
                "Failure sending notification for build "
                "%s: %s", build.id, e)
Пример #3
0
class WikiNotificationSystem(Component):
    from_email = Option('wiki-notification', 'from_email',
                        'trac+wiki@localhost',
                        """Sender address to use in notification emails.""")

    from_name = Option(
        'wiki-notification', 'from_name', None,
        """Sender name to use in notification emails.

        Defaults to project name.""")

    smtp_always_cc = ListOption(
        'wiki-notification',
        'smtp_always_cc', [],
        doc="""Comma separated list of email address(es) to always send
        notifications to.

        Addresses can be seen by all recipients (Cc:).""")

    smtp_always_bcc = ListOption(
        'wiki-notification',
        'smtp_always_bcc', [],
        doc="""Comma separated list of email address(es) to always send
        notifications to.

        Addresses do not appear publicly (Bcc:).""")

    use_public_cc = BoolOption(
        'wiki-notification', 'use_public_cc', False,
        """Recipients can see email addresses of other CC'ed recipients.

        If this option is disabled(the default),
        recipients are put on BCC.

        (values: 1, on, enabled, true or 0, off, disabled, false)""")

    attach_diff = BoolOption(
        'wiki-notification', 'attach_diff', False,
        """Send `diff`'s as an attachment instead of inline in email body.""")

    redirect_time = IntOption(
        'wiki-notification', 'redirect_time', 5,
        """The default seconds a redirect should take when
        watching/un-watching a wiki page""")

    subject_template = Option(
        'wiki-notification', 'subject_template', '$prefix $pagename $action',
        "A Genshi text template snippet used to get the notification subject.")

    banned_addresses = ListOption(
        'wiki-notification',
        'banned_addresses', [],
        doc="""A comma separated list of email addresses that should never be
        sent a notification email.""")
Пример #4
0
class FilenameSearchModule(Component):
    """Search source for filenames in the repository."""

    check_gone = BoolOption('filenamesearch', 'check_gone', default=True,
                            doc='Check if files are present in the youngest rev before returning.')    
    show_gone = BoolOption('filenamesearch', 'show_gone', default=True,
                           doc='Show files that are in the DB, but not in the youngest rev. (These may indicate moved files)')
    
    implements(ISearchSource, IPermissionRequestor)
    
    # ISearchSource methods
    def get_search_filters(self, req):
        if req.perm.has_permission('FILENAME_SEARCH'):
            yield ('filename', 'File Names', False)
            
    def get_search_results(self, req, terms, filters):
        if 'filename' not in filters: return # Early bailout if we aren't active
        
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        repo = self.env.get_repository(req.authname)
        youngest_rev = repo.get_youngest_rev()
        # ???: Ask cboos about this. <NPK>
        if isinstance(youngest_rev, basestring) and youngest_rev.isdigit():
            youngest_rev = int(youngest_rev)        

        cursor.execute("""SELECT max("""+db.cast('rev','int')+"""), path FROM node_change
                          WHERE node_type = %s AND change_type != 'D'
                          GROUP BY path""", ('F',))

        all_files = [(r,p) for r,p in cursor]# if repo.has_node(p, youngest_rev)]
        cset_cache = {}
        
        for term in terms:
            for rev, path in all_files:
                match = None
                if '/' in term:
                    match = fnmatchcase(path, term.lstrip('/'))
                else:
                    match = sum([fnmatchcase(x, term) for x in path.split('/')])
                if match:
                    cset = cset_cache.setdefault(rev, repo.get_changeset(rev))
                    msg = ''
                    if self.check_gone and not repo.has_node(path, youngest_rev):
                        if self.show_gone:
                            msg = 'Not in the youngest revision, file has possibly been moved.'
                        else:
                            continue
                    yield (req.href.browser(path, rev=rev), path, cset.date, cset.author, msg)
        
    # IPermissionRequestor methods
    def get_permission_actions(self):
        yield 'FILENAME_SEARCH'
Пример #5
0
class ViewTicketChangesets(Component):
    """View changesets referencing the ticket in a box on the ticket page.
    
    Changesets are presented just above the description.
    """
    
    collapsed = BoolOption('ticket-changesets', 'collapsed', 'false',
        """Show section on ticket page as initially collapsed.""")
    
    compact = BoolOption('ticket-changesets', 'compact', 'true',
        """Make compact presentation of revision ranges, for example ![1-3]
        instead of ![1] ![2] ![3].""")
        
    showrevlog = BoolOption('ticket-changesets', 'showrevlog', 'true',
        """Show 'Revision Log' link after revisions range.""")
    
    hide_when_none = BoolOption('ticket-changesets', 'hide_when_none', 'false',
        """Hide section on ticket page when no changesets relates to the
        ticket.""")
    
    implements(ITemplateStreamFilter)

    def __init__(self):
        init_translation(self.env.path)

    # ITemplateStreamFilter methods

    def filter_stream(self, req, method, filename, stream, data):
        if filename == 'ticket.html':
            ticket = data.get('ticket')
            if ticket and ticket.exists:
                context = Context.from_request(req, ticket.resource)
                self.changesets = TicketChangesetsFormatter(self.env, context,
                                                            ticket.id)
                exists = self.changesets.exists()
                if exists or not self.hide_when_none:
                    filter = Transformer('//div[@id="attachments"]')
                    return stream | filter.after(self._render(req, ticket,
                                                              exists))
        return stream

    def _render(self, req, ticket, exists):
        if exists:
            message = self.changesets.format()
            return tag.div(tag.h2(_("Repository Changesets"),
                                  class_='foldable'),
                           tag.div(message, id='changelog'),
                           class_=self.collapsed and 'collapsed')
        else:
            message = _("(none)")
            return tag.div(tag.h2(_("Repository Changesets"), ' ', message,
                                  class_='foldable'))
Пример #6
0
class UnderscoreWiki(Component):
    implements(IWikiSyntaxProvider)
    ignore_missing_pages = BoolOption('underscore-wiki', 'ignore_missing_pages', 'false',
        """Enable/disable highlighting Under_Score_Wiki_Page links to missing pages""")
    uppercase_in_words = BoolOption('underscore-wiki', 'uppercase_in_words', 'false',
        """Enable/disable using upper case letters in places other than first character of every word (Foo_BAR, for example)""")
    def get_link_resolvers(self):
        pass
    def get_wiki_syntax(self):
        yield ( r"(?P<underscorewiki>%(word)s(?:_%(word)s)+)" % {'word': '[A-Z][a-z0-9%s]+' % (self.uppercase_in_words and 'A-Z' or '')}, self._format_regex\
_link)
    def _format_regex_link(self, formatter, ns, match):
        return WikiSystem(self.env)._format_link(formatter, ns, match.group('underscorewiki'), match.group('underscorewiki'), self.ignore_missing_pages)
Пример #7
0
class TicketTagProvider(DefaultTagProvider):
    """[main] Tag provider using ticket fields as sources of tags.

    Relevant ticket data is initially copied to plugin's own tag db store for
    more efficient regular access, that matters especially when working with
    large ticket quantities, kept current using ticket change listener events.

    Currently does NOT support custom fields.
    """

    implements(ITicketChangeListener)

#    custom_fields = ListOption('tags', 'custom_ticket_fields',
#        doc=_("List of custom ticket fields to expose as tags."))

    fields = ListOption('tags', 'ticket_fields', 'keywords',
        doc=_("List of ticket fields to expose as tags."))

    ignore_closed_tickets = BoolOption('tags', 'ignore_closed_tickets', True,
        _("Do not collect tags from closed tickets."))

    map = {'view': 'TICKET_VIEW', 'modify': 'TICKET_CHGPROP'}
    realm = 'ticket'
    use_cache = False

    def __init__(self):
        try:
            self._fetch_tkt_tags()
        except self.env.db_exc.IntegrityError, e:
            self.log.warn('tags for ticket already exist: %s', to_unicode(e))
        cfg = self.config
        cfg_key = 'permission_policies'
        default_policies = cfg.defaults().get('trac', {}).get(cfg_key)
        self.fast_permcheck = all(p in default_policies for
                                  p in cfg.get('trac', cfg_key))
Пример #8
0
class MindMapMacro(Component):
    """
Website: http://trac-hacks.org/wiki/MindMapMacro

`$Id$`

    """
    implements(IWikiMacroProvider, IHTMLPreviewRenderer, ITemplateProvider,
               IRequestHandler, IRequestFilter, IEnvironmentSetupParticipant)

    default_width = Option('mindmap', 'default_width', '100%',
                           'Default width for mindmaps')
    default_height = Option('mindmap', 'default_height', '600',
                            'Default height for mindmaps')
    default_flashvars = ListOption(
        'mindmap', 'default_flashvars',
        ['openUrl = _blank', 'startCollapsedToLevel = 5'],
        'Default flashvars for mindmaps')
    resizable = Option(
        'mindmap', 'resizable', True,
        'Allow mindmaps to be resized. Needs several script and style files.')
    default_resizable = BoolOption(
        'mindmap', 'default_resizable', True,
        'Default setting if mindmaps are resizable.')

    SCHEMA = [
        Table('mindmapcache', key='hash')[Column('hash'),
                                          Column('content'), ]
    ]

    DB_VERSION = 0

    def environment_created(self):
        self._upgrade_db(self.env.get_db_cnx())

    def environment_needs_upgrade(self, db):
        cursor = db.cursor()
        try:
            cursor.execute("select count(*) from mindmapcache")
            cursor.fetchone()
            return False
        except:
            db.rollback()
            return True

    def upgrade_environment(self, db):
        self._upgrade_db(db)

    def _upgrade_db(self, db):
        try:
            db_backend, _ = DatabaseManager(self.env)._get_connector()
            cursor = db.cursor()
            for table in self.SCHEMA:
                for stmt in db_backend.to_sql(table):
                    self.log.debug(stmt)
                    cursor.execute(stmt)
        except Exception, e:
            db.rollback()
            self.log.error(e, exc_info=True)
            raise TracError(unicode(e))
class CommentAdminCommandProvider(Component):

	implements(IAdminCommandProvider)

	notify = BoolOption('ticket', 'commit_ticket_update_notify', 'true',
		"""Send ticket change notification when updating a ticket.""")

	def get_admin_commands(self):
		yield ('comment add', '<ticket> <user> <text>',
			   'Add a comment to a ticket',
			   None, self._do_add_comment)

	def _do_add_comment(self, ticket, user, text):
		tickets = {}
		tickets.setdefault(int(ticket), [])
		comment = text.replace('\\n','[[BR]]\n')
		self._update_tickets(tickets, user, comment, datetime_now(utc))

	def _update_tickets(self, tickets, authname, comment, date):
		"""Update the tickets with the given comment."""
		for tkt_id, cmds in tickets.iteritems():
			try:
				self.log.debug("Updating ticket #%d", tkt_id)
				save = False
				with self.env.db_transaction:
					ticket = Ticket(self.env, tkt_id)
					ticket.save_changes(authname, comment, date)
					self._notify(ticket, date)
			except Exception, e:
				self.log.error("Unexpected error while processing ticket "
							   "#%s: %s", tkt_id, exception_to_unicode(e))
Пример #10
0
class CarbonCopySubscriber(Component):
    """Carbon copy subscriber for cc ticket field."""

    implements(IAnnouncementDefaultSubscriber,
               IAnnouncementSubscriber)

    default_on = BoolOption("announcer", "always_notify_cc", 'true',
        """The always_notify_cc will notify users in the cc field by default
        when a ticket is modified.
        """)

    default_distributor = ListOption("announcer",
        "always_notify_cc_distributor", "email",
        doc="""Comma-separated list of distributors to send the message to
        by default.  ex. email, xmpp
        """)

    # IAnnouncementSubscriber methods

    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added'):
            return

        klass = self.__class__.__name__
        cc = event.target['cc'] or ''
        sids = set()
        for chunk in re.split('\s|,', cc):
            chunk = chunk.strip()

            if not chunk or chunk.startswith('@'):
                continue

            if re.match(r'^[^@]+@.+', chunk):
                sid, auth, addr = None, 0, chunk
            else:
                sid, auth, addr = chunk, 1, None

            # Default subscription
            for s in self.default_subscriptions():
                yield (s[0], s[1], sid, auth, addr, None, s[2], s[3])
            if sid:
                sids.add((sid,auth))

        for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield s.subscription_tuple()

    def description(self):
        return _("notify me when I'm listed in the CC field of a ticket "
                 "that is modified")

    def requires_authentication(self):
        return True

    # IAnnouncementDefaultSubscriber method
    def default_subscriptions(self):
        if self.default_on:
            for d in self.default_distributor:
                yield (self.__class__.__name__, d, 101, 'always')
Пример #11
0
class FullBlogMyPostSubscriber(Component):
    """Subscriber for any blog changes to my posts."""

    implements(IAnnouncementSubscriber)

    always_notify_author = BoolOption(
        'fullblog-announcement', 'always_notify_author', 'true',
        """Notify the blog author of any changes to her blogs,
            including changes to comments.
            """)

    def matches(self, event):
        if event.realm != 'blog':
            return
        if not event.category in ('post changed', 'post deleted',
                                  'comment created', 'comment changed',
                                  'comment deleted'):
            return

        sids = ((event.blog_post.author, 1), )
        klass = self.__class__.__name__
        for i in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield i.subscription_tuple()

    def description(self):
        return _("notify me when any blog that I posted "
                 "is modified or commented on.")
Пример #12
0
class PermissionFilter(Component):
    implements(IPermissionPolicy)
    adminmeta = BoolOption(
        'permission-filter', 'adminmeta', True,
        doc="""If this option is set to True (the default) the filters don't
        affect a user with TRAC_ADMIN permission. Note that if the variable is
        set to False filtering has odd effects on users with TRAC_ADMIN
        permission because we reject based on action name and TRAC_ADMIN is
        usually not checked directly"""
    )
    blacklist = ListOption(
        'permission-filter', 'blacklist', '',
        doc="""List of invalid permissions, the ones listed here are always False"""
    )
    whitelist = ListOption(
        'permission-filter', 'whitelist', '',
        doc="""List of valid permissions, the ones not listed here are always False"""
    )
    def check_permission(self, action, username, resource, perm):
        _admin = False
        if self.adminmeta:
            if action == 'TRAC_ADMIN':
		return
            elif 'TRAC_ADMIN' in perm:
                _admin = True
        if self.blacklist and len(self.blacklist) != 0:
            if not _admin and action in self.blacklist:
                return False
        if self.whitelist and len(self.whitelist) != 0:
            if not _admin and action not in self.whitelist:
                return False
        return 
Пример #13
0
class TicketChangeProducer(Component):
    implements(ITicketChangeListener)

    ignore_cc_changes = BoolOption(
        'announcer',
        'ignore_cc_changes',
        'false',
        doc="""When true, the system will not send out announcement events if
        the only field that was changed was CC. A change to the CC field that
        happens at the same as another field will still result in an event
        being created.""")

    def __init__(self, *args, **kwargs):
        pass

    def ticket_created(self, ticket):
        announcer = AnnouncementSystem(ticket.env)
        announcer.send(
            TicketChangeEvent("ticket",
                              "created",
                              ticket,
                              author=ticket['reporter']))

    def ticket_changed(self, ticket, comment, author, old_values):
        if old_values.keys() == ['cc'] and not comment and \
                self.ignore_cc_changes:
            return
        announcer = AnnouncementSystem(ticket.env)
        announcer.send(
            TicketChangeEvent("ticket", "changed", ticket, comment, author,
                              old_values))

    def ticket_deleted(self, ticket):
        pass
Пример #14
0
    def __init__(self, path, params, log,
                 persistent_cache=False,
                 git_bin='git',
                 git_fs_encoding='utf-8',
                 shortrev_len=7,
                 rlookup_uid=lambda _: None,
                 use_committer_id=False,
                 use_committer_time=False,
                 ):

        self.logger = log
        self.gitrepo = path
        self.params = params
        self._shortrev_len = max(4, min(shortrev_len, 40))
        self.rlookup_uid = rlookup_uid
        self._use_committer_time = use_committer_time
        self._use_committer_id = use_committer_id

        _use_svn_id = BoolOption('git', 'use_svn_id', 'false',
                                 "try looking up revisions by git-svn-id if present")

        self.git = PyGIT.StorageFactory(path, log, not persistent_cache,
                                        git_bin=git_bin,
                                        git_fs_encoding=git_fs_encoding,
                                        was_svn=_use_svn_id).getInstance()

        Repository.__init__(self, "git:"+path, self.params, log)
Пример #15
0
class ThemeEngineModule(Component):
    """A module to provide the theme content."""

    custom_css = BoolOption('theme',
                            'enable_css',
                            default='false',
                            doc='Enable or disable custom CSS from theme.')

    implements(ITemplateProvider, IRequestFilter)

    def __init__(self):
        self.system = ThemeEngineSystem(self.env)

    # ITemplateProvider methods
    def get_htdocs_dirs(self):
        try:
            theme = self.system.theme
            if theme and 'htdocs' in theme:
                yield 'theme', resource_filename(theme['module'],
                                                 theme['htdocs'])
        except ThemeNotFound:
            pass

    def get_templates_dirs(self):
        try:
            theme = self.system.theme
            if theme and 'template' in theme:
                yield resource_filename(theme['module'],
                                        os.path.dirname(theme['template']))
        except ThemeNotFound:
            pass

    # IRequestFilter methods
    def pre_process_request(self, req, handler):
        return handler

    def post_process_request(self, req, template, data, content_type):
        try:
            theme = self.system.theme
        except ThemeNotFound, e:
            add_warning(
                req,
                'Unknown theme %s configured. Please check your trac.ini. You may need to enable the theme\'s plugin.'
                % e.theme_name)
            theme = None

        if theme and 'css' in theme:
            add_stylesheet(req, 'theme/' + theme['css'])
        if theme and 'template' in theme:
            req.chrome['theme'] = os.path.basename(theme['template'])
        if theme and theme.get('disable_trac_css'):
            links = req.chrome.get('links')
            if links and 'stylesheet' in links:
                for i, link in enumerate(links['stylesheet']):
                    if link.get('href', '').endswith('common/css/trac.css'):
                        del links['stylesheet'][i]
                        break
        if self.custom_css:
            add_stylesheet(req, '/themeengine/theme.css')
        return template, data, content_type
Пример #16
0
class AuthOpenIdPlugin(Component):

    openid_session_key = 'openid_session_data'

    implements(INavigationContributor, IRequestHandler, ITemplateProvider,
               IAuthenticator, IEnvironmentSetupParticipant)

    connection_uri = Option(
        'trac', 'database', 'sqlite:db/trac.db', """Database connection
        [wiki:TracEnvironment#DatabaseConnectionStrings string] for this
        project""")

    check_ip = BoolOption(
        'trac', 'check_auth_ip', 'true',
        """Whether the IP address of the user should be checked for
         authentication (''since 0.9'').""")
    check_ip_mask = Option('trac', 'check_auth_ip_mask', '255.255.255.0',
                           """What mask should be applied to user address.""")

    def _get_masked_address(self, address):
        mask = struct.unpack('>L', socket.inet_aton(self.check_ip_mask))[0]
        address = struct.unpack('>L', socket.inet_aton(address))[0]
        return socket.inet_ntoa(struct.pack('>L', address & mask))

    def __init__(self):
        db = self.env.get_db_cnx()
        self.store = self._getStore(db)

    def _getStore(self, db):
        scheme, rest = self.connection_uri.split(':', 1)
        if scheme == 'mysql':
            return MySQLStore(db)
        elif scheme == 'postgres':
            return PostgreSQLStore(db)
        elif scheme == 'sqlite':
            return TracSQLiteStore(db)
        else:
            return MemoryStore()

    def _initStore(self, db):
        store = self._getStore(db)
        if type(store) is not MemoryStore:
            store.createTables()

    # IEnvironmentSetupParticipant methods

    def environment_created(self):
        db = self.env.get_db_cnx()
        self._initStore(db)
        db.commit()

    def environment_needs_upgrade(self, db):
        c = db.cursor()
        try:
            c.execute("SELECT count(*) FROM oid_associations")
            return False
        except Exception, e:
            db.rollback()
            return True
Пример #17
0
class SmtpEmailSender(Component):
    """E-mail sender connecting to an SMTP server."""

    implements(IEmailSender)

    smtp_server = Option(
        'notification', 'smtp_server', 'localhost',
        """SMTP server hostname to use for email notifications.""")

    smtp_port = IntOption(
        'notification', 'smtp_port', 25,
        """SMTP server port to use for email notification.""")

    smtp_user = Option('notification', 'smtp_user', '',
                       """Username for SMTP server. (''since 0.9'')""")

    smtp_password = Option('notification', 'smtp_password', '',
                           """Password for SMTP server. (''since 0.9'')""")

    use_tls = BoolOption(
        'notification', 'use_tls', 'false',
        """Use SSL/TLS to send notifications over SMTP. (''since 0.10'')""")

    crlf = re.compile("\r?\n")

    def send(self, from_addr, recipients, message):
        # Ensure the message complies with RFC2822: use CRLF line endings
        message = CRLF.join(self.crlf.split(message))

        self.log.info("Sending notification through SMTP at %s:%d to %s" %
                      (self.smtp_server, self.smtp_port, recipients))
        server = smtplib.SMTP(self.smtp_server, self.smtp_port)
        # server.set_debuglevel(True)
        if self.use_tls:
            server.ehlo()
            if not server.esmtp_features.has_key('starttls'):
                raise TracError(_("TLS enabled but server does not support " \
                                  "TLS"))
            server.starttls()
            server.ehlo()
        if self.smtp_user:
            server.login(self.smtp_user.encode('utf-8'),
                         self.smtp_password.encode('utf-8'))
        start = time.time()
        server.sendmail(from_addr, recipients, message)
        t = time.time() - start
        if t > 5:
            self.log.warning('Slow mail submission (%.2f s), '
                             'check your mail setup' % t)
        if self.use_tls:
            # avoid false failure detection when the server closes
            # the SMTP connection with TLS enabled
            import socket
            try:
                server.quit()
            except socket.sslerror:
                pass
        else:
            server.quit()
Пример #18
0
class TicketReporterSubscriber(Component):
    """Allows the users to subscribe to tickets that they report."""

    implements(IAnnouncementDefaultSubscriber, IAnnouncementSubscriber)

    default_on = BoolOption(
        'announcer', 'always_notify_reporter', 'true',
        """The always_notify_reporter will notify the ticket reporter when a
        ticket is modified by default.
        """)

    default_distributor = ListOption(
        'announcer',
        'always_notify_reporter_distributor',
        'email',
        doc="""Comma-separated list of distributors to send the message to
        by default (ex: email, xmpp).
        """)

    # IAnnouncementSubscriber methods

    def matches(self, event):
        if event.realm != "ticket":
            return
        if event.category not in ('created', 'changed', 'attachment added'):
            return

        ticket = event.target
        if not ticket['reporter'] or ticket['reporter'] == 'anonymous':
            return

        if re.match(r'^[^@]+@.+', ticket['reporter']):
            sid, auth, addr = None, 0, ticket['reporter']
        else:
            sid, auth, addr = ticket['reporter'], 1, None

        # Default subscription
        for s in self.default_subscriptions():
            yield (s[0], s[1], sid, auth, addr, None, s[2], s[3])

        if sid:
            klass = self.__class__.__name__
            for s in Subscription.\
                    find_by_sids_and_class(self.env, ((sid, auth),), klass):
                yield s.subscription_tuple()

    def description(self):
        return _("notify me when a ticket that I reported is modified")

    def requires_authentication(self):
        return True

    # IAnnouncementDefaultSubscriber methods

    def default_subscriptions(self):
        if self.default_on:
            for d in self.default_distributor:
                yield self.__class__.__name__, d, 101, 'always'
Пример #19
0
class GoogleAnalyticsConfig(Component):
    uid = Option(
        'google.analytics', 'uid', None, """Google Analytics' UID.
        The UID is needed for Google Analytics to log your website stats.
        Your UID can be found by looking in the JavaScript Google Analytics
        gives you to put on your page. Look for your UID in between
        `var pageTracker = _gat._getTracker("UA-111111-11");` in the javascript.
        In this example you would put UA-11111-1 in the UID box.""")
    admin_logging = BoolOption(
        'google.analytics', 'admin_logging', False,
        """Disabling this option will prevent all logged in admins from showing
        up on your Google Analytics reports.""")
    authenticated_logging = BoolOption(
        'google.analytics', 'authenticated_logging', True,
        """Disabling this option will prevent all authenticated users from
        showing up on your Google Analytics reports.""")
    outbound_link_tracking = BoolOption(
        'google.analytics', 'outbound_link_tracking', True,
        """Disabling this option will turn off the tracking of outbound links.
        It's recommended not to disable this option unless you're a privacy
        advocate (now why would you be using Google Analytics in the first
        place?) or it's causing some kind of weird issue.""")
    google_external_path = Option(
        'google.analytics', 'google_external_path', '/external/',
        """This will be the path shown on Google Analytics
        regarding external links. Consider the following link:
          http://trac.edgewall.org/"
        The above link will be shown as for example:
          /external/trac.edgewall.org/
        This way you will be able to track outgoing links.
        Outbound link tracking must be enabled for external links to be
        tracked.""")
    extensions = Option(
        'google.analytics', 'extensions', 'zip,tar,tar.gz,tar.bzip,egg',
        """Enter any extensions of files you would like to be tracked as a
        download. For example to track all MP3s and PDFs enter:
            mp3,pdf
        Outbound link tracking must be enabled for downloads to be tracked.""")
    tracking_domain_name = Option(
        'google.analytics', 'tracking_domain_name', None,
        """If you're tracking multiple subdomains with the same Google Analytics
        profile, like what's talked about in:
            https://www.google.com/support/googleanalytics/bin/answer.py?answer=55524
        enter your main domain here. For more info, please visit the previous
        link.""")
Пример #20
0
class AdminEnumListPlugin(Component):

    implements(IRequestFilter, ITemplateProvider, ITemplateStreamFilter)

    hide_selects = BoolOption('adminenumlist', 'hide_selects', 'false',
        "Hide the 'Order' column of select elements.")

    _panels = ('priority', 'resolution', 'severity', 'type')
    _has_add_jquery_ui = hasattr(Chrome, 'add_jquery_ui')

    def __init__(self):
        Component.__init__(self)

        from pkg_resources import parse_version
        version = parse_version(trac_version)
        if version < parse_version('0.11.1'):
            self._jquery_ui_filename = None
        else:
            self._jquery_ui_filename = \
                'adminenumlistplugin/jqueryui-%s/jquery-ui.min.js' % \
                ('1.8.24', '1.6')[version < parse_version('0.12')]

    ### methods for IRequestFilter

    def pre_process_request(self, req, handler):
        return handler

    def post_process_request(self, req, template, data, content_type):
        path_info = req.path_info
        if any(path_info.startswith(panel, len('/admin/ticket/'))
               for panel in self._panels):
            if self._has_add_jquery_ui:
                Chrome(self.env).add_jquery_ui(req)
                add_script(req, 'adminenumlistplugin/adminenumlist.js')
            elif self._jquery_ui_filename:
                add_script(req, self._jquery_ui_filename)
                add_script(req, 'adminenumlistplugin/adminenumlist.js')

        return template, data, content_type

    ### methods for ITemplateStreamFilter

    def filter_stream(self, req, method, filename, stream, data):
        if filename == 'admin_enums.html':
            text = 'var hide_selects = %s' % ('false', 'true')[bool(self.hide_selects)]
            stream |= Transformer('//head').append(tag.script(text, type='text/javascript'))

        return stream

    ### methods for ITemplateProvider

    def get_htdocs_dirs(self):
        from pkg_resources import resource_filename
        return [('adminenumlistplugin', resource_filename(__name__, 'htdocs'))]

    def get_templates_dirs(self):
        return []
Пример #21
0
class TicketUpdaterSubscriber(Component):
    """Allows updaters to subscribe to their own updates."""

    implements(IAnnouncementDefaultSubscriber, IAnnouncementSubscriber)

    default_on = BoolOption(
        'announcer', 'never_notify_updater', 'false',
        """The never_notify_updater stops users from receiving announcements
        when they update tickets.
        """)

    default_distributor = ListOption(
        'announcer',
        'never_notify_updater_distributor',
        'email',
        doc="""Comma-separated list of distributors to send the message to
        by default (ex: email, xmpp).
        """)

    # IAnnouncementSubscriber methods

    def matches(self, event):
        if event.realm != "ticket":
            return
        if event.category not in ('created', 'changed', 'attachment added'):
            return
        if not event.author or event.author == 'anonymous':
            return

        if re.match(r'^[^@]+@.+', event.author):
            sid, auth, addr = None, 0, event.author
        else:
            sid, auth, addr = event.author, 1, None

        # Default subscription
        for s in self.default_subscriptions():
            yield (s[0], s[1], sid, auth, addr, None, s[2], s[3])

        if sid:
            klass = self.__class__.__name__
            for s in Subscription.\
                    find_by_sids_and_class(self.env, ((sid, auth),), klass):
                yield s.subscription_tuple()

    def description(self):
        return _("notify me when I update a ticket")

    def requires_authentication(self):
        return True

    # IAnnouncementDefaultSubscriber method

    def default_subscriptions(self):
        if self.default_on:
            for d in self.default_distributor:
                yield self.__class__.__name__, d, 100, 'never'
Пример #22
0
def get_serverside_charts():
    return BoolOption(
        'estimation-tools',
        'serverside_charts',
        'false',
        doc="""Generate charts links internally and fetch charts server-side
        before returning to client, instead of generating Google links that
        the users browser fetches directly. Particularly useful for sites
        served behind SSL. Server-side charts uses POST requests internally,
        increasing chart data size from 2K to 16K. Defaults to false.""")
Пример #23
0
class BaseIndexer(Component):
    """
    This is base class for Bloodhound Search indexers of specific resource
    """
    silence_on_error = BoolOption('bhsearch', 'silence_on_error', "True",
        """If true, do not throw an exception during indexing a resource""")

    wiki_formatter = ExtensionOption('bhsearch', 'wiki_syntax_formatter',
        ISearchWikiSyntaxFormatter, 'SimpleSearchWikiSyntaxFormatter',
        'Name of the component implementing wiki syntax to text formatter \
        interface: ISearchWikiSyntaxFormatter.')
    class ServiceProvider(Component):
        implements(IGraphDatabaseProvider)

        resource_uri = Option('neo4j', 'resource_uri', doc="""
""")
        options = Options()
        classpath = options.add(
            ListOption('neo4j', 'classpath', sep=os.pathsep, doc="""
"""))
        ext_dirs = options.add(
            ListOption('neo4j', 'ext_dirs', sep=os.pathsep, doc="""
"""))
        start_server = options.add(
            BoolOption('neo4j', 'start_server', doc="""
"""))
        server_path = options.add(
            PathOption('neo4j', 'server_path', doc="""
"""))
        username = options.add(Option('neo4j', 'username', doc="""
"""))
        password = options.add(Option('neo4j', 'password', doc="""
"""))
        jvm = options.add(PathOption('neo4j', 'jvm', doc="""
"""))

        def start(self, resource_uri, params):
            if resource_uri is None:
                resource_uri = self.resource_uri
                if not resource_uri:
                    resource_uri = os.path.join(self.env.path, 'neodb')
            if resource_uri.startswith('file://'):
                resource_uri = 'file://' + normalize_path(
                    self.env.path, resource_uri[7:])
            elif '://' not in resource_uri:
                resource_uri = normalize_path(self.env.path, resource_uri)
            for option, filter in self.options:
                if option not in params:
                    value = getattr(self, option)
                    if value is not None:
                        params[option] = filter(value)
            return resource_uri, neo4j.GraphDatabase(resource_uri, **params)

        instances = {}

        def instance(self, resource_uri, params):
            if resource_uri not in self.instances:
                key = resource_uri
                resource_uri, neo = self.start(resource_uri, params)
                neo = TracNeo(self.env, neo)
                if resource_uri != key:
                    self.instances[resource_uri] = neo
                self.instances[key] = neo
            return self.instances[resource_uri]
Пример #25
0
class ImageTrac(Component):

    implements(ITicketManipulator, ITicketChangeListener, IRequestFilter,
               IAttachmentChangeListener)

    mandatory_image = BoolOption(
        'ticket-image', 'mandatory_image', 'false',
        "Enforce a mandatory image for created tickets")
    thumbnail = Option('ticket-image', 'size.thumbnail', '32x32',
                       "size of the ticket thumbnail image")
    default_size = Option('ticket-image', 'size.default', '488x',
                          "size of the ticket default image")

    ### methods for ITicketManipulator
    """Miscellaneous manipulation of ticket workflow features."""
    def prepare_ticket(self, req, ticket, fields, actions):
        """Not currently called, but should be provided for future
        compatibility."""

    def validate_ticket(self, req, ticket):
        """Validate a ticket after it's been populated from user input.
        
        Must return a list of `(field, message)` tuples, one for each problem
        detected. `field` can be `None` to indicate an overall problem with the
        ticket. Therefore, a return value of `[]` means everything is OK."""
        image = req.args.get('ticket_image')

        if hasattr(image, 'fp'):
            mimeview = Mimeview(self.env)
            mimetype = mimeview.get_mimetype(image.filename)
            if mimetype is None:
                return [('ticket_image', 'Uploaded file is not an image')]
            if mimetype.split('/', 1)[0] != 'image':
                return [('ticket_image',
                         'Uploaded file is not an image, instead it is %s' %
                         mimetype)]

            try:
                Image.open(image.file)
            except IOError, e:
                return [('ticket_image', str(e))]

            # store the image temporarily for new tickets
            if ticket.exists:
                self.attach(ticket, image)
            else:
                if not hasattr(self, 'image'):
                    self.image = {}
                self.image[ticket['summary']] = req.args['ticket_image']

        else:
Пример #26
0
class IttecoEvnSetup(Component):
    """ Initialise database and environment for itteco components """
    implements(IEnvironmentSetupParticipant,ITemplateProvider)

    debug = BoolOption('itteco-config', 'debug',
        doc="Debug switch. Toggles the js from debug to production/minified version.")
        
    milestone_levels = ListOption('itteco-roadmap-config', 'milestone_levels',[],
        doc="All possible levels of hierarhial milestones.")

    scope_element = ListOption('itteco-whiteboard-tickets-config', 'scope_element', ['story'],
        doc="All tickets in a whiteboard would be grouped accorging their tracibility to this type of ticket")

    excluded_element = ListOption('itteco-whiteboard-tickets-config', 'excluded_element', [],
        doc="List of the ticket types, which should be excluded from the whiteboard.")

    work_element = property(lambda self: self._get_work_elements())
    
    final_statuses= ListOption('itteco-whiteboard-config', 'ticket_final_status', 
        ['closed'], doc="List of the final statuses of tickets.")

    
    change_listeners = ExtensionPoint(IMilestoneChangeListener)
    
    def _get_work_elements(self):
        """ Returns ticket types that are taken into consideration 
        while counting milestone progress """        
        ignore_types = set(self.scope_element) \
            | set(self.excluded_element) 
        return [type.name for type in Type.select(self.env) if type.name not in ignore_types]

    # IEnvironmentSetupParticipant
    def environment_created(self):
        self.upgrade_environment(self.env.get_db_cnx())
    
    def environment_needs_upgrade(self, db):
        db_ver = get_version(db)
        return db_ver < [int(i) for i in __version__.split('.')]
    
    def upgrade_environment(self, db):
        db = db or self.env.get_db_cnx()
        do_upgrade(self.env, db, get_version(db))
        db.commit()
    
    # ITemplateProvider methods
    def get_htdocs_dirs(self):
        return [(__package__, pkg_resources.resource_filename(__package__, 'htdocs'))]

    def get_templates_dirs(self):
        return [pkg_resources.resource_filename(__package__, 'templates')]
Пример #27
0
class WikiTagProvider(DefaultTagProvider):
    """[main] Tag provider for Trac wiki."""

    realm = 'wiki'

    exclude_templates = BoolOption(
        'tags',
        'query_exclude_wiki_templates',
        default=True,
        doc="Whether tagged wiki page templates should be queried.")

    first_head = re.compile('=\s+([^=\n]*)={0,1}')

    def check_permission(self, perm, action):
        map = {'view': 'WIKI_VIEW', 'modify': 'WIKI_MODIFY'}
        return super(WikiTagProvider, self).check_permission(perm, action) \
            and map[action] in perm

    def get_tagged_resources(self, req, tags=None, filter=None):
        if self.exclude_templates:
            with self.env.db_query as db:
                like_templates = ''.join([
                    "'",
                    db.like_escape(WikiModule.PAGE_TEMPLATES_PREFIX), "%%'"
                ])
                filter = (' '.join(['name NOT', db.like() % like_templates]), )
        return super(WikiTagProvider,
                     self).get_tagged_resources(req, tags, filter)

    def get_all_tags(self, req, filter=None):
        if not self.check_permission(req.perm, 'view'):
            return Counter()
        if self.exclude_templates:
            with self.env.db_transaction as db:
                like_templates = ''.join([
                    "'",
                    db.like_escape(WikiModule.PAGE_TEMPLATES_PREFIX), "%%'"
                ])
                filter = (' '.join(['name NOT', db.like() % like_templates]), )
        return super(WikiTagProvider, self).get_all_tags(req, filter)

    def describe_tagged_resource(self, req, resource):
        if not self.check_permission(req.perm(resource), 'view'):
            return ''
        page = WikiPage(self.env, resource.id)
        if page.exists:
            ret = self.first_head.search(page.text)
            return ret and ret.group(1) or ''
        return ''
Пример #28
0
class SimpleTicketModule(Component):
    """A request filter to implement the SimpleTicket reduced ticket
    entry form."""
    implements(IPermissionRequestor, IRequestFilter)

    fields = ListOption('simpleticket',
                        'fields',
                        default='',
                        doc="""Fields to hide for the simple ticket entry
                               form.""")

    show_only = BoolOption('simpleticket',
                           'show_only',
                           default=False,
                           doc="""If True, show only the specified fields
                                  rather than hiding the specified fields""")

    ### IPermissionRequestor methods
    def get_permission_actions(self):
        yield 'TICKET_CREATE_SIMPLE', ['TICKET_CREATE']

    ### IRequestFilter methods
    def pre_process_request(self, req, handler):
        return handler

    def post_process_request(self, req, template, data, content_type):
        if req.path_info == '/newticket':
            do_filter = 'TICKET_CREATE_SIMPLE' in req.perm and \
                        not 'TRAC_ADMIN' in req.perm

            self.log.debug('SimpleTicket: Filtering new ticket form for %s',
                           req.authname)
            if do_filter:
                if self.show_only:
                    data['fields'] = [
                        f for f in data['fields']
                        if f['name'] in self.fields and f is not None
                    ]
                else:
                    data['fields'] = [
                        f for f in data['fields']
                        if f['name'] not in self.fields and f is not None
                    ]

        return template, data, content_type
Пример #29
0
class UnsubscribeFilter(Component):
    implements(IAnnouncementSubscriptionFilter, IAnnouncementPreferenceProvider)

    never_announce = BoolOption('announcer', 'never_announce', 'false',
            """Default value for user overridable never_announce setting.

            This setting stops any announcements from ever being sent to the
            user.
            """)
    
    def filter_subscriptions(self, event, subscriptions):
        setting = self._setting()
        for subscription in subscriptions:
            if setting.get_user_setting(subscription[1])[1]:
                self.log.debug(
                    "Filtering %s because of rule: UnsubscribeFilter"\
                    %subscription[1]
                )
                pass
            else:
                yield subscription

    def get_announcement_preference_boxes(self, req):
        if req.authname == "anonymous" and "email" not in req.session:
            return
        yield "unsubscribe_all", _("Unsubscribe From All Announcements")

    def render_announcement_preference_box(self, req, panel):
        setting = self._setting()
        if req.method == "POST":
            setting.set_user_setting(req.session, 
                    value=req.args.get('unsubscribe_all'))
        value = setting.get_user_setting(req.session.sid)[1]
        return "prefs_announcer_unsubscribe_all.html", \
            dict(data=dict(unsubscribe_all = value))

    def _setting(self):
        return BoolSubscriptionSetting(
            self.env,
            'never_announce',
            self.never_announce
        )
Пример #30
0
class ChangeAuthorFilter(Component):
    implements(IAnnouncementSubscriptionFilter)
    implements(IAnnouncementPreferenceProvider)

    never_notify_author = BoolOption(
        'announcer', 'never_notify_author', 'true',
        """User overridable default value.  Stop author from receiving
            an announcement, even if some other rule says they should receive
            one.
            """)

    def filter_subscriptions(self, event, subscriptions):
        for subscription in subscriptions:
            setting = self._setting()
            if event.author == subscription[1] and \
                    setting.get_user_setting(event.author)[1]:
                self.log.debug(
                    "Filtering %s because of rule: ChangeAuthorFilter"\
                    %event.author
                )
                pass
            else:
                yield subscription

    def get_announcement_preference_boxes(self, req):
        if req.authname == 'anonymous' and 'email' not in req.session:
            return
        yield 'author_filter', _('Author Filter')

    def render_announcement_preference_box(self, req, panel):
        setting = self._setting()
        if req.method == "POST":
            setting.set_user_setting(req.session,
                                     value=req.args.get('author_filter'))
        value = setting.get_user_setting(req.session.sid)[1]
        return 'prefs_announcer_author_filter.html', \
                dict(data=dict(author_filter=value))

    def _setting(self):
        return BoolSubscriptionSetting(self.env, 'author_filter',
                                       self.never_notify_author)