Unverified Commit 16ebe7f7 authored by IKEDA Soji's avatar IKEDA Soji Committed by GitHub
Browse files

Merge pull request #1038 from ikedas/limited_pdersonalization-trial2 by ikedas

Limited personalization
parents 10ee7b42 b6a5e80b
......@@ -156,6 +156,8 @@ max_list_members editor hidden
message_hook owner hidden
message_hook privileged_owner read
personalization owner,privileged_owner read
default privileged_owner write
default owner write
default editor read
......
......@@ -53,7 +53,7 @@
<textarea name="body" id="body" cols="80" rows="25">[% body %]</textarea>
<br />
[% IF merge_feature %]
[% IF listconf.merge_feature == 'on' %]
<br />
<b>[%|loc%]Messages customization: use the template syntax:[%END%] <a href="http://www.tt2.org">TT2</a></b>
<br />
......
......@@ -214,7 +214,8 @@ sub process {
if $row_mailer->{verp_bulkmailer};
$message->{shelved}{tracking} = $row_mailer->{tracking_bulkmailer}
if $row_mailer->{tracking_bulkmailer};
$message->{shelved}{merge} = 1 if $row_mailer->{merge_bulkmailer};
# Compatibility: On earlier version only 'all' mode was available.
$message->{shelved}{merge} = 'all' if $row_mailer->{merge_bulkmailer};
# Not a typo: column name was recEipients_bulkmailer.
my $rcpt_string = $row_mailer->{receipients_bulkmailer};
......
......@@ -14562,8 +14562,8 @@ sub do_compose_mail {
$param->{'topic_required'} = $list->is_msg_topic_tagging_required();
}
 
$param->{'merge_feature'} =
Sympa::Tools::Data::smart_eq($list->{'admin'}{'merge_feature'}, 'on');
#$param->{'merge_feature'} =
# Sympa::Tools::Data::smart_eq($list->{'admin'}{'merge_feature'}, 'on');
 
return 1;
}
......@@ -14864,9 +14864,10 @@ sub do_send_mail {
$message->reformat_utf8_message([], $charset);
}
 
