Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 681a9adb

Von Tamino Steinert vor mehr als 1 Jahr hinzugefügt

  • ID 681a9adbfc5f913c0b5d9269c7e0da8c81ed72a7
  • Vorgänger 26ed9919
  • Nachfolger 452f0d6a

IMAPClient: Wrapper für IMAP-Funktionen

Unterschiede anzeigen:

SL/IMAPClient.pm
1
package SL::IMAPClient;
2

  
3
use strict;
4
use warnings;
5
use utf8;
6

  
7
use IO::Socket::INET;
8
use IO::Socket::SSL;
9
use Mail::IMAPClient;
10
use File::MimeInfo::Magic;
11
use Encode qw(encode decode);
12
use Encode::IMAPUTF7;
13

  
14
use SL::SessionFile;
15
use SL::Locale::String qw(t8);
16

  
17
my %TYPE_TO_FOLDER = (
18
  sales_quotation             => t8('Sales Quotations'),
19
  sales_order                 => t8('Sales Orders'),
20
);
21

  
22
sub new {
23
  my ($class, %params) = @_;
24
  my $config = $::lx_office_conf{imap_client} || {};
25
  my $self = bless {
26
    enabled     => $config->{enabled},
27
    hostname    => $config->{hostname},
28
    port        => $config->{port},
29
    ssl         => $config->{ssl},
30
    username    => $config->{username},
31
    password    => $config->{password},
32
    base_folder => $config->{base_folder} || 'INBOX',
33
    %params,
34
  }, $class;
35
  return unless $self->{enabled};
36
  $self->_create_imap_client();
37
  return $self;
38
}
39

  
40
sub DESTROY {
41
  my ($self) = @_;
42
  if ($self->{imap_client}) {
43
    $self->{imap_client}->logout();
44
  }
45
}
46

  
47
sub update_emails_for_record {
48
  my ($self, $record) = @_;
49

  
50
  my $folder_string = $self->_get_folder_string_for_record($record);
51
  return unless $self->{imap_client}->exists($folder_string);
52
  $self->{imap_client}->select($folder_string)
53
    or die "Could not select IMAP folder '$folder_string': $@\n";
54

  
55
  my $msg_uids = $self->{imap_client}->messages
56
    or die "Could not messages via IMAP: $@\n";
57

  
58
  my $dbh = $record->dbh;
59
  my $query = <<SQL;
60
    SELECT uid
61
    FROM files
62
    WHERE object_id = ?
63
      AND object_type = ?
64
      AND source = 'uploaded'
65
      AND file_type = 'attachment'
66
SQL
67
  my $existing_uids = $dbh->selectall_hashref($query, 'uid', undef,
68
                                              $record->id, $record->type);
69
  my @new_msg_uids = grep { !$existing_uids->{$_} } @$msg_uids;
70

  
71
  foreach my $msg_uid (@new_msg_uids) {
72
    my $sess_fname = "mail_download_" . $record->type . "_" . $record->id . "_" . $msg_uid;
73

  
74
    my $file_name =
75
      decode('MIME-Header', $self->{imap_client}->subject($msg_uid)) . '.eml';
76
    my $sfile      = SL::SessionFile->new($sess_fname, mode => 'w');
77
    $self->{imap_client}->message_to_file($sfile->fh, $msg_uid)
78
      or die "Could not fetch message $msg_uid from IMAP: $@\n";
79
    $sfile->fh->close;
80

  
81
    my $mime_type = File::MimeInfo::Magic::magic($sfile->file_name);
82
    my $fileobj = SL::File->save(
83
      object_id        => $record->id,
84
      object_type      => $record->type,
85
      mime_type        => $mime_type,
86
      source           => 'uploaded',
87
      uid              => "$msg_uid",
88
      file_type        => 'attachment',
89
      file_name        => $file_name,
90
      file_path        => $sfile->file_name
91
    );
92
    unlink($sfile->file_name);
93
  }
94

  
95
}
96

  
97
sub create_folder {
98
  my ($self, $folder_name) = @_;
99
  return if $self->{imap_client}->exists($folder_name);
100
  $self->{imap_client}->create($folder_name)
101
    or die "Could not create IMAP folder '$folder_name': $@\n";
102
  return;
103
}
104

  
105
sub get_folder_string_from_path {
106
  my ($self, $folder_path) = @_;
107
  my $separator = $self->{imap_client}->separator();
108
  my $replace_sep = $separator eq '_' ? '-' : '_';
109
  $folder_path =~ s|\Q${separator}|$replace_sep|g; # \Q -> escape special chars
110
  $folder_path =~ s|/|${separator}|g; # replace / with separator
111
  my $folder_string = encode('IMAP-UTF-7', $folder_path);
112
  return $folder_string;
113
}
114

  
115
sub create_folder_for_record {
116
  my ($self, $record) = @_;
117
  my $folder_string = $self->_get_folder_string_for_record($record);
118
  $self->create_folder($folder_string);
119
  return;
120
}
121

  
122
sub _get_folder_string_for_record {
123
  my ($self, $record) = @_;
124

  
125
  my $customer_vendor = $record->customervendor;
126
  my $record_folder_path =
127
    $self->{base_folder} . '/' .
128
    $customer_vendor->number . ' ' . $customer_vendor->name . '/' .
129
    $TYPE_TO_FOLDER{$record->type} . '/' .
130
    $record->number;
131
  my $folder_string = $self->get_folder_string_from_path($record_folder_path);
132
  return $folder_string;
133
}
134

  
135
sub _create_imap_client {
136
  my ($self) = @_;
137

  
138
  my $socket;
139
  if ($self->{ssl}) {
140
    $socket = IO::Socket::SSL->new(
141
      Proto    => 'tcp',
142
      PeerAddr => $self->{hostname},
143
      PeerPort => $self->{port} || 993,
144
    );
145
  } else {
146
    $socket = IO::Socket::INET->new(
147
      Proto    => 'tcp',
148
      PeerAddr => $self->{hostname},
149
      PeerPort => $self->{port} || 143,
150
    );
151
  }
152
  if (!$socket) {
153
    die "Failed to create socket for IMAP client: $@\n";
154
  }
155

  
156
  my $imap_client = Mail::IMAPClient->new(
157
    Socket   => $socket,
158
    User     => $self->{username},
159
    Password => $self->{password},
160
    Uid      => 1,
161
    peek     => 1, # Don't change the \Seen flag
162
  ) or do {
163
    die "Failed to create IMAP Client: $@\n"
164
  };
165

  
166
  $imap_client->IsAuthenticated() or do {
167
    die "IMAP Client login failed: " . $imap_client->LastError() . "\n";
168
  };
169

  
170
  $self->{imap_client} = $imap_client;
171
  return $imap_client;
172
}
173

  
174
1;
175

  
176

  
177
__END__
178

  
179
=pod
180

  
181
=encoding utf8
182

  
183
=head1 NAME
184

  
185
SL::IMAPClient - Base class for interacting with email server from kivitendo
186

  
187
=head1 SYNOPSIS
188

  
189
  use SL::IMAPClient;
