Revision 24ab7ec0
Von Moritz Bunkus vor etwa 9 Jahren hinzugefügt
SL/DB/EmailJournal.pm | ||
---|---|---|
1 |
package SL::DB::EmailJournal; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use SL::DB::MetaSetup::EmailJournal; |
|
6 |
use SL::DB::Manager::EmailJournal; |
|
7 |
|
|
8 |
__PACKAGE__->meta->add_relationship( |
|
9 |
attachments => { |
|
10 |
type => 'one to many', |
|
11 |
class => 'SL::DB::EmailJournalAttachment', |
|
12 |
column_map => { id => 'email_journal_id' }, |
|
13 |
}, |
|
14 |
); |
|
15 |
|
|
16 |
__PACKAGE__->meta->initialize; |
|
17 |
|
|
18 |
1; |
SL/DB/EmailJournalAttachment.pm | ||
---|---|---|
1 |
package SL::DB::EmailJournalAttachment; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use SL::DB::MetaSetup::EmailJournalAttachment; |
|
6 |
use SL::DB::Manager::EmailJournalAttachment; |
|
7 |
use SL::DB::Helper::ActsAsList (group_by => [ qw(email_journal_id) ]); |
|
8 |
|
|
9 |
__PACKAGE__->meta->initialize; |
|
10 |
|
|
11 |
1; |
SL/DB/Helper/ALL.pm | ||
---|---|---|
42 | 42 |
use SL::DB::Draft; |
43 | 43 |
use SL::DB::Dunning; |
44 | 44 |
use SL::DB::DunningConfig; |
45 |
use SL::DB::EmailJournal; |
|
46 |
use SL::DB::EmailJournalAttachment; |
|
45 | 47 |
use SL::DB::Employee; |
46 | 48 |
use SL::DB::Exchangerate; |
47 | 49 |
use SL::DB::Finanzamt; |
SL/DB/Helper/Mappings.pm | ||
---|---|---|
126 | 126 |
drafts => 'draft', |
127 | 127 |
dunning => 'dunning', |
128 | 128 |
dunning_config => 'dunning_config', |
129 |
email_journal => 'EmailJournal', |
|
130 |
email_journal_attachments => 'EmailJournalAttachment', |
|
129 | 131 |
employee => 'employee', |
130 | 132 |
exchangerate => 'exchangerate', |
131 | 133 |
finanzamt => 'finanzamt', |
SL/DB/Manager/EmailJournal.pm | ||
---|---|---|
1 |
package SL::DB::Manager::EmailJournal; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use parent qw(SL::DB::Helper::Manager); |
|
6 |
|
|
7 |
sub object_class { 'SL::DB::EmailJournal' } |
|
8 |
|
|
9 |
__PACKAGE__->make_manager_methods; |
|
10 |
|
|
11 |
1; |
SL/DB/Manager/EmailJournalAttachment.pm | ||
---|---|---|
1 |
# This file has been auto-generated only because it didn't exist. |
|
2 |
# Feel free to modify it at will; it will not be overwritten automatically. |
|
3 |
|
|
4 |
package SL::DB::Manager::EmailJournalAttachment; |
|
5 |
|
|
6 |
use strict; |
|
7 |
|
|
8 |
use parent qw(SL::DB::Helper::Manager); |
|
9 |
|
|
10 |
sub object_class { 'SL::DB::EmailJournalAttachment' } |
|
11 |
|
|
12 |
__PACKAGE__->make_manager_methods; |
|
13 |
|
|
14 |
1; |
SL/DB/MetaSetup/EmailJournal.pm | ||
---|---|---|
1 |
# This file has been auto-generated. Do not modify it; it will be overwritten |
|
2 |
# by rose_auto_create_model.pl automatically. |
|
3 |
package SL::DB::EmailJournal; |
|
4 |
|
|
5 |
use strict; |
|
6 |
|
|
7 |
use parent qw(SL::DB::Object); |
|
8 |
|
|
9 |
__PACKAGE__->meta->table('email_journal'); |
|
10 |
|
|
11 |
__PACKAGE__->meta->columns( |
|
12 |
body => { type => 'text', not_null => 1 }, |
|
13 |
extended_status => { type => 'text', not_null => 1 }, |
|
14 |
from => { type => 'text', not_null => 1 }, |
|
15 |
headers => { type => 'text', not_null => 1 }, |
|
16 |
id => { type => 'integer', not_null => 1, sequence => 'email_journal_id_seq1' }, |
|
17 |
itime => { type => 'timestamp', default => 'now()', not_null => 1 }, |
|
18 |
mtime => { type => 'timestamp', default => 'now()', not_null => 1 }, |
|
19 |
recipients => { type => 'text', not_null => 1 }, |
|
20 |
sender_id => { type => 'integer' }, |
|
21 |
sent_on => { type => 'timestamp', default => 'now()', not_null => 1 }, |
|
22 |
status => { type => 'text', not_null => 1 }, |
|
23 |
subject => { type => 'text', not_null => 1 }, |
|
24 |
); |
|
25 |
|
|
26 |
__PACKAGE__->meta->primary_key_columns([ 'id' ]); |
|
27 |
|
|
28 |
__PACKAGE__->meta->allow_inline_column_values(1); |
|
29 |
|
|
30 |
__PACKAGE__->meta->foreign_keys( |
|
31 |
sender => { |
|
32 |
class => 'SL::DB::Employee', |
|
33 |
key_columns => { sender_id => 'id' }, |
|
34 |
}, |
|
35 |
); |
|
36 |
|
|
37 |
1; |
|
38 |
; |
SL/DB/MetaSetup/EmailJournalAttachment.pm | ||
---|---|---|
1 |
# This file has been auto-generated. Do not modify it; it will be overwritten |
|
2 |
# by rose_auto_create_model.pl automatically. |
|
3 |
package SL::DB::EmailJournalAttachment; |
|
4 |
|
|
5 |
use strict; |
|
6 |
|
|
7 |
use parent qw(SL::DB::Object); |
|
8 |
|
|
9 |
__PACKAGE__->meta->table('email_journal_attachments'); |
|
10 |
|
|
11 |
__PACKAGE__->meta->columns( |
|
12 |
content => { type => 'bytea', not_null => 1 }, |
|
13 |
email_journal_id => { type => 'integer', not_null => 1 }, |
|
14 |
id => { type => 'serial', not_null => 1 }, |
|
15 |
itime => { type => 'timestamp', default => 'now()', not_null => 1 }, |
|
16 |
mime_type => { type => 'text', not_null => 1 }, |
|
17 |
mtime => { type => 'timestamp', default => 'now()', not_null => 1 }, |
|
18 |
name => { type => 'text', not_null => 1 }, |
|
19 |
position => { type => 'integer', not_null => 1 }, |
|
20 |
); |
|
21 |
|
|
22 |
__PACKAGE__->meta->primary_key_columns([ 'id' ]); |
|
23 |
|
|
24 |
__PACKAGE__->meta->allow_inline_column_values(1); |
|
25 |
|
|
26 |
__PACKAGE__->meta->foreign_keys( |
|
27 |
email_journal => { |
|
28 |
class => 'SL::DB::EmailJournal', |
|
29 |
key_columns => { email_journal_id => 'id' }, |
|
30 |
}, |
|
31 |
); |
|
32 |
|
|
33 |
1; |
|
34 |
; |
SL/Mailer.pm | ||
---|---|---|
25 | 25 |
use Email::Address; |
26 | 26 |
use Email::MIME::Creator; |
27 | 27 |
use File::Slurp; |
28 |
use List::UtilsBy qw(bundle_by); |
|
28 | 29 |
|
29 | 30 |
use SL::Common; |
31 |
use SL::DB::EmailJournal; |
|
32 |
use SL::DB::EmailJournalAttachment; |
|
33 |
use SL::DB::Employee; |
|
30 | 34 |
use SL::MIME; |
31 | 35 |
use SL::Template; |
32 | 36 |
|
... | ... | |
185 | 189 |
# Create driver for delivery method (sendmail/SMTP) |
186 | 190 |
$self->{driver} = eval { $self->_create_driver }; |
187 | 191 |
if (!$self->{driver}) { |
188 |
$::lxdebug->leave_sub();
|
|
192 |
$self->_store_in_journal('failed', 'driver could not be created; check your configuration');
|
|
189 | 193 |
return "send email : $@"; |
190 | 194 |
} |
191 | 195 |
|
... | ... | |
198 | 202 |
'X-Mailer' => "kivitendo $self->{version}", |
199 | 203 |
]; |
200 | 204 |
|
201 |
# Clean up To/Cc/Bcc address fields |
|
202 |
$self->_cleanup_addresses; |
|
203 |
$self->_create_address_headers; |
|
205 |
my $error; |
|
206 |
my $ok = eval { |
|
207 |
# Clean up To/Cc/Bcc address fields |
|
208 |
$self->_cleanup_addresses; |
|
209 |
$self->_create_address_headers; |
|
204 | 210 |
|
205 |
my $email = $self->_create_message; |
|
211 |
my $email = $self->_create_message;
|
|
206 | 212 |
|
207 |
# $::lxdebug->message(0, "message: " . $email->as_string); |
|
208 |
# return "boom"; |
|
213 |
# $::lxdebug->message(0, "message: " . $email->as_string);
|
|
214 |
# return "boom";
|
|
209 | 215 |
|
210 |
$self->{driver}->start_mail(from => $self->{from}, to => [ map { @{ $self->{addresses}->{$_} } } qw(to cc bcc) ]);
|
|
211 |
$self->{driver}->print($email->as_string); |
|
212 |
$self->{driver}->send; |
|
216 |
$self->{driver}->start_mail(from => $self->{from}, to => [ $self->_all_recipients ]);
|
|
217 |
$self->{driver}->print($email->as_string);
|
|
218 |
$self->{driver}->send;
|
|
213 | 219 |
|
214 |
return ''; |
|
220 |
1; |
|
221 |
}; |
|
222 |
|
|
223 |
$error = $@ if !$ok; |
|
224 |
|
|
225 |
$self->_store_in_journal; |
|
226 |
|
|
227 |
return $ok ? '' : "send email: $error"; |
|
228 |
} |
|
229 |
|
|
230 |
sub _all_recipients { |
|
231 |
my ($self) = @_; |
|
232 |
|
|
233 |
$self->{addresses} ||= {}; |
|
234 |
return map { @{ $self->{addresses}->{$_} || [] } } qw(to cc bcc); |
|
235 |
} |
|
236 |
|
|
237 |
sub _store_in_journal { |
|
238 |
my ($self, $status, $extended_status) = @_; |
|
239 |
|
|
240 |
$status //= $self->{driver}->status if $self->{driver}; |
|
241 |
$status //= 'failed'; |
|
242 |
$extended_status //= $self->{driver}->extended_status if $self->{driver}; |
|
243 |
$extended_status //= 'unknown error'; |
|
244 |
|
|
245 |
my @attachments = grep { $_ } map { |
|
246 |
my $part = $self->_create_attachment_part($_); |
|
247 |
if ($part) { |
|
248 |
SL::DB::EmailJournalAttachment->new( |
|
249 |
name => $part->filename, |
|
250 |
mime_type => $part->content_type, |
|
251 |
content => $part->body, |
|
252 |
) |
|
253 |
} |
|
254 |
} @{ $self->{attachments} || [] }; |
|
255 |
|
|
256 |
my $headers = join "\r\n", (bundle_by { join(': ', @_) } 2, @{ $self->{headers} || [] }); |
|
257 |
|
|
258 |
SL::DB::EmailJournal->new( |
|
259 |
sender => SL::DB::Manager::Employee->current, |
|
260 |
from => $self->{from} // '', |
|
261 |
recipients => join(', ', $self->_all_recipients), |
|
262 |
subject => $self->{subject} // '', |
|
263 |
headers => $headers, |
|
264 |
body => $self->{message} // '', |
|
265 |
sent_on => DateTime->now_local, |
|
266 |
attachments => \@attachments, |
|
267 |
status => $status, |
|
268 |
extended_status => $extended_status, |
|
269 |
)->save; |
|
215 | 270 |
} |
216 | 271 |
|
217 | 272 |
1; |
SL/Mailer/SMTP.pm | ||
---|---|---|
6 | 6 |
|
7 | 7 |
use Rose::Object::MakeMethods::Generic |
8 | 8 |
( |
9 |
scalar => [ qw(myconfig mailer form) ] |
|
9 |
scalar => [ qw(myconfig mailer form status extended_status) ]
|
|
10 | 10 |
); |
11 | 11 |
|
12 | 12 |
my %security_config = ( |
... | ... | |
18 | 18 |
sub init { |
19 | 19 |
my ($self) = @_; |
20 | 20 |
|
21 |
Rose::Object::init(@_); |
|
21 |
Rose::Object::init( |
|
22 |
@_, |
|
23 |
status => 'failed', |
|
24 |
extended_status => 'no send attempt made', |
|
25 |
); |
|
22 | 26 |
|
23 | 27 |
my $cfg = $::lx_office_conf{mail_delivery} || {}; |
24 | 28 |
$self->{security} = exists $security_config{lc $cfg->{security}} ? lc $cfg->{security} : 'none'; |
25 | 29 |
my $sec_cfg = $security_config{ $self->{security} }; |
26 | 30 |
|
27 |
eval "require $sec_cfg->{require_module}" or die "$@"; |
|
31 |
eval "require $sec_cfg->{require_module}" or do { |
|
32 |
$self->extended_status("$@"); |
|
33 |
die $self->extended_status; |
|
34 |
}; |
|
28 | 35 |
|
29 | 36 |
$self->{smtp} = $sec_cfg->{package}->new($cfg->{host} || 'localhost', Port => $cfg->{port} || $sec_cfg->{port}); |
30 |
die unless $self->{smtp}; |
|
31 |
|
|
32 |
$self->{smtp}->starttls(SSL_verify_mode => 0) || die if $self->{security} eq 'tls'; |
|
37 |
if (!$self->{smtp}) { |
|
38 |
$self->extended_status('SMTP connection could not be initialized'); |
|
39 |
die $self->extended_status; |
|
40 |
} |
|
41 |
|
|
42 |
if ($self->{security} eq 'tls') { |
|
43 |
$self->{smtp}->starttls(SSL_verify_mode => 0) or do { |
|
44 |
$self->extended_status("$@"); |
|
45 |
die $self->extended_status; |
|
46 |
}; |
|
47 |
} |
|
33 | 48 |
|
34 | 49 |
# Backwards compatibility: older Versions used 'user' instead of the |
35 | 50 |
# intended 'login'. Support both. |
... | ... | |
37 | 52 |
|
38 | 53 |
return 1 unless $login; |
39 | 54 |
|
40 |
$self->{smtp}->auth($login, $cfg->{password}) or die; |
|
55 |
if (!$self->{smtp}->auth($login, $cfg->{password})) { |
|
56 |
$self->extended_status('SMTP authentication failed'); |
|
57 |
die $self->extended_status; |
|
58 |
} |
|
41 | 59 |
} |
42 | 60 |
|
43 | 61 |
sub start_mail { |
44 | 62 |
my ($self, %params) = @_; |
45 | 63 |
|
46 |
$self->{smtp}->mail($params{from}); |
|
47 |
$self->{smtp}->recipient(@{ $params{to} }); |
|
48 |
$self->{smtp}->data; |
|
64 |
$self->{smtp}->mail($params{from}) or do { $self->extended_status($self->{smtp}->message); die $self->extended_status; };
|
|
65 |
$self->{smtp}->recipient(@{ $params{to} }) or do { $self->extended_status($self->{smtp}->message); die $self->extended_status; };
|
|
66 |
$self->{smtp}->data or do { $self->extended_status($self->{smtp}->message); die $self->extended_status; };
|
|
49 | 67 |
} |
50 | 68 |
|
51 | 69 |
sub print { |
... | ... | |
76 | 94 |
sub send { |
77 | 95 |
my ($self) = @_; |
78 | 96 |
|
79 |
$self->{smtp}->dataend; |
|
97 |
my $ok = $self->{smtp}->dataend; |
|
98 |
$self->extended_status($self->{smtp}->message); |
|
99 |
$self->status('ok') if $ok; |
|
100 |
|
|
80 | 101 |
$self->{smtp}->quit; |
102 |
|
|
81 | 103 |
delete $self->{smtp}; |
82 | 104 |
} |
83 | 105 |
|
SL/Mailer/Sendmail.pm | ||
---|---|---|
10 | 10 |
|
11 | 11 |
use Rose::Object::MakeMethods::Generic |
12 | 12 |
( |
13 |
scalar => [ qw(myconfig mailer form) ] |
|
13 |
scalar => [ qw(myconfig mailer form status extended_status) ]
|
|
14 | 14 |
); |
15 | 15 |
|
16 | 16 |
sub init { |
17 | 17 |
my ($self) = @_; |
18 | 18 |
|
19 |
Rose::Object::init(@_); |
|
19 |
Rose::Object::init( |
|
20 |
@_, |
|
21 |
status => 'failed', |
|
22 |
extended_status => 'no send attempt made', |
|
23 |
); |
|
20 | 24 |
|
21 | 25 |
my $email = Encode::encode('utf-8', $self->myconfig->{email}); |
22 | 26 |
$email =~ s/[^\w\.\-\+=@]//ig; |
... | ... | |
26 | 30 |
my $sendmail = $::lx_office_conf{applications}->{sendmail} || $::lx_office_conf{mail_delivery}->{sendmail} || "sendmail -t"; |
27 | 31 |
$sendmail = $template->parse_block($sendmail); |
28 | 32 |
|
29 |
$self->{sendmail} = IO::File->new("|$sendmail") || die "sendmail($sendmail): $!";
|
|
33 |
$self->{sendmail} = IO::File->new("|$sendmail") or do { $self->extended_status("sendmail($sendmail): $!"); die $self->extended_status; };
|
|
30 | 34 |
$self->{sendmail}->binmode(':utf8'); |
31 | 35 |
} |
32 | 36 |
|
... | ... | |
36 | 40 |
sub print { |
37 | 41 |
my $self = shift; |
38 | 42 |
|
39 |
$self->{sendmail}->print(@_); |
|
43 |
$self->{sendmail}->print(@_) or do { $self->extended_status("sendmail: $!"); die $self->extended_status; };
|
|
40 | 44 |
} |
41 | 45 |
|
42 | 46 |
sub send { |
43 | 47 |
my ($self) = @_; |
44 |
$self->{sendmail}->close; |
|
48 |
|
|
49 |
$self->{sendmail}->close or do { $self->extended_status("sendmail: $!"); die $self->extended_status; }; |
|
50 |
|
|
51 |
$self->status('ok'); |
|
52 |
$self->extended_status(''); |
|
53 |
|
|
45 | 54 |
delete $self->{sendmail}; |
46 | 55 |
} |
47 | 56 |
|
sql/Pg-upgrade2/email_journal.sql | ||
---|---|---|
1 |
-- @tag: email_journal |
|
2 |
-- @description: Journal für verschickte E-Mails |
|
3 |
-- @depends: release_3_3_0 |
|
4 |
|
|
5 |
-- Note: sender_id may be NULL to indicate a mail sent by the system |
|
6 |
-- without a user being logged in – e.g. by the task server. |
|
7 |
CREATE TABLE email_journal ( |
|
8 |
id SERIAL NOT NULL, |
|
9 |
sender_id INTEGER, |
|
10 |
"from" TEXT NOT NULL, |
|
11 |
recipients TEXT NOT NULL, |
|
12 |
sent_on TIMESTAMP NOT NULL DEFAULT now(), |
|
13 |
subject TEXT NOT NULL, |
|
14 |
body TEXT NOT NULL, |
|
15 |
headers TEXT NOT NULL, |
|
16 |
status TEXT NOT NULL, |
|
17 |
extended_status TEXT NOT NULL, |
|
18 |
itime TIMESTAMP NOT NULL DEFAULT now(), |
|
19 |
mtime TIMESTAMP NOT NULL DEFAULT now(), |
|
20 |
|
|
21 |
PRIMARY KEY (id), |
|
22 |
FOREIGN KEY (sender_id) REFERENCES employee (id), |
|
23 |
CONSTRAINT valid_status CHECK (status IN ('ok', 'failed')) |
|
24 |
); |
|
25 |
|
|
26 |
CREATE TABLE email_journal_attachments ( |
|
27 |
id SERIAL NOT NULL, |
|
28 |
position INTEGER NOT NULL, |
|
29 |
email_journal_id INTEGER NOT NULL, |
|
30 |
name TEXT NOT NULL, |
|
31 |
mime_type TEXT NOT NULL, |
|
32 |
content BYTEA NOT NULL, |
|
33 |
itime TIMESTAMP NOT NULL DEFAULT now(), |
|
34 |
mtime TIMESTAMP NOT NULL DEFAULT now(), |
|
35 |
|
|
36 |
PRIMARY KEY (id), |
|
37 |
FOREIGN KEY (email_journal_id) REFERENCES email_journal (id) ON DELETE CASCADE |
|
38 |
); |
|
39 |
|
|
40 |
CREATE TRIGGER mtime_email_journal BEFORE UPDATE ON email_journal FOR EACH ROW EXECUTE PROCEDURE set_mtime(); |
|
41 |
CREATE TRIGGER mtime_email_journal_attachments BEFORE UPDATE ON email_journal_attachments FOR EACH ROW EXECUTE PROCEDURE set_mtime(); |
Auch abrufbar als: Unified diff
E-Mail-Journal: verschickte E-Mails speichern