Вход Регистрация
Файл: admin/applications/members/sources/classes/messaging/messengerFunctions.php
Строк: 2420
<?php
/**
 * <pre>
 * Invision Power Services
 * IP.Board v3.3.3
 * Comments library
 * Last Updated: $Date: 2012-05-29 13:17:47 -0400 (Tue, 29 May 2012) $
 * </pre>
 *
 * @author         $Author: ips_terabyte $
 * @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: 10813 $
 */

if ( ! defined( 'IN_IPB' ) )
{
    print 
"<h1>Incorrect access</h1>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 = '';
    
    
/**
     * 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'] );
        }
        
        
//-----------------------------------------
        // 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[] = "<option value='move_$id'>{$data['real']}</option>";
        }
        
        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'] = IPSText::getTextClass('bbcode')->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
     *
     * <code>
     * 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
     * </code>
     */
    
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
     *
     * <code>
     * Exception Codes:
     * NO_SUCH_MEMBER:            The member ID does not exist
     * NO_IDS_SELECTED            No IDs to move (empty id array)
     * </code>
     */
    
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
     *
     * <code>
     * Exception Codes:
     * NO_SUCH_MEMBER:            The member ID does not exist
     * NO_IDS_SELECTED            No IDs to move (empty id array)
     * </code>
     */
    
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
     *
     * <code>
     * 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
     * </code>
     */
    
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&section=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&section=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&section=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
     *
     * <code>
     * Exception Codes:
     * NO_SUCH_MEMBER:            The member ID does not exist
     * NO_IDS_TO_DELETE:        No IDs to delete (empty id array)
     * </code>
     */
    
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
     *
     * <code>
     * 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)
     * </code>
     */
    
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
     *
     * <code>
     * 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
     * </code>
     */
    
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
     *
     * <code>
     * 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
     * </code>
     */
    
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
        //-----------------------------------------
        
        
$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;
        
        
$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'        => intval( $replyCount['count'] ),
                                                    
'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'  ) ) );
                }
            }
            
            
//-----------------------------------------
            // 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 );
                
                
IPSText::getTextClass('email')->getTemplate( "personal_convo_new_reply", $toMember['language'] );
            
                
IPSText::getTextClass('email')->buildMessage( array( 'NAME'        => $toMember['members_display_name'],
                                                                     
'POSTER'    => $fromMemberData['members_display_name'],
                                                                     
'TITLE'    => $topicData['mt_title'],
                                                                     
'TEXT'        => $msgContent,
                                                                     
'LINK'        => "?app=members&module=messaging&section=view&do=findMessage&topicID={$topicID}&msgID=__firstUnread__" ) );    

                
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&section=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&section=view&do=findMessage&topicID={$topicID}&msgID=__firstUnread__", 'public' ) );
                
$notifyLibrary->setNotificationText( IPSText::getTextClass('email')->message );
                
$notifyLibrary->setNotificationTitle( IPSText::getTextClass('email')->subject );
                
$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
     *
     * <code>
     * 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
     * </code>
     */
    
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'];
            
            
IPSMember::save( $toMemberData['member_id'], array( 'core' => array( 'msg_count_new' => 'plus:1' ) ) );
        }
        
//-----------------------------------------
        // 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();
            
            if ( ! 
$isDraft )
            {
                
IPSMember::save( $toMemberData['member_id'], array( 'core' => array( 'msg_count_new' => 'plus:1'  ) ) );
            }
        }
        
        
//-----------------------------------------
        // 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 ) );

                
//-----------------------------------------
                // Update profile
                //-----------------------------------------
                
                /**
                 * @link    http://community.invisionpower.com/tracker/issue-37272-pm-notification-counter-not-increasing/page__catfilter__1
                 * 
                 * @todo    This query can actually be moved outside the foreach cycle using an update IN array_keys()
                 */
                //IPSMember::save(  $toMember['member_id'], array( 'core' => array( 'msg_count_total'=> 'plus:1', 'msg_count_reset' => 1 ) ) );
                
IPSMember::save(  $toMember['member_id'], array( 'core' => array( 'msg_count_total'=> 'plus:1' ) ) );

                
//-----------------------------------------
                // 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'];
                            
                    
