function msgQueryLocations($aRequest)
{
    global $gSite;
    global $gGame;
    loadGameSettings();
    $Out = Out::getInstance();
    if (validRaidlead()) {
        $Connector = Connector::getInstance();
        // Locations
        $ListLocations = $Connector->prepare('Select * FROM `' . RP_TABLE_PREFIX . 'Location` WHERE Game = :Game ORDER BY Name');
        $ListLocations->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
        $Locations = array();
        $ListLocations->loop(function ($Data) use(&$Locations) {
            $LocationData = array('id' => $Data['LocationId'], 'name' => $Data['Name'], 'image' => $Data['Image']);
            array_push($Locations, $LocationData);
        });
        $Out->pushValue('location', $Locations);
        // Images
        $Images = @scandir('../themes/icons/' . $gSite['Iconset'] . '/raidsmall');
        $ImageList = array();
        if ($Images != null) {
            foreach ($Images as $Image) {
                if (strripos($Image, '.png') !== false) {
                    array_push($ImageList, $Image);
                }
            }
        }
        $Out->pushValue('locationimage', $ImageList);
    } else {
        $Out->pushError(L('AccessDenied'));
    }
}
function msgQueryNewRaidData($aRequest)
{
    $Out = Out::getInstance();
    if (validRaidlead()) {
        $Connector = Connector::getInstance();
        // Settings
        $NewRaidSettings = $Connector->prepare('SELECT Name, IntValue, TextValue FROM `' . RP_TABLE_PREFIX . 'Setting`');
        $IntOfInterest = array('RaidSize', 'RaidStartHour', 'RaidStartMinute', 'RaidEndHour', 'RaidEndMinute', 'StartOfWeek');
        $TextOfInterest = array('RaidMode');
        $Settings = array();
        $NewRaidSettings->loop(function ($Data) use(&$Settings, $IntOfInterest, $TextOfInterest) {
            $KeyValue = array('name' => $Data['Name'], 'value' => null);
            if (in_array($Data['Name'], $IntOfInterest)) {
                $KeyValue['value'] = $Data['IntValue'];
            } elseif (in_array($Data['Name'], $TextOfInterest)) {
                $KeyValue['value'] = $Data['TextValue'];
            }
            array_push($Settings, $KeyValue);
        });
        $Out->pushValue('setting', $Settings);
        // Locations
        msgQueryLocations($aRequest);
    } else {
        $Out->pushError(L('AccessDenied'));
    }
}
function msgQueryUser($aRequest)
{
    $Out = Out::getInstance();
    if (registeredUser()) {
        $CurrentUser = UserProxy::getInstance();
        $CharacterIds = array();
        $CharacterGames = array();
        $CharacterNames = array();
        $CharacterClasses = array();
        $CharacterRoles1 = array();
        $CharacterRoles2 = array();
        $Settings = array();
        foreach ($CurrentUser->Characters as $Character) {
            array_push($CharacterIds, $Character->CharacterId);
            array_push($CharacterGames, $Character->Game);
            array_push($CharacterNames, $Character->Name);
            array_push($CharacterClasses, explode(':', $Character->ClassName));
            array_push($CharacterRoles1, $Character->Role1);
            array_push($CharacterRoles2, $Character->Role2);
        }
        $Out->pushValue('registeredUser', true);
        $Out->pushValue('id', $CurrentUser->UserId);
        $Out->pushValue('name', $CurrentUser->UserName);
        $Out->pushValue('characterIds', $CharacterIds);
        $Out->pushValue('characterGames', $CharacterGames);
        $Out->pushValue('characterNames', $CharacterNames);
        $Out->pushValue('characterClass', $CharacterClasses);
        $Out->pushValue('role1', $CharacterRoles1);
        $Out->pushValue('role2', $CharacterRoles2);
        $Out->pushValue('validUser', validUser());
        $Out->pushValue('isRaidlead', validRaidlead());
        $Out->pushValue('isAdmin', validAdmin());
        $Out->pushValue('settings', $CurrentUser->Settings);
        $Session = Session::get();
        if (isset($Session['Calendar'])) {
            $Out->pushValue('calendar', $Session['Calendar']);
        } else {
            $Out->pushValue('calendar', null);
        }
    } else {
        $Out->pushValue('registeredUser', false);
    }
}
function msgRaidDelete($aRequest)
{
    if (validRaidlead()) {
        $Connector = Connector::getInstance();
        // Call plugins
        $RaidId = intval($aRequest['id']);
        PluginRegistry::ForEachPlugin(function ($PluginInstance) use($RaidId) {
            $PluginInstance->onRaidRemove($RaidId);
        });
        do {
            // Delete raid
            $Connector->beginTransaction();
            $DeleteRaidQuery = $Connector->prepare('DELETE FROM `' . RP_TABLE_PREFIX . 'Raid` WHERE RaidId = :RaidId LIMIT 1');
            $DeleteRaidQuery->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
            if (!$DeleteRaidQuery->execute()) {
                $Connector->rollBack();
                return;
                // ### return, error ###
            }
            // Delete attendance
            $DeleteAttendanceQuery = $Connector->prepare('DELETE FROM `' . RP_TABLE_PREFIX . 'Attendance` WHERE RaidId = :RaidId');
            $DeleteAttendanceQuery->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
            if (!$DeleteAttendanceQuery->execute()) {
                $Connector->rollBack();
                return;
                // ### return, error ###
            }
        } while (!$Connector->commit());
        $Session = Session::get();
        $ShowMonth = isset($Session['Calendar']) && isset($Session['Calendar']['month']) ? $Session['Calendar']['month'] : $aRequest['month'];
        $ShowYear = isset($Session['Calendar']) && isset($Session['Calendar']['year']) ? $Session['Calendar']['year'] : $aRequest['year'];
        msgQueryCalendar(prepareCalRequest($ShowMonth, $ShowYear));
    } else {
        $Out = Out::getInstance();
        $Out->pushError(L('AccessDenied'));
    }
}
function msgRaidCreate($aRequest)
{
    if (validRaidlead()) {
        global $gGame;
        loadGameSettings();
        $Connector = Connector::getInstance();
        $LocationId = $aRequest['locationId'];
        // Create location
        if ($LocationId == 0) {
            $NewLocationQuery = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Location`' . '(Game, Name, Image) VALUES (:Game, :Name, :Image)');
            $NewLocationQuery->bindValue(':Name', requestToXML($aRequest['locationName'], ENT_COMPAT, 'UTF-8'), PDO::PARAM_STR);
            $NewLocationQuery->bindValue(':Image', $aRequest['raidImage'], PDO::PARAM_STR);
            $NewLocationQuery->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
            if (!$NewLocationQuery->execute()) {
                return;
            }
            // ### return, location could not be created ###
            $LocationId = $Connector->lastInsertId();
        }
        // Create raid
        if ($LocationId != 0) {
            // First raid time calculation
            $StartHour = intval($aRequest['startHour']);
            $StartMinute = intval($aRequest['startMinute']);
            $StartDay = intval($aRequest['startDay']);
            $StartMonth = intval($aRequest['startMonth']);
            $StartYear = intval($aRequest['startYear']);
            $EndHour = intval($aRequest['endHour']);
            $EndMinute = intval($aRequest['endMinute']);
            $EndDay = intval($aRequest['endDay']);
            $EndMonth = intval($aRequest['endMonth']);
            $EndYear = intval($aRequest['endYear']);
            // Get users on vacation
            $UserSettingsQuery = $Connector->prepare('SELECT UserId, Name, IntValue, TextValue FROM `' . RP_TABLE_PREFIX . 'UserSetting` ' . 'WHERE Name = "VacationStart" OR Name = "VacationEnd" OR Name = "VacationMessage" ORDER BY UserId');
            $VactionUsers = array();
            $UserSettingsQuery->loop(function ($Settings) use(&$VactionUsers) {
                if (!isset($VactionUsers[$Settings['UserId']])) {
                    $VactionUsers[$Settings['UserId']] = array('Message' => '');
                }
                switch ($Settings['Name']) {
                    case 'VacationStart':
                        $VactionUsers[$Settings['UserId']]['Start'] = $Settings['IntValue'];
                        break;
                    case 'VacationEnd':
                        $VactionUsers[$Settings['UserId']]['End'] = $Settings['IntValue'];
                        break;
                    case 'VacationMessage':
                        $VactionUsers[$Settings['UserId']]['Message'] = $Settings['TextValue'];
                        break;
                    default:
                        break;
                }
            });
            // Prepare posting raids to forum
            $PostTargets = array();
            PluginRegistry::ForEachBinding(function ($PluginInstance) use(&$PostTargets) {
                if ($PluginInstance->isActive() && $PluginInstance->postRequested()) {
                    array_push($PostTargets, $PluginInstance);
                }
            });
            $LocationData = null;
            if (count($PostTargets) > 0) {
                loadSiteSettings();
                $LocationQuery = $Connector->prepare('SELECT * FROM `' . RP_TABLE_PREFIX . 'Location` WHERE LocationId = :LocationId LIMIT 1');
                $LocationQuery->bindValue(':LocationId', $LocationId, PDO::PARAM_INT);
                $LocationData = $LocationQuery->fetchFirst();
            }
            // Get opt-out list or auto attend users
            $AutoAttendUsers = array();
            if (strtolower($aRequest['mode'] == 'optout')) {
                $UserQuery = $Connector->prepare('SELECT UserId, CharacterId, Class, Role1 FROM `' . RP_TABLE_PREFIX . 'User` ' . 'LEFT JOIN `' . RP_TABLE_PREFIX . 'Character` USING(UserId) ' . 'WHERE Mainchar="true" AND Game=:Game');
                $UserQuery->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
                $UserQuery->loop(function ($aUser) use(&$AutoAttendUsers) {
                    array_push($AutoAttendUsers, $aUser);
                });
            } else {
                $UserQuery = $Connector->prepare('SELECT UserId, CharacterId, Class, Role1 FROM `' . RP_TABLE_PREFIX . 'UserSetting` ' . 'LEFT JOIN `' . RP_TABLE_PREFIX . 'Character` USING(UserId) ' . 'WHERE `' . RP_TABLE_PREFIX . 'UserSetting`.Name="AutoAttend" AND Mainchar="true" AND Game=:Game');
                $UserQuery->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
                $UserQuery->loop(function ($aUser) use(&$AutoAttendUsers) {
                    array_push($AutoAttendUsers, $aUser);
                });
            }
            // Create raids(s)
            $Repeat = max(0, intval($aRequest['repeat'])) + 1;
            // repeat at least once
            $GroupInfo = $gGame['Groups'][$aRequest['locationSize']];
            $SlotRoles = implode(':', array_keys($GroupInfo));
            $SlotCount = implode(':', $GroupInfo);
            $RaidMode = $aRequest['mode'] == 'optout' ? 'manual' : $aRequest['mode'];
            for ($rc = 0; $rc < $Repeat; ++$rc) {
                $NewRaidQuery = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Raid` ' . '(LocationId, Size, Start, End, Mode, Description, SlotRoles, SlotCount ) ' . 'VALUES (:LocationId, :Size, FROM_UNIXTIME(:Start), FROM_UNIXTIME(:End), :Mode, :Description, ' . ':SlotRoles, :SlotCount)');
                $StartDateTime = mktime($StartHour, $StartMinute, 0, $StartMonth, $StartDay, $StartYear);
                $EndDateTime = mktime($EndHour, $EndMinute, 0, $EndMonth, $EndDay, $EndYear);
                // Convert to UTC
                $StartDateTime += $aRequest['startOffset'] * 60;
                $EndDateTime += $aRequest['endOffset'] * 60;
                $NewRaidQuery->bindValue(':LocationId', $LocationId, PDO::PARAM_INT);
                $NewRaidQuery->bindValue(':Size', $aRequest['locationSize'], PDO::PARAM_INT);
                $NewRaidQuery->bindValue(':Start', $StartDateTime, PDO::PARAM_INT);
                $NewRaidQuery->bindValue(':End', $EndDateTime, PDO::PARAM_INT);
                $NewRaidQuery->bindValue(':Mode', $RaidMode, PDO::PARAM_STR);
                $NewRaidQuery->bindValue(':Description', requestToXML($aRequest['description'], ENT_COMPAT, 'UTF-8'), PDO::PARAM_STR);
                $NewRaidQuery->bindValue(':SlotRoles', $SlotRoles, PDO::PARAM_STR);
                $NewRaidQuery->bindValue(':SlotCount', $SlotCount, PDO::PARAM_STR);
                $NewRaidQuery->execute();
                $RaidId = $Connector->lastInsertId();
                // Attend players when mode is optout
                if (count($AutoAttendUsers > 0)) {
                    $Status = $RaidMode == 'all' || $RaidMode == 'attend' ? 'ok' : 'available';
                    foreach ($AutoAttendUsers as $User) {
                        $UserId = intval($User['UserId']);
                        if (isset($VactionUsers[$UserId]) && ($StartDateTime >= $VactionUsers[$UserId]['Start'] && $StartDateTime <= $VactionUsers[$UserId]['End'])) {
                            continue;
                            // ### continue, user is on vacation ###
                        }
                        $Classes = explode(':', $User['Class']);
                        $ClassId = $Classes[0];
                        $RoleId = $gGame['ClassMode'] == 'multi' ? $gGame['Classes'][$ClassId]['roles'][0] : $User['Role1'];
                        $AttendQuery = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Attendance` (UserId, RaidId, CharacterId, Class, Role, Status) ' . 'VALUES (:UserId, :RaidId, :CharId, :Class, :Role, :Status)');
                        $AttendQuery->bindValue(':UserId', $UserId, PDO::PARAM_INT);
                        $AttendQuery->bindValue(':RaidId', $RaidId, PDO::PARAM_INT);
                        $AttendQuery->bindValue(':CharId', $User['CharacterId'], PDO::PARAM_INT);
                        $AttendQuery->bindValue(':Class', $ClassId, PDO::PARAM_STR);
                        $AttendQuery->bindValue(':Role', $RoleId, PDO::PARAM_STR);
                        $AttendQuery->bindValue(':Status', $Status, PDO::PARAM_STR);
                        $AttendQuery->execute();
                    }
                    if ($RaidMode == 'attend') {
                        removeOverbooked($RaidId, $SlotRoles, $SlotCount);
                    }
                }
                // Set vacation attendances
                foreach ($VactionUsers as $UserId => $Settings) {
                    if ($StartDateTime >= $Settings['Start'] && $StartDateTime <= $Settings['End']) {
                        $AbsentQuery = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Attendance` (UserId, RaidId, Status, Comment) ' . 'VALUES (:UserId, :RaidId, "unavailable", :Message)');
                        $AbsentQuery->bindValue(':UserId', $UserId, PDO::PARAM_INT);
                        $AbsentQuery->bindValue(':RaidId', $RaidId, PDO::PARAM_INT);
                        $AbsentQuery->bindValue(':Message', $Settings['Message'], PDO::PARAM_STR);
                        $AbsentQuery->execute();
                    }
                }
                // Post raids to forum
                if (count($PostTargets) > 0) {
                    $RaidQuery = $Connector->prepare('SELECT * FROM `' . RP_TABLE_PREFIX . 'Raid` WHERE RaidId=:RaidId LIMIT 1');
                    $RaidQuery->bindValue(':RaidId', $RaidId, PDO::PARAM_INT);
                    $RaidData = $RaidQuery->fetchFirst();
                    $MessageData = Binding::generateMessage($RaidData, $LocationData);
                    try {
                        foreach ($PostTargets as $PluginInstance) {
                            $PluginInstance->post($MessageData['subject'], $MessageData['message']);
                        }
                    } catch (PDOException $Exception) {
                        Out::getInstance()->pushError($Exception->getMessage());
                    }
                }
                // Call plugins
                PluginRegistry::ForEachPlugin(function ($PluginInstance) use($RaidId) {
                    $PluginInstance->onRaidCreate($RaidId);
                });
                // Increment start/end
                switch ($aRequest['stride']) {
                    case 'day':
                        ++$StartDay;
                        ++$EndDay;
                        break;
                    case 'week':
                        $StartDay += 7;
                        $EndDay += 7;
                        break;
                    case 'month':
                        ++$StartMonth;
                        ++$EndMonth;
                        break;
                    default:
                    case 'once':
                        $rc = $Repeat;
                        // Force done
                        break;
                }
            }
            // reload calendar
            $Session = Session::get();
            $ShowMonth = isset($Session['Calendar']) && isset($Session['Calendar']['month']) ? $Session['Calendar']['month'] : $aRequest['month'];
            $ShowYear = isset($Session['Calendar']) && isset($Session['Calendar']['year']) ? $Session['Calendar']['year'] : $aRequest['year'];
            msgQueryCalendar(prepareCalRequest($ShowMonth, $ShowYear));
        }
    } else {
        $Out = Out::getInstance();
        $Out->pushError(L('AccessDenied'));
    }
}
function msgRaidupdate($aRequest)
{
    if (validRaidlead()) {
        global $gGame;
        loadGameSettings();
        $Connector = Connector::getInstance();
        // The whole update is packed into one transaction.
        // The transaction will be rolled back upon error so no half-updated
        // data is stored in the database. This requires the database to
        // support transactions.
        do {
            $Connector->beginTransaction();
            $LocationId = $aRequest['locationId'];
            // Insert new location if necessary
            if ($LocationId == 0) {
                $NewLocationQuery = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Location`' . '(Name, Game, Image) VALUES (:Name, :Game, :Image)');
                $NewLocationQuery->bindValue(':Name', requestToXML($aRequest['locationName'], ENT_COMPAT, 'UTF-8'), PDO::PARAM_STR);
                $NewLocationQuery->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
                $NewLocationQuery->bindValue(':Image', $aRequest['raidImage'], PDO::PARAM_STR);
                if (!$NewLocationQuery->execute()) {
                    $Connector->rollBack();
                    return;
                    // ### return, error ###
                }
                $LocationId = $Connector->lastInsertId();
            }
            // Update raid
            $UpdateRaidQuery = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Raid` SET ' . 'LocationId = :LocationId, Size = :Size, ' . 'Stage = :Stage, ' . 'Start = FROM_UNIXTIME(:Start), End = FROM_UNIXTIME(:End), ' . 'Description = :Description, ' . 'Mode = :Mode, ' . 'SlotRoles = :SlotRoles, SlotCount = :SlotCount ' . 'WHERE RaidId = :RaidId');
            $StartDateTime = mktime(intval($aRequest['startHour']), intval($aRequest['startMinute']), 0, intval($aRequest['startMonth']), intval($aRequest['startDay']), intval($aRequest['startYear']));
            $EndDateTime = mktime(intval($aRequest['endHour']), intval($aRequest['endMinute']), 0, intval($aRequest['endMonth']), intval($aRequest['endDay']), intval($aRequest['endYear']));
            // Convert to UTC
            $StartDateTime += $aRequest['startOffset'] * 60;
            $EndDateTime += $aRequest['endOffset'] * 60;
            $UpdateRaidQuery->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
            $UpdateRaidQuery->bindValue(':LocationId', $LocationId, PDO::PARAM_INT);
            $UpdateRaidQuery->bindValue(':Stage', $aRequest['stage'], PDO::PARAM_STR);
            $UpdateRaidQuery->bindValue(':Size', $aRequest['locationSize'], PDO::PARAM_INT);
            $UpdateRaidQuery->bindValue(':Start', $StartDateTime, PDO::PARAM_INT);
            $UpdateRaidQuery->bindValue(':End', $EndDateTime, PDO::PARAM_INT);
            $UpdateRaidQuery->bindValue(':Mode', $aRequest['mode'], PDO::PARAM_STR);
            $UpdateRaidQuery->bindValue(':Description', requestToXML($aRequest['description'], ENT_COMPAT, 'UTF-8'), PDO::PARAM_STR);
            $UpdateRaidQuery->bindValue(':SlotRoles', implode(':', $aRequest['slotRoles']), PDO::PARAM_STR);
            $UpdateRaidQuery->bindValue(':SlotCount', implode(':', $aRequest['slotCount']), PDO::PARAM_STR);
            if (!$UpdateRaidQuery->execute()) {
                $Connector->rollBack();
                return;
                // ### return, error ###
            }
            // Remove the attends marked for delete.
            // Only random player attends can be removed.
            $NumRemoved = isset($aRequest['removed']) ? count($aRequest['removed']) : 0;
            for ($i = 0; $i < $NumRemoved; ++$i) {
                $RemoveSlot = $Connector->prepare('DELETE FROM `' . RP_TABLE_PREFIX . 'Attendance` ' . 'WHERE AttendanceId = :AttendanceId AND CharacterId = 0 AND UserId = 0');
                $RemoveSlot->bindValue(':AttendanceId', $aRequest['removed'][$i], PDO::PARAM_INT);
                if (!$RemoveSlot->execute()) {
                    $Connector->rollBack();
                    return;
                    // ### return, error ###
                }
            }
            // Now iterate over all role lists and update the players in it
            // Random player will be converted to 'real' player, i.e. they loose their
            // negative pseudo-id.
            foreach ($gGame['Roles'] as $Role) {
                if (isset($aRequest['role_' . $Role['id']])) {
                    $NumAttends = 0;
                    $AttendsForRole = $aRequest['role_' . $Role['id']];
                    // Attendances are passed in the form [id,status,id,status, … ]
                    // So we iterate with a stride of 2
                    for ($AttendIdx = 0; $AttendIdx < count($AttendsForRole);) {
                        $UpdateSlot = null;
                        // $Id = UserId when not having an attendance record
                        // $Id = AttendanceId for all others
                        $Id = intVal($AttendsForRole[$AttendIdx++]);
                        $Status = $AttendsForRole[$AttendIdx++];
                        $OldTimestamp = $AttendsForRole[$AttendIdx++];
                        $Flags = intVal($AttendsForRole[$AttendIdx++]);
                        if ($Status == 'undecided') {
                            continue;
                        }
                        // ### continue, skip undecided ###
                        // Get extra parameters
                        if (($Flags & PlayerFlagCharId) != 0) {
                            $CharId = intval($AttendsForRole[$AttendIdx++]);
                            $ActiveClass = $AttendsForRole[$AttendIdx++];
                        }
                        if (($Flags & PlayerFlagUserId) != 0) {
                            $UserId = intVal($AttendsForRole[$AttendIdx++]);
                        }
                        if (($Flags & PlayerFlagName) != 0) {
                            $Name = $AttendsForRole[$AttendIdx++];
                        }
                        if (($Flags & PlayerFlagComment) != 0) {
                            $Comment = $AttendsForRole[$AttendIdx++];
                        }
                        if (($Flags & PlayerFlagNew) != 0) {
                            // New entries
                            if (($Flags & PlayerFlagComment) != 0 && ($Flags & PlayerFlagUserId) != 0 && ($Flags & PlayerFlagCharId) != 0) {
                                // Undecided set-up
                                $UpdateSlot = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Attendance` ' . '( CharacterId, Class, UserId, RaidId, Status, Role, Comment ) ' . 'VALUES ( :CharId, :Class, :UserId, :RaidId, :Status, :Role, :Comment )');
                                $UpdateSlot->bindValue(':CharId', $CharId, PDO::PARAM_INT);
                                $UpdateSlot->bindValue(':Class', $ActiveClass, PDO::PARAM_STR);
                                $UpdateSlot->bindValue(':UserId', $UserId, PDO::PARAM_INT);
                                $UpdateSlot->bindValue(':Comment', $Comment, PDO::PARAM_STR);
                            } else {
                                if (($Flags & PlayerFlagComment) != 0 && ($Flags & PlayerFlagCharId) != 0) {
                                    // Undecied absent
                                    $UpdateSlot = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Attendance` ' . '( CharacterId, Class, UserId, RaidId, Status, Role, Comment ) ' . 'VALUES ( :CharId, :Class, :UserId, :RaidId, :Status, :Role, :Comment )');
                                    $UpdateSlot->bindValue(':CharId', $CharId, PDO::PARAM_INT);
                                    $UpdateSlot->bindValue(':Class', $ActiveClass, PDO::PARAM_STR);
                                    $UpdateSlot->bindValue(':UserId', $UserId, PDO::PARAM_INT);
                                    $UpdateSlot->bindValue(':Comment', $Comment, PDO::PARAM_STR);
                                } else {
                                    if (($Flags & PlayerFlagName) != 0) {
                                        // Random player. Set name.
                                        $UpdateSlot = $Connector->prepare('INSERT INTO `' . RP_TABLE_PREFIX . 'Attendance` ' . '( CharacterId, UserId, RaidId, Status, Class, Role, Comment ) ' . 'VALUES ( 0, 0, :RaidId, :Status, :Class, :Role, :Name )');
                                        $UpdateSlot->bindValue(':Name', $Name, PDO::PARAM_STR);
                                        $UpdateSlot->bindValue(':Class', '___', PDO::PARAM_STR);
                                    } else {
                                        $Out = Out::getInstance();
                                        $Out->pushError('Invalid user flags');
                                    }
                                }
                            }
                        } else {
                            // Update existing entries
                            if (($Flags & PlayerFlagComment) != 0 && ($Flags & PlayerFlagCharId) != 0) {
                                // Used when setting up an absent player
                                $UpdateSlot = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Attendance` SET ' . 'Status = :Status, CharacterId = :CharId, Class = :Class, Comment = :Comment, Role = :Role, LastUpdate = FROM_UNIXTIME(:TimestampNow) ' . 'WHERE RaidId = :RaidId AND LastUpdate = FROM_UNIXTIME(:LastUpdate) AND AttendanceId = :AttendanceId LIMIT 1');
                                $UpdateSlot->bindValue(':Comment', $Comment, PDO::PARAM_STR);
                                $UpdateSlot->bindValue(':CharId', $CharId, PDO::PARAM_INT);
                                $UpdateSlot->bindValue(':Class', $ActiveClass, PDO::PARAM_STR);
                            } else {
                                if (($Flags & PlayerFlagCharId) != 0) {
                                    // Used when changing a character
                                    $UpdateSlot = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Attendance` SET ' . 'Status = :Status, CharacterId = :CharId, Class = :Class, Role = :Role, LastUpdate = FROM_UNIXTIME(:TimestampNow) ' . 'WHERE RaidId = :RaidId AND LastUpdate = FROM_UNIXTIME(:LastUpdate) AND AttendanceId = :AttendanceId LIMIT 1');
                                    $UpdateSlot->bindValue(':CharId', $CharId, PDO::PARAM_INT);
                                    $UpdateSlot->bindValue(':Class', $ActiveClass, PDO::PARAM_STR);
                                } else {
                                    if (($Flags & PlayerFlagComment) != 0) {
                                        // Used when setting a player to absent
                                        $UpdateSlot = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Attendance` SET ' . 'Status = :Status, Comment = :Comment, Role = :Role, LastUpdate = FROM_UNIXTIME(:TimestampNow) ' . 'WHERE RaidId = :RaidId AND LastUpdate = FROM_UNIXTIME(:LastUpdate) AND AttendanceId = :AttendanceId LIMIT 1');
                                        $UpdateSlot->bindValue(':Comment', $Comment, PDO::PARAM_STR);
                                    } else {
                                        if (($Flags & PlayerFlagName) != 0) {
                                            // Used when changing the name of a random player
                                            $UpdateSlot = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Attendance` SET ' . 'Status = :Status, Role = :Role, Comment = :Name, LastUpdate = FROM_UNIXTIME(:TimestampNow) ' . 'WHERE RaidId = :RaidId AND LastUpdate = FROM_UNIXTIME(:LastUpdate) AND AttendanceId = :AttendanceId LIMIT 1');
                                            $UpdateSlot->bindValue(':Name', $Name, PDO::PARAM_STR);
                                        } else {
                                            // Existing player, update
                                            $UpdateSlot = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Attendance` SET ' . 'Status = :Status, Role = :Role, LastUpdate = FROM_UNIXTIME(:TimestampNow) ' . 'WHERE RaidId = :RaidId AND LastUpdate = FROM_UNIXTIME(:LastUpdate) AND AttendanceId = :AttendanceId LIMIT 1');
                                        }
                                    }
                                }
                            }
                            $UpdateSlot->bindValue(':AttendanceId', $Id, PDO::PARAM_INT);
                            $UpdateSlot->bindValue(':LastUpdate', $OldTimestamp, PDO::PARAM_INT);
                            $UpdateSlot->bindValue(':TimestampNow', time(), PDO::PARAM_INT);
                        }
                        $UpdateSlot->bindValue(':Status', $Status, PDO::PARAM_STR);
                        $UpdateSlot->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
                        $UpdateSlot->bindValue(':Role', $Role['id'], PDO::PARAM_STR);
                        if (!$UpdateSlot->execute()) {
                            $Connector->rollBack();
                            return;
                            // ### return, error ###
                        }
                    }
                }
            }
            // Assure mode constraints
            if ($aRequest['mode'] == 'all') {
                // Mode 'all' means all players are either 'ok' or 'unavailable'
                $AttendenceQuery = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Attendance` SET Status = "ok" ' . 'WHERE RaidId = :RaidId AND Status = "available"');
                $AttendenceQuery->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
                if (!$AttendenceQuery->execute()) {
                    $Connector->rollBack();
                    return;
                    // ### return, error ###
                }
            } else {
                if ($aRequest['mode'] != 'overbook') {
                    // Assure there not more 'ok' players than allowed by slot size
                    $SlotSizes = array_combine($aRequest['slotRoles'], $aRequest['slotCount']);
                    foreach ($aRequest['slotRoles'] as $RoleId) {
                        if ($SlotSizes[$RoleId] > 0) {
                            $AttendenceQuery = $Connector->prepare('SELECT AttendanceId ' . 'FROM `' . RP_TABLE_PREFIX . 'Attendance` ' . 'WHERE RaidId = :RaidId AND Status = "ok" AND Role = :RoleId ' . 'ORDER BY AttendanceId DESC LIMIT :MaxCount');
                            $AttendenceQuery->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
                            $AttendenceQuery->bindValue(':RoleId', $RoleId, PDO::PARAM_STR);
                            $AttendenceQuery->bindValue(':MaxCount', $SlotSizes[$RoleId], PDO::PARAM_INT);
                            $LastAttend = $AttendenceQuery->fetchFirst();
                            if ($AttendenceQuery->getAffectedRows() == $SlotSizes[$RoleId]) {
                                // Fix the overhead
                                $FixQuery = $Connector->prepare('UPDATE `' . RP_TABLE_PREFIX . 'Attendance` SET Status = "available" ' . 'WHERE RaidId = :RaidId AND Status = "ok" AND Role = :RoleId ' . 'AND AttendanceId > :FirstId');
                                $FixQuery->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
                                $FixQuery->bindValue(':RoleId', $RoleId, PDO::PARAM_STR);
                                $FixQuery->bindValue(':FirstId', $LastAttend['AttendanceId'], PDO::PARAM_INT);
                                if (!$FixQuery->execute()) {
                                    $Connector->rollBack();
                                    return;
                                    // ### return, error ###
                                }
                            }
                        }
                    }
                }
            }
        } while (!$Connector->commit());
        // Call plugins
        $RaidId = intval($aRequest['id']);
        PluginRegistry::ForEachPlugin(function ($PluginInstance) use($RaidId) {
            $PluginInstance->onRaidModify($RaidId);
        });
        // reload detailed view
        msgRaidDetail($aRequest);
    } else {
        $Out = Out::getInstance();
        $Out->pushError(L('AccessDenied'));
    }
}
function msgRaidDetail($aRequest)
{
    if (validUser()) {
        global $gGame;
        loadGameSettings();
        $Out = Out::getInstance();
        $Connector = Connector::getInstance();
        $Out->pushValue('show', $aRequest['showPanel']);
        $ListRaidQuery = $Connector->prepare('SELECT ' . RP_TABLE_PREFIX . 'Raid.*, ' . RP_TABLE_PREFIX . 'Location.Name AS LocationName, ' . RP_TABLE_PREFIX . 'Location.Image AS LocationImage, ' . RP_TABLE_PREFIX . 'Attendance.AttendanceId, ' . RP_TABLE_PREFIX . 'Attendance.UserId, ' . RP_TABLE_PREFIX . 'Attendance.CharacterId, ' . RP_TABLE_PREFIX . 'Attendance.Status, ' . RP_TABLE_PREFIX . 'Attendance.Role, ' . RP_TABLE_PREFIX . 'Attendance.Class AS ActiveClass, ' . RP_TABLE_PREFIX . 'Attendance.Comment, ' . 'UNIX_TIMESTAMP(' . RP_TABLE_PREFIX . 'Attendance.LastUpdate) AS LastUpdate, ' . RP_TABLE_PREFIX . 'Character.Name, ' . RP_TABLE_PREFIX . 'Character.Class, ' . RP_TABLE_PREFIX . 'Character.Mainchar, ' . RP_TABLE_PREFIX . 'Character.Role1, ' . RP_TABLE_PREFIX . 'Character.Role2, ' . 'UNIX_TIMESTAMP(' . RP_TABLE_PREFIX . 'Raid.Start) AS StartUTC, ' . 'UNIX_TIMESTAMP(' . RP_TABLE_PREFIX . 'Raid.End) AS EndUTC ' . 'FROM `' . RP_TABLE_PREFIX . 'Raid` ' . 'LEFT JOIN `' . RP_TABLE_PREFIX . 'Location` USING(LocationId) ' . 'LEFT JOIN `' . RP_TABLE_PREFIX . 'Attendance` USING(RaidId) ' . 'LEFT JOIN `' . RP_TABLE_PREFIX . 'Character` USING(CharacterId) ' . 'WHERE RaidId = :RaidId ORDER BY `' . RP_TABLE_PREFIX . 'Attendance`.AttendanceId');
        $ListRaidQuery->bindValue(':RaidId', $aRequest['id'], PDO::PARAM_INT);
        $Data = $ListRaidQuery->fetchFirstOfLoop();
        if ($Data != null) {
            $Participants = array();
            $StartDate = getdate($Data['StartUTC']);
            $EndDate = getdate($Data['EndUTC']);
            $EndTimestamp = $Data['EndUTC'];
            $Slots = array_combine(explode(':', $Data['SlotRoles']), explode(':', $Data['SlotCount']));
            $Out->pushValue('raidId', $Data['RaidId']);
            $Out->pushValue('locationid', $Data['LocationId']);
            $Out->pushValue('locationname', $Data['LocationName']);
            $Out->pushValue('stage', $Data['Stage']);
            $Out->pushValue('mode', $Data['Mode']);
            $Out->pushValue('image', $Data['LocationImage']);
            $Out->pushValue('size', $Data['Size']);
            $Out->pushValue('startDate', intval($StartDate['year']) . '-' . leadingZero10($StartDate['mon']) . '-' . leadingZero10($StartDate['mday']));
            $Out->pushValue('start', leadingZero10($StartDate['hours']) . ':' . leadingZero10($StartDate['minutes']));
            $Out->pushValue('endDate', intval($EndDate['year']) . '-' . leadingZero10($EndDate['mon']) . '-' . leadingZero10($EndDate['mday']));
            $Out->pushValue('end', leadingZero10($EndDate['hours']) . ':' . leadingZero10($EndDate['minutes']));
            $Out->pushValue('description', $Data['Description']);
            $Out->pushValue('slots', $Slots);
            $Attendees = array();
            $MaxAttendanceId = 1;
            $NumAttended = 0;
            if ($Data['UserId'] != NULL) {
                $ListRaidQuery->loop(function ($Data) use(&$gGame, &$Connector, &$MaxAttendanceId, &$Participants, &$Attendees, &$NumAttended) {
                    // Track max attendance id to give undecided players (without a comment) a distinct one.
                    $MaxAttendanceId = Max($MaxAttendanceId, $Data['AttendanceId']);
                    if ($Data['UserId'] != 0) {
                        array_push($Participants, intval($Data['UserId']));
                    }
                    if ($Data['CharacterId'] == 0) {
                        // CharacterId is 0 on random players or players that are absent
                        if ($Data['UserId'] != 0) {
                            // Fetch the mainchar of the registered player and display this
                            // character as 'absent'
                            $CharQuery = $Connector->prepare('SELECT ' . RP_TABLE_PREFIX . 'Character.*, ' . RP_TABLE_PREFIX . 'User.Login AS UserName ' . 'FROM `' . RP_TABLE_PREFIX . 'Character` LEFT JOIN `' . RP_TABLE_PREFIX . 'User` USING(UserId) ' . 'WHERE UserId = :UserId AND Game = :Game ' . 'ORDER BY Mainchar, CharacterId ASC');
                            $CharQuery->bindValue(':UserId', $Data['UserId'], PDO::PARAM_INT);
                            $CharQuery->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
                            $CharData = $CharQuery->fetchFirstOfLoop();
                            if ($CharData != null && $CharData['CharacterId'] != null) {
                                $Classes = explode(':', $CharData['Class']);
                                $AttendeeData = array('id' => $Data['AttendanceId'], 'hasId' => true, 'userId' => $Data['UserId'], 'timestamp' => $Data['LastUpdate'], 'charid' => $CharData['CharacterId'], 'name' => $CharData['Name'], 'mainchar' => $CharData['Mainchar'], 'classname' => $Classes, 'activeclass' => $Classes[0], 'role' => $CharData['Role1'], 'role1' => $CharData['Role1'], 'role2' => $CharData['Role2'], 'status' => $Data['Status'], 'comment' => $Data['Comment'], 'character' => array());
                                $CharQuery->loop(function ($CharData) use(&$AttendeeData) {
                                    $Character = array('id' => $CharData['CharacterId'], 'name' => $CharData['Name'], 'mainchar' => $CharData['Mainchar'], 'classname' => explode(':', $CharData['Class']), 'role1' => $CharData['Role1'], 'role2' => $CharData['Role2']);
                                    array_push($AttendeeData['character'], $Character);
                                });
                                array_push($Attendees, $AttendeeData);
                            }
                        } else {
                            // CharacterId and UserId set to 0 means 'random player'
                            $AttendeeData = array('id' => $Data['AttendanceId'], 'hasId' => true, 'userId' => 0, 'timestamp' => $Data['LastUpdate'], 'charid' => 0, 'name' => $Data['Comment'], 'mainchar' => false, 'classname' => array('___'), 'activeclass' => '___', 'role' => $Data['Role'], 'role1' => $Data['Role'], 'role2' => $Data['Role'], 'status' => $Data['Status'], 'comment' => '', 'character' => array());
                            array_push($Attendees, $AttendeeData);
                            ++$NumAttended;
                        }
                    } else {
                        // CharacterId is set
                        $AttendeeData = array('id' => $Data['AttendanceId'], 'hasId' => true, 'userId' => $Data['UserId'], 'timestamp' => $Data['LastUpdate'], 'charid' => $Data['CharacterId'], 'name' => $Data['Name'], 'mainchar' => $Data['Mainchar'], 'classname' => explode(':', $Data['Class']), 'activeclass' => $Data['ActiveClass'], 'role' => $Data['Role'], 'role1' => $Data['Role1'], 'role2' => $Data['Role2'], 'status' => $Data['Status'], 'comment' => $Data['Comment'], 'character' => array());
                        $CharQuery = $Connector->prepare('SELECT ' . RP_TABLE_PREFIX . 'Character.*, ' . RP_TABLE_PREFIX . 'User.Login AS UserName ' . 'FROM `' . RP_TABLE_PREFIX . 'User` LEFT JOIN `' . RP_TABLE_PREFIX . 'Character` USING(UserId) ' . 'WHERE UserId = :UserId AND Game = :Game ' . 'ORDER BY Mainchar, CharacterId ASC');
                        $CharQuery->bindValue(':UserId', $Data['UserId'], PDO::PARAM_INT);
                        $CharQuery->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
                        $CharQuery->loop(function ($CharData) use(&$AttendeeData) {
                            $Character = array('id' => $CharData['CharacterId'], 'name' => $CharData['Name'], 'mainchar' => $CharData['Mainchar'], 'classname' => explode(':', $CharData['Class']), 'role1' => $CharData['Role1'], 'role2' => $CharData['Role2']);
                            array_push($AttendeeData['character'], $Character);
                        });
                        if ($Data['Status'] == 'ok' || $Data['Status'] == 'available') {
                            ++$NumAttended;
                        }
                        array_push($Attendees, $AttendeeData);
                    }
                });
            }
            // Fetch all registered and unblocked users
            $AllUsersQuery = $Connector->prepare('SELECT ' . RP_TABLE_PREFIX . 'User.UserId ' . 'FROM `' . RP_TABLE_PREFIX . 'User` ' . 'WHERE `Group` != "none"');
            $AllUsersQuery->loop(function ($User) use(&$gGame, &$Connector, &$MaxAttendanceId, &$EndTimestamp, &$Participants, &$Attendees) {
                if (!in_array(intval($User['UserId']), $Participants)) {
                    // Users that are not registered for this raid are undecided
                    // Fetch their character data, maincharacter first
                    $CharQuery = $Connector->prepare('SELECT ' . RP_TABLE_PREFIX . 'Character.*, ' . RP_TABLE_PREFIX . 'User.Login AS UserName ' . 'FROM `' . RP_TABLE_PREFIX . 'Character` LEFT JOIN `' . RP_TABLE_PREFIX . 'User` USING(UserId) ' . 'WHERE UserId = :UserId AND Created < FROM_UNIXTIME(:RaidEnd) AND Game = :Game ' . 'ORDER BY Mainchar, CharacterId ASC');
                    $CharQuery->bindValue(':UserId', $User['UserId'], PDO::PARAM_INT);
                    $CharQuery->bindValue(':RaidEnd', $EndTimestamp, PDO::PARAM_INT);
                    $CharQuery->bindValue(':Game', $gGame['GameId'], PDO::PARAM_STR);
                    $UserData = $CharQuery->fetchFirstOfLoop();
                    if ($UserData != null) {
                        // Absent user have no attendance Id, so we need to generate one
                        // that is not in use (for this raid).
                        ++$MaxAttendanceId;
                        $Classes = explode(':', $UserData['Class']);
                        $AttendeeData = array('id' => $MaxAttendanceId, 'hasId' => false, 'userId' => $UserData['UserId'], 'timestamp' => time(), 'charid' => $UserData['CharacterId'], 'name' => $UserData['Name'], 'mainchar' => $UserData['Mainchar'], 'classname' => $Classes, 'activeclass' => $Classes[0], 'role' => $UserData['Role1'], 'role1' => $UserData['Role1'], 'role2' => $UserData['Role2'], 'status' => 'undecided', 'comment' => '', 'character' => array());
                        $CharQuery->loop(function ($UserData) use(&$AttendeeData) {
                            $Character = array('id' => $UserData['CharacterId'], 'name' => $UserData['Name'], 'mainchar' => $UserData['Mainchar'], 'classname' => explode(':', $UserData['Class']), 'role1' => $UserData['Role1'], 'role2' => $UserData['Role2']);
                            array_push($AttendeeData['character'], $Character);
                        });
                        array_push($Attendees, $AttendeeData);
                    }
                }
            });
            $Out->pushValue('attendee', $Attendees);
            $Out->pushValue('attended', $NumAttended);
            $ExportParameter = Api::normalizeArgsRaid(array('raid' => intval($aRequest['id']), 'attends' => true));
            $Out->pushValue('token', Api::getPublicToken($ExportParameter));
        }
        if (validRaidlead()) {
            msgQueryLocations($aRequest);
        }
    } else {
        $Out = Out::getInstance();
        $Out->pushError(L('AccessDenied'));
    }
}