1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8/**
9 * Tiki groupmail modules
10 * @package modules
11 * @subpackage tiki
12 */
13
14if (!defined('DEBUG_MODE')) { die(); }
15
16/**
17 * Load IMAP servers for message list page
18 * @subpackage tiki/handler
19 */
20class Hm_Handler_load_data_sources extends Hm_Handler_Module {
21    /**
22     * Used by groupmail view
23     */
24    public function process() {
25        $callback = 'tiki_groupmail_content';
26        // TODO: check IMAP dependency and POP3 support
27        foreach (imap_data_sources($callback, $this->user_config->get('custom_imap_sources', array())) as $vals) {
28            $this->append('data_sources', $vals);
29        }
30    }
31}
32
33/**
34 * Fetch messages for the Groupmail page
35 * @subpackage tiki/handler
36 */
37class Hm_Handler_groupmail_fetch_messages extends Hm_Handler_Module {
38    /**
39     * Returns all messages for an IMAP server
40     */
41    public function process() {
42        list($success, $form) = $this->process_form(array('imap_server_ids'));
43        if ($success) {
44            $limit = $this->user_config->get('all_email_per_source_setting', DEFAULT_PER_SOURCE);
45            $ids = explode(',', $form['imap_server_ids']);
46            $folder = bin2hex('INBOX');
47            if (array_key_exists('folder', $this->request->post)) {
48                $folder = $this->request->post['folder'];
49            }
50            list($status, $msg_list) = merge_imap_search_results($ids, 'ALL', $this->session, $this->config, array(hex2bin($folder)), $limit);
51            $this->out('folder_status', $status);
52            $this->out('groupmail_inbox_data', $msg_list);
53            $this->out('imap_server_ids', $form['imap_server_ids']);
54        }
55    }
56}
57
58/**
59 * Check whether Groupmail is enabled or not
60 * @subpackage tiki/handler
61 */
62class Hm_Handler_check_groupmail_setting extends Hm_Handler_Module {
63    /**
64     * Sets flag based on session
65     */
66    public function process() {
67        $this->out('groupmail_enabled', $this->session->get('groupmail') == 'y');
68    }
69}
70
71/**
72 * Prepare Groupmail session settings for output modules
73 * @subpackage tiki/handler
74 */
75class Hm_Handler_prepare_groupmail_settings extends Hm_Handler_Module {
76    /**
77     * Sets settings based on session
78     */
79    public function process() {
80        foreach(['group', 'trackerId', 'fromFId', 'subjectFId', 'messageFId', 'contentFId', 'accountFId', 'datetimeFId', 'operatorFId'] as $field) {
81            $this->out($field, $this->session->get($field));
82        }
83    }
84}
85
86/**
87 * Take a groupmail message
88 * @subpackage tiki/handler
89 */
90class Hm_Handler_take_groupmail extends Hm_Handler_Module {
91    /**
92     * Take a message
93     */
94    public function process() {
95        list($success, $form) = $this->process_form(array('msgid', 'imap_msg_uid', 'imap_server_id', 'folder'));
96        if (! $success) {
97            return;
98        }
99
100        $cache = Hm_IMAP_List::get_cache($this->cache, $form['imap_server_id']);
101        $imap = Hm_IMAP_List::connect($form['imap_server_id'], $cache);
102        if (! imap_authed($imap)) {
103            return;
104        }
105
106        $imap->read_only = $prefetch;
107        if (! $imap->select_mailbox(hex2bin($form['folder']))) {
108            return;
109        }
110
111        $msg_struct = $imap->get_message_structure($form['imap_msg_uid']);
112        if (!$this->user_config->get('text_only_setting', false)) {
113            list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', 'html', $msg_struct);
114            if (!$part) {
115                list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', false, $msg_struct);
116            }
117        }
118        else {
119            list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', false, $msg_struct);
120        }
121
122        $struct = $imap->search_bodystructure( $msg_struct, array('imap_part_number' => $part));
123        $msg_struct_current = array_shift($struct);
124        if (!trim($msg_text)) {
125            if (is_array($msg_struct_current) && array_key_exists('subtype', $msg_struct_current)) {
126                if ($msg_struct_current['subtype'] == 'plain') {
127                    $subtype = 'html';
128                }
129                else {
130                    $subtype = 'plain';
131                }
132                list($part, $msg_text) = $imap->get_first_message_part($form['imap_msg_uid'], 'text', $subtype, $msg_struct);
133                $struct = $imap->search_bodystructure($msg_struct, array('imap_part_number' => $part));
134                $msg_struct_current = array_shift($struct);
135            }
136        }
137        if (isset($msg_struct_current['subtype']) && strtolower($msg_struct_current['subtype'] == 'html')) {
138            $msg_text = add_attached_images($msg_text, $form['imap_msg_uid'], $msg_struct, $imap);
139        }
140        $msg_headers = $imap->get_message_headers($form['imap_msg_uid']);
141
142        global $prefs, $user;
143
144        $contactlib = TikiLib::lib('contact');
145        $categlib = TikiLib::lib('categ');
146        $tikilib = TikiLib::lib('tiki');
147        $trklib = TikiLib::lib('trk');
148
149        // make tracker item
150        $from       = $msg_headers['From'];
151        $subject    = $msg_headers['Subject'];
152        $realmsgid  = $form['msgid'];
153        $maildate   = $msg_headers['Date'];
154        $maildate   = strtotime($maildate);
155
156        $parsed_from = preg_split('/[<>]/', $from, -1, PREG_SPLIT_NO_EMPTY);
157        $sender = ['name' => $parsed_from[0], 'email' => $parsed_from[1]];
158
159        // check if already taken
160        $itemid = $trklib->get_item_id($this->get('trackerId'), $this->get('messageFId'), $realmsgid);
161        if ($itemid > 0) {
162            Hm_Msgs::add('ERR'.tr('Sorry, that mail has been taken by another operator.'));
163            return;
164        } else {
165            $charset = $prefs['default_mail_charset'];
166            if (empty($charset)) {
167                $charset = 'UTF-8';
168            }
169
170            $items['data'][0]['fieldId'] = $this->get('fromFId');
171            $items['data'][0]['type'] = 't';
172            $items['data'][0]['value'] = $from;
173            $items['data'][1]['fieldId'] = $this->get('operatorFId');
174            $items['data'][1]['type'] = 'u';
175            $items['data'][1]['value'] = $user;
176            $items['data'][2]['fieldId'] = $this->get('subjectFId');
177            $items['data'][2]['type'] = 't';
178            $items['data'][2]['value'] = $subject;
179            $items['data'][3]['fieldId'] = $this->get('messageFId');
180            $items['data'][3]['type'] = 't';
181            $items['data'][3]['value'] = $realmsgid;
182            $items['data'][4]['fieldId'] = $this->get('contentFId');
183            $items['data'][4]['type'] = 'a';
184            $items['data'][4]['value'] = htmlentities($msg_text, ENT_QUOTES, $charset);
185            $items['data'][5]['fieldId'] = $this->get('accountFId');
186            $items['data'][5]['type'] = 't';
187            $items['data'][5]['value'] = $form['imap_server_id'];
188            $items['data'][6]['fieldId'] = $this->get('datetimeFId');
189            $items['data'][6]['type'] = 'f';    // f?
190            $items['data'][6]['value'] = $maildate;
191            $trklib->replace_item($this->get('trackerId'), 0, $items);
192        }
193
194        // make name for wiki page
195        $pageName = str_replace('@', '_AT_', $sender['email']);
196        $contId = $contactlib->get_contactId_email($sender['email'], $user);
197
198        // add or update (?) contact
199        $ext = $contactlib->get_ext_by_name($user, tra('Wiki Page'), $contId);
200        if (! $ext) {
201            $contactlib->add_ext($user, tra('Wiki Page'), true);    // a public field
202            $ext = $contactlib->get_ext_by_name($user, tra('Wiki Page'), $contId);
203        }
204
205        $arr = explode(" ", trim(html_entity_decode($sender['name']), '"\' '), 2);
206        if (count($arr) < 2) {
207            $arr[] = '';
208        }
209        $contactlib->replace_contact($contId, $arr[0], $arr[1], $sender['email'], '', $user, [$this->get('group')], [$ext['fieldId'] => $pageName], true);
210        if (! $contId) {
211            $contId = $contactlib->get_contactId_email($sender['email'], $user);
212        }
213
214        // make or update wiki page
215        $wikilib = TikiLib::lib('wiki');
216
217        if (! $wikilib->page_exists($pageName)) {
218            $comment = 'Generated by GroupMail on ' . date(DATE_RFC822);
219            $description = "Page $comment for " . $sender['email'];
220            $data = '!GroupMail case with ' . $sender['email'] . "\n";
221            $data .= "''$comment''\n\n";
222            $data .= "!!Info\n";
223            $data .= "Contact info: [tiki-contacts.php?contactId=$contId|" . $sender['name'] . "]\n\n";
224            $data .= "!!Logs\n";
225            $data .= '{trackerlist trackerId="' . $this->get('trackerId') . '" ' . 'fields="' . $this->get('fromFId') . ':' . $this->get('operatorFId') . ':' . $this->get('subjectFId') . ':' . $this->get('datetimeFId') . '" ' . 'popup="' . $this->get('fromFId') . ':' . $this->get('contentFId') . '" stickypopup="n" showlinks="y" shownbitems="n" showinitials="n"' . 'showstatus="n" showcreated="n" showlastmodif="n" filterfield="' . $this->get('fromFId') . '" filtervalue="' . $sender['email'] . '"}';
226            $data .= "\n\n";
227
228            $tikilib->create_page($pageName, 0, $data, $tikilib->now, $comment, $user, $tikilib->get_ip_address(), $description);
229            $categlib->update_object_categories([$categlib->get_category_id('Help Team Pages')], $pageName, 'wiki page');       // TODO remove hard-coded cat name
230        }
231
232        $this->out('operator', $user);
233    }
234}
235
236/**
237 * Put back a groupmail message
238 * @subpackage tiki/handler
239 */
240class Hm_Handler_put_back_groupmail extends Hm_Handler_Module {
241    /**
242     * Put back a message
243     */
244    public function process() {
245        list($success, $form) = $this->process_form(array('msgid', 'imap_msg_uid', 'imap_server_id', 'folder'));
246        if (! $success) {
247            return;
248        }
249
250        global $user;
251
252        $trklib = TikiLib::lib('trk');
253
254        $itemid = $trklib->get_item_id($this->get('trackerId'), $this->get('messageFId'), $form['msgid']);
255        if ($itemid > 0 && $user == $trklib->get_item_value($this->get('trackerId'), $itemid, $this->get('operatorFId'))) { // simple security check
256            $trklib->remove_tracker_item($itemid);
257            $this->out('item_removed', true);
258        } else {
259            Hm_Msgs::add('ERR'.tr('Tracker item not found!'));
260        }
261    }
262}
263
264/**
265 * Output the Tiki Groupmail section of the menu
266 * @subpackage tiki/output
267 */
268class Hm_Output_groupmail_page_link extends Hm_Output_Module {
269    /**
270     * Displays the menu link
271     */
272    protected function output() {
273        if (! $this->get('groupmail_enabled')) {
274            return '';
275        }
276        $res = '<li class="menu_groupmail"><a class="unread_link" href="?page=groupmail">';
277        if (!$this->get('hide_folder_icons')) {
278            $res .= '<img class="account_icon" src="'.$this->html_safe(Hm_Image_Sources::$people).'" alt="" width="16" height="16" /> ';
279        }
280        $res .= $this->trans('Groupmail').'</a></li>';
281        if ($this->format == 'HTML5') {
282            return $res;
283        }
284        $this->concat('formatted_folder_list', $res);
285    }
286}
287
288/**
289 * Output the heading for the groupmail page
290 * @subpackage tiki/output
291 */
292class Hm_Output_groupmail_heading extends Hm_Output_Module {
293    /**
294     * Title and message controls
295     */
296    protected function output() {
297        $source_link = '<a href="#" title="'.$this->trans('Sources').'" class="source_link"><img alt="Sources" class="refresh_list" src="'.Hm_Image_Sources::$folder.'" width="20" height="20" /></a>';
298        $refresh_link = '<a class="refresh_link" title="'.$this->trans('Refresh').'" href="#"><img alt="Refresh" class="refresh_list" src="'.Hm_Image_Sources::$refresh.'" width="20" height="20" /></a>';
299
300        $res = '';
301        $res .= '<div class="groupmail"><div class="content_title">';
302        $res .= '<div class="mailbox_list_title">'.$this->trans('Groupmail').'</div>';
303        $res .= '<div class="list_controls">'.$refresh_link.$source_link.'</div>';
304        $res .= list_sources($this->get('data_sources', array()), $this);
305        $res .= '</div>';
306        return $res;
307    }
308}
309
310/**
311 * Start the table for the groupmail page
312 * @subpackage tiki/output
313 */
314class Hm_Output_groupmail_start extends Hm_Output_Module {
315    /**
316     * Uses the message_list_fields input to determine the format.
317     */
318    protected function output() {
319        $res = '<table class="message_table groupmail">';
320        $res .= '<colgroup>
321            <col class="source_col">
322            <col class="from_col">
323            <col class="subject_col">
324            <col class="date_col">
325            <col class="icon_col">
326            <col class="action_col">
327        </colgroup>';
328        $res .= '<thead><tr>
329            <th class="source">Source</th>
330            <th class="from">From</th>
331            <th class="subject">Subject</th>
332            <th class="msg_date">Date</th>
333            <th></th>
334            <th></th>
335        </tr></thead>';
336        $res .= '<tbody class="message_table_body">';
337        return $res;
338    }
339}
340
341/**
342 * End the groupmail table
343 * @subpackage tiki/output
344 */
345class Hm_Output_groupmail_end extends Hm_Output_Module {
346    /**
347     * Close the table opened in Hm_Output_groupmail_start
348     */
349    protected function output() {
350        $res = '</tbody></table><div class="page_links"></div></div>';
351        return $res;
352    }
353}
354
355/**
356 * Format message headers for the Groupmail page
357 * @subpackage tiki/output
358 */
359class Hm_Output_filter_groupmail_data extends Hm_Output_Module {
360    /**
361     * Build ajax response for the Groupmail message list
362     */
363    protected function output() {
364        global $user;
365        $trklib = TikiLib::lib('trk');
366        $contactlib = TikiLib::lib('contact');
367        if ($msg_list = $this->get('groupmail_inbox_data')) {
368            $res = array();
369            if ($msg_list === array(false)) {
370                return $msg_list;
371            }
372            $show_icons = $this->get('msg_list_icons');
373            $list_page = $this->get('list_page', 0);
374            $list_sort = $this->get('list_sort');
375            $list_filter = $this->get('list_filter');
376            foreach($msg_list as $msg) {
377                $row_class = 'email';
378                $icon = 'env_open';
379                $parent_value = sprintf('imap_%d_%s', $msg['server_id'], $msg['folder']);
380                $id = sprintf("imap_%s_%s_%s", $msg['server_id'], $msg['uid'], $msg['folder']);
381                if (!trim($msg['subject'])) {
382                    $msg['subject'] = '[No Subject]';
383                }
384                $subject = $msg['subject'];
385                $from = format_imap_from_fld($msg['from']);
386                $nofrom = '';
387                if (!trim($from)) {
388                    $from = '[No From]';
389                    $nofrom = ' nofrom';
390                }
391                $timestamp = strtotime($msg['internal_date']);
392                $date = translate_time_str(human_readable_interval($msg['internal_date']), $this);
393                $flags = array();
394                if (!stristr($msg['flags'], 'seen')) {
395                    $flags[] = 'unseen';
396                    $row_class .= ' unseen';
397                    if ($icon != 'sent') {
398                        $icon = 'env_closed';
399                    }
400                }
401                if (trim($msg['x_auto_bcc']) === 'cypht') {
402                    $from = preg_replace("/(\<.+\>)/U", '', $msg['to']);
403                    $icon = 'sent';
404                }
405                foreach (array('attachment', 'deleted', 'flagged', 'answered') as $flag) {
406                    if (stristr($msg['flags'], $flag)) {
407                        $flags[] = $flag;
408                    }
409                }
410                $source = $msg['server_name'];
411                $row_class .= ' '.str_replace(' ', '_', $source);
412                if ($msg['folder'] && hex2bin($msg['folder']) != 'INBOX') {
413                    $source .= '-'.preg_replace("/^INBOX.{1}/", '', hex2bin($msg['folder']));
414                }
415                $url = '?page=message&uid='.$msg['uid'].'&list_path='.sprintf('imap_%d_%s', $msg['server_id'], $msg['folder']).'&list_parent='.$parent_value;
416                if ($list_page) {
417                    $url .= '&list_page='.$this->html_safe($list_page);
418                }
419                if ($list_sort) {
420                    $url .= '&sort='.$this->html_safe($list_sort);
421                }
422                if ($list_filter) {
423                    $url .= '&filter='.$this->html_safe($list_filter);
424                }
425                if (!$show_icons) {
426                    $icon = false;
427                }
428                // handle take/taken operator here
429                $itemid = $trklib->get_item_id($this->get('trackerId'), $this->get('messageFId'), $id);
430                if ($itemid > 0) {
431                    $operator = $trklib->get_item_value($this->get('trackerId'), $itemid, $this->get('operatorFId'));
432                } else {
433                    $operator = '';
434                }
435                // check if sender is in contacts
436                $from_email = '';
437                foreach (process_address_fld($msg['from']) as $vals) {
438                    if (trim($vals['email'])) {
439                        $from_email = $vals['email'];
440                    }
441                }
442                $contactId = $contactlib->get_contactId_email($from_email, $user);
443                // check if there's a wiki page
444                $ext = $contactlib->get_ext_by_name($user, tra('Wiki Page'), $contactId);
445                if ($ext) {
446                    $wikiPage = $contactlib->get_contact_ext_val($user, $contactId, $ext['fieldId']);
447                } else {
448                    $wikiPage = '';
449                }
450                $res[$id] = message_list_row(array(
451                        array('safe_output_callback', 'source', $source, $icon),
452                        array('sender_callback', 'from'.$nofrom, $from, $operator, $contactId, $wikiPage),
453                        array('subject_callback', $subject, $url, $flags),
454                        array('date_callback', $date, $timestamp),
455                        array('icon_callback', $flags),
456                        array('take_callback', $id, $operator)
457                    ),
458                    $id,
459                    'email',
460                    $this,
461                    $row_class
462                );
463            }
464            $this->out('formatted_message_list', $res);
465        }
466        elseif (!$this->get('formatted_message_list')) {
467            $this->out('formatted_message_list', array());
468        }
469    }
470}
471
472/**
473 * Ajax response for Take operation
474 * @subpackage tiki/output
475 */
476class Hm_Output_take_groupmail_response extends Hm_Output_Module {
477    /**
478     * Send the response
479     */
480    protected function output() {
481        $this->out('operator', $this->get('operator'));
482    }
483}
484
485/**
486 * Ajax response for Put back operation
487 * @subpackage tiki/output
488 */
489class Hm_Output_put_back_groupmail_response extends Hm_Output_Module {
490    /**
491     * Send the response
492     */
493    protected function output() {
494        $this->out('item_removed', $this->get('item_removed'));
495    }
496}
497
498/**
499 * Callback for TAKE button in groupmail list page
500 * @subpackage tiki/functions
501 * @param array $vals data for the cell
502 * @param string $style message list style
503 * @param object $output_mod Hm_Output_Module
504 * @return string
505 */
506if (!hm_exists('take_callback')) {
507function take_callback($vals, $style, $output_mod) {
508    global $user;
509    list($id, $operator) = $vals;
510    if (! empty($operator)) {
511        if ($operator == $user) {
512            $output = sprintf('<a class="btn btn-outline-secondary btn-sm tips mod_webmail_action webmail_taken" title="%s" onclick="tiki_groupmail_put_back(this, \'%s\'); return false;" href="#">%s</a>',
513                tr('Put this item back'),
514                $id,
515                $operator
516            );
517        } else {
518            $output = sprintf('<span class="btn btn-outline-secondary btn-sm tips mod_webmail_action webmail_taken" title="%s">%s</span>&nbsp;',
519                tr('Taken by %0', $operator),
520                $operator
521            );
522        }
523    } else {
524        $output = sprintf(
525            '<a class="btn btn-outline-secondary btn-sm tips mod_webmail_action" title="%s" onclick="tiki_groupmail_take(this, \'%s\'); return false;" href="#">%s</a>',
526            tr('Take this email'),
527            $vals[0],
528            tr('TAKE')
529        );
530    }
531    return sprintf('<td class="action">%s</td>', $output);
532}}
533
534/**
535 * Callback for FROM column in groupmail list page
536 * @subpackage tiki/functions
537 * @param array $vals data for the cell
538 * @param string $style message list style
539 * @param object $output_mod Hm_Output_Module
540 * @return string
541 */
542if (!hm_exists('sender_callback')) {
543function sender_callback($vals, $style, $output_mod) {
544    global $smarty, $tikiroot;
545    $smarty->loadPlugin('smarty_block_self_link');
546    $smarty->loadPlugin('smarty_modifier_sefurl');
547    list($class, $from, $operator, $contactId, $wikiPage) = $vals;
548    if ($contactId > 0) {
549        $output = smarty_block_self_link([
550                '_script' => $tikiroot.'tiki-contacts.php',
551                'contactId' => $contactId,
552                '_icon_name' => 'user',
553                '_width' => 12,
554                '_height' => 12
555            ], tr('View contact'), $smarty).' ';
556        if (! empty($wikiPage)) {
557            $output .= smarty_block_self_link([
558                '_script' => $tikiroot.smarty_modifier_sefurl($wikiPage),
559                '_class' => "mod_webmail_from"
560            ], $from, $smarty);
561        } else {
562            $output .= smarty_block_self_link([
563                '_script' => $tikiroot.'tiki-contacts.php',
564                'contactId' => $contactId,
565                '_class' => "mod_webmail_from"
566            ], $from, $smarty);
567        }
568    } else {
569        $output = '<span class="mod_webmail_from">'.$from.'</span>';
570    }
571    return sprintf('<td class="%s" title="%s">%s</td>', $output_mod->html_safe($class), $output_mod->html_safe($from), $output);
572}}
573