Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
messaging_service.inc
1 <?php
18 require_once SQ_DATA_PATH.'/private/conf/messaging_service.inc';
19 require_once 'Mail.php';
20 require_once 'Mail/mime.php';
21 
22 define('SQ_MSG_UNREAD' , 'U');
23 define('SQ_MSG_READ' , 'R');
24 define('SQ_MSG_DELETED', 'D');
25 
26 define('SQ_MSG_PRIORITY_VERY_LOW' , 1);
27 define('SQ_MSG_PRIORITY_LOW' , 2);
28 define('SQ_MSG_PRIORITY_NORMAL' , 3);
29 define('SQ_MSG_PRIORITY_HIGH' , 4);
30 define('SQ_MSG_PRIORITY_VERY_HIGH' , 5);
31 
32 
44 {
45 
51  var $_levels = Array(
52  'queues' => Array(),
53  'logs' => Array(),
54  );
55 
56 
62  var $_emails = Array();
63 
73  var $sending_queue = FALSE;
74 
75 
80  function Messaging_Service()
81  {
82  $this->MySource_Object();
83 
84  }//end constructor
85 
86 
97  function getLogTypes($msg_type)
98  {
99 
100  if (!isset($this->_tmp['message_type_logs'][$msg_type])) {
101 
102  $this->_tmp['message_type_logs'][$msg_type] = Array();
103 
104  $msg_type_list = explode('.', $msg_type);
105 
106  foreach (Array('log_to_file', 'log_to_db', 'send_mail') as $log_type) {
107 
108  $def_name_w = 'SQ_MS_'.strtoupper($log_type).'_WHITE_LIST';
109  $def_name_b = 'SQ_MS_'.strtoupper($log_type).'_BLACK_LIST';
110  $types_w = explode("\n", constant($def_name_w));
111  $types_b = explode("\n", constant($def_name_b));
112 
113  if (in_array($msg_type, $types_w)) {
114  // this exact type is white listed
115  $allow_log = TRUE;
116  } else if (in_array($msg_type, $types_b)) {
117  // this exact type is black listed
118  $allow_log = FALSE;
119  } else {
120 
121  // We need to cycle through all the parts for completeness
122  // example of why:
123  // msg_type = asset.attributes.fulllog.scalar
124  // WHITE_LIST: asset.attributes.*
125  // BLACK_LIST: asset.attributes.fulllog.*
126  // If we didn't go through whole list then we would be allowing this log
127  // when we have specifically denied it.
128 
129  $allow_log = FALSE;
130 
131  if (in_array('*', $types_w)) {
132  // all messages of this type are white listed
133  $allow_log = TRUE;
134  }
135 
136  // check the virtual types to see if they clash
137  $check_code = '';
138  foreach ($msg_type_list as $code_part) {
139  if (!empty($check_code)) {
140  $check_code = trim($check_code, '* ');
141  }
142  $check_code .= $code_part.'.*';
143  if (in_array($check_code, $types_w)) {
144  // this virtual type is white listed
145  $allow_log = TRUE;
146 
147  } else if (in_array($check_code, $types_b)) {
148  // this virtual type is explicitly black listed
149  $allow_log = FALSE;
150 
151  }
152 
153  }
154 
155  }//end else
156 
157  // cache this for this script execution
158  $this->_tmp['message_type_logs'][$msg_type][$log_type] = $allow_log;
159 
160  }//end foreach
161 
162  }//end if
163 
164  return $this->_tmp['message_type_logs'][$msg_type];
165 
166  }//end getLogTypes()
167 
168 
169 //-- QUEUES --//
170 
171 
179  function openQueue()
180  {
181  array_push($this->_levels['queues'], Array());
182 
183  }//end openQueue()
184 
185 
193  function closeQueue()
194  {
195  // remove the current levels messages
196  $msgs = array_pop($this->_levels['queues']);
197 
198  // add them to the level above or the main queue
199  if (empty($this->_levels['queues'])) {
200  // this is the last queue
201  return $this->send($msgs);
202  } else {
203  foreach ($msgs as $msg) {
204  array_push($this->_levels['queues'][count($this->_levels['queues']) - 1], $msg);
205  }
206  return TRUE;
207  }
208 
209  }//end closeQueue()
210 
211 
218  function abortQueue()
219  {
220  // remove the current levels messages
221  array_pop($this->_levels['queues']);
222  return TRUE;
223 
224  }//end abortQueue()
225 
226 
237  function enqueueMessage($message)
238  {
239  if (empty($this->_levels['queues'])) {
240  $this->openQueue();
241  }
242  array_push($this->_levels['queues'][count($this->_levels['queues']) - 1], $message);
243 
244  }//end enqueueMessage()
245 
246 
255  function queueContents()
256  {
257  return $this->_levels['queues'][count($this->_levels['queues']) - 1];
258 
259  }//end queueContents()
260 
261 
262 //-- LOGS --//
263 
264 
272  function openLog()
273  {
274  array_push($this->_levels['logs'], Array());
275 
276  }//end openLog()
277 
278 
286  function closeLog()
287  {
288  // remove the current logs messages
289  $msgs = array_pop($this->_levels['logs']);
290 
291  // add them to the level above or the main queue
292  if (empty($this->_levels['logs'])) {
293  // this is the last queue
294  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
295  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
296  foreach ($msgs as $msg) {
297  if (!$msg->send()) {
298  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
299  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
300  return FALSE;
301  }
302  }
303  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
304  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
305  return TRUE;
306 
307  } else {
308  foreach ($msgs as $msg) {
309  array_push($this->_levels['logs'][count($this->_levels['logs']) - 1], $msg);
310  }
311  return TRUE;
312 
313  }//end if
314 
315  }//end closeLog()
316 
317 
324  function abortLog()
325  {
326  // remove the current levels messages
327  array_pop($this->_levels['logs']);
328  return TRUE;
329 
330  }//end abortLog()
331 
332 
343  function logMessage($message)
344  {
345  array_push($this->_levels['logs'][count($this->_levels['logs']) - 1], $message);
346 
347  }//end logMessage()
348 
349 
358  function logContents()
359  {
360  return $this->_levels['logs'][count($this->_levels['logs']) - 1];
361 
362  }//end logContents()
363 
364 
365 //-- MESSAGES --//
366 
367 
382  function newMessage($to=Array(), $type='', $reps=Array())
383  {
384  require_once SQ_INCLUDE_PATH.'/internal_message.inc';
385 
386  $new_message = new Internal_Message();
387  $new_message->sent = ts_iso8601(time());
388  $new_message->from = $GLOBALS['SQ_SYSTEM']->currentUserId();
389  $new_message->to = $to;
390  $new_message->type = $type;
391  $new_message->replacements = $reps;
392  return $new_message;
393 
394  }//end newMessage()
395 
396 
405  function send($msgs)
406  {
407  // lets try and work out if any of the messages to be
408  // sent can actually be amalgamated based on who the message is being
409  // sent to, who it is from, the message type, and the subject line of the message
410  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
411  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
412  $this->sending_queue = TRUE;
413 
414  foreach ($msgs as $msg) {
415  $msg->send();
416  }
417 
418  $this->sending_queue = FALSE;
419  $this->sendQueuedEmails();
420 
421  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
422  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
423 
424  }//end send()
425 
426 
450  function getMessages($userid, $type=NULL, $statii=Array(), $messageids=Array(), $from=NULL, $to=NULL, $get_user_name=NULL, $parameters=Array(), $max_msgs=0, $count_only=FALSE)
451  {
452  $db = DAL::getDb();
453 
454  if (!is_null($get_user_name) && $get_user_name != 'name') {
455  $get_user_name = 'short_name';
456  }
457 
458  if (!$count_only) {
459  $select_clause = 'm.msgid, m.userto, m.userfrom, m.subject, m.body, m.sent, m.priority, m.status, m.type, m.parameters';
460  if (is_string($get_user_name)) {
461  $select_clause .= ',
462  CASE
463  WHEN a.'.$get_user_name.' IS NULL AND m.userfrom = \'0\' THEN '.MatrixDAL::quote(SQ_SYSTEM_SHORT_NAME.' System').'
464  ELSE a.'.$get_user_name.'
465  END as from_name';
466  $select_clause .= ', CASE WHEN a.'.$get_user_name.' IS NULL AND m.userfrom = \'0\' THEN \'root_user\''.
467  ' ELSE a.type_code END as type_code';
468  }
469  } else {
470  $select_clause = 'COUNT(msgid) AS message_count';
471  }
472 
473  $bindVars = array();
474  $sql = 'SELECT ' . $select_clause;
475 
476  $sql .= ' FROM sq_internal_msg m';
477  if (is_null($userid)) {
478  $where = 'm.userto != \'0\'';
479  } else {
480  # See bug 5455 for why this is a bind var and not quoted.
481  # Basically - MatrixDAL::quote and Active directory DN's with backslashes in them
482  # don't play nicely. Changing it to be a bindvar makes them work together.
483  $where = 'm.userto = :userto';
484  $bindVars['userto'] = $userid;
485  }
486 
487  // If we are doing a count only, regardless of whether user_name is a string or not
488  // we don't need to hit the ast table.
489  if (is_string($get_user_name) && $count_only === FALSE) {
490  $sql .= ' LEFT OUTER JOIN '.SQ_TABLE_RUNNING_PREFIX.'ast a ON m.userfrom = a.assetid';
491  $where = $GLOBALS['SQ_SYSTEM']->constructRollbackWhereClause($where, 'a');
492  } else {
493  $where = 'WHERE '.$where;
494  }
495 
496  if (!is_null($type)) {
497  if (substr($type, -2) == '.*') {
498  $where .= ' AND m.type LIKE '.MatrixDAL::quote(substr($type, 0, -1).'%');
499  } else {
500  $where .= ' AND m.type = '.MatrixDAL::quote($type);
501  }
502  }
503  if (!empty($statii)) {
504  $statii_strs = Array();
505  for ($i = 0; $i < count($statii); $i++) {
506  $statii[$i] = '\''.$statii[$i].'\'';
507  }
508  $where .= ' AND m.status IN ('.implode(', ', $statii).')';
509  }
510  if (!empty($messageids)) {
511  for ($i = 0; $i < count($messageids); $i++) {
512  $messageids[$i] = '\''.$messageids[$i].'\'';
513  }
514  $where .= ' AND m.msgid IN ('.implode(', ', $messageids).')';
515  }
516  if (!empty($parameters)) {
517  foreach ($parameters as $param_key => $param_value) {
518  // skip the from and to date params for now
519  if ($param_key == 'from_date' || $param_key == 'to_date') continue;
520  $where .= ' AND m.parameters LIKE '.MatrixDAL::quote('%'.$param_key.':'.$param_value.';%');
521  }
522  if (isset($parameters['assetid']) === TRUE) {
523  $where .= ' AND m.assetid='.MatrixDAL::quote($parameters['assetid']);
524  }
525  // from_date and to_date are to be included in the where clause
526  if (isset($parameters['from_date']) && $parameters['from_date'] != '') {
527  $where .= ' AND sent >= '.MatrixDAL::quote($parameters['from_date']);
528  }
529  if (isset($parameters['to_date']) && $parameters['to_date'] != '') {
530  $where .= ' AND sent <= '.MatrixDAL::quote($parameters['to_date']);
531  }
532  }
533  if (!is_null($from)) {
534  $where .= ' AND m.sent >= '.MatrixDAL::quote(ts_iso8601($from));
535  }
536  if (!is_null($to)) {
537  $where .= ' AND m.sent <= '.MatrixDAL::quote(ts_iso8601($to));
538  }
539 
540  $sql .= ' '.$where;
541 
542  if (!$count_only) {
543  $sql .= ' ORDER BY m.sent DESC';
544  }
545 
546  if ($max_msgs) {
547  $sql = db_extras_modify_limit_clause($sql, MatrixDAL::getDbType(), $max_msgs);
548  }
549 
550  // Begin try block
551  try {
552  if (empty($bindVars) === TRUE) {
553  $result = MatrixDAL::executeSqlAll($sql);
554  } else {
555  $prepare = MatrixDAL::preparePdoQuery($sql);
556  foreach ($bindVars as $bindVar => $bindValue) {
557  MatrixDAL::bindValueToPdo($prepare, $bindVar, $bindValue);
558  }
559  $result = MatrixDAL::executePdoAll($prepare);
560  }
561  } catch (Exception $e) {
562  throw new Exception('Unable to get message information due to database error: '.$e->getMessage());
563  }//end try catch
564 
565  if ($count_only) {
566  return $result[0]['message_count'];
567  }
568 
569  for ($i = 0, $total = count($result); $i < $total; $i++) {
570  $result[$i]['sent'] = iso8601_ts($result[$i]['sent']);
571 
572  // if we are showing the from name, check that any messages from shadow
573  // assets are actually displaying the correct name
574  if (is_string($get_user_name)) {
575  $id_parts = explode(':', $result[$i]['userfrom']);
576  if (isset($id_parts[1])) {
577  $bridgeid = $id_parts[0];
578  $bridge = $GLOBALS['SQ_SYSTEM']->am->getAsset($bridgeid, '', TRUE);
579  if (!is_null($bridge)) {
580  $shadow = $bridge->getAsset($result[$i]['userfrom']);
581  if (!is_null($shadow)) {
582  $result[$i]['from_name'] = $shadow->$get_user_name;
583  $result[$i]['type_code'] = $shadow->type();
584  }
585  }
586  }
587 
588  // if we couldnt find the name of the user, set a default
589  if (empty($result[$i]['from_name'])) {
590  $result[$i]['from_name'] = 'Unknown User';
591  }
592 
593  // if we couldnt find the type code user, set a default
594  if (empty($result[$i]['type_code'])) {
595  $result[$i]['type_code'] = 'user';
596  }
597  }
598 
599  // We want to do the asset tagline replacements
600  $body = $result[$i]['body'];
601  $tagline_reg = '|<SQ_MSG_TAGLINE[^>]*?>(.*?)</SQ_MSG_TAGLINE>|';
602  if (preg_match($tagline_reg, $body, $matches)) {
603  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($matches[1]);
604  if (is_null($asset)) {
605  $body = preg_replace($tagline_reg, 'AssetID #'.$matches[1], $body);
606  } else {
607  $body = preg_replace($tagline_reg, get_asset_tag_line($asset->id), $body);
608  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($asset);
609  }
610  $result[$i]['body'] = $body;
611  }
612 
613  }//end for
614 
615  return $result;
616 
617  }//end getMessages()
618 
619 
633  function getLogs($type, $parameters=Array())
634  {
635  return $this->getMessages(0, $type, Array(), Array(), NULL, NULL, 'name', $parameters);
636 
637  }//end getLogs()
638 
639 
650  function getMessageById($messageid)
651  {
652  $message = $this->newMessage();
653  $message->load($messageid);
654  if (!$message->id) return NULL;
655  return $message;
656 
657  }//end getMessageById()
658 
659 
669  public static function sortMessages($a, $b)
670  {
671  if ($a['sent'] == $b['sent']) return 0;
672  return ($a['sent'] > $b['sent']) ? -1 : 1;
673 
674  }//end sortMessages()
675 
676 
688  function queueEmail($from, $to, $subject, $body, $reply_to='')
689  {
690  if (!isset($this->_emails[$from])) {
691  $this->_emails[$from] = Array();
692  }
693 
694  if (!isset($this->_emails[$from][$to])) {
695  $this->_emails[$from][$to] = Array();
696  }
697 
698  if (!isset($this->_emails[$from][$to][$subject])) {
699  $this->_emails[$from][$to][$subject] = Array();
700  }
701 
702  if (!isset($this->_emails['reply_to']) && !empty($reply_to)) {
703  $this->_emails['reply_to'] = $reply_to;
704  }
705 
706  $this->_emails[$from][$to][$subject][] = $body;
707 
708  }//end queueEmail()
709 
710 
717  function sendQueuedEmails()
718  {
719  $mime_param = Array(
720  'head_charset' => SQ_CONF_DEFAULT_CHARACTER_SET,
721  'text_charset' => SQ_CONF_DEFAULT_CHARACTER_SET,
722  'html_charset' => SQ_CONF_DEFAULT_CHARACTER_SET,
723  );
724  $crlf = "\n";
725  $mime = new Mail_mime($crlf);
726 
727  foreach ($this->_emails as $from => $from_emails) {
728  if ($from == 'reply_to') continue;
729  // work out the email address the the email will be sent from
730  $from_email = SQ_CONF_DEFAULT_EMAIL;
731  $from_string = '';
732  if ($from && assert_valid_assetid($from, '', TRUE, FALSE)) {
733  $user_from = $GLOBALS['SQ_SYSTEM']->am->getAsset($from);
734  if (!is_null($user_from)) {
735  $from_name = preg_replace('/[\[\]\(\)<>\@\,\;\:\\\"\.]+/','', $user_from->name);
736  $from_email = $user_from->attr('email');
737  $from_string = $from_name.' <'.$from_email.'>';
738  }
739  } else {
740  $from_email = $from;
741  }
742  if (empty($from_email)) {
743  if (isset($_SERVER['HOSTNAME']) && isset($_SERVER['HTTP_HOST'])) {
744  $from_email = 'webmaster@'.((SQ_PHP_CLI) ? $_SERVER['HOSTNAME'] : $_SERVER['HTTP_HOST']);
745  } else {
746  $from_email = SQ_CONF_DEFAULT_EMAIL;
747  }
748  }
749  if (empty($from_string)) {
750  $from_string = '"'.SQ_SYSTEM_SHORT_NAME.' System" <'.$from_email.'>';
751  }
752 
753  if (isset($this->_emails['reply_to']) && !isset($reply_to_str)) {
754  $reply_to = $this->_emails['reply_to'];
755  if (assert_valid_assetid($reply_to, '', TRUE, FALSE)) {
756  $user_reply_to = $GLOBALS['SQ_SYSTEM']->am->getAsset($reply_to);
757  if (!is_null($user_reply_to)) {
758  $reply_to_name = preg_replace('/[\[\]\(\)<>\@\,\;\:\\\"\.]+/','', $user_reply_to->name);
759  $reply_to_email = $user_reply_to->attr('email');
760  $reply_to_str = $reply_to_name.' <'.$reply_to_email.'>';
761  }
762  } else {
763  $reply_to_str = $reply_to;
764  }
765  }
766 
767  foreach ($from_emails as $to => $to_emails) {
768  if (!$to) {
769  // if the userid is empty, we are sending a message to the system
770  // so we send to the default email and tech email
771  $default = SQ_CONF_DEFAULT_EMAIL;
772  $tech = SQ_CONF_TECH_EMAIL;
773  $to_email = '';
774 
775  if (!empty($default)) $to_email .= $default;
776  if (!empty($tech)) {
777  if (!empty($to_email)) $to_email .= ',';
778  $to_email .= $tech;
779  }
780 
781  // can't come up with a To: email address - bail for this address
782  if (empty($to_email)) {
783  // We want to skip this user
784  continue;
785  }
786 
787  $user = SQ_CONF_SYSTEM_OWNER;
788  } else {
789  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($to);
790  if (is_null($user) || !$user->canAccessBackend()) continue;
791  $to_email = trim($user->attr('email'));
792  }
793 
794  if (empty($to_email)) continue;
795 
796  foreach ($to_emails as $subject => $bodies) {
797 
798  $body = implode("\n\n".str_repeat('-', 50)."\n\n", $bodies);
799 
800  // workaround to fix the php5 strict warning
801  // $mail = Mail::factory('mail', "-f$from_email");
802  $mf = new Mail();
803  $mail = $mf->factory('mail', "-f$from_email");
804 
805  $mime = new Mail_mime($crlf);
806 
807  $hdrs = Array(
808  'From' => $from_string,
809  'Subject' => $subject,
810  );
811 
812  if (isset($reply_to_str) && $reply_to_str != '') $hdrs['Reply-To'] = $reply_to_str;
813 
814  $text_body = $this->formatEmail($body, $user, 'text');
815  $html_body = $this->formatEmail($body, $user, 'html');
816 
817  $mime->setTXTBody(strip_tags($text_body));
818  $mime->setHTMLBody($html_body);
819 
820  // get() must be called before headers()
821  $body = $mime->get($mime_param);
822  $hdrs = $mime->headers($hdrs);
823  @$mail->send($to_email, $hdrs, $body);
824  unset($mime);
825  unset($mail);
826  }
827  }//end foreach
828  }//end foreach
829 
830  // Remove sent emails
831  unset($this->_emails);
832  $this->_emails = Array();
833 
834  }//end sendQueuedEmails()
835 
836 
847  function formatEmail($body, &$user, $type='text')
848  {
849  $term = ($type=='text') ? "\n" : '<br />';
850 
851  $header = '';
852  if (!is_null($user) && is_object($user) && ($user instanceof User)) {
853  $header = $user->name.','.$term.$term;
854  } else if (is_string($user) && $user != '') {
855  $header = $user.','.$term.$term;
856  }
857  // replace separator line for multiple-bodied message with a h-rule
858  if ($type == 'html') {
859  $body = str_replace("\n\n".str_repeat('-', 50)."\n\n",'<hr />',$body);
860  }
861 
862  // give it newlines
863  if ($type == 'html') $body = nl2br($body);
864 
865  $line = ($type == 'text') ? str_repeat('-', 20).$term : '<hr />';
866 
867  $footer = $term.$term.$line
868  .'This is an automated message from '.SQ_CONF_SYSTEM_NAME.' running '.SQ_SYSTEM_LONG_NAME.$term
869  .'System Root URLs : '.$term.str_replace("\n", $term, SQ_CONF_SYSTEM_ROOT_URLS).$term
870  .$term
871  .'Contact '.SQ_CONF_SYSTEM_OWNER.' ('.SQ_CONF_TECH_EMAIL.') for support.';
872  return $header.$body.$footer;
873 
874  }//end formatEmail()
875 
876 
877 }//end class
878 
879 ?>