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'])
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)
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.""")
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'
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'))
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)
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))
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))
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')
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.")
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
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
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)
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
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
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()
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'
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.""")
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 []
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'
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.""")
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]
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:
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')]
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 ''
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
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 )
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)