def render_property_diff(self, req, ticket, field, old, new, resource_new=None): "Version for Trac 0.11" rendered = None # per type special rendering of diffs type_ = None for f in ticket.fields: if f['name'] == field: type_ = f['type'] break if type_ == 'checkbox': rendered = new == '1' and "set" or "unset" elif type_ == 'textarea': if not resource_new: rendered = _('modified') else: href = get_resource_url(self.env, resource_new, req.href, action='diff') rendered = tag('modified (', tag.a('diff', href=href), ')') # per name special rendering of diffs old_list, new_list = None, None render_elt = lambda x: x sep = ', ' if field == 'cc': chrome = Chrome(self.env) old_list, new_list = chrome.cc_list(old), chrome.cc_list(new) if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): render_elt = obfuscate_email_address elif field == 'keywords': old_list, new_list = (old or '').split(), new.split() sep = ' ' if (old_list, new_list) != (None, None): added = [tag.em(render_elt(x)) for x in new_list if x not in old_list] remvd = [tag.em(render_elt(x)) for x in old_list if x not in new_list] added = added and tag(separated(added, sep), " added") remvd = remvd and tag(separated(remvd, sep), " removed") if added or remvd: rendered = tag(added, added and remvd and '; ', remvd) return rendered if field in ('reporter', 'owner'): if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): old = obfuscate_email_address(old) new = obfuscate_email_address(new) # Added by MS if field == 'attachment': rendered = tag(tag.em(new), " added") # changed 'if' to 'elif': elif old and not new: rendered = tag(tag.em(old), " deleted") elif new and not old: rendered = tag("set to ", tag.em(new)) elif old and new: rendered = tag("changed from ", tag.em(old), " to ", tag.em(new)) return rendered
def diff_cc(self, old, new): oldcc = NotifyEmail.addrsep_re.split(old) newcc = NotifyEmail.addrsep_re.split(new) added = [obfuscate_email_address(x) \ for x in newcc if x and x not in oldcc] removed = [obfuscate_email_address(x) \ for x in oldcc if x and x not in newcc] return (added, removed)
def _chrome_format_author_replacement(self, req, author): """ Audit Chrome.format_author method so that we get link to user profile Downside: This is a hack that interfere with the way trac renders usernames. Will have some unwanted behaviour. One such known unwanted behaviour is in the ticket view where owner and reporter links are changed """ if not author: return "" unwanted_users = [ 'trac', 'tracadmin', 'anonymous', 'authenticated', 'somebody' ] not_ticket = req.path_info.rsplit('/', 2)[1] != 'ticket' contain_email = bool(re.search('<.+>', author)) ok_user = author not in unwanted_users username = author if not contain_email: user = get_userstore().getUser(author) if user: author = user.getDisplayName() elif not Chrome(self.env).show_email_addresses: author = obfuscate_email_address(author) # Create a link to profile page or return author in plain if ok_user and not_ticket and not contain_email and conf.public_user_page_url: return tag.a( author, **{ 'href': conf.public_user_page_url + username, 'class': 'author' }) else: return author
def _chrome_format_author_replacement(self, req, author): """ Audit Chrome.format_author method so that we get link to user profile Downside: This is a hack that interfere with the way trac renders usernames. Will have some unwanted behaviour. One such known unwanted behaviour is in the ticket view where owner and reporter links are changed """ if not author: return "" unwanted_users = ['trac', 'tracadmin', 'anonymous', 'authenticated', 'somebody'] not_ticket = req.path_info.rsplit('/', 2)[1] != 'ticket' contain_email = bool(re.search('<.+>', author)) ok_user = author not in unwanted_users username = author if not contain_email: user = get_userstore().getUser(author) if user: author = user.getDisplayName() elif not Chrome(self.env).show_email_addresses: author = obfuscate_email_address(author) # Create a link to profile page or return author in plain if ok_user and not_ticket and not contain_email and conf.public_user_page_url: return tag.a(author, **{'href':conf.public_user_page_url + username, 'class':'author'}) else: return author
def obfuscate_email(self, text): """ Obfuscate text when `show_email_addresses` is disabled in config. Obfuscation happens once per email, regardless of recipients, so cannot use permission-based obfuscation. """ if self.env.config.getbool('trac', 'show_email_addresses'): return text else: return obfuscate_email_address(text)
def format_emails(self, context, value, sep=', '): """Normalize a list of e-mails and obfuscate them if needed. :param context: the context in which the check for obfuscation should be done :param value: a string containing a comma-separated list of e-mails :param sep: the separator to use when rendering the list again """ all_cc = self.cc_list(value) if not (self.show_email_addresses or 'EMAIL_VIEW' in context.perm): all_cc = [obfuscate_email_address(cc) for cc in all_cc] return sep.join(all_cc)
def notify_attachment(self, ticket, author, filename, modtime, body): """Send ticket attachment notification (untranslated)""" t = deactivate() translated_fields = ticket.fields try: ticket.fields = TicketSystem(self.env).get_ticket_fields() self.ticket = ticket self.modtime = modtime self.newticket = False self.reporter = '' self.owner = '' link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id wrap_kargs = { 'initial_indent': ' ', 'subsequent_indent': ' ', 'linesep': CRLF } if 'ambiwidth' in getargspec(wrap)[0]: wrap_kargs['ambiwidth'] = self.ambiwidth ticket_values['description'] = wrap( ticket_values.get('description', ''), self.COLS, **wrap_kargs ) ticket_values['new'] = self.newticket ticket_values['link'] = link subject = 'Re: ' + self.format_subj(summary) author = obfuscate_email_address(author) change = { 'author': author } self.data.update({ 'ticket_props': self.format_props(), 'ticket_body_hdr': self.format_hdr(), 'subject': subject, 'ticket': ticket_values, 'change': change, 'changes_body': body, }) NotifyEmail.notify(self, ticket.id, subject) finally: ticket.fields = translated_fields reactivate(t)
def format_props(self): tkt = self.ticket fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc')] width = [0, 0, 0, 0] i = 0 for f in [f['name'] for f in fields if f['type'] != 'textarea']: if not tkt.values.has_key(f): continue fval = tkt[f] or '' if fval.find('\n') != -1: continue idx = 2 * (i % 2) if len(f) > width[idx]: width[idx] = len(f) if len(fval) > width[idx + 1]: width[idx + 1] = len(fval) i += 1 format = ('%%%is: %%-%is | ' % (width[0], width[1]), ' %%%is: %%-%is%s' % (width[2], width[3], CRLF)) l = (width[0] + width[1] + 5) sep = l * '-' + '+' + (self.COLS - l) * '-' txt = sep + CRLF big = [] i = 0 for f in [f for f in fields if f['name'] != 'description']: fname = f['name'] if not tkt.values.has_key(fname): continue fval = tkt[fname] or '' if fname in ['owner', 'reporter']: fval = obfuscate_email_address(fval) if f['type'] == 'textarea' or '\n' in unicode(fval): big.append((fname.capitalize(), CRLF.join(fval.splitlines()))) else: txt += format[i % 2] % (fname.capitalize(), fval) i += 1 if i % 2: txt += CRLF if big: txt += sep for name, value in big: txt += CRLF.join(['', name + ':', value, '', '']) txt += sep return txt
def obfuscate_email(self, text): if self.env.config.getbool('trac', 'show_email_addresses'): return text else: return obfuscate_email_address(text)
def expand_macro(self, formatter, name, content, args=None): req = formatter.req # prepare options options, query_args = parse_options(self.env, content, copy.copy(DEFAULT_OPTIONS)) query_args[self.remaining_field + "!"] = None tickets = execute_query(self.env, req, query_args) sum = 0.0 estimations = {} for ticket in tickets: if ticket['status'] in self.closed_states: continue try: estimation = float(ticket[self.remaining_field]) owner = ticket['owner'] sum += estimation if owner in estimations: estimations[owner] += estimation else: estimations[owner] = estimation except: pass estimations_string = [] labels = [] for owner, estimation in estimations.iteritems(): # Note: Unconditional obfuscation of owner in case it represents # an email adress, and as the chart API doesn't support SSL # (plain http transfer only, from either client or server). labels.append("%s %g%s" % (obfuscate_email_address(owner), round(estimation, 2), self.estimation_suffix)) estimations_string.append(str(int(estimation))) # Title title = 'Workload' # calculate remaining work time if options.get('today') and options.get('enddate'): currentdate = options['today'] day = timedelta(days=1) days_remaining = 0 while currentdate <= options['enddate']: if currentdate.weekday() < 5: days_remaining += 1 currentdate += day title += ' %g%s (~%s workdays left)' % (round(sum, 2), self.estimation_suffix, days_remaining) chart_args = unicode_urlencode( {'chs': '%sx%s' % (options['width'], options['height']), 'chf': 'bg,s,00000000', 'chd': 't:%s' % ",".join(estimations_string), 'cht': 'p3', 'chtt': title, 'chl': "|".join(labels), 'chco': options['color']}) self.log.debug("WorkloadChart data: %s", chart_args) if self.serverside_charts: return tag.image( src="%s?data=%s" % (req.href.estimationtools('chart'), unicode_quote(chart_args)), alt="Workload Chart (server)") else: return tag.image( src="http://chart.googleapis.com/chart?%s" % chart_args, alt="Workload Chart (client)")
def format_author(self, req, author): if not author or author == 'anonymous': return _("anonymous") if self.show_email_addresses or not req or 'EMAIL_VIEW' in req.perm: return author return obfuscate_email_address(author)
def render_property_diff(self, req, ticket, field, old, new, resource_new=None): "Version for Trac 0.11" rendered = None # per type special rendering of diffs type_ = None for f in ticket.fields: if f['name'] == field: type_ = f['type'] break if type_ == 'checkbox': rendered = new == '1' and "set" or "unset" elif type_ == 'textarea': if not resource_new: rendered = _('modified') else: href = get_resource_url(self.env, resource_new, req.href, action='diff') rendered = tag('modified (', tag.a('diff', href=href), ')') # per name special rendering of diffs old_list, new_list = None, None render_elt = lambda x: x sep = ', ' if field == 'cc': chrome = Chrome(self.env) old_list, new_list = chrome.cc_list(old), chrome.cc_list(new) if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): render_elt = obfuscate_email_address elif field == 'keywords': old_list, new_list = (old or '').split(), new.split() sep = ' ' if (old_list, new_list) != (None, None): added = [ tag.em(render_elt(x)) for x in new_list if x not in old_list ] remvd = [ tag.em(render_elt(x)) for x in old_list if x not in new_list ] added = added and tag(separated(added, sep), " added") remvd = remvd and tag(separated(remvd, sep), " removed") if added or remvd: rendered = tag(added, added and remvd and '; ', remvd) return rendered if field in ('reporter', 'owner'): if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): old = obfuscate_email_address(old) new = obfuscate_email_address(new) # Added by MS if field == 'attachment': rendered = tag(tag.em(new), " added") # changed 'if' to 'elif': elif old and not new: rendered = tag(tag.em(old), " deleted") elif new and not old: rendered = tag("set to ", tag.em(new)) elif old and new: rendered = tag("changed from ", tag.em(old), " to ", tag.em(new)) return rendered
def expand_macro(self, formatter, name, content): req = formatter.req db = self.env.get_db_cnx() # prepare options options, query_args = parse_options(db, content, copy.copy(DEFAULT_OPTIONS)) query_args[self.estimation_field + "!"] = None query_args['col'] = '|'.join([self.totalhours_field, 'owner']) tickets = execute_query(self.env, req, query_args) sum = 0.0 estimations = {} for ticket in tickets: if ticket['status'] in self.closed_states: continue try: estimation = float(ticket[self.estimation_field]) - float(ticket[self.totalhours_field]) owner = ticket['owner'] sum += estimation if estimations.has_key(owner): estimations[owner] += estimation else: estimations[owner] = estimation except: pass data = [] data.append(['Owner', 'Workload']) for owner, estimation in estimations.iteritems(): estimation = max(0, estimation) label = "%s %g%s" % (obfuscate_email_address(owner), round(estimation, 2), self.estimation_suffix) data.append([label, float(estimation)]) # Title title = 'Workload' # calculate remaining work time if options.get('today') and options.get('enddate'): currentdate = options['today'] day = timedelta(days=1) days_remaining = 0 while currentdate <= options['enddate']: if currentdate.weekday() < 5: days_remaining += 1 currentdate += day title += ' %g%s (~%s workdays left)' % (round(sum, 2), self.estimation_suffix, days_remaining) element_id = 'chart-%d' % random.randint(0, 0xffffffff) args = { 'containerId': element_id, 'chartType': 'PieChart', 'options': { 'width': int(options['width']), 'height': int(options['height']), 'title': title, 'legend': { 'position': 'labeled' }, 'pieSliceText': 'none', 'tooltip': 'percentage', }, } script = "EstimationCharts.push(function() {\n" script += 'var data=' + to_json(data) + ";\n" script += 'var args=' + to_json(args) + ";\n" script += 'DrawWorkloadChart(data, args);' script += '});' return tag.div(tag.div(id=element_id), tag.script(script))
def _notify(self, ticket, newticket=True, modtime=None): self.ticket = ticket self.modtime = modtime self.newticket = newticket changes_body = '' self.reporter = '' self.owner = '' changes_descr = '' change_data = {} link = self.env.abs_href.ticket(ticket.id) summary = self.ticket['summary'] if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env).grouped_changelog_entries( ticket, self.db, when=modtime): if not change['permanent']: # attachment with same time... continue change_data.update({ 'author': obfuscate_email_address(change['author']), 'comment': wrap(change['comment'], self.COLS, ' ', ' ', CRLF) }) link += '#comment:%s' % str(change.get('cnum', '')) for field, values in change['fields'].iteritems(): old = values['old'] new = values['new'] newv = '' if field == 'description': new_descr = wrap(new, self.COLS, ' ', ' ', CRLF) old_descr = wrap(old, self.COLS, '> ', '> ', CRLF) old_descr = old_descr.replace(2 * CRLF, CRLF + '>' + \ CRLF) cdescr = CRLF cdescr += 'Old description:' + 2 * CRLF + old_descr + \ 2 * CRLF cdescr += 'New description:' + 2 * CRLF + new_descr + \ CRLF changes_descr = cdescr elif field == 'summary': summary = "%s (was: %s)" % (new, old) elif field == 'cc': (addcc, delcc) = self.diff_cc(old, new) chgcc = '' if delcc: chgcc += wrap(" * cc: %s (removed)" % ', '.join(delcc), self.COLS, ' ', ' ', CRLF) + CRLF if addcc: chgcc += wrap(" * cc: %s (added)" % ', '.join(addcc), self.COLS, ' ', ' ', CRLF) + CRLF if chgcc: changes_body += chgcc self.prev_cc += old and self.parse_cc(old) or [] else: if field in ['owner', 'reporter']: old = obfuscate_email_address(old) new = obfuscate_email_address(new) newv = new length = 7 + len(field) spacer_old, spacer_new = ' ', ' ' if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = CRLF if len(new) + length > self.COLS: spacer_new = CRLF chg = '* %s: %s%s%s=>%s%s' % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace(CRLF, CRLF + length * ' ') chg = wrap(chg, self.COLS, '', length * ' ', CRLF) changes_body += ' %s%s' % (chg, CRLF) if newv: change_data[field] = {'oldvalue': old, 'newvalue': new} ticket_values = ticket.values.copy() ticket_values['id'] = ticket.id ticket_values['description'] = wrap( ticket_values.get('description', ''), self.COLS, initial_indent=' ', subsequent_indent=' ', linesep=CRLF) ticket_values['new'] = self.newticket ticket_values['link'] = link subject = self.format_subj(summary) if not self.newticket: subject = 'Re: ' + subject self.data.update({ 'ticket_props': self.format_props(), 'ticket_body_hdr': self.format_hdr(), 'subject': subject, 'ticket': ticket_values, 'changes_body': changes_body, 'changes_descr': changes_descr, 'change': change_data }) NotifyEmail.notify(self, ticket.id, subject)
def format_props(self): tkt = self.ticket fields = [f for f in tkt.fields if f['name'] not in ('summary', 'cc', 'time', 'changetime')] width = [0, 0, 0, 0] i = 0 for f in fields: if f['type'] == 'textarea': continue fname = f['name'] if not fname in tkt.values: continue fval = tkt[fname] or '' if fval.find('\n') != -1: continue if fname in ['owner', 'reporter']: fval = obfuscate_email_address(fval) idx = 2 * (i % 2) width[idx] = max(self.get_text_width(f['label']), width[idx]) width[idx + 1] = max(self.get_text_width(fval), width[idx + 1]) i += 1 width_l = width[0] + width[1] + 5 width_r = width[2] + width[3] + 5 half_cols = (self.COLS - 1) / 2 if width_l + width_r + 1 > self.COLS: if ((width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2)): width_l = half_cols width_r = half_cols elif width_l > width_r: width_l = min((self.COLS - 1) * 2 / 3, width_l) width_r = self.COLS - width_l - 1 else: width_r = min((self.COLS - 1) * 2 / 3, width_r) width_l = self.COLS - width_r - 1 sep = width_l * '-' + '+' + width_r * '-' txt = sep + CRLF cell_tmp = [u'', u''] big = [] i = 0 width_lr = [width_l, width_r] for f in [f for f in fields if f['name'] != 'description']: fname = f['name'] if not tkt.values.has_key(fname): continue fval = tkt[fname] or '' if fname in ['owner', 'reporter']: fval = obfuscate_email_address(fval) if f['type'] == 'textarea' or '\n' in unicode(fval): big.append((f['label'], CRLF.join(fval.splitlines()))) else: # Note: f['label'] is a Babel's LazyObject, make sure its # __str__ method won't be called. str_tmp = u'%s: %s' % (f['label'], unicode(fval)) idx = i % 2 cell_tmp[idx] += wrap(str_tmp, width_lr[idx] - 2 + 2 * idx, (width[2 * idx] - self.get_text_width(f['label']) + 2 * idx) * ' ', 2 * ' ', CRLF) cell_tmp[idx] += CRLF i += 1 cell_l = cell_tmp[0].splitlines() cell_r = cell_tmp[1].splitlines() for i in range(max(len(cell_l), len(cell_r))): if i >= len(cell_l): cell_l.append(width_l * ' ') elif i >= len(cell_r): cell_r.append('') fmt_width = width_l - self.get_text_width(cell_l[i]) \ + len(cell_l[i]) txt += u'%-*s|%s%s' % (fmt_width, cell_l[i], cell_r[i], CRLF) if big: txt += sep for name, value in big: txt += CRLF.join(['', name + ':', value, '', '']) txt += sep return txt
def format_author(self, req, author): if self.show_email_addresses or not req or 'EMAIL_VIEW' in req.perm: return author else: return obfuscate_email_address(author)
def expand_macro(self, formatter, name, content): req = formatter.req db = self.env.get_db_cnx() # prepare options options, query_args = parse_options(db, content, copy.copy(DEFAULT_OPTIONS)) query_args[self.estimation_field + "!"] = None tickets = execute_query(self.env, req, query_args) sum = 0.0 estimations = {} for ticket in tickets: if ticket['status'] in self.closed_states: continue try: estimation = float(ticket[self.estimation_field] or 0.0) if options.get('remainingworkload'): completion_cursor = db.cursor() completion_cursor.execute( "SELECT t.value AS totalhours, c.value AS complete, d.value AS due_close FROM ticket tk LEFT JOIN ticket_custom t ON (tk.id = t.ticket AND t.name = 'totalhours') LEFT JOIN ticket_custom c ON (tk.id = c.ticket AND c.name = 'complete') LEFT JOIN ticket_custom d ON (tk.id = d.ticket AND d.name = 'due_close') WHERE tk.id = %s" % ticket['id']) for row in completion_cursor: ticket['totalhours'], ticket['complete'], ticket[ 'due_close'] = row break # skip ticket ticket if due date is later than 'enddate': if options.get('showdueonly'): if not ticket['due_close']: continue # skip tickets with empty ETA when in 'showdueonly' mode due_close = parse_date(ticket['due_close'], ["%Y/%m/%d"]) startdate = options.get('startdate') enddate = options.get('enddate') if startdate and startdate > due_close: continue # skip tickets with ETA in the past if enddate and enddate < due_close: continue # skip tickets with ETA in the future pass totalhours = float(ticket['totalhours'] or 0.0) completed = (float(ticket['complete'] or 0.0) / 100) * estimation completed_hours = min(estimation, max(totalhours, completed)) estimation -= completed_hours pass owner = ticket['owner'] sum += estimation if estimations.has_key(owner): estimations[owner] += estimation else: estimations[owner] = estimation except: raise # Title title = 'Workload' days_remaining = None # calculate remaining work time if options.get('today') and options.get('enddate'): currentdate = options['today'] day = timedelta(days=1) days_remaining = 0 while currentdate <= options['enddate']: if currentdate.weekday() < 5: days_remaining += 1 currentdate += day title += ' %g%s (~%s workdays left)' % (round( sum, 2), self.estimation_suffix, days_remaining) estimations_string = [] labels = [] workhoursperday = max(float(options.get('workhoursperday')), 0.0) chts = '000000' for owner, estimation in estimations.iteritems(): # Note: Unconditional obfuscation of owner in case it represents # an email adress, and as the chart API doesn't support SSL # (plain http transfer only, from either client or server). label = "%s %g%s" % (obfuscate_email_address(owner), round(estimation, 2), self.estimation_suffix) if days_remaining != None: user_remaining_hours = days_remaining * workhoursperday if not user_remaining_hours or (estimation / user_remaining_hours) > 1: label = "%s (~%g hours left)!" % ( label, round(user_remaining_hours, 2) ) # user does not have enough hours left chts = 'FF0000' # set chart title style to red pass pass labels.append(label) estimations_string.append(str(int(estimation))) pass chart_args = unicode_urlencode({ 'chs': '%sx%s' % (options['width'], options['height']), 'chf': 'bg,s,00000000', 'chd': 't:%s' % ",".join(estimations_string), 'cht': 'p3', 'chtt': title, 'chts': chts, 'chl': "|".join(labels), 'chco': options['color'] }) self.log.debug("WorkloadChart data: %s" % repr(chart_args)) if self.serverside_charts: return tag.image( src="%s?data=%s" % (req.href.estimationtools('chart'), unicode_quote(chart_args)), alt="Workload Chart (server)") else: return tag.image(src="https://chart.googleapis.com/chart?%s" % chart_args, alt="Workload Chart (client)")
def get_list(self, realm, wl, req, fields=None): db = self.env.get_db_cnx() cursor = db.cursor() user = req.authname locale = getattr( req, 'locale', LC_TIME) context = Context.from_request(req) wikilist = [] extradict = {} if not fields: fields = set(self.default_fields['wiki']) else: fields = set(fields) if 'changetime' in fields: max_changetime = datetime(1970,1,1,tzinfo=utc) min_changetime = datetime.now(utc) for name, last_visit in wl.get_watched_resources( 'wiki', req.authname ): wikipage = WikiPage(self.env, name, db=db) wikidict = {} if not wikipage.exists: wikidict['deleted'] = True if 'name' in fields: wikidict['name'] = name if 'author' in fields: wikidict['author'] = '?' if 'changetime' in fields: wikidict['changedsincelastvisit'] = 1 wikidict['changetime'] = '?' wikidict['ichangetime'] = 0 if 'comment' in fields: wikidict['comment'] = tag.strong(t_("deleted"), class_='deleted') if 'notify' in fields: wikidict['notify'] = wl.is_notify(req, 'wiki', name) wikilist.append(wikidict) continue comment = wikipage.comment changetime = wikipage.time author = wikipage.author if wl.options['attachment_changes']: latest_attachment = None for attachment in Attachment.select(self.env, 'wiki', name, db): if attachment.date > changetime: latest_attachment = attachment if latest_attachment: changetime = latest_attachment.date author = latest_attachment.author if 'comment' in fields: wikitext = '[attachment:"' + ':'.join([latest_attachment.filename,'wiki',name]) + \ '" ' + latest_attachment.filename + ']' desc = latest_attachment.description comment = tag(tag_("Attachment %(attachment)s added",\ attachment=format_to_oneliner(self.env, context, wikitext, shorten=False)), desc and ': ' or '.', moreless(desc,10)) if 'attachment' in fields: attachments = [] for attachment in Attachment.select(self.env, 'wiki', name, db): wikitext = '[attachment:"' + ':'.join([attachment.filename,'wiki',name]) + '" ' + attachment.filename + ']' attachments.extend([tag(', '), format_to_oneliner(self.env, context, wikitext, shorten=False)]) if attachments: attachments.reverse() attachments.pop() ticketdict['attachment'] = moreless(attachments, 5) if 'name' in fields: wikidict['name'] = name if 'author' in fields: if not (Chrome(self.env).show_email_addresses or 'EMAIL_VIEW' in req.perm(wikipage.resource)): wikidict['author'] = obfuscate_email_address(author) else: wikidict['author'] = author if 'version' in fields: wikidict['version'] = unicode(wikipage.version) if 'changetime' in fields: wikidict['changetime'] = format_datetime( changetime, locale=locale, tzinfo=req.tz ) wikidict['ichangetime'] = to_timestamp( changetime ) wikidict['changedsincelastvisit'] = last_visit < wikidict['ichangetime'] and 1 or 0 wikidict['timedelta'] = pretty_timedelta( changetime ) wikidict['timeline_link'] = req.href.timeline(precision='seconds', from_=trac_format_datetime ( changetime, 'iso8601', tzinfo=req.tz)) if changetime > max_changetime: max_changetime = changetime if changetime < min_changetime: min_changetime = changetime if 'comment' in fields: comment = moreless(comment or "", 200) wikidict['comment'] = comment if 'notify' in fields: wikidict['notify'] = wl.is_notify(req, 'wiki', name) if 'readonly' in fields: wikidict['readonly'] = wikipage.readonly and t_("yes") or t_("no") if 'tags' in fields and self.tagsystem: tags = [] for t in self.tagsystem.get_tags(req, Resource('wiki', name)): tags.extend([tag.a(t,href=req.href('tags',q=t)), tag(', ')]) if tags: tags.pop() wikidict['tags'] = moreless(tags, 10) #if 'ipnr' in fields: # wikidict['ipnr'] = wikipage.ipnr, # Note: Not supported by Trac 0.12 wikilist.append(wikidict) if 'changetime' in fields: extradict['max_changetime'] = format_datetime( max_changetime, locale=locale, tzinfo=req.tz ) extradict['min_changetime'] = format_datetime( min_changetime, locale=locale, tzinfo=req.tz ) return wikilist, extradict
def render_property_diff(env, req, ticket, field, old, new, resource_new=None): "Version for Trac 0.12" rendered = None # per type special rendering of diffs type_ = None for f in ticket.fields: if f['name'] == field: type_ = f['type'] break if type_ == 'checkbox': rendered = new == '1' and _("set") or _("unset") elif type_ == 'textarea': if not resource_new: rendered = _("modified") else: href = get_resource_url(env, resource_new, req.href, action='diff') # TRANSLATOR: modified ('diff') (link) diff = tag.a(_("diff"), href=href) rendered = tag_("modified (%(diff)s)", diff=diff) # per name special rendering of diffs old_list, new_list = None, None render_elt = lambda x: x sep = ', ' if field == 'cc': chrome = Chrome(env) old_list, new_list = chrome.cc_list(old), chrome.cc_list(new) if not (Chrome(env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): render_elt = obfuscate_email_address elif field == 'keywords': old_list, new_list = old.split(), new.split() sep = ' ' if (old_list, new_list) != (None, None): added = [tag.em(render_elt(x)) for x in new_list if x not in old_list] remvd = [tag.em(render_elt(x)) for x in old_list if x not in new_list] added = added and tagn_("%(items)s added", "%(items)s added", len(added), items=separated(added, sep)) remvd = remvd and tagn_("%(items)s removed", "%(items)s removed", len(remvd), items=separated(remvd, sep)) if added or remvd: rendered = tag(added, added and remvd and _("; "), remvd) return rendered if field in ('reporter', 'owner'): if not (Chrome(env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): old = obfuscate_email_address(old) new = obfuscate_email_address(new) # Added by MS # The `wtag_` is the `tag_` from tracwatchlist.translation, e.g. # using its translation domain. if field == 'attachment': rendered = wtag_("%(value)s added", value=tag.em(new)) # changed 'if' to 'elif': elif old and not new: rendered = tag_("%(value)s deleted", value=tag.em(old)) elif new and not old: rendered = tag_("set to %(value)s", value=tag.em(new)) elif old and new: rendered = tag_("changed from %(old)s to %(new)s", old=tag.em(old), new=tag.em(new)) return rendered
def _notify(self, ticket, newticket=True, modtime=None): self.ticket = ticket self.modtime = modtime self.newticket = newticket changes_body = "" self.reporter = "" self.owner = "" changes_descr = "" change_data = {} link = self.env.abs_href.ticket(ticket.id) summary = self.ticket["summary"] if not self.newticket and modtime: # Ticket change from trac.ticket.web_ui import TicketModule for change in TicketModule(self.env).grouped_changelog_entries(ticket, self.db, when=modtime): if not change["permanent"]: # attachment with same time... continue change_data.update( { "author": obfuscate_email_address(change["author"]), "comment": wrap(change["comment"], self.COLS, " ", " ", CRLF), } ) link += "#comment:%s" % str(change.get("cnum", "")) for field, values in change["fields"].iteritems(): old = values["old"] new = values["new"] newv = "" if field == "description": new_descr = wrap(new, self.COLS, " ", " ", CRLF) old_descr = wrap(old, self.COLS, "> ", "> ", CRLF) old_descr = old_descr.replace(2 * CRLF, CRLF + ">" + CRLF) cdescr = CRLF cdescr += "Old description:" + 2 * CRLF + old_descr + 2 * CRLF cdescr += "New description:" + 2 * CRLF + new_descr + CRLF changes_descr = cdescr elif field == "summary": summary = "%s (was: %s)" % (new, old) elif field == "cc": (addcc, delcc) = self.diff_cc(old, new) chgcc = "" if delcc: chgcc += wrap(" * cc: %s (removed)" % ", ".join(delcc), self.COLS, " ", " ", CRLF) + CRLF if addcc: chgcc += wrap(" * cc: %s (added)" % ", ".join(addcc), self.COLS, " ", " ", CRLF) + CRLF if chgcc: changes_body += chgcc self.prev_cc += old and self.parse_cc(old) or [] else: if field in ["owner", "reporter"]: old = obfuscate_email_address(old) new = obfuscate_email_address(new) newv = new length = 7 + len(field) spacer_old, spacer_new = " ", " " if len(old + new) + length > self.COLS: length = 5 if len(old) + length > self.COLS: spacer_old = CRLF if len(new) + length > self.COLS: spacer_new = CRLF chg = "* %s: %s%s%s=>%s%s" % (field, spacer_old, old, spacer_old, spacer_new, new) chg = chg.replace(CRLF, CRLF + length * " ") chg = wrap(chg, self.COLS, "", length * " ", CRLF) changes_body += " %s%s" % (chg, CRLF) if newv: change_data[field] = {"oldvalue": old, "newvalue": new} ticket_values = ticket.values.copy() ticket_values["id"] = ticket.id ticket_values["description"] = wrap( ticket_values.get("description", ""), self.COLS, initial_indent=" ", subsequent_indent=" ", linesep=CRLF ) ticket_values["new"] = self.newticket ticket_values["link"] = link subject = self.format_subj(summary) if not self.newticket: subject = "Re: " + subject self.data.update( { "ticket_props": self.format_props(), "ticket_body_hdr": self.format_hdr(), "subject": subject, "ticket": ticket_values, "changes_body": changes_body, "changes_descr": changes_descr, "change": change_data, } ) NotifyEmail.notify(self, ticket.id, subject)
def render_property_diff(env, req, ticket, field, old, new, resource_new=None): "Version for Trac 0.12" rendered = None # per type special rendering of diffs type_ = None for f in ticket.fields: if f['name'] == field: type_ = f['type'] break if type_ == 'checkbox': rendered = new == '1' and _("set") or _("unset") elif type_ == 'textarea': if not resource_new: rendered = _("modified") else: href = get_resource_url(env, resource_new, req.href, action='diff') # TRANSLATOR: modified ('diff') (link) diff = tag.a(_("diff"), href=href) rendered = tag_("modified (%(diff)s)", diff=diff) # per name special rendering of diffs old_list, new_list = None, None render_elt = lambda x: x sep = ', ' if field == 'cc': chrome = Chrome(env) old_list, new_list = chrome.cc_list(old), chrome.cc_list(new) if not (Chrome(env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): render_elt = obfuscate_email_address elif field == 'keywords': old_list, new_list = old.split(), new.split() sep = ' ' if (old_list, new_list) != (None, None): added = [ tag.em(render_elt(x)) for x in new_list if x not in old_list ] remvd = [ tag.em(render_elt(x)) for x in old_list if x not in new_list ] added = added and tagn_("%(items)s added", "%(items)s added", len(added), items=separated(added, sep)) remvd = remvd and tagn_("%(items)s removed", "%(items)s removed", len(remvd), items=separated(remvd, sep)) if added or remvd: rendered = tag(added, added and remvd and _("; "), remvd) return rendered if field in ('reporter', 'owner'): if not (Chrome(env).show_email_addresses or 'EMAIL_VIEW' in req.perm(resource_new or ticket.resource)): old = obfuscate_email_address(old) new = obfuscate_email_address(new) # Added by MS # The `wtag_` is the `tag_` from tracwatchlist.translation, e.g. # using its translation domain. if field == 'attachment': rendered = wtag_("%(value)s added", value=tag.em(new)) # changed 'if' to 'elif': elif old and not new: rendered = tag_("%(value)s deleted", value=tag.em(old)) elif new and not old: rendered = tag_("set to %(value)s", value=tag.em(new)) elif old and new: rendered = tag_("changed from %(old)s to %(new)s", old=tag.em(old), new=tag.em(new)) return rendered
def format_props(self): tkt = self.ticket fields = [f for f in tkt.fields if f["name"] not in ("summary", "cc", "time", "changetime")] width = [0, 0, 0, 0] i = 0 for f in fields: if f["type"] == "textarea": continue fname = f["name"] if not fname in tkt.values: continue fval = tkt[fname] or "" if fval.find("\n") != -1: continue if fname in ["owner", "reporter"]: fval = obfuscate_email_address(fval) idx = 2 * (i % 2) width[idx] = max(self.get_text_width(f["label"]), width[idx]) width[idx + 1] = max(self.get_text_width(fval), width[idx + 1]) i += 1 width_l = width[0] + width[1] + 5 width_r = width[2] + width[3] + 5 half_cols = (self.COLS - 1) / 2 if width_l + width_r + 1 > self.COLS: if (width_l > half_cols and width_r > half_cols) or (width[0] > half_cols / 2 or width[2] > half_cols / 2): width_l = half_cols width_r = half_cols elif width_l > width_r: width_l = min((self.COLS - 1) * 2 / 3, width_l) width_r = self.COLS - width_l - 1 else: width_r = min((self.COLS - 1) * 2 / 3, width_r) width_l = self.COLS - width_r - 1 sep = width_l * "-" + "+" + width_r * "-" txt = sep + CRLF cell_tmp = [u"", u""] big = [] i = 0 width_lr = [width_l, width_r] for f in [f for f in fields if f["name"] != "description"]: fname = f["name"] if not tkt.values.has_key(fname): continue fval = tkt[fname] or "" if fname in ["owner", "reporter"]: fval = obfuscate_email_address(fval) if f["type"] == "textarea" or "\n" in unicode(fval): big.append((f["label"], CRLF.join(fval.splitlines()))) else: # Note: f['label'] is a Babel's LazyObject, make sure its # __str__ method won't be called. str_tmp = u"%s: %s" % (f["label"], unicode(fval)) idx = i % 2 cell_tmp[idx] += wrap( str_tmp, width_lr[idx] - 2 + 2 * idx, (width[2 * idx] - self.get_text_width(f["label"]) + 2 * idx) * " ", 2 * " ", CRLF, ) cell_tmp[idx] += CRLF i += 1 cell_l = cell_tmp[0].splitlines() cell_r = cell_tmp[1].splitlines() for i in range(max(len(cell_l), len(cell_r))): if i >= len(cell_l): cell_l.append(width_l * " ") elif i >= len(cell_r): cell_r.append("") fmt_width = width_l - self.get_text_width(cell_l[i]) + len(cell_l[i]) txt += u"%-*s|%s%s" % (fmt_width, cell_l[i], cell_r[i], CRLF) if big: txt += sep for name, value in big: txt += CRLF.join(["", name + ":", value, "", ""]) txt += sep return txt