text: # -*- coding: utf-8 -*-
  #
  # Copyright (C) 2010 Edgewall Software
  # All rights reserved.
  #
  # This software is licensed as described in the file COPYING, which
  # you should have received as part of this distribution. The terms
  # are also available at http://trac.edgewall.org/wiki/TracLicense.
  #
  # This software consists of voluntary contributions made by many
  # individuals. For the exact contribution history, see the revision
  # history and logs, available at http://trac.edgewall.org/log/.
  
  from genshi.builder import tag
  from genshi.filters import Transformer
  from genshi.filters.transform import StreamBuffer
  
  from trac.core import Component, TracError, implements
  from trac.ticket.model import Ticket
  from trac.ticket.web_ui import TicketModule
  from trac.util import get_reporter_id
  from trac.util.datefmt import from_utimestamp
  from trac.util.presentation import captioned_button
  from trac.util.translation import _
  from trac.web.api import IRequestFilter, IRequestHandler, ITemplateStreamFilter
  from trac.web.chrome import ITemplateProvider, add_notice, add_stylesheet
  
  
  class TicketDeleter(Component):
      """Ticket and ticket comment deleter.
  
      This component allows deleting ticket comments and complete tickets. For
      users having `TICKET_ADMIN` permission, it adds a "Delete" button next to
      each "Reply" button on the page. The button in the ticket description
      requests deletion of the complete ticket, and the buttons in the change
      history request deletion of a single comment.
  
      '''Comment and ticket deletion are irreversible (and therefore
      ''dangerous'') operations.''' For that reason, a confirmation step is
      requested. The confirmation page shows the ticket box (in the case of a
      ticket deletion) or the ticket change (in the case of a comment deletion).
      """
  
      implements(ITemplateProvider, ITemplateStreamFilter, IRequestFilter,
                 IRequestHandler)
  
      # ITemplateProvider methods
  
      def get_htdocs_dirs(self):
          return []
  
      def get_templates_dirs(self):
          from pkg_resources import resource_filename
          return [resource_filename(__name__, 'templates')]
  
      # ITemplateStreamFilter methods
  
      def filter_stream(self, req, method, filename, stream, data):
          if filename not in ('ticket.html', 'ticket_preview.html'):
              return stream
          ticket = data.get('ticket')
          if not (ticket and ticket.exists
                  and 'TICKET_ADMIN' in req.perm(ticket.resource)):
              return stream
  
          # Insert "Delete" buttons for ticket description and each comment
          def delete_ticket():
              return tag.form(
                  tag.div(
                      tag.input(type='hidden', name='action', value='delete'),
                      tag.input(type='submit',
                                value=captioned_button(req, u'–', # 'EN DASH'
                                                       _("Delete")),
                                title=_('Delete ticket'),
                                class_="trac-delete"),
                      class_="inlinebuttons"),
                  action='#', method='get')
  
          def delete_comment():
              for event in buffer:
                  cnum, cdate = event[1][1].get('id')[12:].split('-', 1)
                  return tag.form(
                      tag.div(
                          tag.input(type='hidden', name='action',
                                    value='delete-comment'),
                          tag.input(type='hidden', name='cnum', value=cnum),
                          tag.input(type='hidden', name='cdate', value=cdate),
                          tag.input(type='submit',
                                    value=captioned_button(req, u'–', # 'EN DASH'
                                                           _("Delete")),
                                    title=_('Delete comment %(num)s', num=cnum),
                                    class_="trac-delete"),
                          class_="inlinebuttons"),
                      action='#', method='get')
  
          buffer = StreamBuffer()
          return stream | Transformer('//div[@class="description"]'
                                      '/h3[@id="comment:description"]') \
              .after(delete_ticket).end() \
              .select('//div[starts-with(@class, "change")]/@id') \
              .copy(buffer).end() \
              .select('//div[starts-with(@class, "change") and @id]'
                      '/div[@class="trac-ticket-buttons"]') \
              .prepend(delete_comment)
  
      # IRequestFilter methods
  
      def pre_process_request(self, req, handler):
          if handler is not TicketModule(self.env):
              return handler
          action = req.args.get('action')
          if action in ('delete', 'delete-comment'):
              return self
          else:
              return handler
  
      def post_process_request(self, req, template, data, content_type):
          return template, data, content_type
  
      # IRequestHandler methods
  
      def match_request(self, req):
          return False
  
      def process_request(self, req):
          id = int(req.args.get('id'))
          req.perm('ticket', id).require('TICKET_ADMIN')
          ticket = Ticket(self.env, id)
          action = req.args['action']
          cnum = req.args.get('cnum')
          if req.method == 'POST':
              if 'cancel' in req.args:
                  href = req.href.ticket(id)
                  if action == 'delete-comment':
                      href += '#comment:%s' % cnum
                  req.redirect(href)
  
              if action == 'delete':
                  ticket.delete()
                  add_notice(req, _('The ticket #%(id)s has been deleted.',
                                    id=ticket.id))
                  req.redirect(req.href())
  
              elif action == 'delete-comment':
                  cdate = from_utimestamp(long(req.args.get('cdate')))
                  ticket.delete_change(cdate=cdate)
                  add_notice(req, _('The ticket comment %(num)s on ticket '
                                    '#%(id)s has been deleted.',
                                    num=cnum, id=ticket.id))
                  req.redirect(req.href.ticket(id))
  
          tm = TicketModule(self.env)
          data = tm._prepare_data(req, ticket)
          tm._insert_ticket_data(req, ticket, data,
                                 get_reporter_id(req, 'author'), {})
          data.update(action=action, cdate=None)
  
          if action == 'delete-comment':
              data['cdate'] = req.args.get('cdate')
              cdate = from_utimestamp(long(data['cdate']))
              for change in data['changes']:
                  if change.get('date') == cdate:
                      data['change'] = change
                      data['cnum'] = change.get('cnum')
                      break
              else:
                  raise TracError(_('Comment %(num)s not found', num=cnum))
  
          add_stylesheet(req, 'common/css/ticket.css')
          return 'ticket_delete.html', data, None
  