* Invision Power Services * IP.Board v3.4.5 * Comments library * Last Updated: $Date: 2013-01-10 22:36:45 -0500 (Thu, 10 Jan 2013) $ * * * @author $Author: AndyMillne $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license http://www.invisionpower.com/company/standards.php#license * @package IP.Board * @subpackage Members * @link http://www.invisionpower.com * @since 20th February 2002 * @version $Revision: 11808 $ */ if ( ! defined( 'IN_IPB' ) ) { print "

Incorrect access

You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files."; exit(); } class messengerFunctions { /** * Registry Object Shortcuts * * @var object */ protected $registry; protected $DB; protected $settings; protected $request; protected $lang; protected $member; protected $memberData; protected $cache; protected $caches; /** * Directory Data * * @var array */ public $_dirData = array(); /** * Folder Jump HTML * * @var string */ public $_jumpMenu; /** * Current folder ID * * @var string */ public $_currentFolderID; /** * Boolean flag * * @var boolean */ public $forceMessageToSend = FALSE; /** * More exception data * Should write a custom exception handler really * * @var array */ public $exceptionData = array(); /** * No. messages per page to view * * @var int */ public $messagesPerPage = 20; /** * Folder filter. * Mostly used for 'myconvo' folder currently, but added * here so it's done proper-like * * @var string */ protected $_folderFilter = ''; protected $Parser; /** * Constructor * * @param object $registry Registry object * @return @e void */ public function __construct( ipsRegistry $registry ) { /* Make object */ $this->registry = $registry; $this->DB = $this->registry->DB(); $this->settings =& $this->registry->fetchSettings(); $this->request =& $this->registry->fetchRequest(); $this->lang = $this->registry->getClass('class_localization'); $this->member = $this->registry->member(); $this->memberData =& $this->registry->member()->fetchMemberData(); $this->cache = $this->registry->cache(); $this->caches =& $this->registry->cache()->fetchCaches(); //----------------------------------------- // Need to reset? //----------------------------------------- if ( $this->memberData['msg_count_reset'] ) { $this->memberData['pconversation_filters'] = $this->resetMembersFolderCounts( $this->memberData['member_id'] ); $this->resetMembersTotalTopicCount( $this->memberData['member_id'] ); $this->resetMembersNewTopicCount( $this->memberData['member_id'] ); } /* Load parser */ $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/text/parser.php', 'classes_text_parser' ); $this->Parser = new $classToLoad(); /* Set up parser */ $this->Parser->set( array( 'memberData' => $this->memberData, 'parseBBCode' => 1, 'parseArea' => 'pms', 'parseHtml' => 0, 'parseEmoticons' => 1 ) ); //----------------------------------------- // INIT Folder contents //----------------------------------------- $folderContents = array(); //----------------------------------------- // Do a little set up, do a litle dance, get // down tonight! *boogie* //----------------------------------------- $this->_dirData = $this->explodeFolderData( $this->memberData['pconversation_filters'] ); //----------------------------------------- // Do we have VID? // No, it's just the way we walk! Haha, etc. //----------------------------------------- if ( $this->request['folderID'] AND $this->request['folderID'] ) { $this->_currentFolderID = $this->request['folderID']; } else { /* Got any new messages? If so, show that. If not, show myconvo I'm sure you could have figured that out without this silly comment...*/ $this->_currentFolderID = ( $this->_dirData['new']['count'] ) ? 'new' : 'myconvo'; } //----------------------------------------- // Print folder links //----------------------------------------- foreach( $this->_dirData as $id => $data ) { if ( $data['protected'] AND $id != 'myconvo' ) { continue; } $folderContents[] = ""; } if ( count( $folderContents ) > 1 ) { $this->_jumpMenu = implode( "\n", $folderContents ); } else { $this->_jumpMenu = ''; } } /** * Flood control check * * @return bool (return TRUE for OK to continue, FALSE for flood stopped) Also populates $this->exceptionData[0] with time that next PM can be made */ public function floodControlCheck() { /* Disabled PM flood? */ if ( ! $this->memberData['g_pm_flood_mins'] ) { return TRUE; } /* Forcing a PM, bypass the check */ if ( $this->forceMessageToSend === TRUE ) { return TRUE; } /* Ensure we have a member */ if ( ! $this->memberData['member_id'] ) { return FALSE; } /* Still here? Grab their last sent PM */ $pm = $this->DB->buildAndFetch( array( 'select' => 'MAX(mt_date) as max', 'from' => 'message_topics', 'where' => 'mt_starter_id=' . $this->memberData['member_id'] ) ); if ( $pm['max'] ) { $_check = time() - ( intval( $this->memberData['g_pm_flood_mins'] ) * 60 ); if ( $pm['max'] >= $_check ) { /* Last PM is more recent */ $this->exceptionData = array( 0 => $this->registry->class_localization->getDate( $pm['max'] + ( intval( $this->memberData['g_pm_flood_mins'] ) * 60 ), 'LONG', 1 ) ); return FALSE; } else { /* Last PM is older */ return TRUE; } } return TRUE; } /** * Check number of messages sent today vs your max per day setting * * @return bool Can send more */ public function checkHasHitMax() { if( $this->memberData['g_pm_perday'] ) { $_time = time() - ( 60 * 60 * 24 ); $_total = $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as total', 'from' => 'message_topics', 'where' => 'mt_start_time > ' . $_time . ' AND mt_starter_id=' . $this->memberData['member_id'] ) ); if( $_total['total'] >= $this->memberData['g_pm_perday'] ) { return true; } } return false; } /** * Set a folder filter * Mostly used for myconvo but added here for extensibility in the future * * @param string * @return @e void */ public function addFolderFilter( $filter ) { $this->_folderFilter = $filter; } /** * Fetch new PM notification * * @param int Member ID * @return array Array of data */ public function fetchUnreadNotifications( $memberID ) { //----------------------------------------- // INIT //----------------------------------------- $return = array(); $memberID = intval( $memberID ); $members = array(); $_members = array(); //----------------------------------------- // Fetch messages //----------------------------------------- $this->DB->build( array( 'select' => 'map.*', 'from' => array( 'message_topic_user_map' => 'map' ), 'where' => 'map.map_user_active=1 AND map.map_user_banned=0 AND map.map_has_unread=1 AND map.map_user_id=' . $memberID, 'add_join' => array( array( 'select' => 't.*', 'from' => array( 'message_topics' => 't' ), 'where' => 't.mt_id=map_topic_id', 'type' => 'inner' ), array( 'select' => 'p.*', 'from' => array( 'message_posts' => 'p' ), 'where' => 'p.msg_id=t.mt_last_msg_id', 'type' => 'inner' ) ) ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $return[ $row['msg_date'] . '.' . $row['msg_id'] ] = $row; /* Members to load */ $_members[ $row['mt_starter_id'] ] = $row['mt_starter_id']; $_members[ $row['msg_author_id'] ] = $row['msg_author_id']; } /* Got anything? */ if ( ! count( $return ) ) { return array(); } /* Load 'em */ if ( count( $_members ) ) { $members = IPSMember::load( $_members, 'all' ); } /* Parse 'em */ if ( count( $members ) ) { foreach( $members as $id => $data ) { $members[ $id ] = IPSMember::buildDisplayData( $data ); } } /* Add 'em */ foreach( $return as $sortID => $data ) { /* membahs */ $data['starterData'] = $members[ $data['mt_starter_id'] ]; $data['authorData'] = $members[ $data['msg_author_id'] ]; /* Format message */ $data['msg_post'] = $this->Parser->stripAllTags( $data['msg_post'] ); //$this->_formatMessageForDisplay( $data['msg_post'] ); /* Type of PM */ if ( $data['msg_id'] == $data['mt_first_msg_id'] ) { $data['_type'] = 'new'; } else { $data['_type'] = 'reply'; } $return[ $sortID ] = $data; } /* Reverse sort it (latest first) */ krsort( $return ); /* Return 'em */ return $return; } /** * Search messages * Searches messages. It really does. * * @param int Member ID who be searching! * @param string Words to search (probably tainted at this point, so be careful!) * @param int Offset start * @param int Number of results to return * @param array Array of folders to search (send nothing to search all) * @return array array( 'totalMatches' => int, 'results' => Array of topics that match the search words ) */ public function searchMessages( $memberID, $words, $start=0, $end=50, $folders=array() ) { //----------------------------------------- // INIT //----------------------------------------- $results = array( 'totalMatches' => 0, 'results' => array() ); $_memberIDs = array(); $_members = array(); $_results = array(); //----------------------------------------- // Load up library //----------------------------------------- $file = IPSLib::getAppDir( 'members' ) . '/sql/messengerSearch_' . strtolower( $this->settings['sql_driver'] ) . '.php'; if ( ! is_file( $file ) ) { return $results; } //----------------------------------------- // Fetch 'em //----------------------------------------- $classToLoad = IPSLib::loadLibrary( $file, 'messengerSearch', 'members' ); $search = new $classToLoad( $this->registry ); $search->execute( $memberID, $words, $start, $end, $folders ); $results['totalMatches'] = $search->fetchTotalRows(); $_results = $search->fetchResults(); //----------------------------------------- // Add in member data... //----------------------------------------- if ( ! count( $_results ) ) { return $results; } foreach( $_results as $mtID => $mtData ) { $_memberIDs[ $mtData['mt_starter_id'] ] = $mtData['mt_starter_id']; $_memberIDs[ $mtData['mt_to_member_id'] ] = $mtData['mt_to_member_id']; } /* Load members */ if ( count( $_memberIDs ) ) { $_members = IPSMember::load( array_keys( $_memberIDs ), 'all' ); } /* Let's sort out the ignore & photos at once for everyone */ if( count($_members) ) { foreach( $_members as $_mid => $_mdata ) { $_mdata['_canBeBlocked'] = IPSMember::isIgnorable( $_mdata['member_group_id'], $_mdata['mgroup_others'], 'pm' ); $_members[ $_mid ] = IPSMember::buildProfilePhoto( $_mdata ); } } /* Final result parse */ foreach( $_results as $mtID => $mtData ) { $mtData['_starterMemberData'] = $_members[ $mtData['mt_starter_id'] ]; $mtData['_toMemberData'] = $_members[ $mtData['mt_to_member_id'] ]; $mtData['_lastMsgAuthor'] = $_members[ $mtData['msg_author_id'] ]; $mtData['_folderName'] = $this->_dirData[ $mtData['map_folder_id'] ]['real']; $results['results'][ $mtID ] = $mtData; } return $results; } /** * Block / unblock a member * * @param int Member ID to block * @param int Member ID who is attempting to block * @param int Topic ID to apply block to * @param boolean TRUE is block, FALSE is unblock * @return @e void * * * Exception Codes: * NO_PERMISSION User does not have permission to access this topic * NO_BLOCK_PERMISSION User does not have the ability to block anyone * CANNOT_BLOCK_STARTER Cannot (un)block the topic starter, that's just plain mean! * CANNOT_BLOCK_USER Cannot (un)block the member because they either are not involved with that topic or they cannot be blocked * */ public function toggleTopicBlock( $blockID, $blockerID, $topicID, $block ) { $topicData = $this->fetchTopicData( $topicID ); $_members = IPSMember::load( array( $blockID, $blockerID ), 'extendedProfile,groups' ); $blockMemberData = $_members[ $blockID ]; $blockerMemberData = $_members[ $blockerID ]; $currentParticipants = $this->fetchTopicParticipants( $topicID ); /* Can access this topic? */ if ( $this->canAccessTopic( $blockerMemberData['member_id'], $topicData, $currentParticipants, TRUE ) !== TRUE ) { throw new Exception( 'NO_PERMISSION' ); } /* Topic starter? */ if ( $topicData['mt_starter_id'] == $blockID ) { throw new Exception( 'CANNOT_BLOCK_STARTER' ); } /* Does the person we are attempting to block exist? */ if ( ! isset($currentParticipants[ $blockID ]) ) { throw new Exception( 'CANNOT_BLOCK_USER' ); } /* Can they be blocked? */ if ( IPSMember::isIgnorable( $blockMemberData['member_group_id'], $blockMemberData['mgroup_others'], 'pm' ) !== TRUE ) { throw new Exception( 'CANNOT_BLOCK_USER' ); } /* Can we actually block anyone? */ if ( ( $topicData['mt_starter_id'] != $blockerID ) AND ( ! $blockerMemberData['g_is_supmod'] ) ) { throw new Exception( 'NO_BLOCK_PERMISSION' ); } /* Ok... */ if ( $block === TRUE ) { $this->DB->update( 'message_topic_user_map', array( 'map_user_active' => 0, 'map_user_banned' => 1 ), 'map_user_id=' . $blockID . ' AND map_topic_id=' . $topicID . ' AND map_user_active=1' ); } else { $this->DB->update( 'message_topic_user_map', array( 'map_user_active' => 1, 'map_user_banned' => 0 ), 'map_user_id=' . $blockID . ' AND map_topic_id=' . $topicID . ' AND map_user_banned=1' ); $this->DB->update( 'message_topics', array( 'mt_to_count' => count( $currentParticipants ) - 1 ), 'mt_id=' . $topicID ); } $this->resetMembersNewTopicCount( $blockID ); $this->resetMembersTotalTopicCount( $blockID ); $this->resetMembersFolderCounts( $blockID ); } /** * Toggles notifications * * @param int Owner Member ID * @param array Array of IDs to toggle read / unread * @param boolean TRUE notify on / FALSE notify off * @return bool * * * Exception Codes: * NO_SUCH_MEMBER: The member ID does not exist * NO_IDS_SELECTED No IDs to move (empty id array) * */ public function toggleNotificationStatus( $memberID, $ids, $notifyStatus='toggle' ) { //----------------------------------------- // Grab data //----------------------------------------- $memberData = IPSMember::load( $memberID, 'groups,extendedProfile' ); if ( ! $memberData['member_id'] ) { throw new Exception( "NO_SUCH_MEMBER"); } //----------------------------------------- // Check... //----------------------------------------- if ( ! is_array( $ids ) OR ! count( $ids ) ) { throw new Exception("NO_IDS_SELECTED"); } $idString = implode( ",", $ids ); //----------------------------------------- // Toggle... //----------------------------------------- if ( $notifyStatus === TRUE ) { $this->DB->update( 'message_topic_user_map', array( 'map_ignore_notification' => 0 ), "map_user_id=".$memberData['member_id']." AND map_topic_id IN ($idString)" ); } else if ( $notifyStatus == 'toggle' ) { $_on = array(); $_off = array(); $this->DB->build( array( 'select' => '*', 'from' => 'message_topic_user_map', 'where' => "map_user_id=".$memberData['member_id']." AND map_topic_id IN ($idString)" ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { if ( $row['map_ignore_notification'] ) { $_on[] = $row['map_topic_id']; } else { $_off[] = $row['map_topic_id']; } } if ( count( $_on ) ) { $this->DB->update( 'message_topic_user_map', array( 'map_ignore_notification' => 0 ), "map_user_id=".$memberData['member_id']." AND map_topic_id IN (" . implode(",",$_on). ")" ); } if ( count( $_off ) ) { $this->DB->update( 'message_topic_user_map', array( 'map_ignore_notification' => 1 ), "map_user_id=".$memberData['member_id']." AND map_topic_id IN (" . implode(",",$_off). ")" ); } } else { $this->DB->update( 'message_topic_user_map', array( 'map_ignore_notification' => 1 ), "map_user_id=".$memberData['member_id']." AND map_topic_id IN ($idString)" ); } /* Recount */ $this->resetMembersFolderCounts( $memberData['member_id'] ); return TRUE; } /** * Toggles read/unread status * * @param int Owner Member ID * @param array Array of IDs to toggle read / unread * @param boolean TRUE mark as read / FALSE mark as unread * @return bool * * * Exception Codes: * NO_SUCH_MEMBER: The member ID does not exist * NO_IDS_SELECTED No IDs to move (empty id array) * */ public function toggleReadStatus( $memberID, $ids, $markAsRead ) { //----------------------------------------- // Grab data //----------------------------------------- $memberData = IPSMember::load( $memberID, 'groups,extendedProfile' ); if ( ! $memberData['member_id'] ) { throw new Exception( "NO_SUCH_MEMBER"); } //----------------------------------------- // Check... //----------------------------------------- if ( ! is_array( $ids ) OR ! count( $ids ) ) { throw new Exception("NO_IDS_SELECTED"); } $idString = implode( ",", $ids ); //----------------------------------------- // Mark as read... //----------------------------------------- if ( $markAsRead === TRUE ) { $this->DB->update( 'message_topic_user_map', array( 'map_has_unread' => 0 ), "map_user_id=".$memberData['member_id']." AND map_topic_id IN ($idString)" ); } else { $this->DB->update( 'message_topic_user_map', array( 'map_has_unread' => 1 ), "map_user_id=".$memberData['member_id']." AND map_topic_id IN ($idString)" ); } /* Recount */ $this->resetMembersFolderCounts( $memberData['member_id'] ); $this->resetMembersNewTopicCount( $memberData['member_id'] ); return TRUE; } /** * Add participants to a topic * * @param int Topic ID * @param array Array of IDs to invite * @param int Member adding them (ie you) * @return mixed bool or exception * * * EXCEPTION CODES: * NO_PERMISSION User does not have permission to access this topic * NOT_ALL_INVITE_USERS_EXIST Not all the users you've invited exist * INVITE_USERS_BLOCKED Some of the users you've invited are blocked * NO_ONE_TO_INVITE No one left to invite * CANT_INVITE_SELF: The sender is in the invite list * CANT_INVITE_RECIPIENT: The main recipient is in the invite list * CANT_INVITE_RECIPIENT_EXIST: One or more recipients are already participating in this conversation * */ public function addTopicParticipants( $topicID, $invitedUsers, $readingMemberID ) { //----------------------------------------- // INIT //----------------------------------------- $topicData = $this->fetchTopicData( $topicID ); $memberData = IPSMember::load( intval( $readingMemberID ), 'extendedProfile,groups' ); $currentParticipants = $this->fetchTopicParticipants( $topicID ); //----------------------------------------- // Can access this topic? //----------------------------------------- if ( $this->canAccessTopic( $memberData['member_id'], $topicData, $currentParticipants ) !== TRUE ) { throw new Exception( 'NO_PERMISSION' ); } //----------------------------------------- // Build invited users //----------------------------------------- $invitedUsersData = $this->checkAndReturnInvitedUsers( $invitedUsers, $memberData, count($currentParticipants) ); if ( isset($invitedUsersData[ $topicData['mt_starter_id'] ]) ) { if ( $memberData['member_id'] == $topicData['mt_starter_id'] ) { throw new Exception( 'CANT_INVITE_SELF' ); } } if ( isset($invitedUsersData[ $topicData['mt_to_member_id'] ]) OR isset($invitedUsersData[ $topicData['mt_starter_id'] ]) ) { throw new Exception( 'CANT_INVITE_RECIPIENT_EXIST' ); } //----------------------------------------- // Knock out ones that are already participated // At this point $currentParticipants contains non-active // participants. This is OK as we don't want the ability // to keep re-inviting those who choose to leave. //----------------------------------------- $existInBoth = array_intersect( array_keys( $currentParticipants ), array_keys( $invitedUsersData ) ); if ( count( $existInBoth ) ) { foreach( $existInBoth as $id ) { unset( $invitedUsersData[ $id ] ); } } //----------------------------------------- // Anything left? //----------------------------------------- if ( ! count( $invitedUsersData ) ) { throw new Exception( 'NO_ONE_TO_INVITE' ); } //----------------------------------------- // Update the topic // Now we can strip non-active participants //----------------------------------------- $__topicParticipants = array_merge( array_keys( $this->_stripNonActiveParticipants( $currentParticipants ) ), array_keys( $invitedUsersData ) ); /* Fix up so they're unique and indexed by member ID */ if ( is_array( $__topicParticipants ) AND count ( $__topicParticipants ) ) { foreach( $__topicParticipants as $_mid ) { $_topicParticipants[ $_mid ] = $_mid; } } /* Remove topic starter */ unset( $_topicParticipants[ $topicData['mt_starter_id'] ] ); /* Remove recipient */ unset( $_topicParticipants[ $topicData['mt_to_member_id'] ] ); $this->DB->update( 'message_topics', array( 'mt_invited_members' => serialize( $_topicParticipants ), 'mt_to_count' => count( $_topicParticipants ) + 1 ), 'mt_id=' . $topicID ); //----------------------------------------- // Add the users to the invite tree //----------------------------------------- foreach( $invitedUsersData as $id => $toMember ) { $this->DB->insert( 'message_topic_user_map', array( 'map_user_id' => $id, 'map_topic_id' => $topicID, 'map_folder_id' => 'myconvo', 'map_user_active' => 1, 'map_has_unread' => 1, 'map_user_banned' => 0, 'map_read_time' => 0, 'map_left_time' => 0, 'map_ignore_notification' => 0, 'map_last_topic_reply' => $topicData['mt_last_post_time'] ) ); $new_vdir = $this->rebuildFolderCount( $toMember['member_id'], array( 'myconvo' => 'plus:1', 'new' => 'plus:1' ), TRUE, array( 'core' => array( 'msg_count_total' => 'plus:1', 'msg_count_new' => 'plus:1' ) ) ); //----------------------------------------- // Notifications library //----------------------------------------- $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . '/sources/classes/member/notifications.php', 'notifications' ); $notifyLibrary = new $classToLoad( $this->registry ); $toMember['language'] = $toMember['language'] == "" ? IPSLib::getDefaultLanguage() : $toMember['language']; IPSText::getTextClass('email')->getTemplate( "personal_convo_invite", $toMember['language'] ); IPSText::getTextClass('email')->buildMessage( array( 'NAME' => $toMember['members_display_name'], 'POSTER' => $memberData['members_display_name'], 'TITLE' => $topicData['mt_title'], 'LINK' => "?app=members&module=messaging§ion=view&do=showConversation&topicID={$topicID}" ) ); IPSText::getTextClass('email')->subject = sprintf( IPSText::getTextClass('email')->subject, $this->registry->output->buildSEOUrl( 'showuser=' . $memberData['member_id'], 'public', $memberData['members_seo_name'], 'showuser' ), $memberData['members_display_name'], $this->registry->output->buildUrl( "app=members&module=messaging§ion=view&do=showConversation&topicID={$topicID}", 'public' ) ); $notifyLibrary->setMember( $toMember ); $notifyLibrary->setFrom( $memberData ); $notifyLibrary->setNotificationKey( 'invite_private_message' ); $notifyLibrary->setNotificationUrl( $this->registry->output->buildUrl( "app=members&module=messaging§ion=view&do=showConversation&topicID={$topicID}", 'public' ) ); $notifyLibrary->setNotificationText( IPSText::getTextClass('email')->message ); $notifyLibrary->setNotificationTitle( IPSText::getTextClass('email')->subject ); try { $notifyLibrary->sendNotification(); } catch( Exception $e ){} } return TRUE; } /** * Delete personal topics * * @param int Owner Member ID * @param array Array of IDs to delete * @param string [ Raw SQL to query on when selecting messages to delete, optional ] * @param bool Force hard delete * @return mixed bool or exception * * * Exception Codes: * NO_SUCH_MEMBER: The member ID does not exist * NO_IDS_TO_DELETE: No IDs to delete (empty id array) * */ public function deleteTopics( $memberID, $ids, $rawSQL=NULL, $hardDelete=false ) { //----------------------------------------- // Grab data //----------------------------------------- $memberData = IPSMember::load( $memberID, 'groups,extendedProfile' ); $final_ids = array(); $final_mts = array(); $starter = array(); $wanttoleave = array(); $participater = array(); $system = array(); $deleted_ids = array(); $attach_ids = array(); $toHardDelete = array(); $membahs = array( $memberID => $memberID ); if ( ! $memberData['member_id'] ) { throw new Exception( "NO_SUCH_MEMBER"); } //----------------------------------------- // Check... //----------------------------------------- if ( ! is_array( $ids ) OR ! count( $ids ) ) { throw new Exception("NO_IDS_TO_DELETE"); } //----------------------------------------- // Finish raw SQL //----------------------------------------- if ( $rawSQL !== NULL ) { $rawSQL = ' AND ' . $rawSQL; } $idString = implode( ",", IPSLib::cleanIntArray( $ids ) ); //----------------------------------------- // Grab all affected members... //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'message_topic_user_map', 'where' => 'map_topic_id IN(' . $idString . ')' ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $membahs[ $row['map_user_id'] ] = $row['map_user_id']; } /* Flag for recount */ $this->_flagForCountReset( $membahs ); //----------------------------------------- // Get messages? //----------------------------------------- $this->DB->build( array( 'select' => 'mt.*', 'from' => array( 'message_topics' => 'mt' ), 'where' => "mt.mt_id IN(" . $idString . ")" . $rawSQL, 'add_join' => array( array( 'select' => 'map.*', 'from' => array( 'message_topic_user_map' => 'map' ), 'where' => 'map.map_topic_id=mt.mt_id AND map.map_user_id=' . $memberData['member_id'], 'type' => 'left' ) ) ) ); $this->DB->execute(); /* Build up topics to remove */ while ( $i = $this->DB->fetch() ) { /* Starter? */ if ( $i['mt_starter_id'] == $memberData['member_id'] ) { $starter[ $i['mt_id'] ] = $i; $allTopics[ $i['mt_id'] ] = $i; } else if ( $i['map_user_id'] AND $i['mt_is_system'] ) { $system[ $i['mt_id'] ] = $i; $allTopics[ $i['mt_id'] ] = $i; } else if ( $i['map_user_id'] ) { $wanttoleave[ $i['mt_id'] ] = $i; $allTopics[ $i['mt_id'] ] = $i; } } //----------------------------------------- // Are we forcing a hard delete //----------------------------------------- if( $hardDelete ) { $idsForAttachments = array(); $this->DB->build( array( 'select' => 'msg_id', 'from' => 'message_posts', 'where' => 'msg_topic_id IN ('. $idString . ')' ) ); $this->DB->execute(); while( $r = $this->DB->fetch() ) { $idsForAttachments[] = $r['msg_id']; } /* Delete Topics */ $this->DB->delete( 'message_topics', 'mt_id IN ('. $idString . ')' . $rawSQL ); /* Delete Posts */ $this->DB->delete( 'message_posts', 'msg_topic_id IN ('. $idString . ')' . $rawSQL ); /* Delete mappings */ $this->DB->delete( 'message_topic_user_map', 'map_topic_id IN ('. $idString . ')' . $rawSQL ); //----------------------------------------- // Delete attachments //----------------------------------------- $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'core' ) . '/sources/classes/attach/class_attach.php', 'class_attach' ); $class_attach = new $classToLoad( $this->registry ); $class_attach->type = 'msg'; $class_attach->init(); $class_attach->bulkRemoveAttachment( $idsForAttachments ); } else { //----------------------------------------- // System PMs, delete everything to do with them //----------------------------------------- if ( count( $system ) ) { /* Delete Topics */ $this->DB->delete( 'message_topics', 'mt_id IN ('. implode( ',', array_keys( $system ) ) . ')' ); /* Delete Posts */ $this->DB->delete( 'message_posts', 'msg_topic_id IN ('. implode( ',', array_keys( $system ) ) . ')' ); /* Delete mappings */ $this->DB->delete( 'message_topic_user_map', 'map_topic_id IN ('. implode( ',', array_keys( $system ) ) . ')' ); } //----------------------------------------- // Ones I started, soft delete //----------------------------------------- if ( count( $starter ) ) { /* First step: Soft delete them */ $this->DB->update( 'message_topics', 'mt_to_count=mt_to_count-1,mt_is_deleted=1', 'mt_id IN (' . implode( ',', array_keys( $starter ) ) . ')', FALSE, TRUE ); /* Update mappings for member */ $this->DB->update( 'message_topic_user_map', array( 'map_user_active' => 0 ), 'map_user_id=' . $memberData['member_id'] . ' AND map_topic_id IN ('. implode( ',', array_keys( $starter ) ) . ')' ); } //----------------------------------------- // Ones that I just want to leave.. //----------------------------------------- if ( count( $wanttoleave ) ) { /* De-activate participant row */ $this->DB->update( 'message_topic_user_map', array( 'map_user_active' => 0 ), 'map_user_id=' . $memberData['member_id'] . ' AND map_topic_id IN ('. implode( ',', array_keys( $wanttoleave ) ) . ')' ); /* Update counts */ $this->DB->update( 'message_topics', 'mt_to_count=mt_to_count-1', 'mt_id IN (' . implode( ',', array_keys( $wanttoleave ) ) . ')', FALSE, TRUE ); } //----------------------------------------- // Right. Now lets find all (partici)pantless topics //----------------------------------------- if ( count( $allTopics ) ) { $this->DB->build( array( 'select' => 'mt.*', 'from' => array( 'message_topics' => 'mt' ), 'where' => "mt.mt_id IN(" . implode( ',', array_keys( $allTopics ) ) . ")", 'add_join' => array( array( 'select' => 'map.*', 'from' => array( 'message_topic_user_map' => 'map' ), 'where' => 'map.map_topic_id=mt.mt_id AND map.map_user_active=1', 'type' => 'left' ) ) ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { /* Not got -any- mapping? */ if ( ! $row['map_user_id'] ) { $toHardDelete[ $row['mt_id'] ] = $row; } } $idsForAttachments = array(); $this->DB->build( array( 'select' => 'msg_id', 'from' => 'message_posts', 'where' => 'msg_topic_id IN ('. implode( ',', array_keys( $allTopics ) ) . ')' ) ); $this->DB->execute(); while( $r = $this->DB->fetch() ) { $idsForAttachments[] = $r['msg_id']; } } //----------------------------------------- // Hard delete MT topics //----------------------------------------- if ( count($toHardDelete) ) { $this->DB->delete( 'message_topics', "mt_id IN (".implode( ',', array_keys( $toHardDelete ) ).")" ); //----------------------------------------- // Delete attachments //----------------------------------------- $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'core' ) . '/sources/classes/attach/class_attach.php', 'class_attach' ); $class_attach = new $classToLoad( $this->registry ); $class_attach->type = 'msg'; $class_attach->init(); $class_attach->bulkRemoveAttachment( array_keys( $idsForAttachments ) ); //----------------------------------------- // Delete posts and mapping //----------------------------------------- $this->DB->delete( 'message_posts', 'msg_topic_id IN (' . implode( ',', array_keys( $toHardDelete ) ) . ')' ); $this->DB->delete( 'message_topic_user_map', 'map_topic_id IN (' . implode( ',', array_keys( $toHardDelete ) ) . ')' ); } } return TRUE; } /** * Moves messages into another folder * * @param int Owner Member ID * @param array Array of IDs to move * @param string 'id' of folder to move to * @return mixed bool or exception * * * Exception Codes: * NO_SUCH_MEMBER: The member ID does not exist * NO_SUCH_FOLDER: Folder you are attemting to move to does not exist * NO_IDS_TO_MOVE No IDs to move (empty id array) * */ public function moveTopics( $memberID, $ids, $toFolderID ) { //----------------------------------------- // Grab data //----------------------------------------- $memberData = IPSMember::load( $memberID, 'groups,extendedProfile' ); if ( ! $memberData['member_id'] ) { throw new Exception( "NO_SUCH_MEMBER"); } //----------------------------------------- // Check... //----------------------------------------- if ( ! is_array( $ids ) OR ! count( $ids ) ) { throw new Exception("NO_IDS_TO_MOVE"); } $idString = implode( ",", $ids ); //----------------------------------------- // First off, get dir data //----------------------------------------- $folders = $this->explodeFolderData( $memberData['pconversation_filters'] ); //----------------------------------------- // Got a folder with that ID? //----------------------------------------- if ( ! $folders[ $toFolderID ] ) { throw new Exception("NO_SUCH_FOLDER"); } //----------------------------------------- // Move the messages... //----------------------------------------- $this->DB->update( 'message_topic_user_map', array( 'map_folder_id' => $toFolderID ), "map_user_id=".$memberData['member_id']." AND map_topic_id IN ($idString)" ); /* Recount */ $this->resetMembersFolderCounts( $memberData['member_id'] ); return TRUE; } /** * Edits a message * * @param int FROM Member ID * @param int Topic ID * @param int Msg ID * @param string Message Content * @return mixed TRUE or FALSE or Exception * * * Exception Codes: * MSG_CONTENT_EMPTY: The 'msgContent' varable is empty * NO_SUCH_TOPIC: No such topic was found * NO_PERMISSION: No permission to read/reply * NO_EDIT_PERMISSION: No permission to edit * TOPIC_HAS_BEEN_DELETED: Topic has been "deleted" by the topic starter * */ public function sendEdit( $fromMemberID, $topicID, $msgID, $msgContent, $options=array() ) { //----------------------------------------- // INIT //----------------------------------------- $topicData = array(); $messageData = array(); $fromMemberData = array(); $remapData = array(); //----------------------------------------- // Check content //----------------------------------------- if ( ! $msgContent ) { throw new Exception( 'MSG_CONTENT_EMPTY' ); } //----------------------------------------- // Format content //----------------------------------------- try { $msgContent = $this->_formatMessageForSaving( $msgContent ); } catch( Exception $error ) { throw new Exception( $error->getMessage() ); } /* Fetch topic data */ $topicData = $this->DB->buildAndFetch( array( 'select' => '*', 'from' => 'message_topics', 'where' => 'mt_id=' . $topicID ) ); if ( ! $topicData['mt_id'] ) { throw new Exception( 'NO_SUCH_TOPIC' ); } if ( $topicData['mt_is_deleted'] ) { throw new Exception( 'TOPIC_HAS_BEEN_DELETED' ); } /* Fetch remap Data */ $this->DB->build( array( 'select' => '*', 'from' => 'message_topic_user_map', 'where' => 'map_topic_id=' . $topicID ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $remapData[ $row['map_user_id'] ] = $row; } /* Got us? */ if ( $this->canAccessTopic( $fromMemberID, $topicData ) !== TRUE ) { throw new Exception( "NO_PERMISSION" ); } /* Fetch message data */ $messageData = $this->fetchMessageData( $topicID, $msgID ); /* Reset Post Key */ $options['postKey'] = ( $messageData['msg_post_key'] ) ? $messageData['msg_post_key'] : md5(microtime()); //----------------------------------------- // Load member data //----------------------------------------- $memberData = IPSMember::load( array_keys( $remapData ), 'groups,extendedProfile' ); $fromMemberData = $memberData[ $fromMemberID ]; /* Can edit? */ if ( $topicData['mt_is_deleted'] OR $this->_conversationCanEdit( $messageData, $topicData, $fromMemberData ) !== TRUE ) { throw new Exception( "NO_EDIT_PERMISSION" ); } //----------------------------------------- // Update the post... //----------------------------------------- $this->DB->update( 'message_posts', array( 'msg_post' => $msgContent ), 'msg_id=' . $msgID ); /* Fetch attachment count */ $count = intval( $this->_makeAttachmentsPermanent( $options['postKey'], $msgID, $topicID ) ); if ( $count ) { $this->DB->update( 'message_topics', array( 'mt_hasattach' => $count ), 'mt_id=' . $topicID ); } return TRUE; } /** * Sends a reply to a PM * * @param int FROM Member ID * @param int Topic ID * @param string Message Content * @return mixed Msg ID or exception * * * Exception Codes: * MSG_CONTENT_EMPTY: The 'msgContent' varable is empty * NO_SUCH_TOPIC: No such topic was found * NO_PERMISSION: No permission to read/reply * TOPIC_HAS_BEEN_DELETED: Topic has been "deleted" by the topic starter * TOPIC_IS_SYSTEM: This is a system topic and cannot be replied to * FROM_USER_BLOCKED: Cannot send as starter has blocked fromMemberID * */ public function sendReply( $fromMemberID, $topicID, $msgContent, $options=array() ) { //----------------------------------------- // INIT //----------------------------------------- $topicData = array(); $fromMemberData = array(); $remapData = array(); $options['postKey'] = ( $options['postKey'] ) ? $options['postKey'] : md5(microtime()); //----------------------------------------- // Check content //----------------------------------------- if ( ! $msgContent ) { throw new Exception( 'MSG_CONTENT_EMPTY' ); } //----------------------------------------- // Format content //----------------------------------------- try { $msgContent = $this->_formatMessageForSaving( $msgContent ); } catch( Exception $error ) { throw new Exception( $error->getMessage() ); } /* Fetch topic data */ $topicData = $this->DB->buildAndFetch( array( 'select' => '*', 'from' => 'message_topics', 'where' => 'mt_id=' . $topicID ) ); if ( ! $topicData['mt_id'] ) { throw new Exception( 'NO_SUCH_TOPIC' ); } if ( $topicData['mt_is_deleted'] ) { throw new Exception( 'TOPIC_HAS_BEEN_DELETED' ); } if ( $topicData['mt_is_system'] ) { throw new Exception( 'TOPIC_IS_SYSTEM' ); } /* Fetch remap Data */ $this->DB->build( array( 'select' => '*', 'from' => 'message_topic_user_map', 'where' => 'map_user_active=1 AND map_topic_id=' . $topicID ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $remapData[ $row['map_user_id'] ] = $row; } /* Got us? */ if ( ! isset($remapData[ $fromMemberID ]) ) { throw new Exception( "NO_PERMISSION" ); } //----------------------------------------- // Load member data //----------------------------------------- $memberData = IPSMember::load( array_keys( $remapData ), 'groups,extendedProfile' ); $fromMemberData = $memberData[ $fromMemberID ]; //----------------------------------------- // Has the 'to' use blocked us? //----------------------------------------- if ( count( $this->blockedByUser( $fromMemberData, $topicData['mt_to_member_id'] ) ) ) { if ( $this->forceMessageToSend !== TRUE ) { $this->exceptionData = array( 0 => $memberData[ $topicData['mt_to_member_id'] ]['members_display_name'] ); throw new Exception( 'FROM_USER_BLOCKED' ); } } //----------------------------------------- // Add the post... //----------------------------------------- $_messagePostData = array( 'msg_date' => time(), 'msg_topic_id' => $topicID, 'msg_post' => $msgContent, 'msg_post_key' => md5( microtime() ), 'msg_author_id' => $fromMemberData['member_id'], 'msg_ip_address' => $this->member->ip_address ); /* Data Hook Location */ IPSLib::doDataHooks( $_messagePostData, 'messengerSendReplyData' ); $this->DB->insert( 'message_posts', $_messagePostData ); $msg_id = $this->DB->getInsertId(); //----------------------------------------- // Update topic //----------------------------------------- $this->DB->update( 'message_topics', array( 'mt_last_post_time' => IPS_UNIX_TIME_NOW, 'mt_hasattach' => ($topicData['mt_hasattach'] + intval( $this->_makeAttachmentsPermanent( $options['postKey'], $msg_id, $topicID ) )), 'mt_replies' => $topicData['mt_replies'] + 1, 'mt_last_msg_id' => $msg_id ), 'mt_id=' . $topicID ); /* Update mapping */ $this->DB->update( 'message_topic_user_map', array( 'map_has_unread' => 1 ), 'map_topic_id=' . $topicID . ' AND map_user_id != ' . $fromMemberID ); /* Update last reply time */ $this->DB->update( 'message_topic_user_map', array( 'map_last_topic_reply' => IPS_UNIX_TIME_NOW ), 'map_topic_id=' . $topicID ); //----------------------------------------- // Update receipients.... //----------------------------------------- foreach ($remapData as $memberID => $_remapData ) { $toMember = $memberData[ $memberID ]; //----------------------------------------- // Disabled messenger //----------------------------------------- if( $toMember['members_disable_pm'] ) { continue; } //----------------------------------------- // Let them know there's a new PM reply //----------------------------------------- if ( $memberID != $fromMemberID ) { /** * @link http://community.invisionpower.com/tracker/issue-37272-pm-notification-counter-not-increasing/page__catfilter__1 */ /*if ( $_remapData['map_ignore_notification'] ) { IPSMember::save( $memberID, array( 'core' => array( 'msg_count_reset' => 1 ) ) ); } else { IPSMember::save( $memberID, array( 'core' => array( 'msg_count_new' => intval( $this->getPersonalTopicsCount( $memberID, 'new' ) ), 'msg_count_reset' => 1 ) ) ); }*/ if ( empty($_remapData['map_ignore_notification']) && empty($_remapData['map_has_unread']) ) { IPSMember::save( $memberID, array( 'core' => array( 'msg_count_new' => 'plus:1', 'msg_count_reset' => 1 ) ) ); } } //----------------------------------------- // Notifications library //----------------------------------------- if ( $memberID != $fromMemberID AND ! $_remapData['map_ignore_notification'] ) { $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . '/sources/classes/member/notifications.php', 'notifications' ); $notifyLibrary = new $classToLoad( $this->registry ); $buildMessage = array( 'NAME' => $toMember['members_display_name'], 'POSTER' => $fromMemberData['members_display_name'], 'TITLE' => $topicData['mt_title'], 'TEXT' => $msgContent, 'LINK' => "?app=members&module=messaging§ion=view&do=findMessage&topicID={$topicID}&msgID=__firstUnread__" ); IPSText::getTextClass('email')->setPlainTextTemplate( IPSText::getTextClass('email')->getTemplate( 'personal_convo_new_reply', $toMember['language'] ) ); IPSText::getTextClass('email')->buildPlainTextContent( $buildMessage ); IPSText::getTextClass('email')->subject = sprintf( IPSText::getTextClass('email')->subject, $this->registry->output->buildSEOUrl( 'showuser=' . $fromMemberData['member_id'], 'public', $fromMemberData['members_seo_name'], 'showuser' ), $fromMemberData['members_display_name'], $this->registry->output->buildUrl( "app=members&module=messaging§ion=view&do=findMessage&topicID={$topicID}&msgID=__firstUnread__", 'public' ) ); $notifyLibrary->setMember( $toMember ); $notifyLibrary->setFrom( $fromMemberData ); $notifyLibrary->setNotificationKey( 'reply_private_message' ); $notifyLibrary->setNotificationUrl( $this->registry->output->buildUrl( "app=members&module=messaging§ion=view&do=findMessage&topicID={$topicID}&msgID=__firstUnread__", 'public' ) ); $notifyLibrary->setNotificationText( IPSText::getTextClass('email')->getPlainTextContent() ); $notifyLibrary->setNotificationTitle( IPSText::getTextClass('email')->subject ); $notifyLibrary->setNotificationHtml( IPSText::getTextClass('email')->buildHtmlContent( $buildMessage ) ); $notifyLibrary->setMetaData( array( 'meta_area' => 'pm', 'meta_id' => $topicID, 'meta_app' => 'members' ) ); try { $notifyLibrary->sendNotification(); } catch( Exception $e ){} } } return $msg_id; } /** * Sends a new personal message. Very simple. * * @param int TO Member ID * @param int FROM Member ID * @param array Array of InviteUser Names (display name) * @param string Message Title * @param string Message Content * @param array Options array[ 'isSystem' (if true, then user will have no record of sending this PM) postKey, 'isDraft', 'sendMode' (invite/copy), 'topicID' ] If a topicID is passed, it's presumed that it was a draft.... * @return mixed TRUE or FALSE or Exception * * * Exception Codes: * TOPIC_ID_NOT_EXISTS: Topic ID does not exist (re-sending a draft) * NOT_ALL_INVITE_USERS_EXIST: Not all invite users exist (check $this->exceptionData for a list of names) * NOT_ALL_INVITE_USERS_CAN_PM: Not all invite users can PM (check $this->exceptionData for a list of names) * INVITE_USERS_BLOCKED: Some invite users have been blocked (check $this->exceptionData for a list of names) * TO_USER_DOES_NOT_EXIST: The 'to' user ID does not exist * FROM_USER_DOES_NOT_EXIST: The 'from' user ID does not exist * TO_USER_CANNOT_USE_PM: The 'to' user does not have access to PM system * TO_USER_FULL: The 'to' user cannot accept any more PMs (inbox full) * FROM_USER_BLOCKED: The 'from' user has been blocked by the 'to' user * CANNOT_SAVE_TO_SENT_FOLDER: The 'from' user does not have space to store a copy of the message in their sent folder * MSG_TITLE_EMPTY: The 'msgTitle' variable is empty * MSG_CONTENT_EMPTY: The 'msgContent' varable is empty * CANT_SEND_TO_SELF: The main recipient and sender are the same * CANT_INVITE_SELF: The sender is in the invite list * CANT_INVITE_RECIPIENT: The main recipient is in the invite list * FLOOD_STOP Flood control will not allow this message to send * */ public function sendNewPersonalTopic( $toMemberID, $fromMemberID, $inviteUsers, $msgTitle, $msgContent, $options=array() ) { //----------------------------------------- // INIT //----------------------------------------- $toMemberData = array(); $fromMemberData = array(); $inviteUsersData = array(); $isDraft = ( $options['isDraft'] ) ? TRUE : FALSE; $isCopyTo = ( $options['sendMode'] == 'copy' ) ? TRUE : FALSE; $isSystem = ( $options['isSystem'] === TRUE || $options['isSystem'] == 1 ) ? 1 : 0; $options['postKey'] = ( $options['postKey'] ) ? $options['postKey'] : md5(microtime()); /* Set up force message*/ $this->forceMessageToSend = ( $this->forceMessageToSend ) ? $this->forceMessageToSend : ( ( $options['forcePm'] ) ? TRUE : FALSE ); //----------------------------------------- // Check content //----------------------------------------- if ( $toMemberID == $fromMemberID ) { throw new Exception( 'CANT_SEND_TO_SELF' ); } if ( ! $msgTitle ) { throw new Exception( 'MSG_TITLE_EMPTY' ); } if ( ! $msgContent ) { throw new Exception( 'MSG_CONTENT_EMPTY' ); } //----------------------------------------- // Format content //----------------------------------------- try { $_originalMessageContent = $msgContent; $msgContent = $this->_formatMessageForSaving( $msgContent ); } catch( Exception $error ) { throw new Exception( $error->getMessage() ); } //----------------------------------------- // First off, load the to and from members //----------------------------------------- $_members = IPSMember::load( array( $toMemberID, $fromMemberID ), 'groups,extendedProfile' ); $toMemberData = $this->_setMaxMessages( $_members[ $toMemberID ] ); $fromMemberData = $this->_setMaxMessages( $_members[ $fromMemberID ] ); if ( empty( $toMemberData['member_id'] ) AND $this->forceMessageToSend !== TRUE ) { throw new Exception( 'TO_USER_DOES_NOT_EXIST' ); } if ( empty( $fromMemberData['member_id'] ) AND $this->forceMessageToSend !== TRUE ) { throw new Exception( 'FROM_USER_DOES_NOT_EXIST' ); } if ( $this->floodControlCheck() !== TRUE ) { throw new Exception( 'FLOOD_STOP' ); } //----------------------------------------- // Sort out invite users //----------------------------------------- if ( is_array( $inviteUsers ) AND count( $inviteUsers ) ) { try { if ( $fromMemberData['g_max_mass_pm'] > 0 OR $this->forceMessageToSend === TRUE ) { $inviteUsersData = $this->checkAndReturnInvitedUsers( $inviteUsers ); } } catch( Exception $error ) { if ( $this->forceMessageToSend !== TRUE ) { throw new Exception( $error->getMessage() ); } } if ( isset($inviteUsersData[ $fromMemberID ]) ) { throw new Exception( 'CANT_INVITE_SELF' ); } if ( isset($inviteUsersData[ $toMemberID ]) ) { throw new Exception( 'CANT_INVITE_RECIPIENT' ); } } //----------------------------------------- // Can the 'to' user accept a PM? //----------------------------------------- if ( $this->canUsePMSystem( $toMemberData ) !== TRUE ) { if ( $this->forceMessageToSend !== TRUE ) { throw new Exception( 'TO_USER_CANNOT_USE_PM' ); } } //----------------------------------------- // Does the 'to' user have enough space? //----------------------------------------- if ( $this->withinPMQuota( $toMemberData ) !== TRUE ) { if ( $this->forceMessageToSend !== TRUE ) { throw new Exception( 'TO_USER_FULL' ); } } //----------------------------------------- // Has the 'to' use blocked us? //----------------------------------------- if ( count( $this->blockedByUser( $fromMemberData, $toMemberData ) ) ) { if ( $this->forceMessageToSend !== TRUE ) { throw new Exception( 'FROM_USER_BLOCKED' ); } } //----------------------------------------- // Is this simply a copy-to? //----------------------------------------- if ( $isCopyTo === TRUE AND !$isDraft ) { /* Send out the main one */ $this->sendNewPersonalTopic( $toMemberID, $fromMemberID, array(), $msgTitle, $_originalMessageContent, array( 'postKey' => $options['postKey'] ) ); /* Send out copy-tos */ foreach ($inviteUsersData as $id => $toMember) { $_newPostKey = md5( microtime() ); $_newContent = $_originalMessageContent; /* We need to duplicate the attachment record for each copy sent */ $this->DB->build( array( 'select' => '*', 'from' => 'attachments', 'where' => "attach_post_key='{$options['postKey']}'" ) ); $outer = $this->DB->execute(); $idRemap = array(); while( $attachData = $this->DB->fetch($outer) ) { $attachData['attach_post_key'] = $_newPostKey; $_oldAttachId = $attachData['attach_id']; unset( $attachData['attach_id'] ); $this->DB->insert( 'attachments', $attachData ); $idRemap[ $_oldAttachId ] = $this->DB->getInsertId(); } if( count($idRemap) ) { foreach( $idRemap as $_oldId => $_newId ) { $_newContent = str_replace( '=' . $_oldId . ':', '=' . $_newId . ':', $_newContent ); } } $this->sendNewPersonalTopic( $toMember['member_id'], $fromMemberID, array(), $msgTitle, $_newContent, array( 'postKey' => $_newPostKey ) ); } /* Done */ return TRUE; } //----------------------------------------- // Insert the user data //----------------------------------------- $_count = count( $inviteUsersData ); //----------------------------------------- // Got a topic ID? //----------------------------------------- if ( $options['topicID'] ) { /* Fetch topic data */ $_topicData = $this->fetchTopicData( $options['topicID'] ); if ( ! $_topicData['mt_id'] AND $this->forceMessageToSend !== TRUE ) { throw new Exception( 'TOPIC_ID_NOT_EXISTS' ); } $this->DB->setDataType( 'mt_title', 'string' ); /* First off, update message_topics and message_posts... */ $this->DB->update( 'message_topics', array( 'mt_date' => time(), 'mt_title' => $msgTitle, 'mt_starter_id' => $fromMemberData['member_id'], 'mt_start_time' => time(), 'mt_last_post_time' => time(), 'mt_invited_members' => serialize( array_keys( $inviteUsersData ) ), 'mt_to_count' => count( array_keys( $inviteUsersData ) ) + 1, 'mt_to_member_id' => $toMemberData['member_id'], 'mt_is_draft' => intval( $isDraft ) ), 'mt_id=' . $_topicData['mt_id'] ); /* Now the posts ... */ $this->DB->update( 'message_posts', array( 'msg_date' => time(), 'msg_topic_id' => $_topicData['mt_id'], 'msg_post' => $msgContent, 'msg_author_id' => $fromMemberData['member_id'], 'msg_is_first_post' => 1, 'msg_ip_address' => $this->member->ip_address ), 'msg_id=' . $_topicData['mt_first_msg_id'] ); /* Delete any current user mapping as this will be sorted out below */ $this->DB->delete( 'message_topic_user_map', 'map_topic_id=' . $_topicData['mt_id'] ); /* Reset variable IDs */ $msg_topic_id = $_topicData['mt_id']; $msg_id = $_topicData['mt_first_msg_id']; } //----------------------------------------- // Insert new... //----------------------------------------- else { $_messageTopicData = array( 'mt_date' => time(), 'mt_title' => $msgTitle, 'mt_starter_id' => $fromMemberData['member_id'], 'mt_start_time' => IPS_UNIX_TIME_NOW, 'mt_last_post_time' => IPS_UNIX_TIME_NOW, 'mt_invited_members' => serialize( array_keys( $inviteUsersData ) ), 'mt_to_count' => count( array_keys( $inviteUsersData ) ) + 1, 'mt_to_member_id' => $toMemberData['member_id'], 'mt_is_draft' => ( $isDraft ) ? 1 : 0, 'mt_is_system' => $isSystem, 'mt_replies' => 0 ); /* Create topic entry */ $this->DB->setDataType( 'mt_title', 'string' ); /* Data Hook Location */ IPSLib::doDataHooks( $_messageTopicData, 'messengerSendTopicData' ); $this->DB->insert( 'message_topics', $_messageTopicData ); $msg_topic_id = $this->DB->getInsertId(); $_messagePostData = array( 'msg_date' => time(), 'msg_topic_id' => $msg_topic_id, 'msg_post' => $msgContent, 'msg_post_key' => $options['postKey'], 'msg_author_id' => $fromMemberData['member_id'], 'msg_is_first_post' => 1, 'msg_ip_address' => $this->member->ip_address ); /* Data Hook Location */ IPSLib::doDataHooks( $_messagePostData, 'messengerSendTopicFirstPostData' ); $this->DB->insert( 'message_posts', $_messagePostData ); $msg_id = $this->DB->getInsertId(); } //----------------------------------------- // Update with last / first msg ID //----------------------------------------- $this->DB->update( 'message_topics', array( 'mt_last_msg_id' => $msg_id, 'mt_first_msg_id' => $msg_id, 'mt_hasattach' => intval( $this->_makeAttachmentsPermanent( $options['postKey'], $msg_id, $msg_topic_id ) ) ), 'mt_id=' . $msg_topic_id ); //----------------------------------------- // Not a draft? //----------------------------------------- if ( $isDraft !== TRUE ) { //----------------------------------------- // Add in 'to user' and 'from user' to the cc array //----------------------------------------- $inviteUsersData[ $toMemberData['member_id'] ] = $toMemberData; $inviteUsersData[ $fromMemberData['member_id'] ] = $fromMemberData; //----------------------------------------- // Loop.... //----------------------------------------- foreach( $inviteUsersData as $id => $toMember ) { //----------------------------------------- // Enter the info into the DB // Target user side. //----------------------------------------- $_isStarter = ( $fromMemberData['member_id'] == $toMember['member_id'] ) ? 1 : 0; $_isSystem = ( $fromMemberData['member_id'] == $toMember['member_id'] AND $isSystem ) ? 1 : 0; $_isActive = ( $_isSystem ) ? 0 : 1; /* Create user map entry */ $this->DB->insert( 'message_topic_user_map', array( 'map_user_id' => $toMember['member_id'], 'map_topic_id' => $msg_topic_id, 'map_folder_id' => 'myconvo', 'map_user_active' => $_isActive, 'map_is_starter' => ( $fromMemberData['member_id'] == $toMember['member_id'] ) ? 1 : 0, 'map_has_unread' => ( $fromMemberData['member_id'] == $toMember['member_id'] ) ? 0 : 1, 'map_read_time' => ( $fromMemberData['member_id'] == $toMember['member_id'] ) ? IPS_UNIX_TIME_NOW : 0, 'map_is_system' => $_isSystem, 'map_user_banned' => 0, 'map_left_time' => 0, 'map_ignore_notification' => 0, 'map_last_topic_reply' => IPS_UNIX_TIME_NOW ) ); //----------------------------------------- // Notifications library //----------------------------------------- if ( $fromMemberData['member_id'] != $toMember['member_id'] ) { $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . '/sources/classes/member/notifications.php', 'notifications' ); $notifyLibrary = new $classToLoad( $this->registry ); $toMember['language'] = $toMember['language'] == "" ? IPSLib::getDefaultLanguage() : $toMember['language']; $buildMessage = array( 'NAME' => $toMember['members_display_name'], 'POSTER' => $fromMemberData['members_display_name'], 'TITLE' => $msgTitle, 'TEXT' => $msgContent, 'LINK' => "?app=members&module=messaging§ion=view&do=showConversation&topicID={$msg_topic_id}#msg{$msg_id}" ); IPSText::getTextClass('email')->setPlainTextTemplate( IPSText::getTextClass('email')->getTemplate( 'personal_convo_new_convo', $toMember['language'] ) ); IPSText::getTextClass('email')->buildPlainTextContent( $buildMessage ); IPSText::getTextClass('email')->subject = sprintf( IPSText::getTextClass('email')->subject, $this->registry->output->buildSEOUrl( 'showuser=' . $fromMemberData['member_id'], 'public', $fromMemberData['members_seo_name'], 'showuser' ), $fromMemberData['members_display_name'], $this->registry->output->buildUrl( "app=members&module=messaging§ion=view&do=showConversation&topicID={$msg_topic_id}#msg{$msg_id}", 'public' ) ); $notifyLibrary->setMember( $toMember ); $notifyLibrary->setFrom( $fromMemberData ); $notifyLibrary->setNotificationKey( 'new_private_message' ); $notifyLibrary->setNotificationUrl( $this->registry->output->buildUrl( "app=members&module=messaging§ion=view&do=showConversation&topicID={$msg_topic_id}#msg{$msg_id}", 'public' ) ); $notifyLibrary->setNotificationText( IPSText::getTextClass('email')->getPlainTextContent() ); $notifyLibrary->setNotificationTitle( IPSText::getTextClass('email')->subject ); $notifyLibrary->setNotificationHtml( IPSText::getTextClass('email')->buildHtmlContent( $buildMessage ) ); $notifyLibrary->setMetaData( array( 'meta_area' => 'pm', 'meta_id' => $msg_topic_id, 'meta_app' => 'members' ) ); try { $notifyLibrary->sendNotification(); } catch( Exception $e ) { } IPSMember::save( $toMember['member_id'], array( 'core' => array( 'msg_count_total'=> 'plus:1', 'msg_count_new' => 'plus:1', 'msg_count_reset' => 1 ) ) ); } else { IPSMember::save( $toMember['member_id'], array( 'core' => array( 'msg_count_total'=> 'plus:1' ) ) ); } } } else { //----------------------------------------- // Is a draft //----------------------------------------- /* Create user map entry */ $this->DB->insert( 'message_topic_user_map', array( 'map_user_id' => $fromMemberData['member_id'], 'map_topic_id' => $msg_topic_id, 'map_folder_id' => 'drafts', 'map_user_active' => 1, 'map_has_unread' => 0, 'map_user_banned' => 0, 'map_read_time' => 0, 'map_left_time' => 0, 'map_ignore_notification' => 0, 'map_last_topic_reply' => IPS_UNIX_TIME_NOW ) ); if( ! $options['topicID'] ) { //----------------------------------------- // Update profile //----------------------------------------- $this->rebuildFolderCount( $fromMemberData['member_id'], array( 'drafts' => 'plus:1' ), TRUE, array( 'core' => array( 'msg_count_total' => 'plus:1' ) ) ); } } return TRUE; } /** * Delete messages from a topic * * @param array Array of message IDs to remove * @param int Deleted by member ID * @return boolean Deleted */ public function deleteMessages( $msgIDs=array(), $deletedByMemberID ) { //----------------------------------------- // INIT //----------------------------------------- $idsToDelete = array(); $topics = array(); $unread = array(); $deletedByMember = IPSMember::load( intval( $deletedByMemberID ), 'all' ); //----------------------------------------- // Check //----------------------------------------- if ( ! is_array( $msgIDs ) or ! count( $msgIDs ) ) { return FALSE; } //----------------------------------------- // Fetch all posts... //----------------------------------------- $this->DB->build( array( 'select' => 'msg.msg_id, msg.msg_topic_id, msg.msg_author_id', 'from' => array( 'message_posts' => 'msg' ), 'where' => 'msg.msg_id IN (' . implode( ',', IPSLib::cleanIntArray( $msgIDs ) ) . ') AND msg.msg_is_first_post != 1', 'add_join' => array( array( 'select' => 'mt.*', 'from' => array( 'message_topics' => 'mt' ), 'where' => 'mt.mt_id=msg.msg_topic_id', 'type' => 'left' ) ) ) ); $this->DB->execute(); while( $msg = $this->DB->fetch() ) { if ( $this->_conversationCanDelete( $msg, $msg, $deletedByMember ) === TRUE ) { $idsToDelete[ $msg['msg_id'] ] = $msg['msg_id']; $topics[ $msg['msg_topic_id'] ] = $msg['msg_topic_id']; } } //----------------------------------------- // Got anything? //----------------------------------------- if ( ! count( $idsToDelete ) ) { return FALSE; } //----------------------------------------- // Is there an attachment to these messages?? //----------------------------------------- $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'core' ) . '/sources/classes/attach/class_attach.php', 'class_attach' ); $class_attach = new $classToLoad( $this->registry ); $class_attach->type = 'msg'; $class_attach->init(); $class_attach->bulkRemoveAttachment( $idsToDelete ); //----------------------------------------- // Delete the messages //----------------------------------------- $this->DB->delete( 'message_posts', 'msg_id IN (' . implode( ',', IPSLib::cleanIntArray( $msgIDs ) ) . ')' ); //----------------------------------------- // Rebuild member's new message count // This MUST go before we rebuild the topic // so we get all those who haven't yet read // the last replies... //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'message_topic_user_map', 'where' => 'map_user_active=1 AND map_topic_id IN (' . implode( ",", array_keys( $topics ) ) . ') AND map_has_unread=1' ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $unread[ $row['map_user_id'] ] = $row['map_user_id']; } //----------------------------------------- // Update all relevant topics //----------------------------------------- foreach( array_keys( $topics ) as $topicID ) { $this->rebuildTopic( $topicID ); } /* Update member counts */ if ( count( $unread ) ) { $this->resetMembersNewTopicCount( $unread ); } return TRUE; } /** * Rebuild a PM topic * * @param int Topic ID * @return boolean */ public function rebuildTopic( $topicID ) { //----------------------------------------- // Get message count //----------------------------------------- $replyCount = $this->DB->buildAndFetch( array( 'select' => 'count(*) as count', 'from' => 'message_posts', 'where' => 'msg_topic_id=' . $topicID ) ); $replyCount['count'] = ( $replyCount['count'] > 0 ) ? $replyCount['count'] - 1 : 0; //----------------------------------------- // Fetch latest msg ID //----------------------------------------- $latestID = $this->DB->buildAndFetch( array( 'select' => 'msg_id, msg_date', 'from' => 'message_posts', 'where' => 'msg_topic_id=' . $topicID, 'order' => 'msg_date DESC', 'limit' => array( 0, 1 ) ) ); //----------------------------------------- // Fetch first msg ID //----------------------------------------- $firstID = $this->DB->buildAndFetch( array( 'select' => 'msg_id, msg_date', 'from' => 'message_posts', 'where' => 'msg_topic_id=' . $topicID, 'order' => 'msg_date ASC', 'limit' => array( 0, 1 ) ) ); //----------------------------------------- // Recount attachments //----------------------------------------- $attach = $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as attachments', 'from' => array( 'attachments' => 'a' ), 'where' => 'm.msg_topic_id=' . $topicID . " AND a.attach_rel_module='msg'", 'add_join' => array( array( 'from' => array( 'message_posts' => 'm' ), 'where' => 'm.msg_id=a.attach_rel_id' ) ) ) ); //----------------------------------------- // Update... //----------------------------------------- $this->DB->update( 'message_posts', array( 'msg_is_first_post' => 1 ), 'msg_id=' . $firstID['msg_id'] ); $this->DB->update( 'message_topics', array( 'mt_last_post_time' => $latestID['msg_date'], 'mt_replies' => intval( $replyCount['count'] ), 'mt_last_msg_id' => $latestID['msg_id'], 'mt_hasattach' => intval($attach['attachments']), 'mt_first_msg_id' => $firstID['msg_id'] ), 'mt_id=' . $topicID ); $this->DB->update( 'message_topic_user_map', array( 'map_last_topic_reply' => $latestID['msg_date'] ), 'map_topic_id=' . $topicID ); return TRUE; } /** * Generate a 'definitive" list of invite users from an array of names * * @param array Array of display names [ 'bob', 'joe', 'dave' ] * @param array Optional[ Array of member data (group + core only required) ] * @param int Number of existing participants * @return array Array of members, indexed by member_id * * * Exception Codes: * NOT_ALL_INVITE_USERS_EXIST: Not all invite users exist (check $this->exceptionData for a list of names) * NOT_ALL_INVITE_USERS_CAN_PM: Not all invite users can PM (check $this->exceptionData for a list of names) * INVITE_USERS_BLOCKED: Some invite users have been blocked (check $this->exceptionData for a list of names) * */ public function checkAndReturnInvitedUsers( $names, $member=array(), $current=0 ) { //----------------------------------------- // INIT //----------------------------------------- $finalArray = array(); $inviteArrayByID = array(); $inviteArrayByName = array(); $member = ( $member['member_id'] ) ? $member : $this->member->fetchMemberData(); $doesNotExist = array(); $cannotUsePM = array(); $current = intval($current); if ( ! is_array( $names ) OR ! count( $names ) ) { return $finalArray; } //----------------------------------------- // Get the members //----------------------------------------- $_members = IPSMember::load( $names, 'groups,extendedProfile', 'displayname' ); if ( ! is_array( $_members ) OR ! count( $_members ) ) { return $finalArray; } //----------------------------------------- // Go Loopy //----------------------------------------- foreach( $_members as $member_id => $_member ) { $inviteArrayByID[ $_member['member_id'] ] = $_member; $inviteArrayByName[ mb_strtolower($_member['members_l_display_name']) ] = $_member['member_id']; # Only allow the first X based on g_max_mass_pm if ( count( $inviteArrayByID ) + $current > $member['g_max_mass_pm'] ) { throw new Exception( "MAX_USER_PM_LIMIT_HIT" ); break; } /* Hard limit of 500 */ if( count( $inviteArrayByID ) > 500 ) { break; } } //----------------------------------------- // Go loopy again... //----------------------------------------- foreach( $names as $name ) { //----------------------------------------- // Check to make sure all invite users exist //----------------------------------------- if ( ! in_array( mb_strtolower( $name ), array_keys( $inviteArrayByName ) ) ) { $doesNotExist[] = $name; } } if ( count( $doesNotExist ) ) { $this->exceptionData = $doesNotExist; throw new Exception( "NOT_ALL_INVITE_USERS_EXIST" ); } //----------------------------------------- // Check the block list.. //----------------------------------------- $blockedInvitedUsers = $this->blockedByUser( $member, array_keys( $inviteArrayByID ) ); if ( count( $blockedInvitedUsers ) ) { $this->exceptionData = $blockedInvitedUsers; throw new Exception( "INVITE_USERS_BLOCKED" ); } //----------------------------------------- // Check to ensure all invite users can use the PM system //----------------------------------------- foreach( $inviteArrayByID as $id => $_member ) { $_member = $this->_setMaxMessages( $_member ); if ( $_member['g_use_pm'] != 1 OR $_member['members_disable_pm'] ) { $cannotUsePM[ $id ] = $_member['members_display_name']; } if ( $this->withinPMQuota( $_member ) !== TRUE ) { $cannotUsePM[ $id ] = $_member['members_display_name']; } if ( count( $cannotUsePM ) ) { if ( $this->forceMessageToSend !== TRUE ) { $this->exceptionData = $cannotUsePM; throw new Exception( "NOT_ALL_INVITE_USERS_CAN_PM" ); } else { continue; } } $finalArray[ $id ] = $_member; } return $finalArray; } /** * Returns number of messages based on folders not a SQL search * * @param string ID of folder to get count from * @param string Folder data line * @return int Count */ public function getFolderTopicCount( $folderID, $folderData ) { $folderArray = $this->explodeFolderData( $folderData ); return intval( $folderArray[ $folderID ]['count'] ); } /** * Rebuild directory count * * @param int Member ID * @param string Folder ID to rebuild * @param int Count of messages * @param boolean Flag; do not save to DB [True is default] * @param array Array of extra data for passing to IPSMember::save() * @return string Rebuilt 'vdir' string */ public function rebuildFolderCount( $memberID, $folderAndCount=array(), $save=TRUE, $extraData=array() ) { //----------------------------------------- // INIT //----------------------------------------- $rebuild = array(); $dirFolders = ''; //----------------------------------------- // Got any current directories? //----------------------------------------- if ( $memberID == $this->memberData['member_id'] ) { if ( $this->memberData['pconversation_filters'] ) { $dirFolders = $this->memberData['pconversation_filters']; } } else { $_member = IPSMember::load( $memberID, 'extendedProfile' ); $dirFolders = $_member['pconversation_filters']; } //----------------------------------------- // Parse folder data //----------------------------------------- $rebuild = $this->explodeFolderData( $dirFolders ); //----------------------------------------- // Sort out new count //----------------------------------------- foreach( $rebuild as $id => $data ) { if ( isset($folderAndCount[ $id ]) ) { $_count = $folderAndCount[ $id ]; if ( strstr( $_count, 'plus:' ) ) { $newCount = $rebuild[ $data['id'] ]['count'] + intval( str_replace( 'plus:', '', $_count ) ); } else if ( strstr( $_count, 'minus:' ) ) { $newCount = $rebuild[ $data['id'] ]['count'] - intval( str_replace( 'minus:', '', $_count ) ); } else { $newCount = $rebuild[ $data['id'] ]['count'] = intval( $_count ); } $rebuild[ $data['id'] ]['count'] = ( $newCount < 1 ) ? 0 : $newCount; } } $finalString = $this->implodeFolderData( $rebuild ); if ( $save === TRUE ) { $extraData['extendedProfile']['pconversation_filters'] = $finalString; IPSMember::save( $memberID, $extraData ); } /* Update 'us' too.. */ if ( $memberID == $this->memberData['member_id'] ) { $this->memberData['pconversation_filters'] = $finalString; } return $finalString; } /** * Resets alert / count flag * @param mixed $memberID */ public function resetMembersAlertCounts( $memberID ) { $memberData = ( is_numeric( $memberID ) ) ? IPSMember::load( $memberID, 'extendedProfile' ) : $memberID; IPSMember::save( $memberData['member_id'], array( 'core' => array( 'msg_count_new' => 0 ) ) ); IPSMember::setToMemberCache( $memberData, array( 'msgAlertReset' => IPS_UNIX_TIME_NOW ) ); } /** * Reset all folder counts * * @param int Member ID to reset * @return boolean */ public function resetMembersFolderCounts( $memberID ) { $memberData = IPSMember::load( $memberID, 'extendedProfile' ); /* Grab folders */ $folders = $this->explodeFolderData( $memberData['pconversation_filters'] ); /* Grab counts */ $counts = $this->getPersonalTopicsCount( $memberData['member_id'] ); /* Zero 'em out */ foreach( $folders as $folderID => $data ) { $folders[ $folderID ]['count'] = 0; } if ( is_array( $counts ) ) { foreach( $counts as $folderID => $count ) { $folders[ $folderID ]['count'] = intval( $count ); } } /* Generate */ $dirs = $this->implodeFolderData( $folders ); /* Save */ IPSMember::save( $memberData['member_id'], array( 'core' => array( 'msg_count_reset' => 0 ), 'extendedProfile' => array( 'pconversation_filters' => $dirs ) ) ); /* Reset our counter */ if ( $memberID == $this->memberData['member_id'] ) { $this->memberData['msg_count_reset'] = 0; } return $dirs; } /** * Resets a user(s) new message count * * @param mixed Either INT member ID or ARRAY of member IDs * @return boolean */ public function resetMembersNewTopicCount( $member ) { //----------------------------------------- // Load members... //----------------------------------------- if ( is_array( $member ) ) { $members = IPSMember::load( $member, 'extendedProfile' ); } else { $_member = IPSMember::load( $member, 'extendedProfile' ); $members = array( $_member['member_id'] => $_member ); } //----------------------------------------- // Loop 'em //----------------------------------------- foreach( $members as $id => $data ) { $count = intval( $this->getPersonalTopicsCount( $id, 'new' ) ); $lastReset = intval( IPSMember::getFromMemberCache( $data, 'msgAlertReset' ) ); $msgAlertCount = intval( $this->getPersonalTopicsCount( $id, 'new', $lastReset ) ); //----------------------------------------- // Parse folder data //----------------------------------------- $rebuild = $this->explodeFolderData( $data['pconversation_filters'] ); $rebuild['new']['count'] = $count; $msg_show_notification = ( $count ) ? $data['msg_show_notification'] : 0; IPSMember::save( $id, array( 'extendedProfile' => array( 'pconversation_filters' => $this->implodeFolderData( $rebuild ) ), 'core' => array( 'msg_count_new' => $msgAlertCount, 'msg_show_notification' => $msg_show_notification ) ) ); IPSMember::setToMemberCache( $data, array( 'msgAlertReset' => IPS_UNIX_TIME_NOW ) ); /* Reset our counter */ if ( $id == $this->memberData['member_id'] ) { $this->memberData['msg_count_new'] = $msgAlertCount; } } return TRUE; } /** * Resets a members total message count * * @param int Member ID * @return int Message count */ public function resetMembersTotalTopicCount( $memberID ) { //----------------------------------------- // INIT //----------------------------------------- $memberID = intval( $memberID ); //----------------------------------------- // Grab count //----------------------------------------- $total = $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as msgTotal', 'from' => 'message_topic_user_map', 'where' => "map_user_id=".$memberID." AND map_user_active=1 AND map_user_banned=0 AND map_is_system=0" ) ); $total['msgTotal'] = intval($total['msgTotal']); IPSMember::save( $memberID, array( 'core' => array( 'msg_count_total' => $total['msgTotal'] ) ) ); return $total['msgTotal']; } /** * Returns PM count on spec * * @param int Member ID * @param string Folder ID (eg: inbox, sent, etc) [ Optional, if omitted, all msg box counts are returned ] * @param int Timestamp to check from * @return mixed If folder ID: Number of messages, otherwise an array of folderID => count */ public function getPersonalTopicsCount( $memberID, $folderID='', $time=null ) { $return = array(); $where = ''; if ( is_numeric( $time ) ) { $where = ( $folderID == 'drafts' ) ? ' AND mt_date > ' . $time : ' AND map_last_topic_reply > ' . $time; } if ( $memberID and $folderID ) { /* New folder */ if ( $folderID == 'new' ) { $t = $this->DB->buildAndFetch( array ( 'select' => 'COUNT(*) as msg_total', 'from' => 'message_topic_user_map', 'where' => "map_user_active=1 AND map_user_id=" . intval( $memberID ) . " AND map_has_unread=1" . $where ) ); return intval( $t['msg_total'] ); } /* Drafts */ else if ( $folderID == 'drafts' ) { $t = $this->DB->buildAndFetch( array ( 'select' => 'COUNT(*) as msg_total', 'from' => 'message_topics', 'where' => "mt_starter_id=" . intval( $memberID ) . " AND mt_is_draft=1 AND mt_is_deleted=0" . $where ) ); return intval( $t['msg_total'] ); } /* All other folders */ else { $_folder = $this->DB->addSlashes( $folderID ); if ( $this->_folderFilter ) { if ( $this->_folderFilter == 'in' ) { $t = $this->DB->buildAndFetch( array ( 'select' => 'COUNT(*) as msg_total', 'from' => 'message_topic_user_map', 'where' => "map_user_id=" . intval( $memberID ) . " AND map_user_active=1 AND map_folder_id='" . $_folder . "' AND map_is_starter=0" . $where ) ); return intval( $t['msg_total'] ); } else if ( $this->_folderFilter == 'sent' ) { $t = $this->DB->buildAndFetch( array ( 'select' => 'COUNT(*) as msg_total', 'from' => 'message_topic_user_map', 'where' => "map_user_id=" . intval( $memberID ) . " AND map_user_active=1 AND map_folder_id='" . $_folder . "' AND map_is_starter=1" . $where ) ); return intval( $t['msg_total'] ); } } else { $t = $this->DB->buildAndFetch( array ( 'select' => 'COUNT(*) as msg_total', 'from' => 'message_topic_user_map', 'where' => "map_user_id=" . intval( $memberID ) . " AND map_user_active=1 AND map_folder_id='" . $_folder . "'" . $where ) ); return intval( $t['msg_total'] ); } } } else if ( $memberID ) { $this->DB->build( array( 'select' => 'COUNT(*) as msg_total, map_folder_id', 'from' => 'message_topic_user_map', 'where' => "map_is_system=0 AND map_user_active=1 AND map_user_id=" . intval( $memberID ) . $where, 'group' => 'map_folder_id' ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $return[ $row['map_folder_id'] ] = $row['msg_total']; } $return['drafts'] = $this->getPersonalTopicsCount( $memberID, 'drafts' ); $return['new'] = $this->getPersonalTopicsCount( $memberID, 'new' ); return $return; } else { return 0; } } /** * Returns PM data based on spec * * @param int Member ID * @param string Folder ID (eg: inbox, sent, etc) * @param array Array of data ( array[ 'sort' => '', offsetStart' => '', 'offsetEnd' => '' ) * @return array Array of PMs indexed by PM ID */ public function getPersonalTopicsList( $memberID, $folderID, $searchAndSort ) { //----------------------------------------- // INIT //----------------------------------------- $sortKey = ''; $where = ''; $memberID = intval( $memberID ); $_memberIDs = array(); $folderID = IPSText::alphanumericalClean( $folderID, '-_' ); $oStart = intval( $searchAndSort['offsetStart'] ); $oEnd = intval( $searchAndSort['offsetEnd'] ); $messages = array(); //----------------------------------------- // Figure out sort key //----------------------------------------- switch ( $searchAndSort['sort'] ) { case 'rdate': $sortKey = 'mm.map_last_topic_reply ASC'; break; case 'title': $sortKey = 'mt.mt_title ASC'; break; case 'nameTo': $sortKey = 'mem.members_display_name ASC'; break; default: $sortKey = 'mm.map_last_topic_reply DESC'; break; } //----------------------------------------- // Figure out where clause (no, not santa) //----------------------------------------- switch( $folderID ) { default: if ( $this->_folderFilter ) { if ( $this->_folderFilter == 'in' ) { $where = " AND mm.map_user_active=1 AND ( mm.map_folder_id='{$folderID}' AND mm.map_is_starter=0 )"; } else if ( $this->_folderFilter == 'sent' ) { $where = " AND mm.map_user_active=1 AND ( mm.map_folder_id='{$folderID}' AND mm.map_is_starter=1)"; } } else if ( $folderID != 'all') { $where = " AND mm.map_user_active=1 AND mm.map_folder_id='{$folderID}'"; } else { $where = " AND mm.map_user_active=1"; } break; case 'drafts': $where = " AND mm.map_user_active=1 AND mt.mt_is_draft=1"; break; case 'new': $where = ' AND mm.map_user_active=1 AND mm.map_has_unread=1'; break; } if ( ! $memberID or ! $folderID ) { return $messages; } else { $this->DB->build( array( 'select' => 'mm.*', 'from' => array( 'message_topic_user_map' => 'mm' ), 'where' => "mm.map_user_id=" . $memberID . $where, 'order' => $sortKey, 'limit' => array( $oStart, $oEnd ), 'add_join' => array( array( 'select' => 'mt.*', 'from' => array( 'message_topics' => 'mt' ), 'where' => 'mm.map_topic_id=mt.mt_id', 'type' => 'left' ), array( 'select' => 'msg.*', 'from' => array( 'message_posts' => 'msg' ), 'where' => 'msg.msg_id=mt.mt_last_msg_id', 'type' => 'left' ), array( 'select' => 'mem.members_display_name', 'from' => array( 'members' => 'mem' ), 'where' => 'mt.mt_to_member_id=mem.member_id', 'type' => 'left' ) ) ) ); $this->DB->execute(); //----------------------------------------- // Get the messages //----------------------------------------- while( $row = $this->DB->fetch() ) { $_toID = intval( $row['mt_to_member_id'] ); $_startID = intval( $row['mt_starter_id'] ); $_lastID = intval( $row['msg_author_id'] ); /* Add member IDs */ $_memberIDs[ $_toID ] = $_toID; $_memberIDs[ $_startID ] = $_startID; $_memberIDs[ $_lastID ] = $_lastID; /* Invited users */ if ( $row['mt_invited_members'] ) { $row['_invitedMembers'] = unserialize( $row['mt_invited_members'] ); if ( is_array( $row['_invitedMembers'] ) AND count( $row['_invitedMembers'] ) ) { foreach ( $row['_invitedMembers'] as $_mid ) { $_memberIDs[ $_mid ] = $_mid; } } } /* Pagination */ if ( ( ($row['mt_replies'] + 1 ) % $this->messagesPerPage ) == 0 ) { $pages = ($row['mt_replies'] + 1) / $this->messagesPerPage; } else { $number = ( ($row['mt_replies'] + 1) / $this->messagesPerPage ); $pages = ceil( $number); } if ( $pages > 1 ) { for ( $i = 0 ; $i < $pages ; ++$i ) { $real_no = $i * $this->messagesPerPage; $page_no = $i + 1; if ( $page_no == 4 and $pages > 4 ) { $row['pages'][] = array( 'last' => 1, 'st' => ($pages - 1) * $this->messagesPerPage, 'page' => $pages ); break; } else { $row['pages'][] = array( 'last' => 0, 'st' => $real_no, 'page' => $page_no ); } } } /* The rest */ $row['_folderName'] = $this->_dirData[ $row['map_folder_id'] ]['real']; $row['_otherInviteeCount'] = intval( $row['mt_to_count'] - 1 ); $messages[ $row['mt_id'] ] = $row; } $members = array(); /* Got any members? */ if ( count( $_memberIDs ) ) { $members = IPSMember::load( $_memberIDs, 'all' ); } /* Let's sort out the ignore & photos at once for everyone */ if( count($members) ) { foreach( $members as $_mid => $_mdata ) { $_mdata['_canBeBlocked'] = IPSMember::isIgnorable( $_mdata['member_group_id'], $_mdata['mgroup_others'], 'pm' ); $members[ $_mid ] = IPSMember::buildProfilePhoto( $_mdata ); } } $members[ 0 ] = IPSMember::buildProfilePhoto( array( 'members_display_name' => $this->lang->words['deleted_user'] ) ); /* Now merge (in turn!) */ foreach( $messages as $id => $row ) { /* From */ $messages[ $id ]['_starterMemberData'] = $members[ $row['mt_starter_id'] ]; /* To */ $messages[ $id ]['_toMemberData'] = $members[ $row['mt_to_member_id'] ]; /* Last msg author */ $messages[ $id ]['_lastMsgAuthor'] = $members[ $row['msg_author_id'] ]; /* Invitees */ if ( is_array( $row['_invitedMembers'] ) AND count( $row['_invitedMembers'] ) ) { foreach ( $row['_invitedMembers'] as $_mid ) { $messages[ $id ]['_invitedMemberData'][ $_mid ] = $members[ $_mid ]; $messages[ $id ]['_invitedMemberNames'][] = $members[ $_mid ]['members_display_name']; } } else { $row['_invitedMembers'] = array(); $messages[ $id ]['_invitedMemberData'] = array(); $messages[ $id ]['_invitedMemberNames'] = array(); } } } return $messages; } /** * Returns a complete topic * * @param int Member ID * @param string Folder ID (eg: inbox, sent, etc) * @param array Array of data ( array[ sort => '', offsetStart' => '', 'offsetEnd' => '' ) * @return array Array of PMs indexed by PM ID * * * Exception Codes * NO_READ_PERMISSION You do not have permission to read the topic * YOU_ARE_BANNED You have been banned * */ public function fetchConversation( $topicID, $readingMemberID, $filters=array() ) { //----------------------------------------- // INIT //----------------------------------------- $readingMemberID = intval( $readingMemberID ); $topicID = intval( $topicID ); $oStart = intval( $filters['offsetStart'] ); $oEnd = intval( $filters['offsetEnd'] ); $replyData = array(); $topicData = array(); $remapData = array(); $memberData = array(); $missingMembers = array(); $whereExtra = ''; //----------------------------------------- // Figure out sort key //----------------------------------------- switch ( $filters['sort'] ) { case 'rdate': $sortKey = 'msg.msg_date DESC'; break; default: $sortKey = 'msg.msg_date ASC'; break; } if ( ! $topicID ) { return array( 'topicData' => $topicData, 'replyData' => $replyData ); } else { /* Get member data */ $memberData = $this->fetchTopicParticipants( $topicID, TRUE ); /* Get reading member's data */ $readingMemberData = $memberData[ $readingMemberID ]; /* Fetch topic data */ $topicData = $this->fetchTopicData( $topicID, FALSE ); /* Topic deleted? Grab topic starter details, as they won't be in the participant array */ if ( $topicData['mt_is_deleted'] AND ( $topicData['mt_starter_id'] > 0 ) ) { $memberData[ $topicData['mt_starter_id'] ] = IPSMember::load( $topicData['mt_starter_id'], 'all' ); $memberData[ $topicData['mt_starter_id'] ]['_canBeBlocked'] = IPSMember::isIgnorable( $memberData[ $topicData['mt_starter_id'] ]['member_group_id'], $memberData[ $topicData['mt_starter_id'] ]['mgroup_others'], 'pm' ); $memberData[ $topicData['mt_starter_id'] ] = IPSMember::buildDisplayData( $memberData[ $topicData['mt_starter_id'] ], array( '__all__' => 1 ) ); $memberData[ $topicData['mt_starter_id'] ]['map_user_active'] = 1; /* Set flag for topic participant starter */ $memberData[ $topicData['mt_starter_id'] ]['map_is_starter'] = 1; foreach( $memberData as $id => $data ) { $memberData[ $id ]['_topicDeleted'] = 1; } } /* Can access this topic? */ if ( $this->canAccessTopic( $readingMemberID, $topicData, $memberData ) !== TRUE ) { /* Banned? */ if ( $readingMemberData['map_user_banned'] ) { throw new Exception( "YOU_ARE_BANNED" ); } else { throw new Exception( "NO_READ_PERMISSION" ); } } /* Reply Data */ $this->DB->build( array( 'select' => 'msg.*', 'from' => array( 'message_posts' => 'msg' ), 'where' => "msg.msg_topic_id=" . $topicID . $whereExtra, 'order' => $sortKey, 'limit' => array( $oStart, $oEnd ), 'add_join' => array( array( 'select' => 'iu.*', 'from' => array( 'ignored_users' => 'iu' ), 'where' => 'iu.ignore_owner_id=' . $readingMemberID . ' AND iu.ignore_ignore_id=msg.msg_author_id', 'type' => 'left' ), array( 'select' => 'm.member_group_id, m.mgroup_others', 'from' => array( 'members' => 'm' ), 'where' => 'm.member_id=msg.msg_author_id', 'type' => 'left' ), ) ) ); $o = $this->DB->execute(); //----------------------------------------- // Get the messages //----------------------------------------- while( $msg = $this->DB->fetch( $o ) ) { $msg['_ip_address'] = ""; /* IP Address */ if ( $msg['msg_ip_address'] AND $readingMemberData['g_is_supmod'] == 1 ) { $msg['_ip_address'] = $msg['msg_ip_address']; } /* Edit */ $msg['_canEdit'] = $this->_conversationCanEdit( $msg, $topicData, $readingMemberData ); /* Delete */ $msg['_canDelete'] = $this->_conversationCanDelete( $msg, $topicData, $readingMemberData ); /* Format Message */ $msg['msg_post'] = $this->_formatMessageForDisplay( $msg['msg_post'], $msg ); /* Member missing? */ if ( ! isset($memberData[ $msg['msg_author_id'] ]) ) { if( !$msg['msg_author_id'] ) { $memberData[0] = array(); } else { $missingMembers[ $msg['msg_author_id'] ] = $msg['msg_author_id']; } } else { if ( ! empty( $memberData[ $msg['msg_author_id'] ]['signature'] ) ) { if ( ! $this->memberData['view_sigs'] || ( $msg['msg_author_id'] && $this->memberData['member_id'] && ! empty( $this->member->ignored_users[ $msg['msg_author_id'] ]['ignore_signatures'] ) ) ) { $memberData[ $msg['msg_author_id'] ]['signature'] = ''; } } } $replyData[ $msg['msg_id'] ] = $msg; } } /* Members who've deleted a closed conversation? */ if ( count( $missingMembers ) ) { $_members = IPSMember::load( array_keys( $missingMembers ), 'all' ); foreach( $_members as $id => $data ) { $data['_canBeBlocked'] = IPSMember::isIgnorable( $memberData[ $topicData['mt_starter_id'] ]['member_group_id'], $memberData[ $topicData['mt_starter_id'] ]['mgroup_others'], 'pm' ); $data['map_user_active'] = 0; $memberData[ $data['member_id'] ] = IPSMember::buildDisplayData( $data, array( '__all__' => 1 ) ); } } /* Update reading member's read time */ $this->DB->update( 'message_topic_user_map', array( 'map_read_time' => time(), 'map_has_unread' => 0 ), 'map_user_id=' . intval($readingMemberData['member_id']) . ' AND map_topic_id=' . $topicID ); /* Reduce the number of 'new' messages */ $_newMsgs = intval( $this->getPersonalTopicsCount( $readingMemberID, 'new') ); if ( $memberData[ $readingMemberID ]['map_has_unread'] ) { $lastReset = intval( IPSMember::getFromMemberCache( $memberData[ $readingMemberID ], 'msgAlertReset' ) ); $msgAlertCount = intval( $this->getPersonalTopicsCount( $readingMemberID, 'new', $lastReset ) ); $_pc = $this->rebuildFolderCount( $readingMemberID, array( 'new' => $_newMsgs ), TRUE ); IPSMember::save( $readingMemberID, array( 'core' => array( 'msg_count_new' => $msgAlertCount, 'msg_show_notification' => 0 ) ) ); /* is this us? */ if ( $readingMemberID == $this->memberData['member_id'] ) { /* Reset folder data */ $this->_dirData = $this->explodeFolderData( $_pc ); /* Reset global new count */ $this->memberData['msg_count_new'] = $msgAlertCount; } } /* Clean up topic title */ $topicData['mt_title'] = str_replace( '[attachmentid=', '[attachmentid=', $topicData['mt_title'] ); /* Flag externals as read */ $this->_flagAsReadForExternals( array( $topicID ) ); /* Ensure our read time is updated */ $memberData[ $readingMemberID ]['map_read_time'] = time(); /* Do we have a deleted user? */ if ( isset( $memberData[0] ) AND $memberData[0]['member_id'] == 0 ) { $memberData[0] = IPSMember::buildDisplayData(IPSMember::setUpGuest( $this->lang->words['deleted_user'] ), array( '__all__' => 1 ) ); } //----------------------------------------- // Attachments? //----------------------------------------- if ( $topicData['mt_hasattach'] ) { //----------------------------------------- // INIT. Yes it is //----------------------------------------- $postHTML = array(); //----------------------------------------- // Separate out post content //----------------------------------------- foreach( $replyData as $id => $post ) { $postHTML[ $id ] = $post['msg_post']; } if ( ! is_object( $this->class_attach ) ) { $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'core' ) . '/sources/classes/attach/class_attach.php', 'class_attach' ); $this->class_attach = new $classToLoad( $this->registry ); } $this->class_attach->type = 'msg'; $this->class_attach->init(); $attachHTML = $this->class_attach->renderAttachments( $postHTML ); /* Now parse back in the rendered posts */ foreach( $attachHTML as $id => $data ) { /* Get rid of any lingering attachment tags */ if ( stristr( $data['html'], "[attachment=" ) ) { $data['html'] = IPSText::stripAttachTag( $data['html'] ); } $replyData[ $id ]['msg_post'] = $data['html']; $replyData[ $id ]['attachmentHtml'] = $data['attachmentHtml']; } } /* Check to make sure we're not the only one left with this topic */ $active = 0; foreach( $memberData as $id => $data ) { if ( $id !== $readingMemberID ) { if ( $data['map_user_active'] ) { $active++; break; // 1 active is enough } } } if ( ! $active ) { $topicData['_everyoneElseHasLeft'] = 1; } /* Return */ return array( 'topicData' => $topicData, 'replyData' => $replyData, 'memberData' => $memberData ); } /** * "Implode" folder string * * @param array Array indexed by 'id' => [ 'id' => string, 'real' => string, 'count' => int ] * @return string Formed folder string */ public function implodeFolderData( $folderArray ) { //----------------------------------------- // INIT //----------------------------------------- $return = array(); if ( ! is_array( $folderArray ) ) { return serialize( $this->_fetchDefaultFolders() ); } else { return serialize( $folderArray ); } } /** * "Explodes" folder string * * @param string Raw folder data * @return array Array indexed by 'id' => [ 'id' => string, 'real' => string, 'count' => int ] */ public function explodeFolderData( $folderData ) { //----------------------------------------- // Set up default pconversation_filters //----------------------------------------- if ( ! $folderData ) { $folderData = $this->_fetchDefaultFolders(); } else { $folderData = unserialize( $folderData ); } /* Fallback */ if ( !is_array( $folderData ) ) { $folderData = $this->_fetchDefaultFolders(); } //----------------------------------------- // Process... //----------------------------------------- foreach( $folderData as $id => $data ) { /* Language key exists? */ if ( isset( $this->lang->words[ 'msgFolder_' . $data['id'] ] ) ) { $folderData[ $id ]['real'] = $this->lang->words[ 'msgFolder_' . $data['id'] ]; } } return $folderData; } /** * Have we been blocked? * * @param array Person A: Array of member data (core + groups required) OR member ID * @param array [Array of] member IDs to check 'person A' has been blocked by them. Array of member data (core + groups required) OR member ID OR array of IDs * @return array */ public function blockedByUser( $fromMember, $toMember ) { $fromMember = ( is_array( $fromMember ) ) ? $fromMember : IPSMember::load( intval( $fromMember ), 'groups,extendedProfile' ); $toMembers = array(); $blockedBy = array(); if ( is_array( $toMember ) AND ( $toMember['member_id'] != '' ) ) { $toMembers = array( $toMember['member_id'] => $toMember ); } else if ( is_array( $toMember ) AND count( $toMember ) ) { $toMembers = IPSMember::load( $toMember, 'groups,extendedProfile' ); } else { $toMembers = array( $toMember => IPSMember::load( intval( $toMember ), 'groups,extendedProfile' ) ); } $justToIDs = array_keys( $toMembers ); //----------------------------------------- // Can anyone block us? //----------------------------------------- if ( $fromMember['_canBeIgnored'] !== TRUE ) { return $blockedBy; } //----------------------------------------- // Grab the data from the ignored table //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'ignored_users', 'where' => 'ignore_messages=1 AND ignore_owner_id IN ( ' . implode( ',', $justToIDs ) . ' ) AND ignore_ignore_id=' . $fromMember['member_id'] ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { if ( in_array( $row['ignore_owner_id'], $justToIDs ) ) { $blockedBy[ $row['ignore_owner_id'] ] = $row['ignore_owner_id']; } } return $blockedBy; } /** * Can we use the PM system? * * @param array Array of member data (core + groups required) OR member ID * @return boolean TRUE is OK */ public function canUsePMSystem( $toMember ) { $toMember = ( is_array( $toMember ) ) ? $toMember : IPSMember::load( intval( $toMember ), 'groups,extendedProfile' ); /* Not using PM system? */ if ( $toMember['members_disable_pm'] ) { return FALSE; } if ( $toMember['mgroup_others'] && ! $toMember['g_use_pm'] ) { $groups_id = explode( ',', $toMember['mgroup_others'] ); $cache = $this->caches['group_cache']; if ( count( $groups_id ) ) { foreach( $groups_id as $pid ) { if ( ! $cache[ $pid ]['g_id'] ) { continue; } if ( $cache[ $pid ]['g_use_pm'] ) { $toMember['g_use_pm'] = 1; break; } } } } return ( $toMember['g_use_pm'] != 1 ) ? FALSE : TRUE; } /** * Get invite users * Does this PM have any invite users attached? Mainly used to detect IPB 3.0.0 methods * over previous methods * * @param string Raw data from DB (msg_cc_users) * @return array Array of IDs */ public function getInvitedUsers( $msg_invite_users ) { if ( ! $msg_invite_users ) { return array(); } $_users = ( is_array( $msg_invite_users ) ) ? $msg_invite_users : unserialize( $msg_invite_users ); if ( ! count( $_users ) ) { return array(); } if ( intval( $_users[0] ) == $_users[0] ) { return $_users; } else { # Assume they're names, then... $members = IPSMember::load( $_users, 'core', 'displayname' ); # Just return the IDs return array_keys( $members ); } } /** * Have we exceeded our PM quota? * * @param array Array of member data (core + groups required) OR member ID * @return boolean TRUE is OK (within quota) */ public function withinPMQuota( $member ) { $member = ( is_array( $member ) ) ? $member : IPSMember::load( intval( $member ), 'groups,extendedProfile' ); $member = $this->_setMaxMessages( $member ); if ( $member['g_max_messages'] > 0 AND ( $member['msg_count_total'] + 1 > $member['g_max_messages'] ) ) { if ( $this->settings['override_inbox_full'] ) { $override = array(); $override = explode( ",", $this->settings['override_inbox_full'] ); $do_override = 0; $my_groups = array( $this->memberData['member_group_id'] ); if ( $this->memberData['mgroup_others'] ) { $my_groups = array_merge( $my_groups, explode( ",", $this->memberData['mgroup_others'] ) ); } foreach( $my_groups as $member_group ) { if ( in_array( $member_group, $override ) ) { $do_override = 1; } } if ( $do_override == 0 ) { return FALSE; } else { return TRUE; } } else { return FALSE; } } else { return TRUE; } } /** * Returns the max PMs allowed based on primary and seconday groups * * @param array Array of member data * @return array Member data */ protected function _setMaxMessages( $member ) { $groups_id = explode( ',', $member['mgroup_others'] ); $groupCache = $this->caches['group_cache']; if ( count( $groups_id ) ) { foreach( $groups_id as $pid ) { if ( empty($groupCache[ $pid ]['g_id']) ) { continue; } if ( $member['g_max_messages'] > 0 AND $groupCache[ $pid ]['g_max_messages'] > $member['g_max_messages'] ) { $member['g_max_messages'] = $groupCache[ $pid ]['g_max_messages']; } else if ( $groupCache[ $pid ]['g_max_messages'] == 0 ) { $member['g_max_messages'] = 0; } } } return $member; } /** * Builds the data for the messenger storage block * * @return array */ public function buildMessageTotals() { /* Get the number of messages we have in total. */ $total = $this->DB->buildAndFetch( array( 'select' => 'COUNT(*) as msg_total', 'from' => 'message_topic_user_map', 'where' => "map_user_active=1 AND map_is_system=0 AND map_user_banned=0 AND map_user_id=" . $this->memberData['member_id'] ) ); $totals['msg_total'] = intval( $total['msg_total'] ); /* if we're not in myconvo with a filter */ if ( ! $this->_folderFilter ) { /* Update the message count if needed */ if ( $totals['msg_total'] != $this->memberData['msg_count_total'] ) { IPSMember::save( $this->memberData['member_id'], array( 'core' => array( 'msg_count_total' => $totals['msg_total'] ) ) ); } } /* Make sure we've not exceeded our alloted allowance. */ $info['full_messenger'] = "
"; $info['img_width'] = 1; $info['vid'] = $this->_currentFolderID; $info['full_percent'] = ''; $info['amount_info'] = sprintf( $this->lang->words['pmpc_info_string'], $total['msg_total'] ,$this->lang->words['pmpc_unlimited'] ); if ( $this->memberData['g_max_messages'] > 0 ) { $totals['amount_info'] = sprintf( $this->lang->words['pmpc_info_string'], $total['msg_total'] ,$this->memberData['g_max_messages'] ); $totals['full_percent'] = $total['msg_total'] ? floor( ($total['msg_total'] / $this->memberData['g_max_messages']) * 100) : 0; $totals['full_percent'] = ( $totals['full_percent'] > 100 ) ? 100 : $totals['full_percent']; $totals['img_width'] = !empty( $info['full_percent'] ) ? intval($info['full_percent']) * 2.4 : 1; if ($totals['img_width'] > 300) { $totals['img_width'] = 300; } if ($totals['msg_total'] >= $this->memberData['g_max_messages']) { $totals['full_messenger'] = "".$this->lang->words['c_msg_full'].""; } else { $totals['full_messenger'] = str_replace( "<#PERCENT#>", $info['full_percent'], $this->lang->words['pmpc_full_string'] ); } } return $totals; } /** * Test to see whether or not we are allowed in this PM topic * * @param int Member ID to test against * @param mixed Topic ID or array of topic data * @param array [ topic participants. If omitted, participants are loaded ] * @param boolean Do not strip non-active users * @return boolean */ public function canAccessTopic( $memberID, $topicData, $topicParticipants=NULL, $noStrip=FALSE ) { $topicData = ( is_array( $topicData ) ) ? $topicData : $this->fetchTopicData( $topicData ); $topicParticipants = ( is_array( $topicParticipants ) ) ? $topicParticipants : $this->fetchTopicParticipants( $topicData['mt_id'] ); /* Is it deleted? Check this before stripped map_is_active=0 users */ if ( $topicData['mt_is_deleted'] AND isset($topicParticipants[ $memberID ]) ) { return TRUE; } /* Is it a system PM? */ if ( $topicData['mt_is_system'] AND $topicData['mt_starter_id'] == $memberID ) { return FALSE; } if ( defined('FROM_REPORT_CENTER') AND FROM_REPORT_CENTER ) { return TRUE; } /* Now remove all 'non-active' participants */ $topicParticipants = ( $noStrip === FALSE ) ? $this->_stripNonActiveParticipants( $topicParticipants ) : $topicParticipants; /* Are we in the participants list? */ if ( isset($topicParticipants[ $memberID ]) ) { if ( $topicParticipants[ $memberID ]['map_user_banned'] ) { return FALSE; } else { return TRUE; } } else { return FALSE; } } /** * Determines whether the conversation can be replied to * * @param int Member ID to test against * @param mixed Topic ID or array of topic data * @param array [ topic participants. If omitted, participants are loaded ] * @return boolean */ public function canReplyTopic( $memberID, $topicData, $topicParticipants=NULL ) { $topicData = ( is_array( $topicData ) ) ? $topicData : $this->fetchTopicData( $topicData ); $topicParticipants = ( is_array( $topicParticipants ) ) ? $topicParticipants : $this->fetchTopicParticipants( $topicData['mt_id'] ); if ( $this->canAccessTopic( $memberID, $topicData, $topicParticipants ) !== TRUE ) { return FALSE; } /* Is it a system PM? */ if ( $topicData['mt_is_system'] ) { return FALSE; } /* Is it a system PM? */ if ( $topicData['mt_is_deleted'] ) { return FALSE; } /* Ok, then */ return TRUE; } /** * Find the previous PID * * @param int Msg ID * @return int Previous Msg ID */ public function fetchPreviousMsgID( $msgID ) { $prevID = $this->DB->buildAndFetch( array( 'select' => 'MAX(msg_id) as max', 'from' => 'message_posts', 'where' => 'msg_id < ' . $msgID ) ); return intval( $prevID['max'] ); } /** * Fetch Message Data * * @param int Topic ID * @param int Msg ID * @param boolean Load starter member data with it * @return array Message data */ public function fetchMessageData( $topicID, $msgID, $loadMember=FALSE ) { $msgData = $this->DB->buildAndFetch( array( 'select' => '*', 'from' => 'message_posts', 'where' => 'msg_topic_id=' . intval( $topicID ) . ' AND msg_id=' . intval( $msgID ) ) ); if ( $loadMember AND $msgData['msg_author_id'] ) { $memberData = IPSMember::load( $msgData['msg_author_id'], 'all' ); $memberData['_canBeBlocked'] = IPSMember::isIgnorable( $memberData['member_group_id'], $memberData['mgroup_others'], 'pm' ); $memberData = IPSMember::buildDisplayData( $memberData, array( '__all__' => 1 ) ); $msgData = array_merge( $msgData, $memberData ); } return $msgData; } /** * Fetch Message Data by ID * * @param int Msg ID * @param boolean Load starter member data with it * @return array Message data */ public function fetchMessageDataById( $msgID, $loadMember=FALSE ) { $msgData = $this->DB->buildAndFetch( array( 'select' => '*', 'from' => 'message_posts', 'where' => 'msg_id=' . intval( $msgID ) ) ); if ( $loadMember AND $msgData['msg_author_id'] ) { $memberData = IPSMember::load( $msgData['msg_author_id'], 'all' ); $memberData['_canBeBlocked'] = IPSMember::isIgnorable( $memberData['member_group_id'], $memberData['mgroup_others'], 'pm' ); $memberData = IPSMember::buildDisplayData( $memberData, array( '__all__' => 1 ) ); $msgData = array_merge( $msgData, $memberData ); } return $msgData; } /** * Fetch topic Data with first message information * * @param int Topic ID * @param boolean Load starter member data with it * @return array Topic + message data */ public function fetchTopicDataWithMessage( $topicID, $loadMember=FALSE ) { $topicData = $this->DB->buildAndFetch( array( 'select' => 'mt.*', 'from' => array( 'message_topics' => 'mt' ), 'where' => 'mt.mt_id=' . intval( $topicID ), 'add_join' => array( array( 'select' => 'm.*', 'from' => array( 'message_posts' => 'm' ), 'where' => 'm.msg_id=mt.mt_first_msg_id', 'type' => 'inner' ) ) ) ); if ( $loadMember ) { $memberData = IPSMember::load( $topicData['mt_starter_id'], 'all' ); $memberData['_canBeBlocked'] = IPSMember::isIgnorable( $memberData['member_group_id'], $memberData['mgroup_others'], 'pm' ); $memberData = IPSMember::buildDisplayData( $memberData, array( '__all__' => 1 ) ); $topicData = is_array($topicData) ? array_merge( $topicData, $memberData ) : $memberData; } return $topicData; } /** * Fetch topic Data * * @param int Topic ID * @param boolean Load starter member data with it * @return array Topic data */ public function fetchTopicData( $topicID, $loadMember=FALSE ) { $topicData = $this->DB->buildAndFetch( array( 'select' => '*', 'from' => 'message_topics', 'where' => 'mt_id=' . intval( $topicID ) ) ); if ( $loadMember ) { $memberData = IPSMember::load( $topicData['mt_starter_id'], 'all' ); $memberData['_canBeBlocked'] = IPSMember::isIgnorable( $memberData['member_group_id'], $memberData['mgroup_others'], 'pm' ); $memberData = IPSMember::buildDisplayData( $memberData, array( '__all__' => 1 ) ); $topicData = array_merge( $topicData, $memberData ); } return $topicData; } /** * Fetch the topic participants * * @param int Topic ID * @param boolean Load and parse member data (TRUE for yes, FALSE for no) * @return array Array of member data indexed by member ID */ public function fetchTopicParticipants( $topicID, $parseThem=FALSE ) { //----------------------------------------- // INIT //----------------------------------------- $memberData = array(); $remapData = array(); $cachedSigs = array(); //----------------------------------------- // Grab 'em //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'message_topic_user_map', 'where' => 'map_topic_id=' . intval( $topicID ), ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $remapData[ $row['map_user_id'] ] = $row; } if( !count($remapData) ) { return array(); } /* Parse 'em? */ if ( $parseThem === TRUE ) { /* Grab member data */ $memberData = IPSMember::load( array_keys( $remapData ), 'all' ); foreach( $memberData as $id => $data ) { $data['cache_content'] = $remapData[ $id ]['cache_content']; $data['_canBeBlocked'] = IPSMember::isIgnorable( $data['member_group_id'], $data['mgroup_others'], 'pm' ); $memberData[ $id ] = IPSMember::buildDisplayData( $data, array( '__all__' => 1 ) ); $memberData[ $id ] = array_merge( $memberData[ $id ], $remapData[ $id ] ); if ( $data['members_disable_pm'] ) { $memberData[ $id ]['map_user_active'] = 0; } } $remapData = $memberData; } return $remapData; } /** * Strip away non-active participants * * @param array Array of current topic participants (as returned by fetchTopicParticipants) * @return array */ protected function _stripNonActiveParticipants( $topicParticipants ) { $_participants = array(); if ( ! is_array( $topicParticipants ) ) { return array(); } foreach( $topicParticipants as $id => $data ) { if ( $data['map_user_active'] and !$data['members_disable_pm'] ) { $_participants[ $id ] = $data; } } return $_participants; } /** * Function to format the actual message (applies BBcode, etc) * * @param string Raw text * @param array PM data * @return string Processed text */ protected function _formatMessageForDisplay( $msgContent, $data=array() ) { /* Load parser */ $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/text/parser.php', 'classes_text_parser' ); $parser = new $classToLoad(); $parser->set( array( 'memberData' => $data, 'parseBBCode' => 1, 'parseArea' => 'pms', 'parseHtml' => 0, 'parseEmoticons' => 1 ) ); return $parser->display( $msgContent ); return $msgContent; } /** * Format Post: Converts BBCode, smilies, etc * * @param string Raw Post * @return string Formatted Post * @author MattMecham */ public function _formatMessageForSaving( $msgContent ) { //----------------------------------------- // Load editor //----------------------------------------- $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . 'sources/classes/editor/composite.php', 'classes_editor_composite' ); $_editor = new $classToLoad(); $_editor->setAllowHtml( false ); $_editor->setLegacyMode( false ); if ( IPS_IS_AJAX ) { /* Was sent via std editor */ $_editor->setRteEnabled( false ); } $msgContent = $_editor->process( $msgContent ); if ( IPS_IS_AJAX ) { $msgContent = nl2br( $msgContent ); } return $msgContent; } /** * Determines whether the message can be edited * * @param array Message data * @param array Topic data * @param array Reading member data * @return bool */ protected function _conversationCanEdit( $msg, $topicData, $readingMemberData ) { if ( $topicData['mt_is_deleted'] ) { return FALSE; } /* Is it a system PM? */ if ( $topicData['mt_is_system'] ) { return FALSE; } if ( ( $msg['msg_author_id'] == $readingMemberData['member_id'] ) OR ( $readingMemberData['g_is_supmod'] == 1 ) ) { return TRUE; } else { return FALSE; } } /** * Determines whether the message can be deleted * * @param array Message data * @param array Topic data * @param array Reading member data * @return bool */ protected function _conversationCanDelete( $msg, $topicData, $readingMemberData ) { if ( $topicData['mt_is_deleted'] ) { return FALSE; } if ( ( $msg['msg_author_id'] == $readingMemberData['member_id'] ) OR ( $readingMemberData['g_is_supmod'] == 1 ) ) { return TRUE; } else { return FALSE; } } /** * Flag members for a PC count reset * * @param array Array of MEMBER ids * @return boolean */ protected function _flagForCountReset( $members ) { if ( count( $members ) ) { /* OK, so this is a bit naughty and should really go via IPSMember::save() however, it would take a fair bit of rewriting to 'fix it' so that it could save out with multiple IDs. So.... */ $this->DB->update( 'members', array( 'msg_count_reset' => 1 ), 'member_id IN (' . implode( ',', array_keys( $members ) ) . ')' ); } return TRUE; } /** * Makes attachments permananent * * @param string Post Key * @param int Msg ID * @param int Topic ID * @return int */ protected function _makeAttachmentsPermanent( $postKey, $msgID, $topicID ) { //----------------------------------------- // INIT //----------------------------------------- $cnt = array( 'cnt' => 0 ); //----------------------------------------- // Attachments: Re-affirm... //----------------------------------------- $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'core' ) . '/sources/classes/attach/class_attach.php', 'class_attach' ); $class_attach = new $classToLoad( $this->registry ); $class_attach->type = 'msg'; $class_attach->attach_post_key = $postKey; $class_attach->attach_rel_id = $msgID; $class_attach->init(); $return = $class_attach->postProcessUpload( array( 'mt_id' => $topicID ) ); return intval( $return['count'] ); } /** * Flag external stuff * @param array $data */ protected function _flagAsReadForExternals( array $data ) { /* Notifications */ $classToLoad = IPSLib::loadLibrary( IPS_ROOT_PATH . '/sources/classes/member/notifications.php', 'notifications' ); $notifyLibrary = new $classToLoad( $this->registry ); $notifyLibrary->setMember( $this->memberData ); $notifyLibrary->deleteNotificationsAsReadByMetaData( array( 'meta_app' => 'members', 'meta_area' => 'pm', 'meta_id' => $data, 'notify_type_key' => 'new_private_message' ) ); } /** * Default folder for people won't don't have any * * @return array */ protected function _fetchDefaultFolders() { return array( 'new' => array( 'id' => 'new', 'real' => $this->lang->words['msgFolder_new'], 'count' => 0, 'protected' => 1 ), 'myconvo' => array( 'id' => 'myconvo', 'real' => $this->lang->words['msgFolder_myconvo'], 'count' => 0, 'protected' => 1 ), 'drafts' => array( 'id' => 'drafts', 'real' => $this->lang->words['msgFolder_drafts'], 'count' => 0, 'protected' => 1 ) ); } }