## Roughly check TT2 syntax for merge_feature.
if (Sympa::Tools::Data::smart_eq($list->{'admin'}{'merge_feature'}, 'on'))
{
# Roughly check TT2 syntax for personalization.
if ( 'on' eq ($list->{'admin'}{'personalization_feature'} || 'off')
and 'all' eq
($list->{'admin'}{'personalization'}{'web_apply_on'} || 'none')) {
my $new_message = $message->dup;
unless (defined $new_message->personalize($list)) {
# FIXME: Get last_error of template object.
......@@ -14905,9 +14906,12 @@ sub do_send_mail {
# - Add footer / header.
$u_message->prepare_message_according_to_mode('mail', $list);
# - Shelve personalization.
$u_message->{shelved}{merge} = 1
if Sympa::Tools::Data::smart_eq($list->{'admin'}{'merge_feature'},
'on');
if ( 'on' eq ($list->{'admin'}{'personalization_feature'} || 'off')
and 'none' ne
($list->{'admin'}{'personalization'}{'web_apply_on'} || 'none')) {
$u_message->{shelved}{merge} =
$list->{'admin'}{'personalization'}{'web_apply_on'};
}
 
$u_message->{envelope_sender} = Sympa::get_address($robot, 'owner');
$u_message->{priority} =
......
......@@ -1737,6 +1737,11 @@ sub _load_config_file_to_hash {
$keyword =
$Sympa::Config::Schema::obsolete_robot_params{$keyword}
// $keyword;
# Resolve renamed parameters FIXME
$keyword = {
merge_feature =>
'personalization_feature', # 6.0b.2 - 6.2.59b.1
}->{$keyword} // $keyword;
if ( exists $params{$keyword}
&& defined $params{$keyword}{'multiple'}
......
......@@ -1126,8 +1126,8 @@ our %pinfo = (
},
'max-size' => {obsolete => 'max_size'},
merge_feature => {
context => [qw(list site)],
personalization_feature => {
context => [qw(list domain site)],
order => 20.16,
group => 'sending', # outgoing / sending?
gettext_id => "Allow message personalization",
......@@ -1135,6 +1135,36 @@ our %pinfo = (
occurrence => '1',
default => 'off',
},
merge_feature => {obsolete => 'personalization_feature'},
personalization => {
context => [qw(list domain site)],
order => 20.161,
group => 'sending',
gettext_id => "Message personalization",
format => {
web_apply_on => {
context => [qw(list domain site)],
order => 1,
group => 'sending',
gettext_id =>
'which part of message from web conversion is applied',
format => ['none', 'footer', 'all'],
default => 'footer',
occurrence => '1'
},
mail_apply_on => {
context => [qw(list domain site)],
order => 2,
group => 'sending',
gettext_id =>
'which part of e-mail message conversion is applied',
format => ['none', 'footer', 'all'],
default => 'none',
occurrence => '1'
},
},
},
message_hook => {
context => [qw(list)],
......
......@@ -44,7 +44,8 @@ our %list_option = (
'list' => {'gettext_id' => 'list'},
# include_ldap_2level_query.select2, include_ldap_2level_query.select1,
# include_ldap_query.select, reply_to_header.value, dmarc_protection.mode
# include_ldap_query.select, reply_to_header.value, dmarc_protection.mode,
# personalization.web_apply_on, personalization.mail_apply_on
'all' => {'gettext_id' => 'all'},
# reply_to_header.value
......@@ -55,10 +56,14 @@ our %list_option = (
'body' => {'gettext_id' => 'message body'},
'subject_and_body' => {'gettext_id' => 'subject and body'},
# personalization.web_apply_on, personalization.mail_apply_on
'footer' => {'gettext_id' => 'header and footer'},
# bouncers_level2.notification, bouncers_level2.action,
# bouncers_level1.notification, bouncers_level1.action,
# spam_protection, dkim_signature_apply_on, web_archive_spam_protection,
# dmarc_protection.mode, automatic_list_removal
# dmarc_protection.mode, automatic_list_removal,
# personalization.web_apply_on, personalization.mail_apply_on
'none' => {'gettext_id' => 'do nothing'},
# automatic_list_removal
......
......@@ -1393,7 +1393,6 @@ sub personalize {
my $self = shift;
my $list = shift;
my $rcpt = shift || undef;
my $data = shift || {};
my $content_type = lc($self->{_head}->mime_attr('Content-Type') || '');
if ( $content_type eq 'multipart/encrypted'
......@@ -1406,8 +1405,23 @@ sub personalize {
my $entity = $self->as_entity->dup;
# Initialize parameters at first only once.
$data->{'headers'} ||= {};
my $data = $self->_personalize_attrs;
unless (defined _merge_msg($entity, $list, $rcpt, $data)) {
return undef;
}
$self->set_entity($entity);
return $self;
}
sub _personalize_attrs {
my $self = shift;
my $entity = $self->as_entity;
my $headers = $entity->head;
my $data = {headers => {}};
foreach my $key (
qw/subject x-originating-ip message-id date x-original-to from to thread-topic content-type/
) {
......@@ -1415,16 +1429,11 @@ sub personalize {
my $value = $headers->get($key, 0);
chomp $value;
$value =~ s/(?:\r\n|\r|\n)(?=[ \t])//g; # unfold
$data->{'headers'}{$key} = $value;
}
$data->{'subject'} = $self->{'decoded_subject'};
unless (defined _merge_msg($entity, $list, $rcpt, $data)) {
return undef;
$data->{headers}{$key} = $value;
}
$data->{subject} = $self->{decoded_subject};
$self->set_entity($entity);
return $self;
return $data;
}
sub _merge_msg {
......@@ -1581,7 +1590,7 @@ sub personalize_text {
my $message_output;
my $user = $list->get_list_member($rcpt);
my $user = $list->get_list_member($rcpt) if $rcpt;
if ($user) {
$user->{'escaped_email'} = URI::Escape::uri_escape($rcpt);
......@@ -1599,9 +1608,18 @@ sub personalize_text {
# Parse the template in the message : replace the tags and the parameters
# by the corresponding values
my $template = Sympa::Template->new(undef);
return undef
unless $template->parse($data, \$body, \$message_output,
is_not_template => 1);
unless (
$template->parse(
$data, \$body, \$message_output, is_not_template => 1
)
) {
$log->syslog(
'err',
'Failed parsing template: %s',
$template->{last_error}
);
return undef;
}
return $message_output;
}
......@@ -1632,9 +1650,10 @@ sub prepare_message_according_to_mode {
if (_as_singlepart($entity, 'text/plain')) {
$log->syslog('notice', 'Multipart message changed to singlepart');
}
## Add a footer
_decorate_parts($entity, $list);
$self->set_entity($entity);
# Add a footer
$self->{shelved}{decorate} = 1;
} elsif ($mode eq 'urlize') {
# Prepare message for urlize reception mode.
# Not extract message/rfc822 parts.
......@@ -1649,44 +1668,40 @@ sub prepare_message_according_to_mode {
my $entity = $parser->parse_data($msg_string);
_urlize_parts($entity, $list, $self->{'message_id'});
## Add a footer
_decorate_parts($entity, $list);
$self->set_entity($entity);
# Add a footer
$self->{shelved}{decorate} = 1;
} else { # 'mail'
# Prepare message for normal reception mode,
# and add a footer.
unless ($self->{'protected'}) {
my $entity = $self->as_entity->dup;
_decorate_parts($entity, $list);
$self->set_entity($entity);
}
$self->{shelved}{decorate} = 1
unless $self->{'protected'};
}
return $self;
}
# OBSOLETED. Use prepare_message_according_to_mode('mail').
# Old name:
# Sympa::List::add_parts() or Message::add_parts(), n.b. not add_part().
# Sympa::Message::_decorate_parts().
sub decorate {
my $self = shift;
$log->syslog('debug3', '(%s, %s, %s => %s)', @_);
my $self = shift;
my $list = shift;
my $rcpt = shift;
my %options = @_;
return $self->prepare_message_according_to_mode('mail', $self->{context});
}
return unless ref $list eq 'Sympa::List';
# Old name:
# Sympa::List::add_parts() or Message::add_parts(), n.b. not add_part().
sub _decorate_parts {
$log->syslog('debug3', '(%s, %s)');
my $entity = shift;
my $list = shift;
my $entity = $self->as_entity->dup;
my $mode = $options{mode} || '';
my $type = $list->{'admin'}{'footer_type'};
my $eff_type = $entity->effective_type || 'text/plain';
## Signed or encrypted messages won't be modified.
if ($eff_type =~ /^multipart\/(signed|encrypted)$/i) {
return $entity;
}
return 1 if $eff_type =~ /^multipart\/(signed|encrypted)$/i;
my $header =
($type eq 'mime')
......@@ -1707,30 +1722,28 @@ sub _decorate_parts {
or $footer and -s $footer
or $global_footer and -s $global_footer;
my $data;
if ($mode) {
$data = $self->_personalize_attrs;
}
if ($type eq 'append') {
## append footer/header
my ($global_footer_text, $footer_text, $header_text) = ('', '', '');
if ($header and -s $header) {
if (open my $fh, '<', $header) {
$header_text = do { local $RS; <$fh> };
close $fh;
}
$header_text = '' unless $header_text =~ /\S/;
}
if ($footer and -s $footer) {
if (open my $fh, '<', $footer) {
$footer_text = do { local $RS; <$fh> };
close $fh;
}
$footer_text = '' unless $footer_text =~ /\S/;
}
if ($global_footer and -s $global_footer) {
if (open my $fh, '<', $global_footer) {
$global_footer_text = do { local $RS; <$fh> };
close $fh;
}
$global_footer_text = '' unless $global_footer_text =~ /\S/;
}
# append footer/header
my $header_text = _footer_text(
$header, $list, $rcpt, $data,
mode => $mode,
type => 'header'
) // '';
my $footer_text = _footer_text(
$footer, $list, $rcpt, $data,
mode => $mode,
type => 'footer'
) // '';
my $global_footer_text = _footer_text(
$global_footer, $list, $rcpt, $data,
mode => $mode,
type => 'global footer'
) // '';
if ( length $header_text
or length $footer_text
or length $global_footer_text) {
......@@ -1745,112 +1758,61 @@ sub _decorate_parts {
}
} else {
## MIME footer/header
my $parser = MIME::Parser->new;
$parser->output_to_core(1);
$parser->tmp_dir($Conf::Conf{'tmpdir'});
if ( $eff_type =~ /^multipart\/alternative/i
|| $eff_type =~ /^multipart\/related/i) {
$log->syslog('debug3', 'Making message %s into multipart/mixed',
$entity);
$entity->make_multipart("mixed", Force => 1);
}
if ($header and -s $header) {
my $fh;
unless (open $fh, '<', $header) {
;
} elsif ($header =~ /\.mime$/) {
my $header_part;
eval { $header_part = $parser->parse($fh); };
close $fh;
if ($EVAL_ERROR) {
$log->syslog('err', 'Failed to parse MIME data %s: %s',
$header, $parser->last_error);
} else {
$entity->make_multipart unless $entity->is_multipart;
## Add AS FIRST PART (0)
$entity->add_part($header_part, 0);
}
} else {
## text/plain header
$entity->make_multipart unless $entity->is_multipart;
my $header_text = do { local $RS; <$fh> };
close $fh;
my $header_part = MIME::Entity->build(
Data => $header_text,
Type => "text/plain",
Filename => undef,
'X-Mailer' => undef,
Encoding => "8bit",
Charset => "UTF-8"
);
$entity->add_part($header_part, 0);
}
_add_footer_part(
$entity, $header, $list, $rcpt, $data,
mode => $mode,
type => 'header',
prepend => 1
);
}
if ($footer and -s $footer) {
my $fh;
unless (open $fh, '<', $footer) {
;
} elsif ($footer =~ /\.mime$/) {
my $footer_part;
eval { $footer_part = $parser->parse($fh); };
close $fh;
if ($EVAL_ERROR) {
$log->syslog('err', 'Failed to parse MIME data %s: %s',
$footer, $parser->last_error);
} else {
$entity->make_multipart unless $entity->is_multipart;
$entity->add_part($footer_part);
}
} else {
## text/plain footer
$entity->make_multipart unless $entity->is_multipart;
my $footer_text = do { local $RS; <$fh> };
close $fh;
$entity->attach(
Data => $footer_text,
Type => "text/plain",
Filename => undef,
'X-Mailer' => undef,
Encoding => "8bit",
Charset => "UTF-8"
);
}
_add_footer_part(
$entity, $footer, $list, $rcpt, $data,
mode => $mode,
type => 'footer'
);
}
if ($global_footer and -s $global_footer) {
my $fh;
unless (open $fh, '<', $global_footer) {
;
} elsif ($global_footer =~ /\.mime$/) {
my $global_footer_part;
eval { $global_footer_part = $parser->parse($fh); };
close $fh;
if ($EVAL_ERROR) {
$log->syslog('err', 'Failed to parse MIME data %s: %s',
$global_footer, $parser->last_error);
} else {
$entity->make_multipart unless $entity->is_multipart;
$entity->add_part($global_footer_part);
}
} else {
## text/plain global_footer
$entity->make_multipart unless $entity->is_multipart;
my $global_footer_text = do { local $RS; <$fh> };
close $fh;
$entity->attach(
Data => $global_footer_text,
Type => "text/plain",
Filename => undef,
'X-Mailer' => undef,
Encoding => "8bit",
Charset => "UTF-8"
);
}
_add_footer_part(
$entity, $global_footer, $list, $rcpt, $data,
mode => $mode,
type => 'global footer'
);
}
}
return $entity;
$self->set_entity($entity);
return 1;
}
sub _footer_text {
my $footer = shift;
my $list = shift;
my $rcpt = shift;
my $data = shift;
my %options = @_;
my $mode = $options{mode};
my $type = $options{type};
my $footer_text = '';
if ($footer and -s $footer) {
if (open my $fh, '<', $footer) {
$footer_text = do { local $RS; <$fh> };
close $fh;
}
if ($mode) {
$footer_text =
personalize_text($footer_text, $list, $rcpt, $data);
unless (defined $footer_text) {
$log->syslog('info', 'Error personalizing %s', $type);
$footer_text = '';
}
}
$footer_text = '' unless $footer_text =~ /\S/;
}
return $footer_text;
}
## Append header/footer/global_footer to text/plain body.
......@@ -1958,6 +1920,70 @@ sub _append_parts {
return undef;
}
sub _add_footer_part {
my $entity = shift;
my $footer = shift;
my $list = shift;
my $rcpt = shift;
my $data = shift;
my %options = @_;
my $mode = $options{mode};
my $type = $options{type};
my $prepend = $options{prepend};
my $parser = MIME::Parser->new;
$parser->output_to_core(1);
$parser->tmp_dir($Conf::Conf{'tmpdir'});
my $fh;
my $footer_part;
my $error;
unless (open $fh, '<', $footer) {
return 0;
} elsif ($footer =~ /\.mime$/) {
eval { $footer_part = $parser->parse($fh); };
close $fh;
$error = $parser->last_error;
} else {
# text/plain footer
my $footer_text = do { local $RS; <$fh> };
close $fh;
eval {
$footer_part = MIME::Entity->build(
Data => $footer_text,
Type => "text/plain",
Filename => undef,
'X-Mailer' => undef,
Encoding => "8bit",
Charset => "UTF-8"
);
};
$error = $EVAL_ERROR;
}
my $eff_type = $entity->effective_type || 'text/plain';
unless ($footer_part) {
$log->syslog('err', 'Failed to parse MIME data %s: %s',
$footer, $error);
} elsif ($mode
and not defined _merge_msg($footer_part, $list, $rcpt, $data)) {
$log->syslog('info', 'Error personalizing %s', $type);
} else {
unless ($entity->is_multipart) {
$entity->make_multipart;
} elsif ($eff_type =~ /^multipart\/alternative/i
or $eff_type =~ /^multipart\/related/i) {
$log->syslog('debug3', 'Making message %s into multipart/mixed',
$entity);
$entity->make_multipart("mixed", Force => 1);
}
$entity->add_part($footer_part, $prepend ? 0 : -1);
}
}
# Styles to cancel local CSS.
my $div_style =
'background: transparent; border: none; clear: both; display: block; float: none; position: static';
......@@ -3953,7 +3979,7 @@ Returns:
C<1> if the message is considered signed.
C<0> otherwise.
=item personalize ( $list, [ $rcpt ], [ $data ] )
=item personalize ( $list, [ $rcpt ] )
I<Instance method>.
Personalizes a message with custom attributes of a user.
......@@ -4040,10 +4066,7 @@ the message is not modified.
Returns modified message object itself, or C<undef> if transformation failed.
=item decorate ( )