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> ', 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