aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorM Somerville <matthew-github@dracos.co.uk>2020-09-30 20:04:10 +0100
committerM Somerville <matthew-github@dracos.co.uk>2020-09-30 20:04:10 +0100
commit57f4048942a8caaf702f47373fabdfa31932659e (patch)
treef9b2272947b0c59c7c1e843186d850d52f21bee4
parent3a511f5facc4bd066f0e7e4d00060979686bb766 (diff)
parentdffc4acfe67fb05f928806601bab8ce1057e7b67 (diff)
Merge branch 'issues/commercial/1928-html-in-response-templates'
-rw-r--r--CHANGELOG.md2
-rw-r--r--docs/_includes/admin-tasks-content.md13
-rw-r--r--perllib/FixMyStreet/App/View/Web.pm49
-rw-r--r--perllib/FixMyStreet/Script/Alerts.pm1
-rw-r--r--perllib/FixMyStreet/Template.pm86
-rw-r--r--perllib/FixMyStreet/TestMech.pm18
-rw-r--r--t/app/controller/alert_new.t43
-rw-r--r--t/app/controller/my.t23
-rw-r--r--t/app/controller/questionnaire.t9
-rw-r--r--t/app/controller/report_updates.t87
-rw-r--r--templates/email/default/_email_comment_list.html2
-rw-r--r--templates/email/default/_email_comment_list.txt2
-rw-r--r--templates/email/fixamingata/_email_comment_list.html2
-rw-r--r--templates/web/base/admin/users/log.html2
-rw-r--r--templates/web/base/my/my.html2
-rw-r--r--templates/web/base/questionnaire/index.html2
-rw-r--r--templates/web/base/report/update.html4
17 files changed, 320 insertions, 27 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 083c9d935..8aa628795 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,8 @@
- Add full text index to speed up admin search.
- Offline process for CSV generation.
- Allow inspectors to change report asset.
+ - Staff users can use HTML tags in updates. #3143
+ - Response templates can include HTML tags. #3143
- Development improvements:
- `#geolocate_link` is now easier to re-style. #3006
- Links inside `#front-main` can be customised using `$primary_link_*` Sass variables. #3007
diff --git a/docs/_includes/admin-tasks-content.md b/docs/_includes/admin-tasks-content.md
index d0d5e4935..0fdc259e9 100644
--- a/docs/_includes/admin-tasks-content.md
+++ b/docs/_includes/admin-tasks-content.md
@@ -825,6 +825,19 @@ Click on ‘Templates’ in the admin menu. You will see a table of existing tem
beside the status you wish to change. You may alter any of the fields as described in the section
above, ‘Creating a template’. Additionally you can delete the template from this page.
+
+#### HTML content in templates
+
+HTML tags are permitted in response templates, which makes it possible to include
+hyperlinks or rich text formatting in the updates which are added to reports.
+
+Be aware that response templates are emailed to users as well as being shown on
+the site, so it's best to keep any HTML formatting quite light-touch due to the
+quirks of email clients' rendering of HTML message.
+
+Refer to the section ["HTML Content in notices"](#html-content-in-notices) above for details of
+what tags and attributes are allowed.
+
</div>
<div class="admin-task" markdown="1" id="view-statistics">
diff --git a/perllib/FixMyStreet/App/View/Web.pm b/perllib/FixMyStreet/App/View/Web.pm
index 1e1b50094..41444fdd4 100644
--- a/perllib/FixMyStreet/App/View/Web.pm
+++ b/perllib/FixMyStreet/App/View/Web.pm
@@ -25,7 +25,7 @@ __PACKAGE__->config(
FILTERS => {
add_links => \&add_links,
escape_js => \&escape_js,
- markup => [ \&markup_factory, 1 ],
+ staff_html_markup => [ \&staff_html_markup_factory, 1 ],
},
COMPILE_EXT => '.ttc',
STAT_TTL => FixMyStreet->config('STAGING_SITE') ? 1 : 86400,
@@ -100,7 +100,7 @@ sub add_links {
my $text = shift;
$text = FixMyStreet::Template::conditional_escape($text);
$text =~ s/\r//g;
- $text =~ s{(https?://)([^\s]+)}{"<a href=\"$1$2\">$1" . _space_slash($2) . '</a>'}ge;
+ $text =~ s{(?<!["'])(https?://)([^\s]+)}{"<a href=\"$1$2\">$1" . _space_slash($2) . '</a>'}ge;
return FixMyStreet::Template::SafeString->new($text);
}
@@ -110,21 +110,50 @@ sub _space_slash {
return $t;
}
-=head2 markup_factory
+=head2 staff_html_markup_factory
-This returns a function that will allow updates to have markdown-style italics.
-Pass in the user that wrote the text, so we know whether it can be privileged.
+This returns a function that processes the text body of an update, applying
+HTML sanitization and markdown-style italics if it was made by a staff user.
+
+Pass in the update extra, so we can determine if it was made by a staff user.
=cut
-sub markup_factory {
- my ($c, $user) = @_;
+sub staff_html_markup_factory {
+ my ($c, $extra) = @_;
+
+ my $staff = $extra->{is_superuser} || $extra->{is_body_user};
+
return sub {
my $text = shift;
- return $text unless $user && ($user->from_body || $user->is_superuser);
- $text =~ s{\*(\S.*?\S)\*}{<i>$1</i>};
- FixMyStreet::Template::SafeString->new($text);
+ return _staff_html_markup($text, $staff);
+ }
+}
+
+sub _staff_html_markup {
+ my ( $text, $staff ) = @_;
+ unless ($staff) {
+ return FixMyStreet::Template::html_paragraph(add_links($text));
+ }
+
+ $text = FixMyStreet::Template::sanitize($text);
+
+ # Apply Markdown-style italics
+ $text =~ s{\*(\S.*?\S)\*}{<i>$1</i>};
+
+ # Mark safe so add_links doesn't escape everything.
+ $text = FixMyStreet::Template::SafeString->new($text);
+
+ $text = add_links($text);
+
+ # If the update already has block-level elements then don't wrap
+ # individual lines in <p> elements, as we assume the user knows what
+ # they're doing.
+ unless ($text =~ /<(p|ol|ul)>/) {
+ $text = FixMyStreet::Template::html_paragraph($text);
}
+
+ return $text;
}
=head2 escape_js
diff --git a/perllib/FixMyStreet/Script/Alerts.pm b/perllib/FixMyStreet/Script/Alerts.pm
index 03373a8cc..fa90ede48 100644
--- a/perllib/FixMyStreet/Script/Alerts.pm
+++ b/perllib/FixMyStreet/Script/Alerts.pm
@@ -41,6 +41,7 @@ sub send() {
$item_table.photo as item_photo,
$item_table.problem_state as item_problem_state,
$item_table.cobrand as item_cobrand,
+ $item_table.extra as item_extra,
$head_table.*
from alert, $item_table, $head_table
where alert.parameter::integer = $head_table.id
diff --git a/perllib/FixMyStreet/Template.pm b/perllib/FixMyStreet/Template.pm
index 6317f7552..35efcc1cf 100644
--- a/perllib/FixMyStreet/Template.pm
+++ b/perllib/FixMyStreet/Template.pm
@@ -7,10 +7,14 @@ use FixMyStreet;
use mySociety::Locale;
use Attribute::Handlers;
use HTML::Scrubber;
+use HTML::TreeBuilder;
use FixMyStreet::Template::SafeString;
use FixMyStreet::Template::Context;
use FixMyStreet::Template::Stash;
+use RABX;
+use IO::String;
+
my %FILTERS;
my %SUBS;
@@ -141,6 +145,8 @@ sub html_paragraph : Filter('html_para') {
sub sanitize {
my $text = shift;
+ $text = $$text if UNIVERSAL::isa($text, 'FixMyStreet::Template::SafeString');
+
my %allowed_tags = map { $_ => 1 } qw( p ul ol li br b i strong em );
my $scrubber = HTML::Scrubber->new(
rules => [
@@ -155,4 +161,84 @@ sub sanitize {
return $text;
}
+
+=head2 email_sanitize_text
+
+Intended for use in the _email_comment_list.txt template to allow HTML
+in updates from staff/superusers. Sanitizes the HTML and then converts
+it all to text.
+
+=cut
+
+sub email_sanitize_text : Fn('email_sanitize_text') {
+ my $update = shift;
+
+ my $text = $update->{item_text};
+ my $extra = $update->{item_extra};
+ $extra = $extra ? RABX::wire_rd(new IO::String($extra)) : {};
+
+ my $staff = $extra->{is_superuser} || $extra->{is_body_user};
+
+ return $text unless $staff;
+
+ $text = FixMyStreet::Template::sanitize($text);
+
+ my $tree = HTML::TreeBuilder->new_from_content($text);
+ _sanitize_elt($tree);
+
+ return $tree->as_text;
+}
+
+my $list_type;
+my $list_num;
+my $sanitize_text_subs = {
+ b => [ '*', '*' ],
+ strong => [ '*', '*' ],
+ i => [ '_', '_' ],
+ em => [ '_', '_' ],
+ p => [ '', "\n\n" ],
+ li => [ '', "\n\n" ],
+};
+sub _sanitize_elt {
+ my $elt = shift;
+ foreach ($elt->content_list) {
+ next unless ref $_;
+ $list_type = $_->tag, $list_num = 1 if $_->tag eq 'ol' || $_->tag eq 'ul';
+ _sanitize_elt($_);
+ $_->replace_with("\n") if $_->tag eq 'br';
+ $_->replace_with('[image: ', $_->attr('alt'), ']') if $_->tag eq 'img';
+ $_->replace_with($_->as_text, ' [', $_->attr('href'), ']') if $_->tag eq 'a';
+ $_->replace_with_content if $_->tag eq 'span' || $_->tag eq 'font';
+ $_->replace_with_content if $_->tag eq 'ul' || $_->tag eq 'ol';
+ if ($_->tag eq 'li') {
+ $sanitize_text_subs->{li}[0] = $list_type eq 'ol' ? "$list_num. " : '* ';
+ $list_num++;
+ }
+ if (my $sub = $sanitize_text_subs->{$_->tag}) {
+ $_->preinsert($sub->[0]);
+ $_->postinsert($sub->[1]);
+ $_->replace_with_content;
+ }
+ }
+}
+
+=head2 email_sanitize_html
+
+Intended for use in the _email_comment_list.html template to allow HTML
+in updates from staff/superusers.
+
+=cut
+
+sub email_sanitize_html : Fn('email_sanitize_html') {
+ my $update = shift;
+
+ my $text = $update->{item_text};
+ my $extra = $update->{item_extra};
+ $extra = $extra ? RABX::wire_rd(new IO::String($extra)) : {};
+
+ my $staff = $extra->{is_superuser} || $extra->{is_body_user};
+
+ return FixMyStreet::App::View::Web::_staff_html_markup($text, $staff);
+}
+
1;
diff --git a/perllib/FixMyStreet/TestMech.pm b/perllib/FixMyStreet/TestMech.pm
index 277eca2b1..f6854fc98 100644
--- a/perllib/FixMyStreet/TestMech.pm
+++ b/perllib/FixMyStreet/TestMech.pm
@@ -276,6 +276,24 @@ sub get_text_body_from_email {
return $body;
}
+sub get_html_body_from_email {
+ my ($mech, $email, $obj) = @_;
+ unless ($email) {
+ $email = $mech->get_email;
+ $mech->clear_emails_ok;
+ }
+
+ my $body;
+ $email->walk_parts(sub {
+ my $part = shift;
+ return if $part->subparts;
+ return if $part->content_type !~ m{text/html};
+ $body = $obj ? $part : $part->body_str;
+ ok $body, "Found HTML body";
+ });
+ return $body;
+}
+
sub get_link_from_email {
my ($mech, $email, $multiple, $mismatch) = @_;
unless ($email) {
diff --git a/t/app/controller/alert_new.t b/t/app/controller/alert_new.t
index d968b56b1..31d8c52ed 100644
--- a/t/app/controller/alert_new.t
+++ b/t/app/controller/alert_new.t
@@ -866,4 +866,47 @@ subtest 'check setting include dates in new updates cobrand option' => sub {
$include_date_in_alert_override->restore();
};
+subtest 'check staff updates can include sanitized HTML' => sub {
+ my $user1 = $mech->create_user_ok('reporter@example.com', name => 'Reporter User');
+ my $user2 = $mech->create_user_ok('staff@example.com', name => 'Staff User', from_body => $body);
+ my $user3 = $mech->create_user_ok('updater@example.com', name => 'Another User');
+
+ my $dt = DateTime->now->add( minutes => -30 );
+ my $r_dt = $dt->clone->add( minutes => 20 );
+
+ my ($report) = $mech->create_problems_for_body(1, $body->id, 'Testing', {
+ user => $user1,
+ });
+
+ my $update1 = $mech->create_comment_for_problem($report, $user2, 'Staff User', '<p>This is some update text with <strong>HTML</strong> and *italics*.</p> <ul><li>Even a list</li><li>Which might work</li><li>In the <a href="https://www.fixmystreet.com/">text</a> part</li></ul> <script>not allowed</script>', 't', 'confirmed', undef, { confirmed => $r_dt->clone->add( minutes => 8 ) });
+ $update1->set_extra_metadata(is_body_user => $user2->from_body->id);
+ $update1->update;
+
+ $mech->create_comment_for_problem($report, $user3, 'Updater User', 'Public users <i>cannot</i> use HTML. <script>not allowed</script>', 't', 'confirmed', undef, { confirmed => $r_dt->clone->add( minutes => 9 ) });
+
+ my $alert_user1 = FixMyStreet::DB->resultset('Alert')->create( {
+ user => $user1,
+ alert_type => 'new_updates',
+ parameter => $report->id,
+ confirmed => 1,
+ whensubscribed => $dt,
+ } );
+ ok $alert_user1, "alert created";
+
+ FixMyStreet::DB->resultset('AlertType')->email_alerts();
+ my $email = $mech->get_email;
+ my $plain = $mech->get_text_body_from_email($email);
+ like $plain, qr/This is some update text with \*HTML\* and \*italics\*\.\r\n\r\n\* Even a list\r\n\r\n\* Which might work\r\n\r\n\* In the text \[https:\/\/www.fixmystreet.com\/\] part/, 'plain text part contains no HTML tags from staff update';
+ like $plain, qr/Public users <i>cannot<\/i> use HTML\./, 'plain text part contains exactly what was entered';
+
+ my $html = $mech->get_html_body_from_email($email);
+ like $html, qr{This is some update text with <strong>HTML</strong> and <i>italics</i>\.}, 'HTML part contains HTML tags';
+ unlike $html, qr/<script>/, 'HTML part contains no script tags';
+
+ $mech->delete_user( $user1 );
+ $mech->delete_user( $user2 );
+ $mech->delete_user( $user3 );
+};
+
+
done_testing();
diff --git a/t/app/controller/my.t b/t/app/controller/my.t
index 673addf0c..85902ae1a 100644
--- a/t/app/controller/my.t
+++ b/t/app/controller/my.t
@@ -14,17 +14,12 @@ my $other_user = FixMyStreet::DB->resultset('User')->find_or_create({ email => '
my @other = $mech->create_problems_for_body(1, 1234, 'Another Title', { user => $other_user });
my $user = $mech->log_in_ok( 'test@example.com' );
-$mech->get_ok('/my');
-is $mech->uri->path, '/my', "stayed on '/my' page";
-
-$mech->content_contains('Test Title');
-$mech->content_lacks('Another Title');
-
my @update;
my $i = 0;
+my $staff_text = '<p>this is <script>how did this happen</script> <strong>an update</strong></p><ul><li>With</li><li>A</li><li>List</li></ul>';
foreach ($user, $user, $other_user) {
$update[$i] = FixMyStreet::DB->resultset('Comment')->create({
- text => 'this is an update',
+ text => $staff_text,
user => $_,
state => 'confirmed',
problem => $problems[0],
@@ -35,6 +30,20 @@ foreach ($user, $user, $other_user) {
$i++;
}
+subtest 'Check loading of /my page' => sub {
+ $mech->get_ok('/my');
+ is $mech->uri->path, '/my', "stayed on '/my' page";
+
+ $mech->content_contains('Test Title');
+ $mech->content_lacks('Another Title');
+ $mech->content_contains('&lt;p&gt;this is');
+ $mech->content_lacks('<p>this is <strong>an update</strong></p><ul><li>With');
+
+ $update[0]->update({ extra => { is_superuser => 1 } });
+ $mech->get_ok('/my');
+ $mech->content_contains('<p>this is <strong>an update</strong></p><ul><li>With');
+};
+
foreach (
{ type => 'problem', id => 0, result => 404, desc => 'nothing' },
{ type => 'problem', obj => $problems[0], result => 200, desc => 'own report' },
diff --git a/t/app/controller/questionnaire.t b/t/app/controller/questionnaire.t
index b561b271a..592507288 100644
--- a/t/app/controller/questionnaire.t
+++ b/t/app/controller/questionnaire.t
@@ -351,7 +351,7 @@ my $comment = FixMyStreet::DB->resultset('Comment')->find_or_create(
user_id => $user->id,
name => 'A User',
mark_fixed => 'false',
- text => 'This is some update text',
+ text => 'This is some <strong>update</strong> text',
state => 'confirmed',
confirmed => $sent_time,
anonymous => 'f',
@@ -360,7 +360,12 @@ my $comment = FixMyStreet::DB->resultset('Comment')->find_or_create(
subtest 'Check updates are shown correctly on questionnaire page' => sub {
$mech->get_ok("/Q/" . $token->token);
$mech->content_contains( 'Show all updates' );
- $mech->content_contains( 'This is some update text' );
+ $mech->content_contains( 'This is some &lt;strong&gt;update&lt;/strong&gt; text' );
+};
+subtest 'Check staff update is shown correctly on questionnaire page' => sub {
+ $comment->update({ extra => { is_superuser => 1 } });
+ $mech->get_ok("/Q/" . $token->token);
+ $mech->content_contains( 'This is some <strong>update</strong> text' );
};
for my $test (
diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t
index 760a5a45b..2b60867b8 100644
--- a/t/app/controller/report_updates.t
+++ b/t/app/controller/report_updates.t
@@ -2218,4 +2218,91 @@ subtest 'check disabling of updates per category' => sub {
$mech->content_lacks('Provide an update');
};
+subtest 'check that only staff can display HTML in updates' => sub {
+ $report->comments->delete;
+ $user->update({ from_body => undef, is_superuser => 0 });
+
+ my @lines = (
+ "This update contains:",
+ "1. <strong>some staff-allowed HTML</strong>",
+ "2. *some Markdown-style italics*",
+ "3. <script>some disallowed HTML</script>",
+ "4. An automatic link: https://myfancylink.fixmystreet.com/",
+ "5. A block-level element: <p>This is its own para</p>",
+ ""
+ );
+ my $comment = FixMyStreet::DB->resultset('Comment')->create(
+ {
+ user => $user,
+ problem_id => $report->id,
+ text => join("\n\n", @lines),
+ confirmed => DateTime->now( time_zone => 'local'),
+ problem_state => 'confirmed',
+ anonymous => 0,
+ mark_open => 0,
+ mark_fixed => 0,
+ state => 'confirmed',
+ }
+ );
+
+ # First check that comments from a public user don't receive special treatment
+ $mech->get_ok( "/report/" . $report->id );
+
+ $mech->content_contains("1. &lt;strong&gt;some staff-allowed HTML&lt;/strong&gt;");
+ $mech->content_lacks("<strong>some staff-allowed HTML</strong>");
+
+ $mech->content_contains("2. *some Markdown-style italics*");
+ $mech->content_lacks("<i>some Markdown-style italics</i>");
+ $mech->content_lacks("&lt;i&gt;some Markdown-style italics&lt;/i&gt;");
+
+ $mech->content_contains("3. &lt;script&gt;some disallowed HTML&lt;/script&gt;");
+ $mech->content_lacks("<script>some disallowed HTML</script>");
+
+ $mech->content_contains('4. An automatic link: <a href="https://myfancylink.fixmystreet.com/">https://myfancylink.fixmystreet.com/</a>') or diag $mech->content;
+
+ $mech->content_contains("5. A block-level element: &lt;p&gt;This is its own para&lt;/p&gt;");
+ $mech->content_lacks("5. A block-level element: <p>This is its own para</p>");
+
+ # Now check that comments from a member of staff user do allow HTML/italic markup
+ $comment->set_extra_metadata(is_body_user => $body->id);
+ $comment->update;
+ $mech->get_ok( "/report/" . $report->id );
+
+ $mech->content_contains("1. <strong>some staff-allowed HTML</strong>");
+ $mech->content_lacks("&lt;strong&gt;some staff-allowed HTML&lt;/strong&gt;");
+
+ $mech->content_contains("2. <i>some Markdown-style italics</i>");
+ $mech->content_lacks("*some Markdown-style italics*");
+ $mech->content_lacks("&lt;i&gt;some Markdown-style italics&lt;/i&gt;");
+
+ $mech->content_lacks("some disallowed HTML");
+
+ $mech->content_contains('4. An automatic link: <a href="https://myfancylink.fixmystreet.com/">https://myfancylink.fixmystreet.com/</a>');
+
+ $mech->content_contains("5. A block-level element: <p>This is its own para</p>");
+ $mech->content_lacks("<p>\n5. A block-level element: <p>This is its own para</p></p>");
+
+ # and the same for superusers
+ $comment->unset_extra_metadata('is_body_user');
+ $comment->set_extra_metadata(is_superuser => 1);
+ $comment->update;
+ $mech->get_ok( "/report/" . $report->id );
+
+ $mech->content_contains("1. <strong>some staff-allowed HTML</strong>");
+ $mech->content_lacks("&lt;strong&gt;some staff-allowed HTML&lt;/strong&gt;");
+
+ $mech->content_contains("2. <i>some Markdown-style italics</i>");
+ $mech->content_lacks("*some Markdown-style italics*");
+ $mech->content_lacks("&lt;i&gt;some Markdown-style italics&lt;/i&gt;");
+
+ $mech->content_lacks("some disallowed HTML");
+
+ $mech->content_contains('4. An automatic link: <a href="https://myfancylink.fixmystreet.com/">https://myfancylink.fixmystreet.com/</a>');
+
+ $mech->content_contains("5. A block-level element: <p>This is its own para</p>");
+ $mech->content_lacks("<p>\n5. A block-level element: <p>This is its own para</p></p>");
+
+};
+
+
done_testing();
diff --git a/templates/email/default/_email_comment_list.html b/templates/email/default/_email_comment_list.html
index 346efadfb..fe6cd5c4c 100644
--- a/templates/email/default/_email_comment_list.html
+++ b/templates/email/default/_email_comment_list.html
@@ -5,7 +5,7 @@
<img style="[% list_item_photo_style %]" src="[% inline_image(update.get_first_image_fp) %]" alt="">
</a>
[%~ END %]
- [% update.item_text | html_para | replace('<p>', '<p style="' _ list_item_p_style _ '">') %]
+ [% email_sanitize_html(update) | html_para | replace('<p>', '<p style="' _ list_item_p_style _ '">') %]
<p style="[% list_item_date_style %]">
[%~ update.item_name | html IF update.item_name AND NOT update.item_anonymous -%]
[% '(' _ cobrand.prettify_dt(update.confirmed) _ ') ' IF cobrand.include_time_in_update_alerts -%]
diff --git a/templates/email/default/_email_comment_list.txt b/templates/email/default/_email_comment_list.txt
index dbf00640f..3e01580c3 100644
--- a/templates/email/default/_email_comment_list.txt
+++ b/templates/email/default/_email_comment_list.txt
@@ -1,7 +1,7 @@
[% FOR row IN data -%]
[% row.item_name _ ' : ' IF row.item_name AND NOT row.item_anonymous -%]
[% '(' _ cobrand.prettify_dt(row.confirmed) _ ') ' IF cobrand.include_time_in_update_alerts -%]
-[% row.item_text %]
+[% email_sanitize_text(row) %]
------
diff --git a/templates/email/fixamingata/_email_comment_list.html b/templates/email/fixamingata/_email_comment_list.html
index 346efadfb..fe6cd5c4c 100644
--- a/templates/email/fixamingata/_email_comment_list.html
+++ b/templates/email/fixamingata/_email_comment_list.html
@@ -5,7 +5,7 @@
<img style="[% list_item_photo_style %]" src="[% inline_image(update.get_first_image_fp) %]" alt="">
</a>
[%~ END %]
- [% update.item_text | html_para | replace('<p>', '<p style="' _ list_item_p_style _ '">') %]
+ [% email_sanitize_html(update) | html_para | replace('<p>', '<p style="' _ list_item_p_style _ '">') %]
<p style="[% list_item_date_style %]">
[%~ update.item_name | html IF update.item_name AND NOT update.item_anonymous -%]
[% '(' _ cobrand.prettify_dt(update.confirmed) _ ') ' IF cobrand.include_time_in_update_alerts -%]
diff --git a/templates/web/base/admin/users/log.html b/templates/web/base/admin/users/log.html
index 4b426e0ba..5c3f36321 100644
--- a/templates/web/base/admin/users/log.html
+++ b/templates/web/base/admin/users/log.html
@@ -49,7 +49,7 @@ action_map = {
[%- tprintf(loc('Problem %s created on behalf of %s'), mark_safe(report_link), item.obj.name) %], ‘[% item.obj.title | html %]’
[%~ CASE 'update' %]
[% tprintf(loc("Update %s created for problem %d"), mark_safe(report_link), item.obj.problem_id) %]
- [% item.obj.text | add_links | markup(item.obj.user) | html_para %]
+ [% item.obj.text | staff_html_markup(item.obj.extra) %]
[%~ CASE 'shortlistAdded' %]
[%- tprintf(loc('Problem %s added to shortlist'), mark_safe(report_link)) %]
[%~ CASE 'shortlistRemoved' %]
diff --git a/templates/web/base/my/my.html b/templates/web/base/my/my.html
index 04c5b6941..d78461cb5 100644
--- a/templates/web/base/my/my.html
+++ b/templates/web/base/my/my.html
@@ -113,7 +113,7 @@ li .my-account-buttons a {
<div class="item-list__update-wrap">
[% INCLUDE 'report/photo.html' object=u %]
<div class="item-list__update-text">
- [% u.text | add_links | html_para %]
+ [% u.text | staff_html_markup(u.extra) %]
<p class="meta-2">
[% tprintf( loc("Added %s"), prettify_dt( u.confirmed, 'date' ) ) %]
diff --git a/templates/web/base/questionnaire/index.html b/templates/web/base/questionnaire/index.html
index 00911c5b1..416200d25 100644
--- a/templates/web/base/questionnaire/index.html
+++ b/templates/web/base/questionnaire/index.html
@@ -55,7 +55,7 @@
<strong>[% loc('Last update') %]</strong>
<a href="/report/[% problem.id %]">[% loc('Show all updates') %]</a>
</p>
- <p class="questionnaire-report-reminder__last-update">&ldquo;[% updates.last.text | add_links %]&rdquo;</p>
+ <p class="questionnaire-report-reminder__last-update">&ldquo;[% updates.last.text | staff_html_markup(updates.last.extra) %]&rdquo;</p>
[% END %]
</div>
diff --git a/templates/web/base/report/update.html b/templates/web/base/report/update.html
index ca9397bd4..3b1f092ef 100644
--- a/templates/web/base/report/update.html
+++ b/templates/web/base/report/update.html
@@ -35,7 +35,7 @@
[% INCLUDE 'report/photo.html' object=update %]
<div class="item-list__update-text">
<div class="moderate-display">
- [% update.text | add_links | markup(update.user) | html_para %]
+ [% update.text | staff_html_markup(update.extra) %]
</div>
[% IF can_moderate %]
<div class="moderate-edit">
@@ -43,7 +43,7 @@
<label><input type="checkbox" name="update_revert_text" class="revert-textarea">
[% loc('Revert to original') %]</label>
[% END %]
- <textarea class="form-control" name="update_text">[% update.text | add_links %]</textarea>
+ <textarea class="form-control" name="update_text">[% update.text %]</textarea>
</div>
[% END %]