From c67652a21941dff65017b4ac93f335afa9cb821d Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 3 Oct 2024 15:40:37 +0200 Subject: [PATCH 1/6] 2456: Updated field label and description --- config/sync/field.field.node.hearing.field_delete_date.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/sync/field.field.node.hearing.field_delete_date.yml b/config/sync/field.field.node.hearing.field_delete_date.yml index 7aa5e825..76d97345 100644 --- a/config/sync/field.field.node.hearing.field_delete_date.yml +++ b/config/sync/field.field.node.hearing.field_delete_date.yml @@ -11,8 +11,8 @@ id: node.hearing.field_delete_date field_name: field_delete_date entity_type: node bundle: hearing -label: 'Slette dato' -description: 'Bruges til at sætte dato for sletning af høringssvar.' +label: 'Dato for sletning af høringssvar' +description: 'Vælg hvornår høringssvar skal slettes fra høringen.' required: true translatable: false default_value: { } From 65fe6e05bcb3dcbb738dae749e955a2ac432b3b7 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Thu, 3 Oct 2024 17:06:31 +0200 Subject: [PATCH 2/6] 2456: Added stuff for deleting hearing replies --- CHANGELOG.md | 4 +- .../hoeringsportal_data.services.yml | 6 +- .../src/Drush/Commands/DrushCommands.php | 71 +++++++++++++++++++ .../src/Helper/HearingHelper.php | 69 +++++++++++++++--- .../hoeringsportal_deskpro.module | 1 + .../src/Plugin/Block/HearingTicketsBlock.php | 1 + .../src/Service/HearingHelper.php | 36 ++++++++++ .../hoeringsportal-hearing-tickets.html.twig | 29 ++++---- 8 files changed, 190 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de9a1fd..83cb7766 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +* [PR-419](https://github.com/itk-dev/hoeringsportal/pull/419) + Added Drush command to delete hearing replies * [PR-418](https://github.com/itk-dev/hoeringsportal/pull/418) - Add message when hearing tickets is deleted. + Add message when hearing replies are deleted * [PR-417](https://github.com/itk-dev/hoeringsportal/pull/417) Added more test setup stuff diff --git a/web/modules/custom/hoeringsportal_data/hoeringsportal_data.services.yml b/web/modules/custom/hoeringsportal_data/hoeringsportal_data.services.yml index c745ec57..97f611f1 100644 --- a/web/modules/custom/hoeringsportal_data/hoeringsportal_data.services.yml +++ b/web/modules/custom/hoeringsportal_data/hoeringsportal_data.services.yml @@ -1,4 +1,8 @@ services: + logger.channel.hoeringsportal_data: + parent: logger.channel_base + arguments: [ 'hoeringsportal_data' ] + hoeringsportal_data.plandata: class: Drupal\hoeringsportal_data\Service\Plandata arguments: ['@settings'] @@ -8,7 +12,7 @@ services: hoeringsportal_data.hearing_helper: class: Drupal\hoeringsportal_data\Helper\HearingHelper - arguments: ['@entity_type.manager'] + arguments: ['@entity_type.manager', '@logger.channel.hoeringsportal_data'] hoeringsportal_data.map_item_helper: class: Drupal\hoeringsportal_data\Helper\MapItemHelper diff --git a/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php b/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php index a9fbf190..018cd829 100644 --- a/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php +++ b/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php @@ -2,7 +2,11 @@ namespace Drupal\hoeringsportal_data\Drush\Commands; +use Drupal\Component\Datetime\TimeInterface; +use Drupal\Core\Datetime\DrupalDateTime; +use Drupal\Core\State\StateInterface; use Drupal\hoeringsportal_data\Helper\HearingHelper; +use Drupal\hoeringsportal_deskpro\Service\HearingHelper as DeskproHearingHelper; use Drush\Attributes as CLI; use Drush\Commands\DrushCommands as BaseDrushCommands; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -17,6 +21,9 @@ final class DrushCommands extends BaseDrushCommands { */ public function __construct( private readonly HearingHelper $helper, + private readonly DeskproHearingHelper $deskproHelper, + private readonly TimeInterface $time, + private readonly StateInterface $state, ) { parent::__construct(); } @@ -27,6 +34,9 @@ public function __construct( public static function create(ContainerInterface $container) { return new static( $container->get('hoeringsportal_data.hearing_helper'), + $container->get('hoeringsportal_deskpro.helper'), + $container->get('datetime.time'), + $container->get('state') ); } @@ -66,4 +76,65 @@ public function showHearingState() { } } + /** + * A drush command for deleting replies from hearings. + */ + #[CLI\Command(name: 'hoeringsportal:data:delete-replies')] + public function processDeleteHearingReplies( + array $options = [ + 'last-run-at' => NULL, + ], + ): void { + $lastRunAt = $options['last-run-at'] ? new DrupalDateTime($options['last-run-at']) : $this->getLastRunAt(__METHOD__); + $requestTime = $this->getRequestTime(); + + $hearingIds = $this->helper->findHearingWhoseRepliesMustBeDeleted($lastRunAt, + $requestTime); + + if ($this->io()->isVerbose()) { + $this->io()->info(sprintf('Deleting hearing replies between %s and %s: %s', $lastRunAt->format('Y-m-d'), $requestTime->format('Y-m-d'), implode(', ', $hearingIds))); + } + + if ($options['yes'] || $this->confirm(sprintf('Delete replies on hearings %s', implode(', ', $hearingIds)))) { + $result = $this->deskproHelper->deleteHearingReplies($hearingIds); + + $this->io()->success(1 === $result + ? sprintf('Replies deleted from 1 hearing: %s', implode(', ', $hearingIds)) + : sprintf('Replies deleted from %d hearings: %s', $result, implode(', ', $hearingIds)) + ); + + $this->setLastRunAt(__METHOD__); + } + } + + /** + * Get request time. + */ + private function getRequestTime(): DrupalDateTime { + return DrupalDateTime::createFromTimestamp($this->time->getRequestTime()); + } + + /** + * Get time of last run. + */ + private function getLastRunAt(string $method): DrupalDateTime { + $time = $this->state->get($this->getLastRunKey($method)); + + return DrupalDateTime::createFromTimestamp($time ?? 0); + } + + /** + * Set time of last run. + */ + private function setLastRunAt(string $method) { + $this->state->set($this->getLastRunKey($method), $this->time->getRequestTime()); + } + + /** + * Get last run key. + */ + private function getLastRunKey(string $method): string { + return $method . '_last_run_at'; + } + } diff --git a/web/modules/custom/hoeringsportal_data/src/Helper/HearingHelper.php b/web/modules/custom/hoeringsportal_data/src/Helper/HearingHelper.php index f57db833..bc590ea6 100644 --- a/web/modules/custom/hoeringsportal_data/src/Helper/HearingHelper.php +++ b/web/modules/custom/hoeringsportal_data/src/Helper/HearingHelper.php @@ -4,30 +4,32 @@ use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Logger\LoggerChannelInterface; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\node\Entity\Node; use Drupal\node\NodeInterface; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; /** * Hearing helper. */ -class HearingHelper { +class HearingHelper implements LoggerAwareInterface { + use LoggerAwareTrait; + const NODE_TYPE_HEARING = 'hearing'; const STATE_UPCOMING = 'upcoming'; const STATE_ACTIVE = 'active'; const STATE_FINISHED = 'finished'; - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - private $entityTypeManager; - /** * Constructor. */ - public function __construct(EntityTypeManagerInterface $entityTypeManager) { - $this->entityTypeManager = $entityTypeManager; + public function __construct( + private readonly EntityTypeManagerInterface $entityTypeManager, + LoggerChannelInterface $logger, + ) { + $this->setLogger($logger); } /** @@ -90,6 +92,51 @@ public function isDeadlinePassed(NodeInterface $node) { return $this->getDateTime() > new DrupalDateTime($deadline); } + /** + * Check if hearing's delete replies date is passed. + */ + public function isDeleteRepliesDatePassed(NodeInterface $node) { + if (!$this->isHearing($node)) { + return FALSE; + } + + $deadline = $node->field_delete_date->date; + + if (empty($deadline)) { + return FALSE; + } + + return $this->getDateTime() > new DrupalDateTime($deadline); + } + + /** + * Find hearings whose replies must be deleted. + * + * @return array + * A list of hearing ids. + */ + public function findHearingWhoseRepliesMustBeDeleted(DrupalDateTime $from, DrupalDateTime $to): array { + try { + return $this->entityTypeManager + ->getStorage('node') + ->getQuery() + ->condition('type', 'hearing') + ->condition('field_delete_date', [ + $from->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + $to->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), + ], 'BETWEEN') + ->accessCheck(FALSE) + ->execute(); + } + catch (\Exception $exception) { + $this->logger->error('Error finding hearing whose replies must be deleted: @message', [ + '@message' => $exception->getMessage(), + ]); + + return []; + } + } + /** * A list of conditions. * @@ -125,7 +172,7 @@ public function isHearing($node) { /** * Get a date time object. */ - private function getDateTime($time = 'now', $timezone = 'UTC') { + private function getDateTime($time = 'now', $timezone = 'UTC'): DrupalDateTime { return new DrupalDateTime($time, $timezone); } diff --git a/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module b/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module index d5026c34..cafea7c2 100644 --- a/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module +++ b/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module @@ -37,6 +37,7 @@ function hoeringsportal_deskpro_theme() { 'is_deadline_passed' => NULL, 'tickets' => NULL, 'is_hearing_started' => NULL, + 'is_delete_replies_date_passed' => NULL, ], ], 'hoeringsportal_hearing_ticket' => [ diff --git a/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php b/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php index 92ff684b..a562ef6f 100644 --- a/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php +++ b/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php @@ -35,6 +35,7 @@ public function build() { '#is_deadline_passed' => $this->helper->isDeadlinePassed($node), '#tickets' => $this->helper->getHearingTickets($node), '#is_hearing_started' => $is_hearing_started, + '#is_delete_replies_date_passed' => $this->helper->isDeleteRepliesDatePassed($node), ]; } diff --git a/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php b/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php index 70561593..883178ba 100644 --- a/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php +++ b/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php @@ -81,6 +81,10 @@ public function __construct(DeskproService $deskpro, EntityTypeManagerInterface * Check if hearing deadline is passed. */ public function isDeadlinePassed(NodeInterface $node) { + if ($this->isDeleteRepliesDatePassed($node)) { + return TRUE; + } + if (!$this->isHearing($node)) { return FALSE; } @@ -99,6 +103,23 @@ public function isDeadlinePassed(NodeInterface $node) { return new DrupalDateTime() > new DrupalDateTime($deadline); } + /** + * Check if hearing's delete replies date is passed. + */ + public function isDeleteRepliesDatePassed(NodeInterface $node): bool { + if (!$this->isHearing($node)) { + return FALSE; + } + + $deadline = $node->field_delete_date->date; + + if (empty($deadline)) { + return FALSE; + } + + return new DrupalDateTime() > new DrupalDateTime($deadline); + } + /** * Check if node is a hearing. */ @@ -627,6 +648,21 @@ public function getDeskproTicket(NodeInterface $node, int $ticketId, bool $reset return $info[$bundle][$entity_id][$ticketId] ?? NULL; } + /** + * Delete hearing replies. + */ + public function deleteHearingReplies(array $nodeIds): int { + if (empty($nodeIds)) { + return 0; + } + + return $this->database->delete('hoeringsportal_deskpro_deskpro_tickets') + ->condition('entity_type', 'node', '=') + ->condition('bundle', 'hearing') + ->condition('entity_id', $nodeIds, 'IN') + ->execute(); + } + /** * Set Deskpro data for a node. * diff --git a/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig b/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig index 9ba31c31..999a3975 100644 --- a/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig +++ b/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig @@ -40,6 +40,20 @@ {% else %} + {% if is_delete_replies_date_passed %} + {# Deleted tickets information #} +
+ +

{{ 'You can read more about why and how we delete old tickets'|trans }}

+
+ + {% else %}
{% if hearing_ticket_list_url %} @@ -80,20 +94,7 @@ {% endfor %} {% endif %} - {% endif %} - - {# Deleted tickets information #} - {% if node.field_delete_date.value < "now"|date('Y-m-d') %} -
- -

{{ 'You can read more about why and how we delete old tickets'|trans }}

-
+ {% endif %} {% endif %}
From 47bae008c566df731aca41fb9e57fadd796a0e06 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Fri, 4 Oct 2024 09:55:25 +0200 Subject: [PATCH 3/6] 2456: Improved stuff --- .../custom/hoeringsportal_data/README.md | 13 ++++++ .../src/Drush/Commands/DrushCommands.php | 44 ++++++++++++++----- .../hoeringsportal_deskpro.module | 2 +- .../src/Plugin/Block/HearingTicketsBlock.php | 2 +- .../src/Service/HearingHelper.php | 27 ++++++------ .../hoeringsportal-hearing-tickets.html.twig | 20 ++++----- 6 files changed, 71 insertions(+), 37 deletions(-) diff --git a/web/modules/custom/hoeringsportal_data/README.md b/web/modules/custom/hoeringsportal_data/README.md index 659ae791..89c15094 100644 --- a/web/modules/custom/hoeringsportal_data/README.md +++ b/web/modules/custom/hoeringsportal_data/README.md @@ -36,6 +36,19 @@ e.g. every minute */5 * * * * drush hoeringsportal:data:hearing-state-update ``` +Delete replies on hearings by running + +```sh +drush hoeringsportal:data:delete-replies +``` + +This should be done regularly by `cron` or other similar means, +e.g. daily at 03:00 + +```sh +0 3 * * * drush hoeringsportal:data:delete-replies +``` + ## Building assets First, install tools and requirements: diff --git a/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php b/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php index 018cd829..20dbd832 100644 --- a/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php +++ b/web/modules/custom/hoeringsportal_data/src/Drush/Commands/DrushCommands.php @@ -3,6 +3,7 @@ namespace Drupal\hoeringsportal_data\Drush\Commands; use Drupal\Component\Datetime\TimeInterface; +use Drupal\Core\Cache\Cache; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\State\StateInterface; use Drupal\hoeringsportal_data\Helper\HearingHelper; @@ -80,28 +81,47 @@ public function showHearingState() { * A drush command for deleting replies from hearings. */ #[CLI\Command(name: 'hoeringsportal:data:delete-replies')] + #[CLI\Option(name: 'ids', description: 'Comma separated list of ids')] public function processDeleteHearingReplies( array $options = [ + 'ids' => NULL, 'last-run-at' => NULL, ], ): void { - $lastRunAt = $options['last-run-at'] ? new DrupalDateTime($options['last-run-at']) : $this->getLastRunAt(__METHOD__); - $requestTime = $this->getRequestTime(); + if (!empty($options['ids'])) { + $hearingIds = preg_split('/\s*,\s*/', $options['ids'] ?? '', PREG_SPLIT_NO_EMPTY); + } + else { + $lastRunAt = $options['last-run-at'] ? new DrupalDateTime($options['last-run-at']) : $this->getLastRunAt(__METHOD__); + $requestTime = $this->getRequestTime(); - $hearingIds = $this->helper->findHearingWhoseRepliesMustBeDeleted($lastRunAt, - $requestTime); + if ($this->io()->isVerbose()) { + $this->io()->info(sprintf('Finding hearings with delete replies date between %s and %s', $lastRunAt->format('Y-m-d'), $requestTime->format('Y-m-d'))); + } - if ($this->io()->isVerbose()) { - $this->io()->info(sprintf('Deleting hearing replies between %s and %s: %s', $lastRunAt->format('Y-m-d'), $requestTime->format('Y-m-d'), implode(', ', $hearingIds))); + $hearingIds = $options['ids'] ?? $this->helper->findHearingWhoseRepliesMustBeDeleted($lastRunAt, $requestTime); } - if ($options['yes'] || $this->confirm(sprintf('Delete replies on hearings %s', implode(', ', $hearingIds)))) { - $result = $this->deskproHelper->deleteHearingReplies($hearingIds); + if (empty($hearingIds)) { + $this->io()->info('No hearings found'); + return; + } - $this->io()->success(1 === $result - ? sprintf('Replies deleted from 1 hearing: %s', implode(', ', $hearingIds)) - : sprintf('Replies deleted from %d hearings: %s', $result, implode(', ', $hearingIds)) - ); + if ($options['yes'] || $this->confirm(sprintf('Delete replies on hearings %s', implode(', ', $hearingIds)))) { + $hearings = $this->helper->loadHearings([['nid', $hearingIds, 'IN']]); + foreach ($hearings as $hearing) { + $hearingRepliesDeletedOn = $this->deskproHelper->getHearingRepliesDeletedOn($hearing); + if (NULL !== $hearingRepliesDeletedOn) { + $this->deskproHelper->deleteHearingReplies([$hearing->id()]); + Cache::invalidateTags($hearing->getCacheTags()); + + $this->io()->success(sprintf('Replies deleted from hearing %s', + $hearing->id())); + } + else { + $this->io()->warning(sprintf('Replies on hearing %s must not yet be deleted', $hearing->id())); + } + } $this->setLastRunAt(__METHOD__); } diff --git a/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module b/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module index cafea7c2..0fb6d0e1 100644 --- a/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module +++ b/web/modules/custom/hoeringsportal_deskpro/hoeringsportal_deskpro.module @@ -37,7 +37,7 @@ function hoeringsportal_deskpro_theme() { 'is_deadline_passed' => NULL, 'tickets' => NULL, 'is_hearing_started' => NULL, - 'is_delete_replies_date_passed' => NULL, + 'hearing_replies_deleted_on' => NULL, ], ], 'hoeringsportal_hearing_ticket' => [ diff --git a/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php b/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php index a562ef6f..5f14af24 100644 --- a/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php +++ b/web/modules/custom/hoeringsportal_deskpro/src/Plugin/Block/HearingTicketsBlock.php @@ -35,7 +35,7 @@ public function build() { '#is_deadline_passed' => $this->helper->isDeadlinePassed($node), '#tickets' => $this->helper->getHearingTickets($node), '#is_hearing_started' => $is_hearing_started, - '#is_delete_replies_date_passed' => $this->helper->isDeleteRepliesDatePassed($node), + '#hearing_replies_deleted_on' => $this->helper->getHearingRepliesDeletedOn($node), ]; } diff --git a/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php b/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php index 883178ba..551360b3 100644 --- a/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php +++ b/web/modules/custom/hoeringsportal_deskpro/src/Service/HearingHelper.php @@ -81,7 +81,7 @@ public function __construct(DeskproService $deskpro, EntityTypeManagerInterface * Check if hearing deadline is passed. */ public function isDeadlinePassed(NodeInterface $node) { - if ($this->isDeleteRepliesDatePassed($node)) { + if (NULL !== $this->getHearingRepliesDeletedOn($node)) { return TRUE; } @@ -104,20 +104,23 @@ public function isDeadlinePassed(NodeInterface $node) { } /** - * Check if hearing's delete replies date is passed. + * Get the date when a hearing's replies should be deleted. + * + * @return \Drupal\Core\Datetime\DrupalDateTime|null + * The delete date if it's set and not in the future. */ - public function isDeleteRepliesDatePassed(NodeInterface $node): bool { + public function getHearingRepliesDeletedOn(NodeInterface $node): ?DrupalDateTime { if (!$this->isHearing($node)) { - return FALSE; + return NULL; } - $deadline = $node->field_delete_date->date; + $date = $node->field_delete_date->date; - if (empty($deadline)) { - return FALSE; + if (empty($date)) { + return NULL; } - return new DrupalDateTime() > new DrupalDateTime($deadline); + return new DrupalDateTime() > $date ? $date : NULL; } /** @@ -651,15 +654,13 @@ public function getDeskproTicket(NodeInterface $node, int $ticketId, bool $reset /** * Delete hearing replies. */ - public function deleteHearingReplies(array $nodeIds): int { - if (empty($nodeIds)) { - return 0; - } + public function deleteHearingReplies(int|array $nodeIds): int { + $nodeId = (array) $nodeIds; return $this->database->delete('hoeringsportal_deskpro_deskpro_tickets') ->condition('entity_type', 'node', '=') ->condition('bundle', 'hearing') - ->condition('entity_id', $nodeIds, 'IN') + ->condition('entity_id', $nodeIds ?: [0], 'IN') ->execute(); } diff --git a/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig b/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig index 999a3975..3b1029b8 100644 --- a/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig +++ b/web/modules/custom/hoeringsportal_deskpro/templates/block/hoeringsportal-hearing-tickets.html.twig @@ -40,18 +40,18 @@ {% else %} - {% if is_delete_replies_date_passed %} - {# Deleted tickets information #} -
-