IPSText::getTextClass('email')->setPlainTextTemplate( IPSText::getTextClass('email')->getTemplate( 'personal_convo_new_convo', $toMember['language'] ) );
                    
                    
IPSText::getTextClass('email')->buildMessage( array(
                                                                        
'NAME'        => $toMember['members_display_name'],
                                                                        
'POSTER'    => $fromMemberData['members_display_name'],
                                                                        
'TITLE'        => $msgTitle,
                                                                        
'TEXT'        => $msgContent,
                                                                        
'LINK'        => "?app=members&module=messaging&section=view&do=showConversation&topicID={$msg_topic_id}#msg{$msg_id}" ) );

                    
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&section=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&section=view&do=showConversation&topicID={$msg_topic_id}#msg{$msg_id}", 'public' ) );
                    
$notifyLibrary->setNotificationText( IPSText::getTextClass('email')->getPlainTextContent() );
                    
$notifyLibrary->setNotificationTitle( IPSText::getTextClass('email')->subject );
                    
$notifyLibrary->setMetaData( array( 'meta_area' => 'pm', 'meta_id' => $msg_topic_id, 'meta_app' => 'members' ) );
                    try
                    {
                        
$notifyLibrary->sendNotification();
                    }
                    catch( 
Exception $e ){}
                }
            }
        }
        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
     *
     * <code>
     * 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)
     * </code>
     */
    
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
            {
                
$where = " AND mm.map_user_active=1 AND mm.map_folder_id='{$folderID}'";
            }
            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
     *
     * <code>
     * Exception Codes
     * NO_READ_PERMISSION        You do not have permission to read the topic
     * YOU_ARE_BANNED            You have been banned
     * </code>
     */
    
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=', '&#91;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'] = "<br />";
         
$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'] ? sprintf( "%.0f", ( ($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'] = "<span class='highlight'>".$this->lang->words['c_msg_full']."</span>";
             }
             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() )
    {
        
//-----------------------------------------
        // Reset Classes
        //-----------------------------------------
        
        
IPSText::resetTextClass('bbcode');
        
        
//-----------------------------------------
        // Post process the editor
        // Now we have safe HTML and bbcode
        //-----------------------------------------
        
        
$this->settings['max_emos'] =  0 ;

         
IPSText::getTextClass('bbcode')->parse_smilies                = 1;
         
IPSText::getTextClass('bbcode')->parse_nl2br                = 1;
         
IPSText::getTextClass('bbcode')->parse_html                    = 0;//$this->caches['group_cache'][ $data['member_group_id'] ]['g_dohtml'];
         
IPSText::getTextClass('bbcode')->parse_bbcode                = 1;
         
IPSText::getTextClass('bbcode')->parsing_section            = 'pms';
        
IPSText::getTextClass( 'bbcode' )->parsing_mgroup            = $data['member_group_id'];
        
IPSText::getTextClass( 'bbcode' )->parsing_mgroup_others    = $data['mgroup_others'];
         
         
$msgContent = IPSText::getTextClass('bbcode')->preDisplayParse( $msgContent );
     
         if ( 
IPSText::getTextClass('bbcode')->error != "" )
         {
             
//throw new Exception( "BBCODE_" . IPSText::getTextClass('bbcode')->error );
         
}

        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 );
        
        if ( 
IPS_IS_AJAX )
        {
            
/* Was sent via std editor */
            
$_editor->setRteEnabled( false );
        }
        
        
$msgContent = $_editor->process( $msgContent );
    
        if ( 
IPS_IS_AJAX )
        {
            
$msgContent = nl2br( $msgContent );
        }
        
        
//-----------------------------------------
        // Parse post
        //-----------------------------------------

        
IPSText::getTextClass('bbcode')->parse_smilies     = 1;
         
IPSText::getTextClass('bbcode')->parse_nl2br        = 1;
         
IPSText::getTextClass('bbcode')->parse_html         = 0;//$this->memberData['g_dohtml'];
         
IPSText::getTextClass('bbcode')->parse_bbcode    = 1;
         
IPSText::getTextClass('bbcode')->parsing_section = 'pms';
        
        
$msgContent = IPSText::getTextClass( 'bbcode' )->preDbParse( $msgContent );
        
        if ( 
IPSText::getTextClass('bbcode')->error != "" )
         {
             throw new 
Exception( "BBCODE_" . IPSText::getTextClass('bbcode')->error );
         }

        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 ) );
    }
}
Онлайн: 3
Реклама