190

  
191
  # uses the config in config/kivitendo.conf
192
  my $imap_client = SL::IMAPClient->new();
193

  
194
  # can also be used with a custom config, overriding the global config
195
  my %config = (
196
    enabled     => 1,
197
    hostname    => 'imap.example.com',
198
    username    => 'test_user',
199
    password    => 'test_password',
200
    ssl         => 1,
201
    base_folder => 'INBOX',
202
  );
203
  my $imap_client = SL::IMAPClient->new(%config);
204

  
205
  # create email folder for record
206
  # folder structure: base_folder/customer_vendor_number customer_vendor_name/type/record_number
207
  # e.g. INBOX/1234 Testkunde/Angebot/123
208
  # if the folder already exists, nothing happens
209
  $imap_client->create_folder_for_record($record);
210

  
211
  # update emails for record
212
  # fetches all emails from the IMAP server and saves them as attachments
213
  $imap_client->update_emails_for_record($record);
214

  
215
=head1 OVERVIEW
216

  
217
Mail can be sent from kivitendo via the sendmail command or the smtp protocol.
218

  
219

  
220
=head1 INTERNAL DATA TYPES
221

  
222
=over 2
223

  
224
=item C<%TYPE_TO_FOLDER>
225

  
226
  Due to the lack of a single global mapping for $record->type,
227
  type is mapped to the corresponding translation. All types which
228
  use this module are currently mapped and should be mapped.
229

  
230
=back
231

  
232
=head1 FUNCTIONS
233

  
234
=over 4
235

  
236
=item C<new>
237

  
238
  Creates a new SL::IMAPClient object. If no config is passed, the config
239
  from config/kivitendo.conf is used. If a config is passed, the global
240
  config is overridden.
241

  
242
=item C<DESTROY>
243

  
244
  Destructor. Disconnects from the IMAP server.
245

  
246
=item C<update_emails_for_record>
247

  
248
  Updates the emails for a record. Checks which emails are missing and
249
  fetches these from the IMAP server.
250

  
251
=item C<create_folder>
252

  
253
  Creates a folder on the IMAP server. If the folder already exists, nothing
254
  happens.
255

  
256
=item C<get_folder_string_from_path>
257

  
258
  Converts a folder path to a folder string. The folder path is like path
259
  on unix filesystem. The folder string is the path on the IMAP server.
260
  The folder string is encoded in IMAP-UTF-7.
261

  
262
=item C<create_folder_for_record>
263

  
264
  Creates a folder for a record on the IMAP server. The folder structure
265
  is like this: base_folder/customer_vendor_number customer_vendor_name/type/record_number
266
  e.g. INBOX/1234 Testkunde/Angebot/123
267
  If the folder already exists, nothing happens.
268

  
269
=item C<_get_folder_string_for_record>
270

  
271
  Returns the folder string for a record. The folder structure is like this:
272
  base_folder/customer_vendor_number customer_vendor_name/type/record_number
273
  e.g. INBOX/1234 Testkunde/Angebot/123. This is passed through
274
  C<get_folder_string_from_path>.
275

  
276
=item C<_create_imap_client>
277

  
278
  Creates a new IMAP client and logs in. The IMAP client is stored in
279
  $self->{imap_client}.
280

  
281
=back
282

  
283
=head1 BUGS
284

  
285
Nothing here yet.
286

  
287
=head1 AUTHOR
288

  
289
Tamino Steinert E<lt>tamino.steinert@tamino.stE<gt>
290

  
291
=cut
config/kivitendo.conf.default
162 162
login =
163 163
password =
164 164

  
165
[imap_client]
166
enabled = 0
167
hostname = local
168
username =
169
password =
170
# Port only needs to be changed if it is not the default port.
171
# port = 993
172
base_folder = INBOX
173
# If SSL is to be used, then set port to 993 or leave empty
174
ssl = 1
175

  
165 176
[sent_emails_in_imap]
166 177
enabled = 0
167 178
hostname = localhost

Auch abrufbar als: Unified diff