1<?php 2/** 3 * MyBB 1.8 4 * Copyright 2014 MyBB Group, All Rights Reserved 5 * 6 * Website: http://www.mybb.com 7 * License: http://www.mybb.com/about/license 8 * 9 */ 10 11// Disallow direct access to this file for security reasons 12if(!defined("IN_MYBB")) 13{ 14 die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined."); 15} 16 17/** 18 * PM handling class, provides common structure to handle private messaging data. 19 * 20 */ 21class PMDataHandler extends DataHandler 22{ 23 /** 24 * The language file used in the data handler. 25 * 26 * @var string 27 */ 28 public $language_file = 'datahandler_pm'; 29 30 /** 31 * The prefix for the language variables used in the data handler. 32 * 33 * @var string 34 */ 35 public $language_prefix = 'pmdata'; 36 37 /** 38 * Array of data inserted in to a private message. 39 * 40 * @var array 41 */ 42 public $pm_insert_data = array(); 43 44 /** 45 * Array of data used to update a private message. 46 * 47 * @var array 48 */ 49 public $pm_update_data = array(); 50 51 /** 52 * PM ID currently being manipulated by the datahandlers. 53 * 54 * @var int 55 */ 56 public $pmid = 0; 57 58 /** 59 * Values to be returned after inserting a PM. 60 * 61 * @var array 62 */ 63 public $return_values = array(); 64 65 /** 66 * Verifies a private message subject. 67 * 68 * @return boolean True when valid, false when invalid. 69 */ 70 function verify_subject() 71 { 72 $subject = &$this->data['subject']; 73 74 // Subject is over 85 characters, too long. 75 if(my_strlen($subject) > 85) 76 { 77 $this->set_error("too_long_subject"); 78 return false; 79 } 80 // No subject, apply the default [no subject] 81 if(!trim_blank_chrs($subject)) 82 { 83 $this->set_error("missing_subject"); 84 return false; 85 } 86 return true; 87 } 88 89 /** 90 * Verifies if a message for a PM is valid. 91 * 92 * @return boolean True when valid, false when invalid. 93 */ 94 function verify_message() 95 { 96 $message = &$this->data['message']; 97 98 // No message, return an error. 99 if(trim_blank_chrs($message) == '') 100 { 101 $this->set_error("missing_message"); 102 return false; 103 } 104 105 // If the length of message is beyond SQL limitation for 'text' field 106 else if(strlen($message) > 65535) 107 { 108 $this->set_error("message_too_long", array('65535', strlen($message))); 109 return false; 110 } 111 112 return true; 113 } 114 115 /** 116 * Verifies if the specified sender is valid or not. 117 * 118 * @return boolean True when valid, false when invalid. 119 */ 120 function verify_sender() 121 { 122 global $db, $mybb, $lang; 123 124 $pm = &$this->data; 125 126 // Return if we've already validated 127 if(!empty($pm['sender'])) 128 { 129 return true; 130 } 131 132 // Fetch the senders profile data. 133 $sender = get_user($pm['fromid']); 134 135 // Collect user permissions for the sender. 136 $sender_permissions = user_permissions($pm['fromid']); 137 138 // Check if the sender is over their quota or not - if they are, disable draft sending 139 if(isset($pm['options']['savecopy']) && $pm['options']['savecopy'] != 0 && empty($pm['saveasdraft'])) 140 { 141 if($sender_permissions['pmquota'] != 0 && $sender['totalpms'] >= $sender_permissions['pmquota'] && $this->admin_override != true) 142 { 143 $pm['options']['savecopy'] = 0; 144 } 145 } 146 147 // Assign the sender information to the data. 148 $pm['sender'] = array( 149 "uid" => $sender['uid'], 150 "username" => $sender['username'] 151 ); 152 153 return true; 154 } 155 156 /** 157 * Verifies if an array of recipients for a private message are valid 158 * 159 * @return boolean True when valid, false when invalid. 160 */ 161 function verify_recipient() 162 { 163 global $cache, $db, $mybb, $lang; 164 165 $pm = &$this->data; 166 167 $recipients = array(); 168 169 $invalid_recipients = array(); 170 // We have our recipient usernames but need to fetch user IDs 171 if(array_key_exists("to", $pm)) 172 { 173 foreach(array("to", "bcc") as $recipient_type) 174 { 175 if(!isset($pm[$recipient_type])) 176 { 177 $pm[$recipient_type] = array(); 178 } 179 if(!is_array($pm[$recipient_type])) 180 { 181 $pm[$recipient_type] = array($pm[$recipient_type]); 182 } 183 184 $pm[$recipient_type] = array_map('trim', $pm[$recipient_type]); 185 $pm[$recipient_type] = array_filter($pm[$recipient_type]); 186 187 // No recipients? Skip query 188 if(empty($pm[$recipient_type])) 189 { 190 if($recipient_type == 'to' && empty($pm['saveasdraft'])) 191 { 192 $this->set_error("no_recipients"); 193 return false; 194 } 195 continue; 196 } 197 198 $recipientUsernames = array_map(array($db, 'escape_string'), $pm[$recipient_type]); 199 $recipientUsernames = "'".implode("','", $recipientUsernames)."'"; 200 201 $query = $db->simple_select('users', '*', 'username IN('.$recipientUsernames.')'); 202 203 $validUsernames = array(); 204 205 while($user = $db->fetch_array($query)) 206 { 207 if($recipient_type == "bcc") 208 { 209 $user['bcc'] = 1; 210 } 211 212 $recipients[] = $user; 213 $validUsernames[] = $user['username']; 214 } 215 216 foreach($pm[$recipient_type] as $username) 217 { 218 if(!in_array($username, $validUsernames)) 219 { 220 $invalid_recipients[] = $username; 221 } 222 } 223 } 224 } 225 // We have recipient IDs 226 else 227 { 228 foreach(array("toid", "bccid") as $recipient_type) 229 { 230 if(!isset($pm[$recipient_type])) 231 { 232 $pm[$recipient_type] = array(); 233 } 234 if(!is_array($pm[$recipient_type])) 235 { 236 $pm[$recipient_type] = array($pm[$recipient_type]); 237 } 238 $pm[$recipient_type] = array_map('intval', $pm[$recipient_type]); 239 $pm[$recipient_type] = array_filter($pm[$recipient_type]); 240 241 // No recipients? Skip query 242 if(empty($pm[$recipient_type])) 243 { 244 if($recipient_type == 'toid' && !$pm['saveasdraft']) 245 { 246 $this->set_error("no_recipients"); 247 return false; 248 } 249 continue; 250 } 251 252 $recipientUids = "'".implode("','", $pm[$recipient_type])."'"; 253 254 $query = $db->simple_select('users', '*', 'uid IN('.$recipientUids.')'); 255 256 $validUids = array(); 257 258 while($user = $db->fetch_array($query)) 259 { 260 if($recipient_type == "bccid") 261 { 262 $user['bcc'] = 1; 263 } 264 265 $recipients[] = $user; 266 $validUids[] = $user['uid']; 267 } 268 269 foreach($pm[$recipient_type] as $uid) 270 { 271 if(!in_array($uid, $validUids)) 272 { 273 $invalid_recipients[] = $uid; 274 } 275 } 276 } 277 } 278 279 // If we have one or more invalid recipients and we're not saving a draft, error 280 if(count($invalid_recipients) > 0) 281 { 282 $invalid_recipients = implode($lang->comma, array_map("htmlspecialchars_uni", $invalid_recipients)); 283 $this->set_error("invalid_recipients", array($invalid_recipients)); 284 return false; 285 } 286 287 $sender_permissions = user_permissions($pm['fromid']); 288 289 // Are we trying to send this message to more users than the permissions allow? 290 if($sender_permissions['maxpmrecipients'] > 0 && count($recipients) > $sender_permissions['maxpmrecipients'] && $this->admin_override != true) 291 { 292 $this->set_error("too_many_recipients", array($sender_permissions['maxpmrecipients'])); 293 } 294 295 // Now we're done with that we loop through each recipient 296 foreach($recipients as $user) 297 { 298 // Collect group permissions for this recipient. 299 $recipient_permissions = user_permissions($user['uid']); 300 301 // See if the sender is on the recipients ignore list and that either 302 // - admin_override is set or 303 // - sender is an administrator 304 if($this->admin_override != true && $sender_permissions['canoverridepm'] != 1) 305 { 306 if(!empty($user['ignorelist']) && strpos(','.$user['ignorelist'].',', ','.$pm['fromid'].',') !== false) 307 { 308 $this->set_error("recipient_is_ignoring", array(htmlspecialchars_uni($user['username']))); 309 } 310 311 // Is the recipient only allowing private messages from their buddy list? 312 if(empty($pm['saveasdraft']) && $mybb->settings['allowbuddyonly'] == 1 && $user['receivefrombuddy'] == 1 && !empty($user['buddylist']) && strpos(','.$user['buddylist'].',', ','.$pm['fromid'].',') === false) 313 { 314 $this->set_error('recipient_has_buddy_only', array(htmlspecialchars_uni($user['username']))); 315 } 316 317 // Can the recipient actually receive private messages based on their permissions or user setting? 318 if(($user['receivepms'] == 0 || $recipient_permissions['canusepms'] == 0) && empty($pm['saveasdraft'])) 319 { 320 $this->set_error("recipient_pms_disabled", array(htmlspecialchars_uni($user['username']))); 321 return false; 322 } 323 } 324 325 // Check to see if the user has reached their private message quota - if they have, email them. 326 if($recipient_permissions['pmquota'] != 0 && $user['totalpms'] >= $recipient_permissions['pmquota'] && $sender_permissions['cancp'] != 1 && empty($pm['saveasdraft']) && !$this->admin_override) 327 { 328 if(trim($user['language']) != '' && $lang->language_exists($user['language'])) 329 { 330 $uselang = trim($user['language']); 331 } 332 elseif($mybb->settings['bblanguage']) 333 { 334 $uselang = $mybb->settings['bblanguage']; 335 } 336 else 337 { 338 $uselang = "english"; 339 } 340 if($uselang == $mybb->settings['bblanguage'] || !$uselang) 341 { 342 $emailsubject = $lang->emailsubject_reachedpmquota; 343 $emailmessage = $lang->email_reachedpmquota; 344 } 345 else 346 { 347 $userlang = new MyLanguage; 348 $userlang->set_path(MYBB_ROOT."inc/languages"); 349 $userlang->set_language($uselang); 350 $userlang->load("messages"); 351 $emailsubject = $userlang->emailsubject_reachedpmquota; 352 $emailmessage = $userlang->email_reachedpmquota; 353 } 354 $emailmessage = $lang->sprintf($emailmessage, $user['username'], $mybb->settings['bbname'], $mybb->settings['bburl']); 355 $emailsubject = $lang->sprintf($emailsubject, $mybb->settings['bbname'], $pm['subject']); 356 357 $new_email = array( 358 "mailto" => $db->escape_string($user['email']), 359 "mailfrom" => '', 360 "subject" => $db->escape_string($emailsubject), 361 "message" => $db->escape_string($emailmessage), 362 "headers" => '' 363 ); 364 365 $db->insert_query("mailqueue", $new_email); 366 $cache->update_mailqueue(); 367 368 if($this->admin_override != true) 369 { 370 $this->set_error("recipient_reached_quota", array(htmlspecialchars_uni($user['username']))); 371 } 372 } 373 374 // Everything looks good, assign some specifics about the recipient 375 $pm['recipients'][$user['uid']] = array( 376 "uid" => $user['uid'], 377 "username" => $user['username'], 378 "email" => $user['email'], 379 "lastactive" => $user['lastactive'], 380 "pmnotice" => $user['pmnotice'], 381 "pmnotify" => $user['pmnotify'], 382 "language" => $user['language'] 383 ); 384 385 // If this recipient is defined as a BCC recipient, save it 386 if(isset($user['bcc']) && $user['bcc'] == 1) 387 { 388 $pm['recipients'][$user['uid']]['bcc'] = 1; 389 } 390 } 391 return true; 392 } 393 394 /** 395 * Verify that the user is not flooding the system. 396 * 397 * @return boolean 398 */ 399 function verify_pm_flooding() 400 { 401 global $mybb, $db; 402 403 $pm = &$this->data; 404 405 // Check if post flooding is enabled within MyBB or if the admin override option is specified. 406 if($mybb->settings['pmfloodsecs'] > 0 && $pm['fromid'] != 0 && $this->admin_override == false && !is_moderator(0, '', $pm['fromid'])) 407 { 408 // Fetch the senders profile data. 409 $sender = get_user($pm['fromid']); 410 411 // Calculate last post 412 $query = $db->simple_select("privatemessages", "dateline", "fromid='".$db->escape_string($pm['fromid'])."' AND toid != '0'", array('order_by' => 'dateline', 'order_dir' => 'desc', 'limit' => 1)); 413 $sender['lastpm'] = $db->fetch_field($query, "dateline"); 414 415 // A little bit of calculation magic and moderator status checking. 416 if(TIME_NOW-$sender['lastpm'] <= $mybb->settings['pmfloodsecs']) 417 { 418 // Oops, user has been flooding - throw back error message. 419 $time_to_wait = ($mybb->settings['pmfloodsecs'] - (TIME_NOW-$sender['lastpm'])) + 1; 420 if($time_to_wait == 1) 421 { 422 $this->set_error("pm_flooding_one_second"); 423 } 424 else 425 { 426 $this->set_error("pm_flooding", array($time_to_wait)); 427 } 428 return false; 429 } 430 } 431 // All is well that ends well - return true. 432 return true; 433 } 434 435 /** 436 * Verifies if the various 'options' for sending PMs are valid. 437 * 438 * @return boolean True when valid, false when invalid. 439 */ 440 function verify_options() 441 { 442 $options = &$this->data['options']; 443 444 $this->verify_yesno_option($options, 'signature', 1); 445 $this->verify_yesno_option($options, 'savecopy', 1); 446 $this->verify_yesno_option($options, 'disablesmilies', 0); 447 448 // Requesting a read receipt? 449 if(isset($options['readreceipt']) && $options['readreceipt'] == 1) 450 { 451 $options['readreceipt'] = 1; 452 } 453 else 454 { 455 $options['readreceipt'] = 0; 456 } 457 return true; 458 } 459 460 /** 461 * Validate an entire private message. 462 * 463 * @return boolean True when valid, false when invalid. 464 */ 465 function validate_pm() 466 { 467 global $plugins; 468 469 $pm = &$this->data; 470 471 if(empty($pm['savedraft'])) 472 { 473 $this->verify_pm_flooding(); 474 } 475 476 // Verify all PM assets. 477 $this->verify_subject(); 478 479 $this->verify_sender(); 480 481 $this->verify_recipient(); 482 483 $this->verify_message(); 484 485 $this->verify_options(); 486 487 $plugins->run_hooks("datahandler_pm_validate", $this); 488 489 // Choose the appropriate folder to save in. 490 if(!empty($pm['saveasdraft'])) 491 { 492 $pm['folder'] = 3; 493 } 494 else 495 { 496 $pm['folder'] = 1; 497 } 498 499 // We are done validating, return. 500 $this->set_validated(true); 501 if(count($this->get_errors()) > 0) 502 { 503 return false; 504 } 505 else 506 { 507 return true; 508 } 509 } 510 511 /** 512 * Insert a new private message. 513 * 514 * @return array Array of PM useful data. 515 */ 516 function insert_pm() 517 { 518 global $cache, $db, $mybb, $plugins, $lang; 519 520 // Yes, validating is required. 521 if(!$this->get_validated()) 522 { 523 die("The PM needs to be validated before inserting it into the DB."); 524 } 525 if(count($this->get_errors()) > 0) 526 { 527 die("The PM is not valid."); 528 } 529 530 // Assign data to common variable 531 $pm = &$this->data; 532 533 if(empty($pm['pmid'])) 534 { 535 $pm['pmid'] = 0; 536 } 537 $pm['pmid'] = (int)$pm['pmid']; 538 539 if(empty($pm['icon']) || $pm['icon'] < 0) 540 { 541 $pm['icon'] = 0; 542 } 543 544 $uid = 0; 545 546 if(!is_array($pm['recipients'])) 547 { 548 $recipient_list = array(); 549 } 550 else 551 { 552 // Build recipient list 553 foreach($pm['recipients'] as $recipient) 554 { 555 if(!empty($recipient['bcc'])) 556 { 557 $recipient_list['bcc'][] = $recipient['uid']; 558 } 559 else 560 { 561 $recipient_list['to'][] = $recipient['uid']; 562 $uid = $recipient['uid']; 563 } 564 } 565 } 566 567 $this->pm_insert_data = array( 568 'fromid' => (int)$pm['sender']['uid'], 569 'folder' => $pm['folder'], 570 'subject' => $db->escape_string($pm['subject']), 571 'icon' => (int)$pm['icon'], 572 'message' => $db->escape_string($pm['message']), 573 'dateline' => TIME_NOW, 574 'status' => 0, 575 'includesig' => $pm['options']['signature'], 576 'smilieoff' => $pm['options']['disablesmilies'], 577 'receipt' => (int)$pm['options']['readreceipt'], 578 'readtime' => 0, 579 'recipients' => $db->escape_string(my_serialize($recipient_list)), 580 'ipaddress' => $db->escape_binary($pm['ipaddress']) 581 ); 582 583 // Check if we're updating a draft or not. 584 $query = $db->simple_select("privatemessages", "pmid, deletetime", "folder='3' AND uid='".(int)$pm['sender']['uid']."' AND pmid='{$pm['pmid']}'"); 585 $draftcheck = $db->fetch_array($query); 586 587 // This PM was previously a draft 588 if(!empty($draftcheck['pmid'])) 589 { 590 if($draftcheck['deletetime']) 591 { 592 // This draft was a reply to a PM 593 $pm['pmid'] = $draftcheck['deletetime']; 594 $pm['do'] = "reply"; 595 } 596 597 // Delete the old draft as we no longer need it 598 $db->delete_query("privatemessages", "pmid='{$draftcheck['pmid']}'"); 599 } 600 601 // Saving this message as a draft 602 if(!empty($pm['saveasdraft'])) 603 { 604 $this->pm_insert_data['uid'] = $pm['sender']['uid']; 605 606 // If this is a reply, then piggyback into the deletetime to let us know in the future 607 if($pm['do'] == "reply" || $pm['do'] == "replyall") 608 { 609 $this->pm_insert_data['deletetime'] = $pm['pmid']; 610 } 611 612 $plugins->run_hooks("datahandler_pm_insert_updatedraft", $this); 613 614 $this->pmid = $db->insert_query("privatemessages", $this->pm_insert_data); 615 616 $plugins->run_hooks("datahandler_pm_insert_updatedraft_commit", $this); 617 618 // If this is a draft, end it here - below deals with complete messages 619 return array( 620 "draftsaved" => 1 621 ); 622 } 623 624 $this->pmid = array(); 625 626 // Save a copy of the PM for each of our recipients 627 foreach($pm['recipients'] as $recipient) 628 { 629 // Send email notification of new PM if it is enabled for the recipient 630 $query = $db->simple_select("privatemessages", "dateline", "uid='".$recipient['uid']."' AND folder='1'", array('order_by' => 'dateline', 'order_dir' => 'desc', 'limit' => 1)); 631 $lastpm = $db->fetch_array($query); 632 if($recipient['pmnotify'] == 1 && (empty($lastpm['dateline']) || $recipient['lastactive'] > $lastpm['dateline'])) 633 { 634 if($recipient['language'] != "" && $lang->language_exists($recipient['language'])) 635 { 636 $uselang = $recipient['language']; 637 } 638 elseif($mybb->settings['bblanguage']) 639 { 640 $uselang = $mybb->settings['bblanguage']; 641 } 642 else 643 { 644 $uselang = "english"; 645 } 646 if($uselang == $mybb->settings['bblanguage'] && !empty($lang->emailsubject_newpm)) 647 { 648 $emailsubject = $lang->emailsubject_newpm; 649 $emailmessage = $lang->email_newpm; 650 } 651 else 652 { 653 $userlang = new MyLanguage; 654 $userlang->set_path(MYBB_ROOT."inc/languages"); 655 $userlang->set_language($uselang); 656 $userlang->load("messages"); 657 $emailsubject = $userlang->emailsubject_newpm; 658 $emailmessage = $userlang->email_newpm; 659 } 660 661 if(!$pm['sender']['username']) 662 { 663 $pm['sender']['username'] = $lang->mybb_engine; 664 } 665 666 require_once MYBB_ROOT.'inc/class_parser.php'; 667 $parser = new Postparser; 668 669 $parser_options = array( 670 'me_username' => $pm['sender']['username'], 671 'filter_badwords' => 1 672 ); 673 674 $pm['message'] = $parser->text_parse_message($pm['message'], $parser_options); 675 676 $emailmessage = $lang->sprintf($emailmessage, $recipient['username'], $pm['sender']['username'], $mybb->settings['bbname'], $mybb->settings['bburl'], $pm['message']); 677 $emailsubject = $lang->sprintf($emailsubject, $mybb->settings['bbname'], $pm['subject']); 678 679 $new_email = array( 680 "mailto" => $db->escape_string($recipient['email']), 681 "mailfrom" => '', 682 "subject" => $db->escape_string($emailsubject), 683 "message" => $db->escape_string($emailmessage), 684 "headers" => '' 685 ); 686 687 $db->insert_query("mailqueue", $new_email); 688 $cache->update_mailqueue(); 689 } 690 691 $this->pm_insert_data['uid'] = $recipient['uid']; 692 $this->pm_insert_data['toid'] = $recipient['uid']; 693 694 $plugins->run_hooks("datahandler_pm_insert", $this); 695 696 $this->pmid[] = $db->insert_query("privatemessages", $this->pm_insert_data); 697 698 $plugins->run_hooks("datahandler_pm_insert_commit", $this); 699 700 // If PM noices/alerts are on, show! 701 if($recipient['pmnotice'] == 1) 702 { 703 $updated_user = array( 704 "pmnotice" => 2 705 ); 706 $db->update_query("users", $updated_user, "uid='{$recipient['uid']}'"); 707 } 708 709 // Update private message count (total, new and unread) for recipient 710 require_once MYBB_ROOT."/inc/functions_user.php"; 711 update_pm_count($recipient['uid'], 7, $recipient['lastactive']); 712 } 713 714 // Are we replying or forwarding an existing PM? 715 if($pm['pmid']) 716 { 717 if($pm['do'] == "reply" || $pm['do'] == "replyall") 718 { 719 $sql_array = array( 720 'status' => 3, 721 'statustime' => TIME_NOW 722 ); 723 $db->update_query("privatemessages", $sql_array, "pmid={$pm['pmid']} AND uid={$pm['sender']['uid']}"); 724 } 725 elseif($pm['do'] == "forward") 726 { 727 $sql_array = array( 728 'status' => 4, 729 'statustime' => TIME_NOW 730 ); 731 $db->update_query("privatemessages", $sql_array, "pmid={$pm['pmid']} AND uid={$pm['sender']['uid']}"); 732 } 733 } 734 735 // If we're saving a copy 736 if($pm['options']['savecopy'] != 0) 737 { 738 if(isset($recipient_list['to']) && is_array($recipient_list['to']) && count($recipient_list['to']) == 1) 739 { 740 $this->pm_insert_data['toid'] = $uid; 741 } 742 else 743 { 744 $this->pm_insert_data['toid'] = 0; 745 } 746 $this->pm_insert_data['uid'] = (int)$pm['sender']['uid']; 747 $this->pm_insert_data['folder'] = 2; 748 $this->pm_insert_data['status'] = 1; 749 $this->pm_insert_data['receipt'] = 0; 750 751 $plugins->run_hooks("datahandler_pm_insert_savedcopy", $this); 752 753 $db->insert_query("privatemessages", $this->pm_insert_data); 754 755 $plugins->run_hooks("datahandler_pm_insert_savedcopy_commit", $this); 756 757 // Because the sender saved a copy, update their total pm count 758 require_once MYBB_ROOT."/inc/functions_user.php"; 759 update_pm_count($pm['sender']['uid'], 1); 760 } 761 762 // Return back with appropriate data 763 $this->return_values = array( 764 "messagesent" => 1, 765 "pmids" => $this->pmid 766 ); 767 768 $plugins->run_hooks("datahandler_pm_insert_end", $this); 769 770 return $this->return_values; 771 } 772} 773