b2evolution PHP Cross Reference Blogging Systems

Source: /inc/messaging/model/_message.class.php - 570 lines - 15838 bytes - Summary - Text - Print

Description: This file is part of b2evolution - {@link http://b2evolution.net/} See also {@link http://sourceforge.net/projects/evocms/}.

   1  <?php
   2  /**
   3   * This file is part of b2evolution - {@link http://b2evolution.net/}
   4   * See also {@link http://sourceforge.net/projects/evocms/}.
   5   *
   6   * @copyright (c)2009-2014 by Francois PLANQUE - {@link http://fplanque.net/}
   7   * Parts of this file are copyright (c)2009 by The Evo Factory - {@link http://www.evofactory.com/}.
   8   *
   9   * Released under GNU GPL License - {@link http://b2evolution.net/about/license.html}
  10   *
  11   * {@internal Open Source relicensing agreement:
  12   * The Evo Factory grants Francois PLANQUE the right to license
  13   * The Evo Factory's contributions to this file and the b2evolution project
  14   * under any OSI approved OSS license (http://www.opensource.org/licenses/).
  15   * }}
  16   *
  17   * @package messaging
  18   *
  19   * {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  20   * @author efy-maxim: Evo Factory / Maxim.
  21   * @author fplanque: Francois Planque.
  22   *
  23   * @version $Id: _message.class.php 6136 2014-03-08 07:59:48Z manuel $
  24   */
  25  if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
  26  
  27  load_class( '_core/model/dataobjects/_dataobject.class.php', 'DataObject' );
  28  
  29  /**
  30   * Message Class
  31   *
  32   */
  33  class Message extends DataObject
  34  {
  35      /**
  36       * Available message statuses constants
  37       *
  38       * @internal Tblue> Class constants are PHP5-only!
  39       */
  40      var $AUTHOR = 0;
  41      var $READ = 1;
  42      var $UNREAD = 2;
  43  
  44      var $thread_ID;
  45      var $author_user_ID;
  46      var $author_name = '';
  47      var $datetime = '';
  48  
  49      /**
  50       * The content of the message
  51       * WARNING: It may contains MALICIOUS HTML and javascript snippets. They must ALWAYS be ESCAPED prior to display!
  52       * 
  53       * @var string
  54       */
  55      var $text = '';
  56  
  57      /**
  58       * Thread lazy filled
  59       *
  60       * @var instance of Thread class
  61       */
  62      var $Thread;
  63  
  64  
  65      /**
  66       * Constructor
  67       *
  68       * @param db_row database row
  69       */
  70  	function Message( $db_row = NULL )
  71      {
  72          // Call parent constructor:
  73          parent::DataObject( 'T_messaging__message', 'msg_', 'msg_ID', 'datetime', '', 'author_user_ID' );
  74  
  75            $this->delete_cascades = array();
  76            $this->delete_restrictions = array();
  77  
  78           if( $db_row != NULL )
  79          {
  80              $this->ID                = $db_row->msg_ID;
  81              $this->thread_ID         = $db_row->msg_thread_ID;
  82              $this->author_user_ID    = $db_row->msg_author_user_ID;
  83              $this->datetime          = $db_row->msg_datetime;
  84              $this->text              = $db_row->msg_text;
  85          }
  86      }
  87  
  88  
  89      /**
  90       * Load data from Request form fields.
  91       *
  92       * @return boolean true if loaded data seems valid.
  93       */
  94  	function load_from_Request()
  95      {
  96          $new_thread = empty($this->thread_ID);
  97  
  98          // Text
  99          // WARNING: the messages may contain MALICIOUS HTML and javascript snippets. They must ALWAYS be ESCAPED prior to display!
 100          param( 'msg_text', 'html' );
 101          if( ! $new_thread )
 102          {
 103              param_check_not_empty( 'msg_text' );
 104          }
 105          $this->set( 'text', get_param( 'msg_text' ) );
 106  
 107          // Thread
 108          if( $new_thread )
 109          {
 110              $this->Thread->load_from_Request();
 111          }
 112          else
 113          { // this is a reply to an existing conversation, check if current User is allowed to reply
 114              $this->get_Thread();
 115              $this->Thread->check_allow_reply();
 116          }
 117  
 118          return ! param_errors_detected();
 119      }
 120  
 121  
 122      /**
 123       * Get Thread object
 124       */
 125      function & get_Thread()
 126      {
 127          if( is_null($this->Thread) && !empty($this->thread_ID) )
 128          {
 129              $ThreadCache = & get_ThreadCache();
 130              $this->Thread = $ThreadCache->get_by_ID( $this->thread_ID );
 131          }
 132  
 133          return $this->Thread;
 134      }
 135  
 136  
 137      /**
 138       * Insert discussion (one thread for all recipients)
 139       *
 140       * @param User who sent the message, it must be set only if it is not the current User
 141       * @return true if success, false otherwise
 142       */
 143  	function dbinsert_discussion( $from_User = NULL )
 144      {
 145          global $DB;
 146  
 147          if( $this->ID != 0 ) die( 'Existing object cannot be inserted!' );
 148  
 149          $DB->begin();
 150  
 151          $this->get_Thread();
 152  
 153          if ( $this->Thread->dbinsert() )
 154          {
 155              $this->set_param( 'thread_ID', 'integer', $this->Thread->ID);
 156  
 157              if( parent::dbinsert() )
 158              {
 159                  if( $this->dbinsert_threadstatus( $this->Thread->recipients_list ) )
 160                  {
 161                      if( $this->dbinsert_contacts( $this->Thread->recipients_list ) )
 162                      {
 163                          if( $this->dbupdate_last_contact_datetime() )
 164                          {
 165                              $DB->commit();
 166  
 167                              $this->send_email_notifications( true, $from_User );
 168                              return true;
 169                          }
 170                      }
 171                  }
 172              }
 173          }
 174  
 175          $DB->rollback();
 176          return false;
 177      }
 178  
 179  
 180      /**
 181       * Insert new thread for each recipient
 182       *
 183       * @param User who sent the message, it must be set only if it is not the current User
 184       * @return true if success, instead false
 185       */
 186  	function dbinsert_individual( $from_User = NULL )
 187      {
 188          foreach( $this->Thread->recipients_list as $recipient_ID )
 189          {
 190              $message = $this->clone_message( $this );
 191  
 192              $message->Thread->recipients_list = array( $recipient_ID );
 193  
 194              if ( !$message->dbinsert_discussion( $from_User ) )
 195              {
 196                  return false;
 197              }
 198          }
 199  
 200          return true;
 201      }
 202  
 203  
 204      /**
 205       * Insert message in existing thread
 206       *
 207       * @return true if success, instead false
 208       */
 209  	function dbinsert_message()
 210      {
 211          global $DB, $localtimenow;
 212  
 213          if( $this->ID != 0 ) die( 'Existing object cannot be inserted!' );
 214  
 215          $DB->begin();
 216  
 217          $this->get_Thread();
 218  
 219          $this->Thread->set_param( 'datemodified', 'string', date( 'Y-m-d H:i:s', $localtimenow ) );
 220  
 221          if( $this->Thread->dbupdate() )
 222          {
 223              $this->set_param( 'thread_ID', 'integer', $this->Thread->ID);
 224  
 225              if( parent::dbinsert() )
 226              {
 227                  $sql = 'UPDATE T_messaging__threadstatus
 228                          SET tsta_first_unread_msg_ID = '.$this->ID.'
 229                          WHERE tsta_thread_ID = '.$this->Thread->ID.'
 230                              AND tsta_user_ID <> '.$this->author_user_ID.'
 231                              AND tsta_first_unread_msg_ID IS NULL';
 232  
 233                  $DB->query( $sql, 'Insert thread statuses' );
 234  
 235                  // check if contact pairs between sender and recipients exists
 236                  $recipient_list = $this->Thread->load_recipients();
 237                  // remove author user from recipient list
 238                  $recipient_list = array_diff( $recipient_list, array( $this->author_user_ID ) );
 239                  // insert missing contact pairs if required
 240                  if( $this->dbinsert_contacts( $recipient_list ) )
 241                  {
 242                      if( $this->dbupdate_last_contact_datetime() )
 243                      {
 244                          $DB->commit();
 245  
 246                          $this->send_email_notifications( false );
 247                          return true;
 248                      }
 249                  }
 250              }
 251          }
 252  
 253          $DB->rollback();
 254          return false;
 255      }
 256  
 257  
 258      /**
 259       * Insert recipients into database
 260       *
 261       * @param recipients
 262       * @return true if success, instead false
 263       */
 264  	function dbinsert_threadstatus( $recipients_list )
 265      {
 266          global $DB;
 267  
 268          $sql = 'INSERT INTO T_messaging__threadstatus (tsta_thread_ID, tsta_user_ID, tsta_first_unread_msg_ID)
 269                              VALUES';
 270  
 271          foreach ( $recipients_list as $recipient_ID )
 272          {
 273              $sql .= ' ('.$this->Thread->ID.', '.$recipient_ID.', '.$this->ID.'),';
 274          }
 275          $sql .= ' ('.$this->Thread->ID.', '.$this->author_user_ID.', NULL)';
 276  
 277          return $DB->query( $sql, 'Insert thread statuses' );
 278      }
 279  
 280  
 281      /**
 282       * Insert contacts into database
 283       *
 284       * @param recipients
 285       * @return true if success, instead false
 286       */
 287  	function dbinsert_contacts( $recipients )
 288      {
 289          global $DB, $localtimenow;
 290  
 291          // select contacts of the current user
 292          $SQL = new SQL();
 293  
 294          $SQL->SELECT( 'mct_to_user_ID' );
 295          $SQL->FROM( 'T_messaging__contact' );
 296          $SQL->WHERE( 'mct_from_user_ID = '.$this->author_user_ID );
 297  
 298          $contact_list = array();
 299          foreach( $DB->get_results( $SQL->get() ) as $row )
 300          {
 301              $contact_list[] = $row->mct_to_user_ID;
 302          }
 303  
 304          // get users/recipients which are not in contact list
 305          $contact_list = array_diff( $recipients, $contact_list );
 306  
 307          // select users who have author User on their contact list
 308          $SQL = new SQL();
 309  
 310          $SQL->SELECT( 'mct_from_user_ID' );
 311          $SQL->FROM( 'T_messaging__contact' );
 312          $SQL->WHERE( 'mct_to_user_ID = '.$this->author_user_ID );
 313  
 314          $reverse_contact_list = array();
 315          foreach( $DB->get_results( $SQL->get() ) as $row )
 316          {
 317              $reverse_contact_list[] = $row->mct_from_user_ID;
 318          }
 319  
 320          // get users/recipients which are not in reverse contact list
 321          $reverse_contact_list = array_diff( $recipients, $reverse_contact_list );
 322  
 323          if( !empty( $contact_list ) || !empty( $reverse_contact_list ) )
 324          {    // insert users/recipients which are not in contact list
 325  
 326              $sql = 'INSERT INTO T_messaging__contact (mct_from_user_ID, mct_to_user_ID, mct_last_contact_datetime)
 327                                  VALUES';
 328  
 329              $datetime = date( 'Y-m-d H:i:s', $localtimenow );
 330  
 331              $statements = array();
 332              foreach ( $contact_list as $contact_ID )
 333              {
 334                  $statements[] = ' ('.$this->author_user_ID.', '.$contact_ID.', \''.$datetime.'\')';
 335              }
 336              foreach ( $reverse_contact_list as $contact_ID )
 337              {
 338                  $statements[] = ' ('.$contact_ID.', '.$this->author_user_ID.', \''.$datetime.'\')';
 339              }
 340              $sql .= implode( ', ', $statements );
 341  
 342              return $DB->query( $sql, 'Insert contacts' );
 343          }
 344  
 345          return true;
 346      }
 347  
 348  
 349      /**
 350       * Update last contact datetimes
 351       *
 352       * @return true if success
 353       */
 354  	function dbupdate_last_contact_datetime()
 355      {
 356          global $DB, $localtimenow;
 357  
 358          // efy-maxim> TODO: two SQL queries are used instead one update with subselect,
 359          // because T_messaging__threadstatus alias is not converted to real table name.
 360          // Also, it can't be improved right now because it depends of
 361          // (pls. see blueyed's comment for $DB->query() function)
 362  
 363          $select_SQL = new SQL();
 364          $select_SQL->SELECT( 'GROUP_CONCAT(tsta_user_ID SEPARATOR \',\')' );
 365          $select_SQL->FROM( 'T_messaging__threadstatus' );
 366          $select_SQL->WHERE( 'tsta_thread_ID = '.$this->Thread->ID );
 367  
 368          $recipients = $DB->get_var( $select_SQL->get() );
 369  
 370          $datetime = date( 'Y-m-d H:i:s', $localtimenow );
 371  
 372          $update_sql = 'UPDATE T_messaging__contact
 373                      SET mct_last_contact_datetime = \''.$datetime.'\'
 374                      WHERE mct_from_user_ID = '.$this->author_user_ID.'
 375                          AND mct_to_user_ID IN ('.$recipients.')';
 376  
 377          $DB->query( $update_sql, 'Update last contact datetimes' );
 378  
 379          return true;
 380      }
 381  
 382  
 383      /**
 384       * Clone current message and convert cloned message from 'individual' to 'discussion'.
 385       *
 386       * @param instance of Message class
 387       * @return cloned message
 388       */
 389  	function clone_message( $message )
 390      {
 391          $new_Message = new Message();
 392          $new_Message->set( 'text', $message->text );
 393          if( !empty( $message->author_user_ID ) )
 394          {
 395              $new_Message->set( 'author_user_ID', $message->author_user_ID );
 396          }
 397          if( !empty( $message->creator_user_ID ) )
 398          {
 399              $new_Message->creator_user_ID = $message->creator_user_ID;
 400          }
 401  
 402          $new_Thread = new Thread();
 403          $new_Thread->set( 'title', $message->Thread->title );
 404  
 405          $new_Message->Thread = & $new_Thread;
 406  
 407          return $new_Message;
 408      }
 409  
 410  
 411      /**
 412       * Delete message and dependencies from database
 413       *
 414       * @param Log Log object where output gets added (by reference).
 415       */
 416  	function dbdelete()
 417      {
 418          global $DB;
 419  
 420          if( $this->ID == 0 ) debug_die( 'Non persistant object cannot be deleted!' );
 421  
 422          // Remember ID, because parent method resets it to 0
 423          $thread_ID = $this->thread_ID;
 424  
 425          $DB->begin();
 426  
 427          // UPDATE last unread msg_ID on this thread statuses from this message ID to the next message ID or NULL if there is no next message
 428          $DB->query( 'UPDATE T_messaging__threadstatus
 429                          SET tsta_first_unread_msg_ID =
 430                              ( SELECT msg_ID
 431                                  FROM T_messaging__message
 432                                  WHERE msg_thread_ID = '.$thread_ID.' AND msg_datetime > '.$DB->quote( $this->datetime ).'
 433                                  ORDER BY msg_datetime ASC
 434                                  LIMIT 1
 435                              )
 436                          WHERE tsta_first_unread_msg_ID = '.$this->ID );
 437  
 438          // Delete Message
 439          if( ! parent::dbdelete() )
 440          {
 441              $DB->rollback();
 442  
 443              return false;
 444          }
 445  
 446          // Get a count of the messages in the current thread
 447          $SQL = new SQL();
 448          $SQL->SELECT( 'COUNT( msg_ID )' );
 449          $SQL->FROM( $this->dbtablename );
 450          $SQL->WHERE( 'msg_thread_ID = '.$DB->quote( $thread_ID ) );
 451          $msg_count = $DB->get_var( $SQL->get() );
 452  
 453          if( $msg_count == 0 )
 454          {    // Last message was deleted from thread now, We should also delete this thread
 455              load_class( 'messaging/model/_thread.class.php', 'Thread' );
 456              $ThreadCache = & get_ThreadCache();
 457              $Thread = & $ThreadCache->get_by_ID( $thread_ID );
 458              $Thread->dbdelete();
 459          }
 460  
 461          $DB->commit();
 462  
 463          return true;
 464      }
 465  
 466  
 467      /**
 468       * Check permission on a message
 469       *
 470       * @return boolean true if granted
 471       */
 472  	function check_perm( $action, $assert = true )
 473      {
 474          global $current_User;
 475  
 476          return $current_User->check_perm( 'perm_messaging', $action, $assert );
 477      }
 478  
 479  
 480      /**
 481       * Send email notification to recipients on new thread or new message event.
 482       *
 483       * @param boolean true if new thread, false if new message in the current thread
 484       * @param boolean the User who sent the message, in case of current User it may be NULL ( This is not the current User e.g. in case of welcome messages )
 485       * @return boolean True if all messages could be sent, false otherwise.
 486       */
 487  	function send_email_notifications( $new_thread = true, $from_User = NULL )
 488      {
 489          global $DB, $current_User, $admin_url, $baseurl, $app_name;
 490          global $Settings, $UserSettings, $servertimenow;
 491  
 492          // Select recipients of the current thread:
 493          $SQL = new SQL();
 494          $SQL->SELECT( 'u.user_ID, us.uset_value as notify_messages' );
 495          $SQL->FROM( 'T_messaging__threadstatus ts
 496                          INNER JOIN T_messaging__contact c
 497                              ON ts.tsta_user_ID = c.mct_to_user_ID AND c.mct_from_user_ID = '.$this->author_user_ID.' AND c.mct_blocked = 0
 498                          INNER JOIN T_users u
 499                              ON ts.tsta_user_ID = u.user_ID
 500                          LEFT OUTER JOIN T_users__usersettings us ON u.user_ID = us.uset_user_ID AND us.uset_name = "notify_messages"' );
 501          $SQL->WHERE( 'ts.tsta_thread_ID = '.$this->Thread->ID.' AND ts.tsta_user_ID <> '.$this->author_user_ID );
 502  
 503          $thrd_recipients = $DB->get_assoc( $SQL->get() );
 504  
 505          // set message link:
 506          list( $message_link, $prefs_link ) = get_messages_link_to( $this->thread_ID );
 507  
 508          // Construct message subject and body:
 509          if( $new_thread )
 510          {
 511              $subject = NT_( '%s just sent you a new message!' );
 512          }
 513          elseif( count( $thrd_recipients ) == 1 )
 514          {
 515              $subject = NT_( '%s just replied to your message!' );
 516          }
 517          else
 518          {
 519              $subject = NT_( '%s just replied to a conversation you are involved in!' );
 520          }
 521  
 522          // Get other unread threads
 523          $other_unread_threads = get_users_unread_threads( array_keys( $thrd_recipients ), $this->thread_ID, 'array', 'html' );
 524  
 525          // Load all users who will be notified
 526          $UserCache = & get_UserCache();
 527          $UserCache->load_list( array_keys( $thrd_recipients ) );
 528  
 529          // Send email notifications.
 530          $ret = true;
 531          $def_notify_messages = $Settings->get( 'def_notify_messages' );
 532          foreach( $thrd_recipients as $recipient_ID => $notify_messages )
 533          { // Send mail to recipients who needs to be notified. recipients are already loaded into the UserCache
 534              if( !( $notify_messages || ( is_null( $notify_messages ) && $def_notify_messages ) ) )
 535              { // User should NOT be notified
 536                  continue;
 537              }
 538  
 539              $email_template_params = array(
 540                      'recipient_ID'         => $recipient_ID,
 541                      'new_thread'           => $new_thread,
 542                      'thrd_recipients'      => $thrd_recipients,
 543                      'Message'              => $this,
 544                      'message_link'         => $message_link,
 545                      'other_unread_threads' => $other_unread_threads[$recipient_ID],
 546                      'from_User'            => $from_User,
 547                  );
 548              $notify_User = $UserCache->get_by_ID( $recipient_ID );
 549              // Change locale here to localize the email subject and content
 550              locale_temp_switch( $notify_User->get( 'locale' ) );
 551              $sender_login = ( $from_User === NULL ) ? $current_User->login : $from_User->login;
 552              $localized_subject = sprintf( T_( $subject ), $sender_login );
 553              // Note: Not activated users won't get notification email
 554              if( send_mail_to_User( $recipient_ID, $localized_subject, 'private_message_new', $email_template_params ) )
 555              { // email sent successful, update las_unread_message_reminder timestamp, because the notification contains all unread messages
 556                  $UserSettings->set( 'last_unread_messages_reminder', date2mysql( $servertimenow ), $recipient_ID );
 557              }
 558              else
 559              { // message was not sent
 560                  $ret = false;
 561              }
 562              locale_restore_previous();
 563          }
 564          // update reminder timestamp changes
 565          $UserSettings->dbupdate();
 566          return $ret;
 567      }
 568  }
 569  
 570  ?>

title

Description

title

Description

title

Description

title

title

Body