Mercurial > hg > rc1
comparison plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php @ 0:1e000243b222
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:50:29 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1e000243b222 |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * Managesieve Vacation Engine | |
5 * | |
6 * Engine part of Managesieve plugin implementing UI and backend access. | |
7 * | |
8 * Copyright (C) 2011-2014, Kolab Systems AG | |
9 * | |
10 * This program is free software: you can redistribute it and/or modify | |
11 * it under the terms of the GNU General Public License as published by | |
12 * the Free Software Foundation, either version 3 of the License, or | |
13 * (at your option) any later version. | |
14 * | |
15 * This program is distributed in the hope that it will be useful, | |
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 * GNU General Public License for more details. | |
19 * | |
20 * You should have received a copy of the GNU General Public License | |
21 * along with this program. If not, see http://www.gnu.org/licenses/. | |
22 */ | |
23 | |
24 class rcube_sieve_vacation extends rcube_sieve_engine | |
25 { | |
26 protected $error; | |
27 protected $script_name; | |
28 protected $vacation = array(); | |
29 | |
30 function actions() | |
31 { | |
32 $error = $this->start('vacation'); | |
33 | |
34 // find current vacation rule | |
35 if (!$error) { | |
36 $this->vacation_rule(); | |
37 $this->vacation_post(); | |
38 } | |
39 | |
40 $this->plugin->add_label('vacation.saving'); | |
41 $this->rc->output->add_handlers(array( | |
42 'vacationform' => array($this, 'vacation_form'), | |
43 )); | |
44 | |
45 $this->rc->output->set_pagetitle($this->plugin->gettext('vacation')); | |
46 $this->rc->output->send('managesieve.vacation'); | |
47 } | |
48 | |
49 /** | |
50 * Find and load sieve script with/for vacation rule | |
51 * | |
52 * @param string $script_name Optional script name | |
53 * | |
54 * @return int Connection status: 0 on success, >0 on failure | |
55 */ | |
56 protected function load_script($script_name = null) | |
57 { | |
58 if ($this->script_name !== null) { | |
59 return 0; | |
60 } | |
61 | |
62 $list = $this->list_scripts(); | |
63 $master = $this->rc->config->get('managesieve_kolab_master'); | |
64 $included = array(); | |
65 | |
66 $this->script_name = false; | |
67 | |
68 // first try the active script(s)... | |
69 if (!empty($this->active)) { | |
70 // Note: there can be more than one active script on KEP:14-enabled server | |
71 foreach ($this->active as $script) { | |
72 if ($this->sieve->load($script)) { | |
73 foreach ($this->sieve->script->as_array() as $rule) { | |
74 if (!empty($rule['actions'])) { | |
75 if ($rule['actions'][0]['type'] == 'vacation') { | |
76 $this->script_name = $script; | |
77 return 0; | |
78 } | |
79 else if (empty($master) && $rule['actions'][0]['type'] == 'include') { | |
80 $included[] = $rule['actions'][0]['target']; | |
81 } | |
82 } | |
83 } | |
84 } | |
85 } | |
86 | |
87 // ...else try scripts included in active script (not for KEP:14) | |
88 foreach ($included as $script) { | |
89 if ($this->sieve->load($script)) { | |
90 foreach ($this->sieve->script->as_array() as $rule) { | |
91 if (!empty($rule['actions']) && $rule['actions'][0]['type'] == 'vacation') { | |
92 $this->script_name = $script; | |
93 return 0; | |
94 } | |
95 } | |
96 } | |
97 } | |
98 } | |
99 | |
100 // try all other scripts | |
101 if (!empty($list)) { | |
102 // else try included scripts | |
103 foreach (array_diff($list, $included, $this->active) as $script) { | |
104 if ($this->sieve->load($script)) { | |
105 foreach ($this->sieve->script->as_array() as $rule) { | |
106 if (!empty($rule['actions']) && $rule['actions'][0]['type'] == 'vacation') { | |
107 $this->script_name = $script; | |
108 return 0; | |
109 } | |
110 } | |
111 } | |
112 } | |
113 | |
114 // none of the scripts contains existing vacation rule | |
115 // use any (first) active or just existing script (in that order) | |
116 if (!empty($this->active)) { | |
117 $this->sieve->load($this->script_name = $this->active[0]); | |
118 } | |
119 else { | |
120 $this->sieve->load($this->script_name = $list[0]); | |
121 } | |
122 } | |
123 | |
124 return $this->sieve->error(); | |
125 } | |
126 | |
127 private function vacation_rule() | |
128 { | |
129 if ($this->script_name === false || $this->script_name === null || !$this->sieve->load($this->script_name)) { | |
130 return; | |
131 } | |
132 | |
133 $list = array(); | |
134 $active = in_array($this->script_name, $this->active); | |
135 | |
136 // find (first) vacation rule | |
137 foreach ($this->script as $idx => $rule) { | |
138 if (empty($this->vacation) && !empty($rule['actions']) && $rule['actions'][0]['type'] == 'vacation') { | |
139 foreach ($rule['actions'] as $act) { | |
140 if ($act['type'] == 'discard' || $act['type'] == 'keep') { | |
141 $action = $act['type']; | |
142 } | |
143 else if ($act['type'] == 'redirect') { | |
144 $action = $act['copy'] ? 'copy' : 'redirect'; | |
145 $target = $act['target']; | |
146 } | |
147 } | |
148 | |
149 $this->vacation = array_merge($rule['actions'][0], array( | |
150 'idx' => $idx, | |
151 'disabled' => $rule['disabled'] || !$active, | |
152 'name' => $rule['name'], | |
153 'tests' => $rule['tests'], | |
154 'action' => $action ?: 'keep', | |
155 'target' => $target, | |
156 )); | |
157 } | |
158 else if ($active) { | |
159 $list[$idx] = $rule['name']; | |
160 } | |
161 } | |
162 | |
163 $this->vacation['list'] = $list; | |
164 } | |
165 | |
166 private function vacation_post() | |
167 { | |
168 if (empty($_POST)) { | |
169 return; | |
170 } | |
171 | |
172 $date_extension = in_array('date', $this->exts); | |
173 $regex_extension = in_array('regex', $this->exts); | |
174 | |
175 // set user's timezone | |
176 try { | |
177 $timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT')); | |
178 } | |
179 catch (Exception $e) { | |
180 $timezone = new DateTimeZone('GMT'); | |
181 } | |
182 | |
183 $status = rcube_utils::get_input_value('vacation_status', rcube_utils::INPUT_POST); | |
184 $from = rcube_utils::get_input_value('vacation_from', rcube_utils::INPUT_POST); | |
185 $subject = rcube_utils::get_input_value('vacation_subject', rcube_utils::INPUT_POST, true); | |
186 $reason = rcube_utils::get_input_value('vacation_reason', rcube_utils::INPUT_POST, true); | |
187 $addresses = rcube_utils::get_input_value('vacation_addresses', rcube_utils::INPUT_POST, true); | |
188 $interval = rcube_utils::get_input_value('vacation_interval', rcube_utils::INPUT_POST); | |
189 $interval_type = rcube_utils::get_input_value('vacation_interval_type', rcube_utils::INPUT_POST); | |
190 $date_from = rcube_utils::get_input_value('vacation_datefrom', rcube_utils::INPUT_POST); | |
191 $date_to = rcube_utils::get_input_value('vacation_dateto', rcube_utils::INPUT_POST); | |
192 $time_from = rcube_utils::get_input_value('vacation_timefrom', rcube_utils::INPUT_POST); | |
193 $time_to = rcube_utils::get_input_value('vacation_timeto', rcube_utils::INPUT_POST); | |
194 $after = rcube_utils::get_input_value('vacation_after', rcube_utils::INPUT_POST); | |
195 $action = rcube_utils::get_input_value('vacation_action', rcube_utils::INPUT_POST); | |
196 $target = rcube_utils::get_input_value('action_target', rcube_utils::INPUT_POST, true); | |
197 $target_domain = rcube_utils::get_input_value('action_domain', rcube_utils::INPUT_POST); | |
198 | |
199 $interval_type = $interval_type == 'seconds' ? 'seconds' : 'days'; | |
200 $vacation_action['type'] = 'vacation'; | |
201 $vacation_action['reason'] = $this->strip_value(str_replace("\r\n", "\n", $reason)); | |
202 $vacation_action['subject'] = trim($subject); | |
203 $vacation_action['from'] = trim($from); | |
204 $vacation_action['addresses'] = $addresses; | |
205 $vacation_action[$interval_type] = $interval; | |
206 $vacation_tests = (array) $this->vacation['tests']; | |
207 | |
208 foreach ((array) $vacation_action['addresses'] as $aidx => $address) { | |
209 $vacation_action['addresses'][$aidx] = $address = trim($address); | |
210 | |
211 if (empty($address)) { | |
212 unset($vacation_action['addresses'][$aidx]); | |
213 } | |
214 else if (!rcube_utils::check_email($address)) { | |
215 $error = 'noemailwarning'; | |
216 break; | |
217 } | |
218 } | |
219 | |
220 if (!empty($vacation_action['from']) && !rcube_utils::check_email($vacation_action['from'])) { | |
221 $error = 'noemailwarning'; | |
222 } | |
223 | |
224 if ($vacation_action['reason'] == '') { | |
225 $error = 'managesieve.emptyvacationbody'; | |
226 } | |
227 | |
228 if ($vacation_action[$interval_type] && !preg_match('/^[0-9]+$/', $vacation_action[$interval_type])) { | |
229 $error = 'managesieve.forbiddenchars'; | |
230 } | |
231 | |
232 // find and remove existing date/regex/true rules | |
233 foreach ((array) $vacation_tests as $idx => $t) { | |
234 if ($t['test'] == 'currentdate' || $t['test'] == 'true' | |
235 || ($t['test'] == 'header' && $t['type'] == 'regex' && $t['arg1'] == 'received') | |
236 ) { | |
237 unset($vacation_tests[$idx]); | |
238 } | |
239 } | |
240 | |
241 if ($date_extension) { | |
242 $date_format = $this->rc->config->get('date_format', 'Y-m-d'); | |
243 foreach (array('date_from', 'date_to') as $var) { | |
244 $time = ${str_replace('date', 'time', $var)}; | |
245 $date = rcube_utils::format_datestr($$var, $date_format); | |
246 $date = trim($date . ' ' . $time); | |
247 | |
248 if ($date && ($dt = rcube_utils::anytodatetime($date, $timezone))) { | |
249 if ($time) { | |
250 $vacation_tests[] = array( | |
251 'test' => 'currentdate', | |
252 'part' => 'iso8601', | |
253 'type' => 'value-' . ($var == 'date_from' ? 'ge' : 'le'), | |
254 'zone' => $dt->format('O'), | |
255 'arg' => str_replace('+00:00', 'Z', strtoupper($dt->format('c'))), | |
256 ); | |
257 } | |
258 else { | |
259 $vacation_tests[] = array( | |
260 'test' => 'currentdate', | |
261 'part' => 'date', | |
262 'type' => 'value-' . ($var == 'date_from' ? 'ge' : 'le'), | |
263 'zone' => $dt->format('O'), | |
264 'arg' => $dt->format('Y-m-d'), | |
265 ); | |
266 } | |
267 } | |
268 } | |
269 } | |
270 else if ($regex_extension) { | |
271 // Add date range rules if range specified | |
272 if ($date_from && $date_to) { | |
273 if ($tests = self::build_regexp_tests($date_from, $date_to, $error)) { | |
274 $vacation_tests = array_merge($vacation_tests, $tests); | |
275 } | |
276 } | |
277 } | |
278 | |
279 if ($action == 'redirect' || $action == 'copy') { | |
280 if ($target_domain) { | |
281 $target .= '@' . $target_domain; | |
282 } | |
283 | |
284 if (empty($target) || !rcube_utils::check_email($target)) { | |
285 $error = 'noemailwarning'; | |
286 } | |
287 } | |
288 | |
289 if (empty($vacation_tests)) { | |
290 $vacation_tests = $this->rc->config->get('managesieve_vacation_test', array(array('test' => 'true'))); | |
291 } | |
292 | |
293 if (!$error) { | |
294 $rule = $this->vacation; | |
295 $rule['type'] = 'if'; | |
296 $rule['name'] = $rule['name'] ?: $this->plugin->gettext('vacation'); | |
297 $rule['disabled'] = $status == 'off'; | |
298 $rule['tests'] = $vacation_tests; | |
299 $rule['join'] = $date_extension ? count($vacation_tests) > 1 : false; | |
300 $rule['actions'] = array($vacation_action); | |
301 $rule['after'] = $after; | |
302 | |
303 if ($action && $action != 'keep') { | |
304 $rule['actions'][] = array( | |
305 'type' => $action == 'discard' ? 'discard' : 'redirect', | |
306 'copy' => $action == 'copy', | |
307 'target' => $action != 'discard' ? $target : '', | |
308 ); | |
309 } | |
310 | |
311 if ($this->save_vacation_script($rule)) { | |
312 $this->rc->output->show_message('managesieve.vacationsaved', 'confirmation'); | |
313 $this->rc->output->send(); | |
314 } | |
315 } | |
316 | |
317 $this->rc->output->show_message($error ?: 'managesieve.saveerror', 'error'); | |
318 $this->rc->output->send(); | |
319 } | |
320 | |
321 /** | |
322 * Independent vacation form | |
323 */ | |
324 public function vacation_form($attrib) | |
325 { | |
326 // check supported extensions | |
327 $date_extension = in_array('date', $this->exts); | |
328 $regex_extension = in_array('regex', $this->exts); | |
329 $seconds_extension = in_array('vacation-seconds', $this->exts); | |
330 | |
331 // build FORM tag | |
332 $form_id = $attrib['id'] ?: 'form'; | |
333 $out = $this->rc->output->request_form(array( | |
334 'id' => $form_id, | |
335 'name' => $form_id, | |
336 'method' => 'post', | |
337 'task' => 'settings', | |
338 'action' => 'plugin.managesieve-vacation', | |
339 'noclose' => true | |
340 ) + $attrib); | |
341 | |
342 $from_addr = $this->rc->config->get('managesieve_vacation_from_init'); | |
343 $auto_addr = $this->rc->config->get('managesieve_vacation_addresses_init'); | |
344 | |
345 if (count($this->vacation) < 2) { | |
346 if ($auto_addr) { | |
347 $this->vacation['addresses'] = $this->user_emails(); | |
348 } | |
349 if ($from_addr) { | |
350 $default_identity = $this->rc->user->list_emails(true); | |
351 $this->vacation['from'] = $default_identity['email']; | |
352 } | |
353 } | |
354 | |
355 // form elements | |
356 $from = new html_inputfield(array('name' => 'vacation_from', 'id' => 'vacation_from', 'size' => 50)); | |
357 $subject = new html_inputfield(array('name' => 'vacation_subject', 'id' => 'vacation_subject', 'size' => 50)); | |
358 $reason = new html_textarea(array('name' => 'vacation_reason', 'id' => 'vacation_reason', 'cols' => 60, 'rows' => 8)); | |
359 $interval = new html_inputfield(array('name' => 'vacation_interval', 'id' => 'vacation_interval', 'size' => 5)); | |
360 $addresses = '<textarea name="vacation_addresses" id="vacation_addresses" data-type="list" data-size="30" style="display: none">' | |
361 . rcube::Q(implode("\n", (array) $this->vacation['addresses']), 'strict', false) . '</textarea>'; | |
362 $status = new html_select(array('name' => 'vacation_status', 'id' => 'vacation_status')); | |
363 $action = new html_select(array('name' => 'vacation_action', 'id' => 'vacation_action', 'onchange' => 'vacation_action_select()')); | |
364 $addresses_link = new html_inputfield(array( | |
365 'type' => 'button', | |
366 'href' => '#', | |
367 'class' => 'button', | |
368 'onclick' => rcmail_output::JS_OBJECT_NAME . '.managesieve_vacation_addresses()' | |
369 )); | |
370 | |
371 $status->add($this->plugin->gettext('vacation.on'), 'on'); | |
372 $status->add($this->plugin->gettext('vacation.off'), 'off'); | |
373 | |
374 $action->add($this->plugin->gettext('vacation.keep'), 'keep'); | |
375 $action->add($this->plugin->gettext('vacation.discard'), 'discard'); | |
376 $action->add($this->plugin->gettext('vacation.redirect'), 'redirect'); | |
377 if (in_array('copy', $this->exts)) { | |
378 $action->add($this->plugin->gettext('vacation.copy'), 'copy'); | |
379 } | |
380 | |
381 if ($this->rc->config->get('managesieve_vacation') != 2 && count($this->vacation['list'])) { | |
382 $after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after')); | |
383 | |
384 $after->add('', ''); | |
385 foreach ($this->vacation['list'] as $idx => $rule) { | |
386 $after->add($rule, $idx); | |
387 } | |
388 } | |
389 | |
390 $interval_txt = $interval->show(self::vacation_interval($this->vacation)); | |
391 if ($seconds_extension) { | |
392 $interval_select = new html_select(array('name' => 'vacation_interval_type')); | |
393 $interval_select->add($this->plugin->gettext('days'), 'days'); | |
394 $interval_select->add($this->plugin->gettext('seconds'), 'seconds'); | |
395 $interval_txt .= ' ' . $interval_select->show(isset($this->vacation['seconds']) ? 'seconds' : 'days'); | |
396 } | |
397 else { | |
398 $interval_txt .= ' ' . $this->plugin->gettext('days'); | |
399 } | |
400 | |
401 if ($date_extension || $regex_extension) { | |
402 $date_from = new html_inputfield(array('name' => 'vacation_datefrom', 'id' => 'vacation_datefrom', 'class' => 'datepicker', 'size' => 12)); | |
403 $date_to = new html_inputfield(array('name' => 'vacation_dateto', 'id' => 'vacation_dateto', 'class' => 'datepicker', 'size' => 12)); | |
404 $date_format = $this->rc->config->get('date_format', 'Y-m-d'); | |
405 } | |
406 | |
407 if ($date_extension) { | |
408 $time_from = new html_inputfield(array('name' => 'vacation_timefrom', 'id' => 'vacation_timefrom', 'size' => 7)); | |
409 $time_to = new html_inputfield(array('name' => 'vacation_timeto', 'id' => 'vacation_timeto', 'size' => 7)); | |
410 $time_format = $this->rc->config->get('time_format', 'H:i'); | |
411 $date_value = array(); | |
412 | |
413 foreach ((array) $this->vacation['tests'] as $test) { | |
414 if ($test['test'] == 'currentdate') { | |
415 $idx = $test['type'] == 'value-ge' ? 'from' : 'to'; | |
416 | |
417 if ($test['part'] == 'date') { | |
418 $date_value[$idx]['date'] = $test['arg']; | |
419 } | |
420 else if ($test['part'] == 'iso8601') { | |
421 $date_value[$idx]['datetime'] = $test['arg']; | |
422 } | |
423 } | |
424 } | |
425 | |
426 foreach ($date_value as $idx => $value) { | |
427 $date = $value['datetime'] ?: $value['date']; | |
428 $date_value[$idx] = $this->rc->format_date($date, $date_format, false); | |
429 | |
430 if (!empty($value['datetime'])) { | |
431 $date_value['time_' . $idx] = $this->rc->format_date($date, $time_format, true); | |
432 } | |
433 } | |
434 } | |
435 else if ($regex_extension) { | |
436 // Sieve 'date' extension not available, read start/end from RegEx based rules instead | |
437 if ($date_tests = self::parse_regexp_tests($this->vacation['tests'])) { | |
438 $date_value['from'] = $this->rc->format_date($date_tests['from'], $date_format, false); | |
439 $date_value['to'] = $this->rc->format_date($date_tests['to'], $date_format, false); | |
440 } | |
441 } | |
442 | |
443 // force domain selection in redirect email input | |
444 $domains = (array) $this->rc->config->get('managesieve_domains'); | |
445 $redirect = $this->vacation['action'] == 'redirect' || $this->vacation['action'] == 'copy'; | |
446 | |
447 if (!empty($domains)) { | |
448 sort($domains); | |
449 | |
450 $domain_select = new html_select(array('name' => 'action_domain', 'id' => 'action_domain')); | |
451 $domain_select->add(array_combine($domains, $domains)); | |
452 | |
453 if ($redirect && $this->vacation['target']) { | |
454 $parts = explode('@', $this->vacation['target']); | |
455 if (!empty($parts)) { | |
456 $this->vacation['domain'] = array_pop($parts); | |
457 $this->vacation['target'] = implode('@', $parts); | |
458 } | |
459 } | |
460 } | |
461 | |
462 // redirect target | |
463 $action_target = ' <span id="action_target_span" style="display:' . ($redirect ? 'inline' : 'none') . '">' | |
464 . '<input type="text" name="action_target" id="action_target"' | |
465 . ' value="' .($redirect ? rcube::Q($this->vacation['target'], 'strict', false) : '') . '"' | |
466 . (!empty($domains) ? ' size="20"' : ' size="35"') . '/>' | |
467 . (!empty($domains) ? ' @ ' . $domain_select->show($this->vacation['domain']) : '') | |
468 . '</span>'; | |
469 | |
470 // Message tab | |
471 $table = new html_table(array('cols' => 2)); | |
472 | |
473 $table->add('title', html::label('vacation_subject', $this->plugin->gettext('vacation.subject'))); | |
474 $table->add(null, $subject->show($this->vacation['subject'])); | |
475 $table->add('title', html::label('vacation_reason', $this->plugin->gettext('vacation.body'))); | |
476 $table->add(null, $reason->show($this->vacation['reason'])); | |
477 | |
478 if ($date_extension || $regex_extension) { | |
479 $table->add('title', html::label('vacation_datefrom', $this->plugin->gettext('vacation.start'))); | |
480 $table->add(null, $date_from->show($date_value['from']) . ($time_from ? ' ' . $time_from->show($date_value['time_from']) : '')); | |
481 $table->add('title', html::label('vacation_dateto', $this->plugin->gettext('vacation.end'))); | |
482 $table->add(null, $date_to->show($date_value['to']) . ($time_to ? ' ' . $time_to->show($date_value['time_to']) : '')); | |
483 } | |
484 | |
485 $table->add('title', html::label('vacation_status', $this->plugin->gettext('vacation.status'))); | |
486 $table->add(null, $status->show(!isset($this->vacation['disabled']) || $this->vacation['disabled'] ? 'off' : 'on')); | |
487 | |
488 $out .= html::tag('fieldset', $class, html::tag('legend', null, $this->plugin->gettext('vacation.reply')) . $table->show($attrib)); | |
489 | |
490 // Advanced tab | |
491 $table = new html_table(array('cols' => 2)); | |
492 | |
493 $table->add('title', html::label('vacation_from', $this->plugin->gettext('vacation.from'))); | |
494 $table->add(null, $from->show($this->vacation['from'])); | |
495 $table->add('title', html::label('vacation_addresses', $this->plugin->gettext('vacation.addresses'))); | |
496 $table->add(null, $addresses . $addresses_link->show($this->plugin->gettext('filladdresses'))); | |
497 $table->add('title', html::label('vacation_interval', $this->plugin->gettext('vacation.interval'))); | |
498 $table->add(null, $interval_txt); | |
499 | |
500 if ($after) { | |
501 $table->add('title', html::label('vacation_after', $this->plugin->gettext('vacation.after'))); | |
502 $table->add(null, $after->show($this->vacation['idx'] - 1)); | |
503 } | |
504 | |
505 $table->add('title', html::label('vacation_action', $this->plugin->gettext('vacation.action'))); | |
506 $table->add('vacation', $action->show($this->vacation['action']) . $action_target); | |
507 | |
508 $out .= html::tag('fieldset', $class, html::tag('legend', null, $this->plugin->gettext('vacation.advanced')) . $table->show($attrib)); | |
509 | |
510 $out .= '</form>'; | |
511 | |
512 $this->rc->output->add_gui_object('sieveform', $form_id); | |
513 | |
514 if ($time_format) { | |
515 $this->rc->output->set_env('time_format', $time_format); | |
516 } | |
517 | |
518 return $out; | |
519 } | |
520 | |
521 public static function build_regexp_tests($date_from, $date_to, &$error) | |
522 { | |
523 $tests = array(); | |
524 $dt_from = rcube_utils::anytodatetime($date_from); | |
525 $dt_to = rcube_utils::anytodatetime($date_to); | |
526 $interval = $dt_from->diff($dt_to); | |
527 | |
528 if ($interval->invert || $interval->days > 365) { | |
529 $error = 'managesieve.invaliddateformat'; | |
530 return; | |
531 } | |
532 | |
533 $dt_i = $dt_from; | |
534 $interval = new DateInterval('P1D'); | |
535 $matchexp = ''; | |
536 | |
537 while (!$dt_i->diff($dt_to)->invert) { | |
538 $days = (int) $dt_i->format('d'); | |
539 $matchexp .= $days < 10 ? "[ 0]$days" : $days; | |
540 | |
541 if ($days == $dt_i->format('t') || $dt_i->diff($dt_to)->days == 0) { | |
542 $test = array( | |
543 'test' => 'header', | |
544 'type' => 'regex', | |
545 'arg1' => 'received', | |
546 'arg2' => '('.$matchexp.') '.$dt_i->format('M Y') | |
547 ); | |
548 | |
549 $tests[] = $test; | |
550 $matchexp = ''; | |
551 } | |
552 else { | |
553 $matchexp .= '|'; | |
554 } | |
555 | |
556 $dt_i->add($interval); | |
557 } | |
558 | |
559 return $tests; | |
560 } | |
561 | |
562 public static function parse_regexp_tests($tests) | |
563 { | |
564 $rx_from = '/^\(([0-9]{2}).*\)\s([A-Za-z]+)\s([0-9]{4})/'; | |
565 $rx_to = '/^\(.*([0-9]{2})\)\s([A-Za-z]+)\s([0-9]{4})/'; | |
566 $result = array(); | |
567 | |
568 foreach ((array) $tests as $test) { | |
569 if ($test['test'] == 'header' && $test['type'] == 'regex' && $test['arg1'] == 'received') { | |
570 $textexp = preg_replace('/\[ ([^\]]*)\]/', '0', $test['arg2']); | |
571 | |
572 if (!$result['from'] && preg_match($rx_from, $textexp, $matches)) { | |
573 $result['from'] = $matches[1]." ".$matches[2]." ".$matches[3]; | |
574 } | |
575 | |
576 if (preg_match($rx_to, $textexp, $matches)) { | |
577 $result['to'] = $matches[1]." ".$matches[2]." ".$matches[3]; | |
578 } | |
579 } | |
580 } | |
581 | |
582 return $result; | |
583 } | |
584 | |
585 /** | |
586 * Get current vacation interval | |
587 */ | |
588 public static function vacation_interval(&$vacation) | |
589 { | |
590 $rcube = rcube::get_instance(); | |
591 | |
592 if (isset($vacation['seconds'])) { | |
593 $interval = $vacation['seconds']; | |
594 } | |
595 else if (isset($vacation['days'])) { | |
596 $interval = $vacation['days']; | |
597 } | |
598 else if ($interval_cfg = $rcube->config->get('managesieve_vacation_interval')) { | |
599 if (preg_match('/^([0-9]+)s$/', $interval_cfg, $m)) { | |
600 if ($seconds_extension) { | |
601 $vacation['seconds'] = ($interval = intval($m[1])) ? $interval : null; | |
602 } | |
603 else { | |
604 $vacation['days'] = $interval = ceil(intval($m[1])/86400); | |
605 } | |
606 } | |
607 else { | |
608 $vacation['days'] = $interval = intval($interval_cfg); | |
609 } | |
610 } | |
611 | |
612 return $interval ?: ''; | |
613 } | |
614 | |
615 /** | |
616 * Saves vacation script (adding some variables) | |
617 */ | |
618 protected function save_vacation_script($rule) | |
619 { | |
620 // if script does not exist create a new one | |
621 if ($this->script_name === null || $this->script_name === false) { | |
622 $this->script_name = $this->rc->config->get('managesieve_script_name'); | |
623 if (empty($this->script_name)) { | |
624 $this->script_name = 'roundcube'; | |
625 } | |
626 | |
627 // use default script contents | |
628 if (!$this->rc->config->get('managesieve_kolab_master')) { | |
629 $script_file = $this->rc->config->get('managesieve_default'); | |
630 if ($script_file && is_readable($script_file)) { | |
631 $content = file_get_contents($script_file); | |
632 } | |
633 } | |
634 | |
635 // create and load script | |
636 if ($this->sieve->save_script($this->script_name, $content)) { | |
637 $this->sieve->load($this->script_name); | |
638 } | |
639 } | |
640 | |
641 $script_active = in_array($this->script_name, $this->active); | |
642 | |
643 // re-order rules if needed | |
644 if (isset($rule['after']) && $rule['after'] !== '') { | |
645 // reset original vacation rule | |
646 if (isset($this->vacation['idx'])) { | |
647 $this->script[$this->vacation['idx']] = null; | |
648 } | |
649 | |
650 // add at target position | |
651 if ($rule['after'] >= count($this->script) - 1) { | |
652 $this->script[] = $rule; | |
653 } | |
654 else { | |
655 $script = array(); | |
656 | |
657 foreach ($this->script as $idx => $r) { | |
658 if ($r) { | |
659 $script[] = $r; | |
660 } | |
661 | |
662 if ($idx == $rule['after']) { | |
663 $script[] = $rule; | |
664 } | |
665 } | |
666 | |
667 $this->script = $script; | |
668 } | |
669 | |
670 $this->script = array_values(array_filter($this->script)); | |
671 } | |
672 // update original vacation rule if it exists | |
673 else if (isset($this->vacation['idx'])) { | |
674 $this->script[$this->vacation['idx']] = $rule; | |
675 } | |
676 // otherwise put vacation rule on top | |
677 else { | |
678 array_unshift($this->script, $rule); | |
679 } | |
680 | |
681 // if the script was not active, we need to de-activate | |
682 // all rules except the vacation rule, but only if it is not disabled | |
683 if (!$script_active && !$rule['disabled']) { | |
684 foreach ($this->script as $idx => $r) { | |
685 if (empty($r['actions']) || $r['actions'][0]['type'] != 'vacation') { | |
686 $this->script[$idx]['disabled'] = true; | |
687 } | |
688 } | |
689 } | |
690 | |
691 if (!$this->sieve->script) { | |
692 return false; | |
693 } | |
694 | |
695 $this->sieve->script->content = $this->script; | |
696 | |
697 // save the script | |
698 $saved = $this->save_script($this->script_name); | |
699 | |
700 // activate the script | |
701 if ($saved && !$script_active && !$rule['disabled']) { | |
702 $this->activate_script($this->script_name); | |
703 } | |
704 | |
705 return $saved; | |
706 } | |
707 | |
708 /** | |
709 * API: get vacation rule | |
710 * | |
711 * @return array Vacation rule information | |
712 */ | |
713 public function get_vacation() | |
714 { | |
715 $this->exts = $this->sieve->get_extensions(); | |
716 $this->init_script(); | |
717 $this->vacation_rule(); | |
718 | |
719 // check supported extensions | |
720 $date_extension = in_array('date', $this->exts); | |
721 $regex_extension = in_array('regex', $this->exts); | |
722 $seconds_extension = in_array('vacation-seconds', $this->exts); | |
723 | |
724 // set user's timezone | |
725 try { | |
726 $timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT')); | |
727 } | |
728 catch (Exception $e) { | |
729 $timezone = new DateTimeZone('GMT'); | |
730 } | |
731 | |
732 if ($date_extension) { | |
733 $date_value = array(); | |
734 foreach ((array) $this->vacation['tests'] as $test) { | |
735 if ($test['test'] == 'currentdate') { | |
736 $idx = $test['type'] == 'value-ge' ? 'start' : 'end'; | |
737 | |
738 if ($test['part'] == 'date') { | |
739 $date_value[$idx]['date'] = $test['arg']; | |
740 } | |
741 else if ($test['part'] == 'iso8601') { | |
742 $date_value[$idx]['datetime'] = $test['arg']; | |
743 } | |
744 } | |
745 } | |
746 | |
747 foreach ($date_value as $idx => $value) { | |
748 $$idx = new DateTime($value['datetime'] ?: $value['date'], $timezone); | |
749 } | |
750 } | |
751 else if ($regex_extension) { | |
752 // Sieve 'date' extension not available, read start/end from RegEx based rules instead | |
753 if ($date_tests = self::parse_regexp_tests($this->vacation['tests'])) { | |
754 $from = new DateTime($date_tests['from'] . ' ' . '00:00:00', $timezone); | |
755 $to = new DateTime($date_tests['to'] . ' ' . '23:59:59', $timezone); | |
756 } | |
757 } | |
758 | |
759 if (isset($this->vacation['seconds'])) { | |
760 $interval = $this->vacation['seconds'] . 's'; | |
761 } | |
762 else if (isset($this->vacation['days'])) { | |
763 $interval = $this->vacation['days'] . 'd'; | |
764 } | |
765 | |
766 $vacation = array( | |
767 'supported' => $this->exts, | |
768 'interval' => $interval, | |
769 'start' => $start, | |
770 'end' => $end, | |
771 'enabled' => $this->vacation['reason'] && empty($this->vacation['disabled']), | |
772 'message' => $this->vacation['reason'], | |
773 'subject' => $this->vacation['subject'], | |
774 'action' => $this->vacation['action'], | |
775 'target' => $this->vacation['target'], | |
776 'addresses' => $this->vacation['addresses'], | |
777 'from' => $this->vacation['from'], | |
778 ); | |
779 | |
780 return $vacation; | |
781 } | |
782 | |
783 /** | |
784 * API: set vacation rule | |
785 * | |
786 * @param array $vacation Vacation rule information (see self::get_vacation()) | |
787 * | |
788 * @return bool True on success, False on failure | |
789 */ | |
790 public function set_vacation($data) | |
791 { | |
792 $this->exts = $this->sieve->get_extensions(); | |
793 $this->error = false; | |
794 | |
795 $this->init_script(); | |
796 $this->vacation_rule(); | |
797 | |
798 // check supported extensions | |
799 $date_extension = in_array('date', $this->exts); | |
800 $regex_extension = in_array('regex', $this->exts); | |
801 $seconds_extension = in_array('vacation-seconds', $this->exts); | |
802 | |
803 $vacation['type'] = 'vacation'; | |
804 $vacation['reason'] = $this->strip_value(str_replace("\r\n", "\n", $data['message'])); | |
805 $vacation['addresses'] = $data['addresses']; | |
806 $vacation['subject'] = trim($data['subject']); | |
807 $vacation['from'] = trim($data['from']); | |
808 $vacation_tests = (array) $this->vacation['tests']; | |
809 | |
810 foreach ((array) $vacation['addresses'] as $aidx => $address) { | |
811 $vacation['addresses'][$aidx] = $address = trim($address); | |
812 | |
813 if (empty($address)) { | |
814 unset($vacation['addresses'][$aidx]); | |
815 } | |
816 else if (!rcube_utils::check_email($address)) { | |
817 $this->error = "Invalid address in vacation addresses: $address"; | |
818 return false; | |
819 } | |
820 } | |
821 | |
822 if (!empty($vacation['from']) && !rcube_utils::check_email($vacation['from'])) { | |
823 $this->error = "Invalid address in 'from': " . $vacation['from']; | |
824 return false; | |
825 } | |
826 | |
827 if ($vacation['reason'] == '') { | |
828 $this->error = "No vacation message specified"; | |
829 return false; | |
830 } | |
831 | |
832 if ($data['interval']) { | |
833 if (!preg_match('/^([0-9]+)\s*([sd])$/', $data['interval'], $m)) { | |
834 $this->error = "Invalid vacation interval value: " . $data['interval']; | |
835 return false; | |
836 } | |
837 else if ($m[1]) { | |
838 $vacation[strtolower($m[2]) == 's' ? 'seconds' : 'days'] = $m[1]; | |
839 } | |
840 } | |
841 | |
842 // find and remove existing date/regex/true rules | |
843 foreach ((array) $vacation_tests as $idx => $t) { | |
844 if ($t['test'] == 'currentdate' || $t['test'] == 'true' | |
845 || ($t['test'] == 'header' && $t['type'] == 'regex' && $t['arg1'] == 'received') | |
846 ) { | |
847 unset($vacation_tests[$idx]); | |
848 } | |
849 } | |
850 | |
851 if ($date_extension) { | |
852 foreach (array('start', 'end') as $var) { | |
853 if ($dt = $data[$var]) { | |
854 $vacation_tests[] = array( | |
855 'test' => 'currentdate', | |
856 'part' => 'iso8601', | |
857 'type' => 'value-' . ($var == 'start' ? 'ge' : 'le'), | |
858 'zone' => $dt->format('O'), | |
859 'arg' => str_replace('+00:00', 'Z', strtoupper($dt->format('c'))), | |
860 ); | |
861 } | |
862 } | |
863 } | |
864 else if ($regex_extension) { | |
865 // Add date range rules if range specified | |
866 if ($data['start'] && $data['end']) { | |
867 if ($tests = self::build_regexp_tests($data['start'], $data['end'], $error)) { | |
868 $vacation_tests = array_merge($vacation_tests, $tests); | |
869 } | |
870 | |
871 if ($error) { | |
872 $this->error = "Invalid dates specified or unsupported period length"; | |
873 return false; | |
874 } | |
875 } | |
876 } | |
877 | |
878 if ($data['action'] == 'redirect' || $data['action'] == 'copy') { | |
879 if (empty($data['target']) || !rcube_utils::check_email($data['target'])) { | |
880 $this->error = "Invalid address in action taget: " . $data['target']; | |
881 return false; | |
882 } | |
883 } | |
884 else if ($data['action'] && $data['action'] != 'keep' && $data['action'] != 'discard') { | |
885 $this->error = "Unsupported vacation action: " . $data['action']; | |
886 return false; | |
887 } | |
888 | |
889 if (empty($vacation_tests)) { | |
890 $vacation_tests = $this->rc->config->get('managesieve_vacation_test', array(array('test' => 'true'))); | |
891 } | |
892 | |
893 $rule = $this->vacation; | |
894 $rule['type'] = 'if'; | |
895 $rule['name'] = $rule['name'] ?: 'Out-of-Office'; | |
896 $rule['disabled'] = isset($data['enabled']) && !$data['enabled']; | |
897 $rule['tests'] = $vacation_tests; | |
898 $rule['join'] = $date_extension ? count($vacation_tests) > 1 : false; | |
899 $rule['actions'] = array($vacation); | |
900 | |
901 if ($data['action'] && $data['action'] != 'keep') { | |
902 $rule['actions'][] = array( | |
903 'type' => $data['action'] == 'discard' ? 'discard' : 'redirect', | |
904 'copy' => $data['action'] == 'copy', | |
905 'target' => $data['action'] != 'discard' ? $data['target'] : '', | |
906 ); | |
907 } | |
908 | |
909 return $this->save_vacation_script($rule); | |
910 } | |
911 | |
912 /** | |
913 * API: connect to managesieve server | |
914 */ | |
915 public function connect($username, $password) | |
916 { | |
917 $error = parent::connect($username, $password); | |
918 | |
919 if ($error) { | |
920 return $error; | |
921 } | |
922 | |
923 return $this->load_script(); | |
924 } | |
925 | |
926 /** | |
927 * API: Returns last error | |
928 * | |
929 * @return string Error message | |
930 */ | |
931 public function get_error() | |
932 { | |
933 return $this->error; | |
934 } | |
935 } |