diff -Naur beta5//scripts/config.inc forums//scripts/config.inc --- beta5//scripts/config.inc 2011-02-05 10:09:57.904335002 +0100 +++ forums//scripts/config.inc 2011-03-12 15:47:09.431300051 +0100 @@ -39,7 +39,7 @@ "widgetURL" => "http://www.legacyworlds.com/downloads/LegacyWorlds-Dashboard-latest.zip", // Version numbers to make us feel good - "v_engine" => "0.85a", + "v_engine" => "0.86", "v_game" => "Beta 5", "v_rev" => "2218", diff -Naur beta5//scripts/game/beta5/actions/getForums.inc forums//scripts/game/beta5/actions/getForums.inc --- beta5//scripts/game/beta5/actions/getForums.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/beta5/actions/getForums.inc 2011-02-05 10:10:03.244335002 +0100 @@ -0,0 +1,84 @@ + array( + 'en' => array('General forums', "LegacyWorlds' public forums"), + 'fr' => array('Forums généraux', "Les forums publics de LegacyWorlds") + ), + 'U' => array( + 'en' => array('Player forums', "Forums belonging to other LegacyWorlds players"), + 'fr' => array('Forums des joueurs', "Forums appartenant à d'autres joueurs de LegacyWorlds") + ) + ); + + public function __construct($game) { + $this->game = $game; + $this->db = $this->game->getDBAccess(); + + // Load the forums' access libraries + $this->gForums = $this->game->getLib('main/gforums'); + $this->uForums = $this->game->getLib('main/uforums'); + $this->aForums = $this->game->getLib('beta5/aforums'); + + // Load the container class + loader::needClasses('main/forums', 'fl_container'); + } + + public function run( $player ) { + // Get the player's user ID + $q = $this->db->query("SELECT userid FROM player WHERE id = $1", (int) $player); + if (!( $q && dbCount($q) == 1)) { + return null; + } + list($user) = dbFetchArray($q); + + // Get the language string for the user + // FIXME: that thing would not work inside anything *but* a web session + $lang = getLanguage(); + + // Get the list of general, user-specific and alliance forums + $gForums = $this->gForums->call('getStructure', $user); + $aForums = $this->aForums->call('getStructure', $user); + $uForums = $this->uForums->call('getStructure', $user); + + // Generate the container for the whole thing + $forums = new fl_container('/', '', ''); + + // Generate the container for general forums + $gContainer = new fl_container('G', self::$names['G'][$lang][0], self::$names['G'][$lang][1]); + foreach ($gForums as $gc) { + $gContainer->addCategory($gc); + } + $forums->addCategory($gContainer); + + // Add the alliance forums + if (count($aForums) == 1) { + $forums->addCategory($aForums[0]); + } + + // Add the player's own forums + $forums->addCategory(array_shift($uForums)); + + // Add the other players' forums + if (count($uForums)) { + $uContainer = new fl_container('U', self::$names['U'][$lang][0], self::$names['U'][$lang][1]); + foreach ($uForums as $uc) { + $uContainer->addCategory($uc); + } + $forums->addCategory($uContainer); + } + + return $forums; + } +} + +?> diff -Naur beta5//scripts/game/beta5/actions.inc forums//scripts/game/beta5/actions.inc --- beta5//scripts/game/beta5/actions.inc 2011-02-05 10:09:57.784335002 +0100 +++ forums//scripts/game/beta5/actions.inc 2011-03-12 14:56:24.591300053 +0100 @@ -29,6 +29,10 @@ var $fleetDepartures = array(); var $ePower = array(); + var $index = array( + 'getForums' + ); + function actions_beta5($game) { $this->game = $game; diff -Naur beta5//scripts/game/beta5/aforums/library.inc forums//scripts/game/beta5/aforums/library.inc --- beta5//scripts/game/beta5/aforums/library.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/beta5/aforums/library.inc 2011-02-05 10:10:03.334335002 +0100 @@ -0,0 +1,256 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->version = $this->game->version; + $this->db = $this->game->db; + + $this->fLib = $this->game->getLib('main/forums'); + loader::needClasses('main/forums', array('fl_category', 'fl_forum')); + + $this->getPlayerQ = $this->db->prepare( + "SELECT id FROM player WHERE userid = $1 AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW()))", + array("user")); + $this->getAllianceCatQ = $this->db->prepare( + "SELECT a.f_category FROM player p, alliance a " + . "WHERE a.id = p.alliance AND p.alliance IS NOT NULL AND p.a_status='IN ' AND p.id = $1", + array("player") ); + $this->getAllianceQ = $this->db->prepare( + "SELECT a.tag, a.name FROM alliance a WHERE a.f_category = $1", + array("category") ); + $this->getCatForumsQ = $this->db->prepare( + "SELECT id FROM forums.t_forum WHERE category = $1 ORDER BY f_order", array("category") ); + $this->getForumPrivQ = $this->db->prepare( + "SELECT * FROM get_aforums_privs( $1, $2 )", array("player", "forum") ); + $this->getForumQ = $this->db->prepare( + "SELECT * FROM alliance_forum WHERE forum = $1", array("forum") ); + $this->getForumRanksQ = $this->db->prepare( + "SELECT rank, is_mod FROM al_rank_forum WHERE forum = $1", array("forum") ); + $this->getRankNameQ = $this->db->prepare( + "SELECT CASE name IS NULL WHEN TRUE THEN '-' ELSE name END FROM alliance_grade " + . "WHERE id = $1", + array("rank") ); + } + + public function getStructure( $user ) { + $cats = array(); + + $q = $this->getPlayerQ->execute($user); + if ($q && dbCount($q) == 1) { + list($player) = dbFetchArray($q); + + $q = $this->getAllianceCatQ->execute($player); + if ($q && dbCount($q)) { + list($catId) = dbFetchArray($q); + $cat = $this->fLib->call('getCategory', $catId, $user); + if (! is_null($cat)) { + array_push($cats, $cat); + } + } + } + + return $cats; + } + + public function getCategory( $catId , $user ) { + $q = $this->getAllianceQ->execute($catId); + if (! ($q && dbCount($q) == 1)) { + return null; + } + list($tag, $name) = dbFetchArray($q); + + $title = "[$tag] $name"; + $description = "These are the forums for the alliance you are a member of, $name."; + + $cat = new fl_category( $this->game, $user, $catId, $title, $description ); + + $q = $this->getCatForumsQ->execute($catId); + while ($r = dbFetchArray($q)) { + $forum = $this->fLib->call('getForum', $r[0], $user); + if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { + $cat->addForum( $forum ); + } + } + + return $cat; + } + + + public function getForum( $forumId, $user ) { + $q = $this->getPlayerQ->execute($user); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($player) = dbFetchArray($q); + + $q = $this->getForumPrivQ->execute($player, $forumId); + if (! ($q && dbCount($q) == 1)) { + return null; + } + $privs = dbFetchHash($q); + + try { + $forum = new fl_forum( $this->game, $forumId, $user ); + } catch (Exception $e) { + return null; + } + + $forum->setAdmin( $privs['is_admin'] == 't' ); + $forum->setMod( $privs['is_mod'] == 't' ); + $forum->setCreateTopic( $privs['can_create'] == 't' ); + $forum->setCreatePoll( $privs['can_poll'] == 't' ); + $forum->setPost( $privs['can_post'] == 't' ); + $forum->setView( $privs['can_view'] == 't' ); + + return $forum; + } + + public function getAdmins($forum) { + $q = $this->getForumQ->execute($forum); + if (!($q && dbCount($q) == 1)) { + return null; + } + $r = dbFetchHash($q); + $alliance = $r['alliance']; + + if (! is_null(self::$allianceAdmins[$alliance])) { + return self::$allianceAdmins[$alliance]; + } + + if (is_null($this->alliance)) { + $this->alliance = $this->game->getLib('beta5/alliance'); + } + + $l = array_keys($this->alliance->call('getRanks', $alliance)); + $admin = array(); + foreach ($l as $rId) { + $pr = $this->alliance->call('getRankPrivileges', $rId); + if (! $pr['forum_admin']) { + continue; + } + array_push($admin, $rId); + } + + return (self::$allianceAdmins[$alliance] = $admin); + } + + public function getModerators($forum) { + $rl = $this->getRankAccess($forum); + $rv = array(); + foreach ($rl as $rId => $isMod) { + if ($isMod) { + array_push($rv, $rId); + } + } + return $rv; + } + + public function getUsers($forum) { + $rl = $this->getRankAccess($forum); + $rv = array(); + foreach ($rl as $rId => $isMod) { + if (! $isMod) { + array_push($rv, $rId); + } + } + return $rv; + } + + + private function getRankAccess($forum) { + if (! is_null(self::$rAccess[$forum])) { + return self::$rAccess[$forum]; + } + + $rv = array(); + $q = $this->getForumRanksQ->execute($forum); + + if ($q && dbCount($q)) { + while ($r = dbFetchHash($q)) { + $rv[$r['rank']] = ($r['is_mod'] == 't'); + } + } + + return (self::$rAccess[$forum] = $rv); + } + + + public function aclIdToName($id) { + $q = $this->getRankNameQ->execute($id); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($rv) = dbFetchArray($q); + return $rv; + } + + + + public function getUserPrivileges($forum) { + $q = $this->getForumQ->execute($forum); + if (!($q && dbCount($q) == 1)) { + return null; + } + $r = dbFetchHash($q); + return $r['access_mode']; + } + + + public function create($player, $user, $alliance, $order, $name, $description, $accessMode) { + $q = $this->db->query("SELECT create_alliance_forum($1, $2, $3, $4, $5, $6)", + $player, $alliance, $order, $name, $description, $accessMode); + if (!($q && dbCount($q))) { + return -6; + } + list($rv) = dbFetchArray($q); + + if ($rv > 0) { + $forum = $this->fLib->call('getForum', $rv, $user); + if (is_null($forum)) { + return -6; + } + $forum->getCategory()->insertNewForum($forum); + $rv = $forum; + } else { + $rv = -$rv; + } + + return $rv; + } + + public function modifyForum($forum, $player, $name, $description, $accessMode) { + $this->db->query("SELECT modify_alliance_forum($1,$2,$3,$4,$5)", + $player, $forum, $name, $description, $accessMode); + if (!($q && dbCount($q))) { + return -5; + } + list($rv) = dbFetchArray($q); + return $rv; + } + + public function clearForumACL($forum) { + $this->db->query("DELETE FROM al_rank_forum WHERE forum = $1", $forum); + } + + public function addForumModerator( $forum, $id ) { + $this->db->query("INSERT INTO al_rank_forum (rank, forum, is_mod) VALUES ($1, $2, TRUE)", + $id, $forum); + } + + public function addForumUser( $forum, $id ) { + $this->db->query("INSERT INTO al_rank_forum (rank, forum, is_mod) VALUES ($1, $2, FALSE)", + $id, $forum); + } +} + +?> diff -Naur beta5//scripts/game/beta5/forums/topic/library.inc forums//scripts/game/beta5/forums/topic/library.inc --- beta5//scripts/game/beta5/forums/topic/library.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/beta5/forums/topic/library.inc 2011-02-05 10:10:03.334335002 +0100 @@ -0,0 +1,330 @@ +lib = $lib; + $this->game = $this->lib->game; + } + + public function initialize($pageHandler) { + $this->pHandler = $pageHandler; + } + + public function forumCommand($player, $commandArgs) { + + // Read the arguments + if (strstr($commandArgs, "#") !== FALSE) { + list( $topicIdS, $hexId ) = explode('#', $commandArgs); + if (strlen($hexId) != 32 || preg_match('/[^A-Z0-9]/i', $hexId)) { + $hexId = md5(uniqid(rand())); + } + + $topicId = (int) $topicIdS; + $data = $this->pHandler->getSessionData('topic_' . $hexId); + if (is_null($data) || $topicId != $data['topic']) { + $hexId = md5(uniqid(rand())); + $firstTime = true; + } else { + $firstTime = false; + } + } else { + $topicId = (int) $commandArgs; + $hexId = md5(uniqid(rand())); + $firstTime = true; + } + + // Get the player's user ID + $pinf = $this->game->getLib('beta5/player')->call('get', $player); + $user = $pinf['uid']; + + if ($firstTime) { + $this->game->action('getForums', $player); + $data = $this->initTopicData($user, $topicId); + $this->makeDataString($data, $hexId); + } else { + $this->updateTopicData($topicId, $data); + } + $this->pHandler->setSessionData('topic_' . $hexId, $data); + $this->data = $data; + + if ($data['exists']) { + $rv = array("V#F#{$data['forum']}", "vTopic", "$topicId#$hexId"); + } else { + $rv = array("", "vTopic", "$topicId#$hexId"); + } + return $rv; + } + + + /* This method initialises the stored session data for the specified topic ID. + */ + private function initTopicData($user, $topicId) { + $data = array( + "viewTime" => time(), + "topic" => $topicId, + "user" => $user + ); + + // Check whether the topic actually exists + $topic = $this->game->getLib('main/forums')->call('getTopic', $topicId, $user); + if (is_null($topic)) { + $data['exists'] = false; + return $data; + } + + // Check whether the topic is available + $forum = $this->forum = $topic->getForum(); + if (is_null($forum) || ! $forum->canView()) { + $data['exists'] = false; + return $data; + } + + // The topic exists + $data['exists'] = true; + $data['forum'] = $forum->getId(); + $data['title'] = $topic->getTitle(); + + // Has it been deleted? + if ($topic->isDeleted()) { + $data['deleted'] = true; + $data['deletedAt'] = $topic->getDeletionTime(); + $data['deletedBy'] = $topic->getDeletionMod(); + return $data; + } + + // Get additional data + $users = array(); + $data['lastChange'] = $topic->getLastChange(); + $data['lastRead'] = $topic->getLastRead(); + $data['isLocked'] = $topic->isLocked(); + $data['isMod'] = $forum->isMod(); + $data['canPost'] = $forum->canPost(); + + // Get the topic's posts + $data['postOrder'] = array(); + + // -> linear, newest first + $list = $topic->getPostList(false, false); + $data['postOrder']['ln'] = array(); + foreach ($list as $p) { + array_push($data['postOrder']['ln'], $p->getId()); + } + + // -> linear, oldest first + $list = $topic->getPostList(false, true); + $data['postOrder']['lo'] = array(); + foreach ($list as $p) { + array_push($data['postOrder']['lo'], $p->getId()); + } + + // -> threaded, newest first + $list = $topic->getPostList(true, false); + $data['postOrder']['tn'] = array(); + foreach ($list as $p) { + array_push($data['postOrder']['tn'], $p->getId()); + } + + // -> threaded, oldest first + $list = $topic->getPostList(true, true); + $data['postOrder']['to'] = array(); + foreach ($list as $p) { + array_push($data['postOrder']['to'], $p->getId()); + } + + // -> post data + $data['posts'] = array(); + foreach ($list as $p) { + // post_id # depth # author_id # posted_at # is_unread # lc_time # lc_author + // post_title + $pStr = $p->getId() . "#" . $p->getDepth() . "#" . $p->getPostedBy() + . "#" . $p->getPostedAt() . "#" . ($p->isUnread() ? 1 : 0) + . "#" . $p->getLastChange() . "#" . $p->getLastChangeAuthor() + . "\n" . utf8entities($p->getTitle()); + array_push($data['posts'], $pStr); + if (!in_array($p->getPostedBy(), $users)) { + array_push($users, $p->getPostedBy()); + } + if (!(is_null($p->getLastChangeAuthor()) || in_array($p->getLastChangeAuthor(), $users))) { + array_push($users, $p->getLastChangeAuthor()); + } + } + + // Get poll data + $poll = $topic->getPoll(); + $data['hasPoll'] = ! is_null($poll); + if ($data['hasPoll']) { + // FIXME: get poll data + } + + // Get user names + $data['users'] = $this->dumpNames($users); + + return $data; + } + + private function dumpNames($ids) { + $accLib = $this->game->getLib('main/account'); + + $output = array(); + array_push($output, count($ids)); + foreach ($ids as $id) { + array_push($output, "$id#" . utf8entities($accLib->call('getUserName', $id))); + } + return $output; + } + + /* This method generates the data string to be sent the first time a topic + * page is loaded. + */ + private function makeDataString(&$data, $hexId) { + $result = array( + "{$data['topic']}#$hexId" + ); + if ($data['exists']) { + // Fetch the path to the topic + $parents = array(); + $obj = $this->forum; + do { + array_push($parents, array( + $obj->getId(), utf8entities($obj->getTitle()) + )); + $obj = $obj->getParent(); + } while (! is_null($obj)); + + if ($data['deleted']) { + // If the topic has been deleted + array_push($result, "DELETED#{$data['deletedAt']}#" . count($parents)); + array_push($result, utf8entities($data['title'])); + // FIXME: fetch moderator's name +// array_push($result, $this->game->getLib(' + + // Dump the path + foreach (array_reverse($parents) as $p) { + array_push($result, join('#', $p)); + } + + } else { + // If the topic is available + array_push($result, "TOPIC#" . count($parents)); + array_push($result, utf8entities($data['title'])); + array_push($result, "{$data['user']}#" . ($data['isMod'] ? 1 : 0) + . "#" . ($data['canPost'] ? 1 : 0) . "#" . ($data['hasPoll'] ? 1 : 0)); + + // Dump the path + foreach (array_reverse($parents) as $p) { + array_push($result, join('#', $p)); + } + + // Post orders + array_push($result, join('#', $data['postOrder']['ln'])); + array_push($result, join('#', $data['postOrder']['lo'])); + array_push($result, join('#', $data['postOrder']['tn'])); + array_push($result, join('#', $data['postOrder']['to'])); + + // Posts + foreach ($data['posts'] as $post) { + array_push($result, $post); + } + + // Users + foreach ($data['users'] as $user) { + array_push($result, $user); + } + + // Get the viewing options + array_push($result, join('#', $this->getOptions($data['topic']))); + } + } else { + array_push($result, "MEH"); + } + + $data['initString'] = join("\n", $result); + } + + /** This method checks for updates on the topic */ + private function updateTopicData($topicId, &$data) { + $this->topic = $topic = $this->game->getLib('main/forums')->call('getTopic', $topicId, $user); + if (is_null($topic)) { + $data['stillExists'] = false; + return $data; + } + + // Check whether the topic is available + $forum = $this->forum = $topic->getForum(); + if (is_null($forum) || ! $forum->canView()) { + $data['stillExists'] = false; + return $data; + } + + // The topic exists + $data['stillExists'] = true; + + // Has it been deleted? + if ($topic->isDeleted()) { + $data['nowDeleted'] = true; + $data['deletedAt'] = $topic->getDeletionTime(); + $data['deletedBy'] = $topic->getDeletionMod(); + return $data; + } + + // Topic hasn't been deleted, check for updates + $data['lastChange'] = $topic->getLastChange(); + } + + public function getData() { + if (is_null($this->data['updateString'])) { + return $this->data['initString']; + } + } + + + private function getOptions( $id ) { + if (! is_null($this->forum)) { + $isMod = $this->forum->isMod(); + } else { + $isMod = false; + } + + // perPage = posts / page, default 50, possible values 25 50 75 100 + $perPage = prefs::get("main/T#PP#$id", prefs::get("main/T#PP", 50)); + // vDeleted = view deleted posts, default "no" + $vDeleted = $isMod ? prefs::get("main/T#VD#$id", prefs::get("main/T#VD", 0)) : 0; + // threaded = enable threaded mode, default "yes" + $threaded = prefs::get("main/T#TV#$id", prefs::get("main/T#TV", 1)); + // order = show oldest posts first, default "no" + $order = prefs::get("main/T#PO#$id", prefs::get("main/T#PO", 0)); + // openPosts = posts open by default, default 1, possible values 0: none, 1: new only, 2: all + $openPosts = prefs::get("main/T#OP#$id", prefs::get("main/T#OP", 1)); + + return array($perPage, $vDeleted, $threaded, $order, $openPosts); + } + + + public function getPostContents($postId) { + $postId = (int) $postId; + if (! ($this->data['exists'] && $this->data['stillExists'])) { + return "-#$postId"; + } + + $post = $this->topic->getPostById($postId); + if (is_null($post)) { + return "-#$postId"; + } + + $contents = $this->game->getLib('main/forums')->call('substitute', + $post->getContents($this->data['viewTime']), + $post->codeEnabled($this->data['viewTime']) + ); + + return "+#$postId\n$contents"; + } +} + +?> diff -Naur beta5//scripts/game/beta5/forums/view/library.inc forums//scripts/game/beta5/forums/view/library.inc --- beta5//scripts/game/beta5/forums/view/library.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/beta5/forums/view/library.inc 2011-02-05 10:10:03.334335002 +0100 @@ -0,0 +1,405 @@ +lib = $lib; + $this->game = $this->lib->game; + } + + public function initialize($pageHandler) { + $this->pHandler = $pageHandler; + } + + public function forumCommand($player, $commandArgs) { + // Examine the arguments + $args = explode('#', $commandArgs); + if (count($args) != 2 || ! in_array($args[0], array('F', 'C'))) { + $args = array('C', '/'); + } + + // Try to find the requested object + $this->fStructure = $this->game->action('getForums', $player); + if ($args[0] == 'C') { + $this->displayObject = $this->fStructure->findCategory($args[1]); + } else { + $this->displayObject = $this->fStructure->findForum($args[1]); + if (! is_null($this->displayObject) && ( + ($this->displayObject->isDeleted() && ! $this->displayObject->isAdmin()) + || ! $this->displayObject->canView()) ) { + $this->displayObject = null; + } + } + if (is_null($this->displayObject)) { + $this->displayObject = $this->fStructure; + $args = array('C', '/'); + } + + $view = join('#', $args); + return array("V#$view", $args[0] == 'C' ? 'vCat' : 'vForum', $view); + } + + public function getData($oldMD5 = null) { + if (is_null($this->displayObject)) { + $r = null; + } else { + if ($this->displayObject instanceof fl_forum) { + $r = $this->getForumData(); + } else { + $r = $this->getCategoryData(); + } + $md5 = md5(serialize($r)); + if ($md5 != $oldMD5) { + array_unshift($r, $md5); + $r = join("\n", $r); + } else { + $r = '-'; + } + } + return $r; + } + + public function categoryRead($category) { + if (is_null($this->displayObject)) { + return; + } + + $cat = $this->displayObject->findCategory((int) $category); + if (is_null($cat) || ! ($cat instanceof fl_category)) { + return; + } + $cat->markRead(); + } + + public function forumRead() { + if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum ) { + return; + } + + $this->displayObject->markRead(); + } + + public function restoreTopics($topicList) { + if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum + || ! $this->displayObject->isMod() ) { + return; + } + + foreach ($topicList as $topicId) { + $topic = $this->displayObject->findTopic($topicId); + if (! is_null($topic) && $topic->isInForum($this->displayObject)) { + $topic->restore(); + } + } + $this->displayObject->refresh(); + } + + public function deleteTopics($topicList) { + if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum + || ! $this->displayObject->isMod() ) { + return; + } + + foreach ($topicList as $topicId) { + $topic = $this->displayObject->findTopic($topicId); + if (!is_null($topic) && $topic->isInForum($this->displayObject)) { + $topic->delete(); + } + } + $this->displayObject->refresh(); + } + + public function changeTopicsLevel($topicList, $change) { + $change = (int) $change; + if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum + || ! $this->displayObject->isMod() || $change != -1 && $change != 1 ) { + return; + } + + foreach ($topicList as $topicId) { + $topic = $this->displayObject->findTopic($topicId); + if (!is_null($topic) && $topic->isInForum($this->displayObject)) { + $topic->setStickyLevel($topic->getStickyLevel() + $change); + } + } + $this->displayObject->refresh(); + } + + public function setTopicsLevel($topicList, $level) { + $level = (int) $level; + if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum + || ! $this->displayObject->isMod() || $level < 0 || $level > 10) { + return; + } + + foreach ($topicList as $topicId) { + $topic = $this->displayObject->findTopic($topicId); + if (!is_null($topic) && $topic->isInForum($this->displayObject)) { + $topic->setStickyLevel($level); + } + } + $this->displayObject->refresh(); + } + + public function setTopicsLock($topicList, $lock) { + if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum + || ! $this->displayObject->isMod()) { + return; + } + + foreach ($topicList as $topicId) { + $topic = $this->displayObject->findTopic($topicId); + if (!is_null($topic) && $topic->isInForum($this->displayObject)) { + $topic->setLock($lock); + } + } + } + + public function moveTopics($topicList, $destId) { + if (is_null($this->displayObject) || ! $this->displayObject instanceof fl_forum + || ! $this->displayObject->isMod() || $destId == $this->displayObject->getId()) { + return; + } + + $destination = $this->fStructure->findForum((int) $destId); + if (is_null($destination) || ! $destination->isMod()) { + return; + } + + foreach ($topicList as $topicId) { + $topic = $this->displayObject->findTopic($topicId); + if (! is_null($topic) && $topic->isInForum($this->displayObject)) { + $topic->moveTo($destination); + } + } + $this->displayObject->refresh(); + $destination->refresh(); + } + + + private function dumpNames(&$output, $ids) { + $accLib = $this->game->getLib('main/account'); + + array_push($output, count($ids)); + foreach ($ids as $id) { + array_push($output, "$id#" . utf8entities($accLib->call('getUserName', $id))); + } + } + + private function getCategoryData() { + $output = array( + "C#" . $this->displayObject->getId() + ); + $namesNeeded = array(); + + $this->dumpCategory( &$output, &$namesNeeded, $this->displayObject ); + $this->dumpNames( &$output, $namesNeeded ); + + return $output; + } + + private function dumpCategory( &$output, &$namesNeeded, $object ) { + if ( $object instanceof fl_container ) { + $contents = $object->getCategories(); + $type = 'C'; + } else { + $contents = array(); + foreach ($object->getForums() as $forum) { + if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { + array_push($contents, $forum); + } + } + $type = 'F'; + } + + $fullDesc = explode("\n", $object->getDescription()); + $hasUnread = ($object->getUnread() > 0) ? 1 : 0; + array_push( $output, $object->getId() . "#$type#$hasUnread#" + . count($contents) . "#" . count($fullDesc) ); + if ($type == 'F') { + array_push( $output, $object->getTypeName(getLanguage()) ); // FIXME getLanguage() ... + } + array_push( $output, utf8entities($object->getTitle()) ); + foreach ($fullDesc as $dLine ) { + array_push( $output, utf8entities($dLine) ); + } + + if ( $object instanceof fl_container ) { + foreach ($contents as $cat) { + $this->dumpCategory( &$output, &$namesNeeded, $cat ); + } + } else { + foreach ($contents as $forum) { + $this->dumpCatForum( &$output, &$namesNeeded, $forum ); + } + } + } + + private function dumpCatForum( &$output, &$namesNeeded, $forum ) { + $fullDesc = explode("\n", $forum->getDescription()); + + $deleted = $forum->isDeleted() ? '1' : '0'; + $deletedAt = $forum->deletedAt(); + $deletedBy = $forum->deletedBy(); + if (! (is_null($deletedBy) || in_array($deletedBy, $namesNeeded))) { + array_push($namesNeeded, $deletedBy); + } + + $topics = $forum->getTopics(); + $posts = is_null($lp = $forum->getLastPost()) ? 0 : $forum->getPosts(); + $unread = ($forum->getUnread() > 0 ? 1 : 0); + + array_push( $output, $forum->getId() . "#" . count($fullDesc) + . "#$deleted#$deletedAt#$deletedBy#$topics#$posts#$unread" ); + + if ($posts != 0 && !is_null($lp)) { + $moment = $lp->getLastChange(); + $author = $lp->getLastChangeAuthor(); + if (! in_array($author, $namesNeeded) ) { + array_push($namesNeeded, $author); + } + array_push( $output, "$author#$moment" ); + } + + array_push( $output, utf8entities($forum->getTitle()) ); + foreach ($fullDesc as $dLine ) { + array_push( $output, utf8entities($dLine) ); + } + } + + private function getForumData() { + $output = array( + "F#" . $this->displayObject->getId() + ); + $namesNeeded = array(); + $fob = $this->displayObject; + $id = $fob->getId(); + + $perPage = prefs::get("main/F#PP#$id", prefs::get("main/F#PP", 10)); + $vDeleted = $fob->isMod() ? prefs::get("main/F#VD#$id", prefs::get("main/F#VD", 0)) : 0; + + $rTopics = $fob->getTopicList(); + $topics = array(); + foreach ($rTopics as $topic) { + if ( ! ($topic->isInForum($fob) || !is_null($topic->getForum()) && $topic->getForum()->canView()) + || ($topic->isDeleted() && ! $vDeleted) ) { + continue; + } + array_push($topics, $topic); + } + + $nTopics = count($topics); + $hasUnread = ($fob->getUnread() > 0) ? 1 : 0; + $isMod = $fob->isMod() ? 1 : 0; + $canPost = $fob->canCreateTopic() ? 1 : 0; + $fDesc = explode("\n", $fob->getDescription()); + $admins = $fob->getAdministrators(true); + $mods = $fob->getModerators(true); + $users = $fob->getUsers(true); + + if ($fob->isMod()) { + $fMoveTo = array(); + $list = $this->fStructure->findModForums(); + foreach ($list as $forum) { + $fId = $forum->getId(); + if ($fId == $id) { + continue; + } + $fName = array($forum->getParent()->getTitle(), $forum->getTitle()); + array_push($fMoveTo, "$fId#" . utf8entities(join(' > ', $fName))); + } + } + + $parents = array(); + $obj = $fob->getParent(); + do { + array_push($parents, array( + $obj->getId(), utf8entities($obj->getTitle()) + )); + $obj = $obj->getParent(); + } while (! is_null($obj)); + + array_push($output, "$id#$nTopics#$hasUnread#$isMod#$canPost#$perPage#$vDeleted#" . count($fDesc) + . "#" . count($parents) . "#" . count($admins) . "#" . count($mods) + . "#" . count($users) . ($isMod ? ("#" . count($fMoveTo)) : "")); + array_push($output, utf8entities($fob->getTitle())); + + // Dump description + foreach ($fDesc as $dLine) { + array_push($output, utf8entities($dLine)); + } + // Parent categories + foreach (array_reverse($parents) as $p) { + array_push($output, join('#', $p)); + } + // Administrators + foreach ($admins as $name) { + array_push($output, utf8entities($name)); + } + // Moderators + foreach ($mods as $name) { + array_push($output, utf8entities($name)); + } + // Users + foreach ($users as $name) { + array_push($output, utf8entities($name)); + } + // List of forums one can move a topic to + if ($isMod) { + foreach ($fMoveTo as $l) { + array_push($output, $l); + } + } + + foreach ($topics as $topic) { + if ($topic->isDeleted() && ! $vDeleted) { + continue; + } + + $tid = $topic->getId(); + $movedTo = $topic->isInForum($fob) ? '' : $topic->getForum()->getId(); + $unread = ($topic->getLastRead() < $topic->getLastChange()) ? 1 : 0; + $sticky = $topic->getStickyLevel(); + $tReplies = $topic->getPosts() - 1; + + $fpTime = $topic->getPosted(); + $fpAuth = $topic->getAuthor(); + if (! (is_null($fpAuth) || in_array($fpAuth, $namesNeeded))) { + array_push($namesNeeded, $fpAuth); + } + $lcTime = $topic->getLastChange(); + $lcAuth = $topic->getLastChangeAuthor(); + if (! (is_null($lcAuth) || in_array($lcAuth, $namesNeeded))) { + array_push($namesNeeded, $lcAuth); + } + + $isLocked = $topic->isLocked() ? 1 : 0; + $hasPoll = is_null($topic->getPoll()) ? 0 : 1; + + $isDeleted = $topic->isDeleted() ? 1 : 0; + $deletedAt = $topic->getDeletionTime(); + $deletedBy = $topic->getDeletionMod(); + if (! (is_null($deletedBy) || in_array($deletedBy, $namesNeeded))) { + array_push($namesNeeded, $deletedBy); + } + + array_push($output, "$tid#$movedTo#$unread#$sticky#$tReplies#$fpTime#$fpAuth#$lcTime#$lcAuth" + . "#$isLocked#$hasPoll#$isDeleted#$deletedAt#$deletedBy"); + array_push($output, utf8entities($topic->getTitle())); + if ($movedTo != '') { + array_push($output, utf8entities($topic->getForum()->getTitle())); + } + } + + $this->dumpNames( &$output, $namesNeeded ); + return $output; + } +} + +?> diff -Naur beta5//scripts/game/main/forums/fl_category.inc forums//scripts/game/main/forums/fl_category.inc --- beta5//scripts/game/main/forums/fl_category.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/forums/fl_category.inc 2011-02-05 10:10:03.434335002 +0100 @@ -0,0 +1,270 @@ +game = $game; + $this->db = $this->game->getDBAccess(); + + $this->user = $user; + $this->id = $id; + $this->title = $title; + $this->description = $description; + } + + private static function initQueries() { + if (!is_null(self::$getLibQuery)) { + return; + } + + $db = config::getMainInterface()->getDBAccess(); + self::$getLibQuery = $db->prepare( + "SELECT t.id AS id,t.lib_path AS path " + . "FROM forums.category_type t, forums.category c " + . "WHERE c.id = $1 AND t.id = c.acl_lib", + array("id") ); + self::$getNameQuery = $db->prepare( + "SELECT name FROM forums.cat_type_text " + . "WHERE id = $1 AND lang = $2", + array("id", "lang") ); + } + + public function findCategory( $catId ) { + if ($this->getId() == $catId) { + return $this; + } + return null; + } + + + // ---------------------------------------------------------------------- + // READING CATEGORY PROPERTIES + // ---------------------------------------------------------------------- + + public function getId() { + return $this->id; + } + + public function getTitle() { + return $this->title; + } + + public function getDescription() { + return $this->description; + } + + public function getLibrary() { + if (is_null($this->hdlLib)) { + $q = self::$getLibQuery->execute(array("id" => $this->id)); + if (! ($q && dbCount($q) == 1)) { + return null; + } + + $r = dbFetchHash($q); + $r['library'] = $this->game->getLib($r['path']); + $r['text'] = array(); + $this->hdlLib = $r; + } + return $this->hdlLib['library']; + } + + public function getTypeName($lang) { + if (is_null($this->hdlLib)) { + if (is_null($this->getLibrary())) { + return null; + } + } + + if (! isset($this->hdlLib['text'][$lang]) ) { + $q = self::$getNameQuery->execute(array( + "id" => $this->hdlLib['id'], + "lang" => $lang + )); + if (! ($q || dbCount($q) == 1)) { + return null; + } + list($this->hdlLib['text'][$lang]) = dbFetchArray($q); + } + + return $this->hdlLib['text'][$lang]; + } + + public function getUnread( ) { + if (!is_null($this->unread)) { + return $this->unread; + } + + $total = 0; + foreach ($this->forums as $f) { + if ( $f->canView() && ! $f->isDeleted() ) { + $total += $f->getUnread(); + } + } + return ($this->unread = $total); + } + + public function getTopics( ) { + if (!is_null($this->topics)) { + return $this->topics; + } + + $total = 0; + foreach ($this->forums as $f) { + if ( $f->canView() && ! $f->isDeleted() ) { + $total += $f->getTopics(); + } + } + return ($this->topics = $total); + } + + public function getPosts( ) { + if (!is_null($this->posts)) { + return $this->posts; + } + + $total = 0; + foreach ($this->forums as $f) { + if ( $f->canView() && ! $f->isDeleted() ) { + $total += $f->getPosts(); + } + } + return ($this->posts = $total); + } + + public function setParent( $category ) { + $this->parent = $category; + } + + public function getParent() { + return $this->parent; + } + + + // ---------------------------------------------------------------------- + // FORUM ACCESS + // ---------------------------------------------------------------------- + + public function getForums() { + return $this->oForums; + } + + public function findModForums() { + $r = array(); + foreach ($this->oForums as $f) { + if ($f->isMod()) { + array_push($r, $f); + } + } + return $r; + } + + public function addForum( $forum ) { + if (is_null($forum)) { + return; + } + $this->forums[(string) $forum->getId()] = $forum; + array_push($this->oForums, $forum); + $forum->setParent($this); + } + + public function insertNewForum($forum) { + if (isset($this->forums[$id])) { + return; + } + + $this->forums[$forum->getId()] = $forum; + $forum->setParent($this); + $order = array(); + for ($i = 0; $i < count($this->oForums); $i ++) { + if ($i == $forum->getOrder()) { + array_push($order, $forum); + } + if ($i >= $forum->getOrder()) { + $this->oForums[$i]->increaseOrder(); + } + array_push($order, $this->oForums[$i]); + } + if ($i == $forum->getOrder()) { + array_push($order, $forum); + } + $this->oForums = $order; + } + + public function findForum( $id ) { + return isset($this->forums[$id]) ? $this->forums[$id] : null; + } + + public function moveForum( $id, $moveUp ) { + if (!isset($this->forums[$id])) { + return false; + } + + $qs = "SELECT forums.move_" . ($moveUp ? 'up' : 'down') . '($1)'; + $q = $this->db->query($qs, $id); + if (! ($q && dbCount($q))) { + return false; + } + list($rc) = dbFetchArray($q); + + if ($rc == 't') { + $n = count($this->oForums); + for ($i = 0; $i < $n; $i ++) { + if ($this->oForums[$i]->getId() == $id) { + if ($moveUp) { + $rv = $this->oForums[$i - 1]; + $this->oForums[$i - 1] = $this->oForums[$i]; + $this->oForums[$i] = $rv; + } else { + $rv = $this->oForums[$i + 1]; + $this->oForums[$i + 1] = $this->oForums[$i]; + $this->oForums[$i] = $rv; + } + break; + } + } + } else { + $rv = false; + } + + return $rv; + } + + public function markRead() { + foreach ($this->oForums as $forum) { + $forum->markRead(); + } + } + + public function refresh() { + $this->unread = $this->topics = $this->posts = null; + if ($this->parent) { + $this->parent->refresh(); + } + } +} + + +?> diff -Naur beta5//scripts/game/main/forums/fl_container.inc forums//scripts/game/main/forums/fl_container.inc --- beta5//scripts/game/main/forums/fl_container.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/forums/fl_container.inc 2011-02-05 10:10:03.434335002 +0100 @@ -0,0 +1,116 @@ +id = $id; + $this->title = $title; + $this->description = $description; + } + + public function addCategory( $category ) { + if (! is_null($category)) { + array_push($this->categories, $category); + $category->setParent( $this ); + } + } + + public function setParent( $category ) { + $this->parent = $category; + } + + public function getParent() { + return $this->parent; + } + + public function getCategories() { + return $this->categories; + } + + public function getId() { + return $this->id; + } + + public function getTitle() { + return $this->title; + } + + public function getDescription() { + return $this->description; + } + + public function getUnread( ) { + if (!is_null($this->unread)) { + return $this->unread; + } + + $total = 0; + foreach ($this->categories as $c) { + $total += $c->getUnread(); + } + return ($this->unread = $total); + } + + public function getTopics( ) { + if (!is_null($this->topics)) { + return $this->topics; + } + + $total = 0; + foreach ($this->categories as $c) { + $total += $c->getTopics(); + } + return ($this->topics = $total); + } + + public function findCategory( $catId ) { + if ($this->getId() == $catId) { + return $this; + } + foreach ($this->categories as $cat) { + $x = $cat->findCategory( $catId ); + if (! is_null($x)) { + return $x; + } + } + return null; + } + + public function findForum( $fId ) { + foreach ($this->categories as $cat) { + $x = $cat->findForum( $fId ); + if (! is_null($x)) { + return $x; + } + } + return null; + } + + public function findModForums() { + $r = array(); + foreach ($this->categories as $cat) { + $r = array_merge($r, $cat->findModForums()); + } + return $r; + } + + public function refresh() { + $this->unread = $this->topics = null; + if ($this->parent) { + $this->parent->refresh(); + } + } +} + +?> diff -Naur beta5//scripts/game/main/forums/fl_forum.inc forums//scripts/game/main/forums/fl_forum.inc --- beta5//scripts/game/main/forums/fl_forum.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/forums/fl_forum.inc 2011-02-05 10:10:03.434335002 +0100 @@ -0,0 +1,533 @@ + array(), + "mods" => array(), + "users" => array() + ); + + public function __construct($game, $id, $user) { + self::initQueries(); + + $this->game = $game; + $this->db = $this->game->getDBAccess(); + $this->user = $user; + + $q = self::$queries['getInfo']->execute($id); + if (! ($q && dbCount($q) == 1)) { + throw new Exception("Unable to read forum record"); + } + $this->record = dbFetchHash($q); + + $this->privs = array( + 'adm' => false, + 'mod' => false, + 'ntop' => false, + 'poll' => false, + 'post' => false, + 'view' => false + ); + } + + + private static function initQueries() { + if (is_array(self::$queries)) { + return; + } + + $db = config::getMainInterface()->getDBAccess(); + + self::$queries['getInfo'] = $db->prepare( + "SELECT * FROM forums.forum WHERE id = $1", + array("id") ); + self::$queries['getRead'] = $db->prepare( + "SELECT forums.get_read_topics( $1 , $2 )", + array("forum", "user") ); + self::$queries['getCurrentSig'] = $db->prepare( + "SELECT id FROM forums.get_signature( $1, UNIX_TIMESTAMP(NOW()) )", + array("user") ); + self::$queries['createTopic'] = $db->prepare( + "SELECT forums.create_topic($1, $2, $3, $4, $5, $6, $7, $8)", + array("forum", "user", "sticky", "title", "contents", "code", "smileys", "sig") ); + self::$queries['killTopic'] = $db->prepare( + "DELETE FROM forums.t_topic WHERE id = $1", + array("topic") ); + self::$queries['getTopics'] = $db->prepare( + "SELECT * FROM forums.topic WHERE forum = $1 OR moved_from = $1", + array("forum") ); + } + + + // ---------------------------------------------------------------------- + // READING FORUM PROPERTIES + // ---------------------------------------------------------------------- + + public function getId() { + return $this->record['id']; + } + + public function getCategory() { + if (is_null($this->mainLib)) { + $this->mainLib = $this->game->getLib('main/forums'); + } + return $this->mainLib->call('getCategory', $this->record['category'], $this->user); + } + + public function getOrder() { + return $this->record['f_order']; + } + + public function getTitle() { + return $this->record['title']; + } + + public function getDescription() { + return $this->record['description']; + } + + public function isDeleted() { + return !is_null($this->record['deleted']); + } + + public function deletedAt() { + return $this->isDeleted() ? $this->record['deleted'] : null; + } + + public function deletedBy() { + return $this->isDeleted() ? $this->record['deleted_by'] : null; + } + + public function getTopics() { + return $this->record['topics']; + } + + public function getPosts() { + return $this->record['posts']; + } + + public function getUnread() { + if (is_null($this->unread)) { + $q = self::$queries['getRead']->execute($this->record['id'], $this->user); + if (! ($q && dbCount($q) == 1)) { + return 0; + } + list($r) = dbFetchArray($q); + $this->unread = $this->record['topics'] - $r; + } + return $this->unread; + } + + public function isAdmin() { + return $this->privs['adm']; + } + + public function isMod() { + return $this->privs['mod']; + } + + public function canCreateTopic() { + return $this->privs['ntop']; + } + + public function canCreatePoll() { + return $this->privs['poll']; + } + + public function canPost() { + return $this->privs['post']; + } + + public function canView() { + return $this->privs['view']; + } + + public function getLastPost() { + $q = $this->db->query("SELECT * FROM forums.get_last_post($1)", $this->record['id']); + if (! ($q && dbCount($q))) { + return null; + } + + if (! $this->mainLib) { + $this->mainLib = $this->game->getLib('main/forums'); + } + return $this->mainLib->call('getPost', dbFetchHash($q), $this->user); + } + + public function setParent( $category ) { + $this->parent = $category; + } + + public function getParent() { + return $this->parent; + } + + + // ---------------------------------------------------------------------- + // FORUM'S ACCESS LIST + // ---------------------------------------------------------------------- + + private function getAcl($type, $libFunc, $iWantNames) { + // Check if we read that already + $aKey = $iWantNames ? 'names' : 'ids'; + if (array_key_exists($aKey, $this->acl[$type])) { + return $this->acl[$type][$aKey]; + } + + // Get the library and extract the ACL IDs + $catLib = $this->getCategory()->getLibrary(); + if (! array_key_exists('ids', $this->acl[$type])) { + $this->acl[$type]['ids'] = $catLib->call($libFunc, $this->record['id']); + } + + // If we were requesting the IDs, leave + if ($aKey == 'ids') { + return $this->acl[$type]['ids']; + } + + // If the IDs list is null, names are null as well + if (is_null($this->acl[$type]['ids'])) { + $this->acl[$type]['names'] = null; + return $this->acl[$type]['names']; + } + + // Convert the identifiers to names and return + $this->acl[$type]['names'] = array(); + foreach ($this->acl[$type]['ids'] as $id) { + $this->acl[$type]['names'][$id] = $catLib->call('aclIdToName', $id); + } + return $this->acl[$type]['names']; + } + + public function getAdministrators($iWantNames = false) { + return $this->getAcl("admins", "getAdmins", $iWantNames); + } + + public function getModerators($iWantNames = false) { + return $this->getAcl("mods", "getModerators", $iWantNames); + } + + public function getUsers($iWantNames = false) { + return $this->getAcl("users", "getUsers", $iWantNames); + } + + public function clearACL() { + $this->getCategory()->getLibrary()->call('clearForumACL', $this->record['id']); + $this->acl = array( + 'users' => array(), + 'mods' => array(), + 'admins' => array() + ); + } + + public function addModerator($id) { + $this->getCategory()->getLibrary()->call('addForumModerator', $this->record['id'], $id); + $this->acl['mods'] = array(); + } + + public function addUser($id) { + $this->getCategory()->getLibrary()->call('addForumUser', $this->record['id'], $id); + $this->acl['users'] = array(); + } + + + // ---------------------------------------------------------------------- + // SETTING FORUM PROPERTIES + // ---------------------------------------------------------------------- + + public function setAdmin($value) { + $this->privs['adm'] = $value; + } + + public function setMod($value) { + $this->privs['mod'] = $value; + } + + public function setCreateTopic($value) { + $this->privs['ntop'] = $value; + } + + public function setCreatePoll($value) { + $this->privs['poll'] = $value; + } + + public function setPost($value) { + $this->privs['post'] = $value; + } + + public function setView($value) { + $this->privs['view'] = $value; + } + + public function increaseOrder() { + $this->record['f_order'] ++; + } + + + // ---------------------------------------------------------------------- + // INTERNAL METHODS FOR CHECKS AND FORMATTING + // ---------------------------------------------------------------------- + + private function checkPostContents($title, $contents) { + if (strlen($title) < 2) { + $e = 1; + } elseif (strlen($title) > 100) { + $e = 2; + } elseif (strlen($contents) < 3) { + $e = 3; + } else { + $e = 0; + } + return $e; + } + + + private function reformatContents($contents) { + // Max. line breaks + $maxNL = 500; + // Max. characters without break + $maxNC = 100; + + $ot = $contents; + $nt = ""; + $nl = 0; + + while ($ot != '' && $nl < $maxNL) { + $p = strpos($ot, '\n'); + if ($p !== false && $p < $maxNC) { + $nt .= substr($ot, 0, $p+1); + $ot = substr($ot, $p+1); + } else if (strlen($ot) < $maxNC) { + $nt .= $ot; + $ot = ""; + } else { + $s = substr($ot, 0, $maxNC); + $p = strrpos($s, ' '); + $ot = substr($ot, $maxNC); + $nt .= $s; + if ($p === false) { + $nt .= "\n"; + } + } + $nl ++; + } + + if ($nl >= $maxNL) { + return null; + } + return $nt; + } + + + + // ---------------------------------------------------------------------- + // OPERATIONS + // ---------------------------------------------------------------------- + + /* This method creates a new topic in the forum. It returns the + * corresponding fl_topic instance or a numeric error code. + * + * Error codes: + * -1 - Permission denied (topic creation) + * -2 - Permission denied (poll creation) + * -3 - Title too short + * -4 - Title too long + * -5 - Contents too short + * -6 - Contents too long + * -7 - Permission denied (sticky) + * -8 - Database access error + * -9 - Poll title too short + * -10 - Poll title too long + * -11 - Not enough poll options + * -12 - Too many poll options + */ + public function createTopic( + $title, $contents, $stickyLevel, + $enableCode, $enableSmileys, $enableSignature, + $poll = null + ) { + // Check posting privileges + if ( ! $this->canCreateTopic() ) { + return -1; + } + if ( ! (is_null($poll) || $this->canCreatePoll()) ) { + return -2; + } + + // Check the title and contents + $title = trim($title); + $contents = trim($contents); + $pc = $this->checkPostContents($title, $contents); + if ($pc) { + return - $pc - 2; + } + + // Reformat the post's contents + $contents = $this->reformatContents($contents); + if (is_null($contents)) { + return -6; + } + + // Check the sticky level + $stickyLevel = (int) $stickyLevel; + if ( $stickyLevel < 0 || $stickyLevel > 10 || ($stickyLevel > 0 && ! $this->isMod()) ) { + return -7; + } + + // Check the poll's contents + if (! is_null($poll)) { + $pc = $poll->checkData(); + if ($pc != 0) { + return - $pc - 8; + } + } + + // Get the user's current signature if needed + if ($enableSignature) { + $q = self::$queries['getCurrentSig']->execute($this->user); + if ( ! ($q && dbCount($q)) ) { + return -8; + } + + list($sig) = dbFetchArray($q); + } else { + $sig = null; + } + + // Add the topic + $q = self::$queries['createTopic']->execute(array( + "forum" => $this->record['id'], + "user" => $this->user, + "sticky" => $stickyLevel, + "title" => $title, + "contents" => $contents, + "code" => dbBool($enableCode), + "smileys" => dbBool($enableSmileys), + "sig" => $sig + )); + if (! ($q && dbCount($q)) ) { + return -8; + } + list($tid) = dbFetchArray($q); + if (is_null($tid)) { + return -8; + } + + // Insert poll if needed + if (! (is_null($poll) || $poll->insertIntoDB( $this->game, $this->user, $tid ) ) ) { + self::$queries['killTopic']->execute($tid); + return -8; + } + + if (! $this->mainLib) { + $this->mainLib = $this->game->getLib('main/forums'); + } + $topic = $this->mainLib->call('getTopic', $tid, $this->user); + + $this->refresh(); + return $topic; + } + + + public function getTopicList() { + if (is_null($this->topicList)) { + $q = self::$queries['getTopics']->execute($this->record['id']); + if (! $q) { + return null; + } + + if (is_null($this->mainLib)) { + $this->mainLib = $this->game->getLib('main/forums'); + } + $this->topicList = array(); + while ($r = dbFetchHash($q)) { + $t = $this->mainLib->call('getTopic', $r, $this->user); + $this->topicList[$t->getId()] = $t; + } + } + + return $this->topicList; + } + + public function findTopic( $id ) { + if (is_null($this->topicList)) { + $this->getTopicList(); + } + return array_key_exists($id, $this->topicList) ? $this->topicList[$id] : null; + } + + /* Deletes the forum */ + function delete() { + if ($this->isDeleted()) { + return; + } + + $this->db->query("SELECT forums.delete_forum($1, $2)", $this->record['id'], $this->user); + + $q = self::$queries['getInfo']->execute($this->record['id']); + if ($q && dbCount($q) == 1) { + $this->record = dbFetchHash($q); + } + } + + /* Restore the forum */ + function restore() { + if (!$this->isDeleted()) { + return; + } + + $this->db->query("SELECT forums.restore_forum($1)", $this->record['id']); + + $q = self::$queries['getInfo']->execute($this->record['id']); + if ($q && dbCount($q) == 1) { + $this->record = dbFetchHash($q); + } + } + + /* Moves the forum either up or down */ + function move($moveUp) { + $rv = $this->getCategory()->moveForum($this->record['id'], $moveUp); + if ($rv !== false) { + $this->record['f_order'] += ($moveUp ? -1 : 1); + $rv->record['f_order'] += ($moveUp ? 1 : -1); + } + } + + /* Mark all topics as read */ + function markRead() { + $this->db->query("SELECT forums.mark_forum_read($1, $2)", $this->record['id'], $this->user); + } + + /* Forces the object to be re-read from the DB */ + function refresh() { + $q = self::$queries['getInfo']->execute($this->record['id']); + if (! ($q && dbCount($q) == 1)) { + return; + } + $this->record = dbFetchHash($q); + $this->unread = $this->topics = $this->topicList = null; + + if ($this->parent) { + $this->parent->refresh(); + } + } +} + +?> diff -Naur beta5//scripts/game/main/forums/fl_poll.inc forums//scripts/game/main/forums/fl_poll.inc --- beta5//scripts/game/main/forums/fl_poll.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/forums/fl_poll.inc 2011-02-05 10:10:03.434335002 +0100 @@ -0,0 +1,373 @@ +fromDB = false; + $this->checked = false; + $this->record = array( + 'title' => preg_replace('/\s+/', ' ', trim($args[0])) + ); + } else { + // Load from database + $this->fromDB = true; + $this->checked = true; + $this->game = $args[0]; + $this->db = $this->game->getDBAccess(); + $this->mainLib = $this->game->getLib('main/forums'); + $this->user = $args[1]; + $id = $args[2]; + + // Fetch the poll's data + $q = $this->db->query("SELECT * FROM forums.poll WHERE topic = $1 FOR UPDATE", $id); + if (! ($q && dbCount($q) == 1)) { + throw new Exception("No poll found for topic #$id"); + } + $this->record = dbFetchHash($q); + + // Fetch the poll options + $q = $this->db->query("SELECT * FROM forums.poll_option " + . "WHERE poll = $1 ORDER BY po_order " + . "FOR UPDATE", $id); + if (! ($q && dbCount($q) > 1)) { + throw new Exception("Poll options not found for topic #$id"); + } + while ($r = dbFetchHash($q)) { + array_push($this->options, $r); + } + + $this->prepareNbVotesQuery(); + } + } + + + private function prepareNbVotesQuery() { + $this->nbVotesQ = $this->db->prepare( + "SELECT COUNT(*) FROM forums.poll_vote WHERE vote = $1", array("option") ); + } + + + // ---------------------------------------------------------------------- + // READING POLL PROPERTIES + // ---------------------------------------------------------------------- + + public function getTopic() { + if (! $this->fromDB) { + return null; + } + return $this->mainLib->call('getTopic', $this->record['topic'] ); + } + + public function getTitle() { + return $this->record['title']; + } + + public function isClosed() { + return $this->fromDB && ($this->record['closed'] == 't'); + } + + public function isDeleted() { + return $this->fromDB && !is_null($this->record['deleted']); + } + + public function getDeletionTime() { + return $this->fromDB ? $this->record['deleted'] : null; + } + + public function getDeletionMod() { + return $this->fromDB ? $this->record['deleted_by'] : null; + } + + + // ---------------------------------------------------------------------- + // POLL CREATION + // ---------------------------------------------------------------------- + + /* Checks the poll's data before inserting it in the database. + * Returns null if the poll is already inside the database, 0 if the poll + * is ok, or one of the following error codes: + * 1 - Title too short + * 2 - Title too long + * 3 - Not enough options + * 4 - Too many options + */ + public function checkData() { + if ($this->fromDB || $this->checked) { + return null; + } + + $l = strlen($this->record['title']); + if ($l < 3) { + return 1; + } elseif ($l > 64) { + return 2; + } + + $c = count($this->options); + if ($c < 2) { + return 3; + } elseif ($c > 20) { + return 4; + } + + $this->checked = true; + return 0; + } + + /* Insert the poll's data into the database. */ + public function insertIntoDB($game, $user, $topicId) { + if ($this->fromDB || ! $this->checked) { + return false; + } + $db = $game->getDBAccess(); + + // Insert the poll + $q = $db->query("SELECT * FROM forums.create_poll($1, $2)", $topicId, $this->record['title']); + if (! ($q && dbCount($q) == 1)) { + return false; + } + + $rec = dbFetchHash($q); + if (is_null($rec['topic'])) { + return false; + } + + // Insert the options + $pollQuery = $db->prepare("SELECT * FROM forums.create_option($1, $2, $3)", + array("poll", "order", "title") ); + try { + $nOpts = array(); + for ($i = 0; $i < count($this->options); $i ++) { + $q = $pollQuery->execute(array( + "poll" => $topicId, + "order" => $i, + "title" => $this->options[$i]['title'] + )); + if (! ($q && dbCount($q) == 1)) { + throw new Exception('abort'); + } + + $orec = dbFetchHash($q); + if (is_null($orec['id'])) { + throw new Exception('abort'); + } + + array_push($nOpts, $orec); + } + $pollQuery->destroy(); + } catch (Exception $e) { + $pollQuery->destroy(); + $db->query("DELETE FROM forums.poll WHERE topic = $1", $topicId); + return false; + } + + // Finalize + $this->record = $rec; + $this->options = $nOpts; + $this->game = $game; + $this->db = $db; + $this->user = $user; + $this->mainLib = $this->game->getLib('main/forums'); + $this->prepareNbVotesQuery(); + $this->fromDB = true; + return true; + } + + + // ---------------------------------------------------------------------- + // OPTIONS + // ---------------------------------------------------------------------- + + /* Adds a new option at the end of the list. + * Returns 0 on success or one of the following error codes: + * -1 - Title too short + * -2 - Title too long + * -3 - Too many options + * -4 - Database error + * -5 - Adding options after the poll has been checked is not permitted + */ + public function appendOption($title) { + $title = preg_replace('/\s+/', ' ', trim($title)); + $l = strlen($title); + if ($l < 2) { + return -1; + } elseif ($l > 64) { + return -2; + } + + if ($this->fromDB) { + $c = count($this->options); + if ($c == 20) { + return -3; + } + + $q = $this->db->query("SELECT * FROM forums.create_option($1, $2, $3)", + $this->record['topic'], $c, $title); + if (! ($q && dbCount($q) == 1)) { + return -4; + } + array_push($this->options, dbFetchHash($q)); + } else if (!$this->checked) { + array_push($this->options, array( + 'title' => $title + )); + } else { + return -5; + } + return 0; + } + + /* Removes the poll option with the specified index. */ + public function removeOption( $order ) { + $order = (int) $order; + if ($order < 0 || $order >= count($this->options)) { + return false; + } + + if ($this->fromDB) { + $q = $this->db->query("SELECT forums.delete_option( $1, $2 )", $this->record['topic'], $order); + if (! ($q && dbCount($q) == 1)) { + return false; + } + list($ok) = dbFetchArray($q); + if ($ok != 't') { + return false; + } + + for ($i = $order + 1; $i < count($this->options); $i ++) { + $this->options[$i]['po_order'] --; + } + } + array_splice($this->options, $order, 1); + return true; + } + + /* Get the options */ + public function getOptions() { + $opts = $this->options; + if (! $this->fromDB) { + for ($i = 0; $i < count($opts); $i++) { + $opts[$i]['po_order'] = $i; + } + } + return $opts; + } + + /* Move an option up in the list of options */ + public function moveUp( $order ) { + if ($order <= 0 || $order >= count($this->options)) { + return false; + } + + if ($this->fromDB) { + $q = $this->db->query("SELECT forums.move_opt_up( $1, $2 )", $this->record['topic'], $order); + if (! ($q && dbCount($q) == 1)) { + return false; + } + list($ok) = dbFetchArray($q); + if ($ok != 't') { + return false; + } + + $this->options[$order]['po_order'] --; + $this->options[$order - 1]['po_order'] ++; + } + + $tmp = $this->options[$order]; + $this->options[$order] = $this->options[$order - 1]; + $this->options[$order - 1] = $tmp; + return true; + } + + /* Move an option down in the list of options */ + public function moveDown( $order ) { + if ($order < 0 || $order >= count($this->options) - 1) { + return false; + } + + if ($this->fromDB) { + $q = $this->db->query("SELECT forums.move_opt_down($1,$2)", $this->record['topic'], $order); + if (! ($q && dbCount($q) == 1)) { + return false; + } + list($ok) = dbFetchArray($q); + if ($ok != 't') { + return false; + } + + $this->options[$order]['po_order'] ++; + $this->options[$order + 1]['po_order'] --; + } + + $tmp = $this->options[$order]; + $this->options[$order] = $this->options[$order + 1]; + $this->options[$order + 1] = $tmp; + return true; + } + + /* Vote */ + public function setVote( $order ) { + if (! $this->fromDB || $order < 0 || $order >= count($this->options)) { + return false; + } + + $q = $this->db->query("SELECT forums.set_vote($1,$2,$3)", $this->user, $this->record['topic'], + $this->options[$order]['id']); + if (! $q) { + return false; + } + return true; + } + + /* Returns the order of the option the user is voting for */ + public function getVote() { + if (! $this->fromDB) { + return null; + } + + $q = $this->db->query("SELECT o.po_order FROM forums.poll_option o, forums.poll_vote v " + . " WHERE o.id = v.vote AND o.poll = $1 AND v.account = $2", + $this->record['topic'], $this->user); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($order) = dbFetchArray($q); + return $order; + } + + /* Returns the amount of votes for an option */ + public function getNbVotes( $order ) { + if (! $this->fromDB || $order < 0 || $order >= count($this->options)) { + return 0; + } + + $q = $this->nbVotesQ->execute($this->options[$order]['id']); + if (!($q && dbCount($q) == 1)) { + return 0; + } + + list($votes) = dbFetchArray($q); + return $votes; + } +} + + +?> diff -Naur beta5//scripts/game/main/forums/fl_post.inc forums//scripts/game/main/forums/fl_post.inc --- beta5//scripts/game/main/forums/fl_post.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/forums/fl_post.inc 2011-02-05 10:10:03.434335002 +0100 @@ -0,0 +1,219 @@ +game = $game; + $this->user = $user; + $this->db = $this->game->getDBAccess(); + $this->mainLib = $this->game->getLib('main/forums'); + + if (is_array($data)) { + $this->record = $data; + } else { + $data = (int) $data; + $row = self::$getQ->execute($data); + if (! ($row && dbCount($row) == 1)) { + throw new Exception("Post #$data not found"); + } + $this->record = dbFetchHash($row); + } + } + + + static private function initQueries() { + if (! is_null(self::$getQ)) { + return; + } + + $db = config::getMainInterface()->getDBAccess(); + self::$getQ = $db->prepare( "SELECT * FROM forums.post WHERE id = $1", array("id") ); + self::$historyQ = $db->prepare( "SELECT * FROM forums.post_text WHERE post = $1 ORDER BY moment", + array( "post" )); + self::$repliesQ = $db->prepare( "SELECT id FROM forums.post WHERE reply_to = $1 ORDER BY post_moment", + array( "post" )); + } + + + // ---------------------------------------------------------------------- + // READING POST PROPERTIES + // ---------------------------------------------------------------------- + + public function getId() { + return $this->record['id']; + } + + + public function getForum() { + return $this->mainLib->call('getForum', $this->record['forum'], $this->user); + } + + public function getTopic() { + return $this->mainLib->call('getTopic', $this->record['topic'], $this->user); + } + + + public function getParent() { + return is_null($this->record['reply_to']) ? null + : $this->mainLib->call('getPost', $this->record['reply_to'], $this->user); + } + + public function getDepth() { + return $this->record['depth']; + } + + + public function getPostedAt() { + return $this->record['post_moment']; + } + + public function getPostedBy() { + return $this->record['author']; + } + + public function getLastChange() { + return $this->record['last_change']; + } + + public function getLastChangeAuthor() { + return $this->record['last_author']; + } + + public function isUnread() { + $topic = $this->getTopic(); + if (is_null($topic)) { + return false; + } + + return $topic->getLastRead() < $this->record['last_change']; + } + + + public function isDeleted() { + return ! is_null($this->record['deleted']); + } + + public function getDeletionTime() { + return $this->record['deleted']; + } + + public function getDeletionMod() { + return $this->record['deleted_by']; + } + + + public function getTitle() { + return $this->record['title']; + } + + public function getContents($time = null) { + if (is_null($time)) { + $v = $this->record['contents']; + } else { + $hist = $this->getFullHistory(); + $v = ''; + foreach ($hist as $he) { + if ($he['moment'] > $time) { + break; + } + $v = $he['contents']; + } + } + return $v; + } + + public function codeEnabled($time = null) { + if (is_null($time)) { + $v = $this->record['enable_code']; + } else { + $hist = $this->getFullHistory(); + $v = false; + foreach ($hist as $he) { + if ($he['moment'] > $time) { + break; + } + $v = $he['enable_code']; + } + } + return ($v == 't'); + } + + public function smileysEnabled($time = null) { + if (is_null($time)) { + $v = $this->record['enable_smileys']; + } else { + $hist = $this->getFullHistory(); + $v = false; + foreach ($hist as $he) { + if ($he['moment'] > $time) { + break; + } + $v = $he['enable_smileys']; + } + } + return ($v == 't'); + } + + + public function getSignature() { + throw new Exception("FIXME: getSignature() is not implemented"); + } + + /* This function returns a post's full history */ + public function getFullHistory() { + if (is_null( $this->history )) { + $result = self::$historyQ->execute($this->record['id']); + if (! ($result && dbCount($result))) { + return null; + } + $history = array(); + while ($row = dbFetchHash($result)) { + array_push($history, $row); + } + $this->history = $history; + } + return $this->history; + } + + /* This function returns the replies to a post */ + public function getReplies() { + if (is_null( $this->replies )) { + $result = self::$repliesQ->execute($this->record['id']); + if (! $result) { + return null; + } + $this->replies = array(); + while ($row = dbFetchArray($result)) { + $reply = $this->mainLib->call('getPost', $row[0], $this->user); + if (! is_null($reply)) { + array_push($this->replies, $reply); + } + } + } + return $this->replies; + } + + /* This function deletes a post if it is not the top-level post */ + public function delete() { + } +} + + +?> diff -Naur beta5//scripts/game/main/forums/fl_topic.inc forums//scripts/game/main/forums/fl_topic.inc --- beta5//scripts/game/main/forums/fl_topic.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/forums/fl_topic.inc 2011-02-05 10:10:03.434335002 +0100 @@ -0,0 +1,339 @@ +game = $game; + $this->db = $this->game->getDBAccess(); + $this->user = $user; + + if (is_array($data)) { + $this->record = $data; + } else { + $data = (int) $data; + $q = self::$getQuery->execute($data); + if (! ($q && dbCount($q) == 1)) { + throw new Exception("Topic #$data not found"); + } + $this->record = dbFetchHash($q); + } + } + + static private function initQueries() { + if (is_null(self::$getQuery)) { + $db = config::getMainInterface()->getDBAccess(); + self::$getQuery = $db->prepare( + "SELECT * FROM forums.topic WHERE id = $1", + array("id") ); + self::$restoreQuery = $db->prepare( "SELECT forums.restore_post( $1 )", array("id") ); + self::$deleteQuery = $db->prepare( "SELECT forums.delete_post( $1 , $2 )", + array("id", "user") ); + self::$stickyLevelQuery = $db->prepare( "UPDATE forums.t_topic SET sticky_level = $2 " + . "WHERE id = $1", array("id", "level") ); + self::$setLockQuery = $db->prepare( "UPDATE forums.t_topic SET locked = $2 WHERE id = $1", + array("id", "locked") ); + self::$moveQuery = $db->prepare( "SELECT forums.move_topic( $1, $2, $3)", + array("topic", "destination", "user") ); + self::$postsQuery = $db->prepare( "SELECT * FROM forums.post WHERE topic = $1", + array("topic") ); + } + } + + + // ---------------------------------------------------------------------- + // READING TOPIC PROPERTIES + // ---------------------------------------------------------------------- + + public function getId() { + return $this->record['id']; + } + + public function getForum() { + if (is_null($this->mainLib)) { + $this->mainLib = $this->game->getLib('main/forums'); + } + return $this->mainLib->call('getForum', $this->record['forum'], $this->user); + } + + public function getMovedFrom() { + if (is_null($this->record['moved_from'])) { + return null; + } + + if (is_null($this->mainLib)) { + $this->mainLib = $this->game->getLib('main/forums'); + } + return $this->mainLib->call('getForum', $this->record['moved_from'], $this->user); + } + + public function isInForum($forum) { + return ($forum->getId() == $this->record['forum']); + } + + public function getPoll() { + if (is_null($this->mainLib)) { + $this->mainLib = $this->game->getLib('main/forums'); + } + return $this->mainLib->call('getPoll', $this->record['id'], $this->user); + } + + public function getStickyLevel() { + return $this->record['sticky_level']; + } + + public function getTitle() { + return $this->record['title']; + } + + public function getPosted() { + return $this->record['fp_moment']; + } + + public function getAuthor() { + return $this->record['fp_author']; + } + + public function getLastChange() { + return $this->record['lc_moment']; + } + + public function getLastChangeAuthor() { + return $this->record['lc_author']; + } + + public function isDeleted() { + return ! is_null($this->record['deleted']); + } + + public function getDeletionTime() { + return $this->record['deleted']; + } + + public function getDeletionMod() { + return $this->record['deleted_by']; + } + + public function isLocked() { + return ($this->record['locked'] == 't'); + } + + public function getLastRead() { + if (is_null($this->lastRead)) { + $q = $this->db->query("SELECT read_at FROM forums.topic_read WHERE topic = $1 AND read_by = $2", + $this->record['id'], $this->user); + if (!($q && dbCount($q) == 1)) { + return 0; + } + + list($this->lastRead) = dbFetchArray($q); + } + + return $this->lastRead; + } + + + // ---------------------------------------------------------------------- + // MANAGEMENT FUNCTIONS + // ---------------------------------------------------------------------- + + public function restore() { + if (! $this->isDeleted() ) { + return; + } + self::$restoreQuery->execute( $this->record['first_post'] ); + $q = self::$getQuery->execute( $this->record['id'] ); + if (! ($q && dbCount($q))) { + return; + } + $this->record = dbFetchHash($q); + } + + public function delete() { + if ($this->isDeleted() ) { + return; + } + self::$deleteQuery->execute( $this->record['first_post'], $this->user ); + $q = self::$getQuery->execute( $this->record['id'] ); + if (! ($q && dbCount($q))) { + return; + } + $this->record = dbFetchHash($q); + } + + public function setStickyLevel( $level ) { + if ($this->isDeleted() ) { + return; + } + if ($level < 0 || $level > 10) { + return; + } + self::$stickyLevelQuery->execute( $this->record['id'], $level ); + $this->record['sticky_level'] = $level; + } + + public function setLock( $locked ) { + if ($this->isDeleted() ) { + return; + } + self::$setLockQuery->execute( $this->record['id'], dbBool($locked) ); + $this->record['locked'] = $locked ? 't' : 'f'; + } + + public function moveTo( $forum ) { + if ($this->isDeleted() ) { + return; + } + self::$moveQuery->execute($this->record['id'], $forum->getId(), $this->user); + $q = self::$getQuery->execute( $this->record['id'] ); + if (! ($q && dbCount($q))) { + return; + } + $this->record = dbFetchHash($q); + } + + + // ---------------------------------------------------------------------- + // ACCESSING THE POSTS + // ---------------------------------------------------------------------- + + public function getPosts() { + return $this->record['posts']; + } + + public function getFirstPost() { + if (is_null($this->mainLib)) { + $this->mainLib = $this->game->getLib('main/forums'); + } + return $this->mainLib->call('getPost', $this->record['first_post'], $this->user); + } + + private function loadPosts() { + if (is_null($this->allPosts)) { + $q = self::$postsQuery->execute($this->record['id']); + if (! ($q && dbCount($q))) { + return null; + } + + $this->allPosts = array(); + while ($r = dbFetchHash($q)) { + $this->allPosts[$r['id']] = $this->mainLib->call('getPost', $r, $this->user); + } + } + } + + public function getPostList($isThreaded, $oldFirst) { + $this->loadPosts(); + + if ($isThreaded) { + $result = $this->threadedSort($this->getFirstPost()->getReplies(), $oldFirst); + if (count($result)) { + array_shift($result); + } + array_unshift($result, $this->getFirstPost()); + } else { + // If we are not in threaded mode, generate the list + // of reverse post <=> time associations and sort it + // in the appropriate direction + $timeList = array(); + $result = array(); + + foreach ($this->allPosts as $id => $p) { + $time = (int) $p->getPostedAt(); + if (! is_array($timeList[$time])) { + $timeList[$time] = array(); + } + array_push($timeList[$time], $p); + } + + $times = array_keys($timeList); + if ($oldFirst) { + sort($times); + } else { + rsort($times); + } + + foreach ($times as $t) { + foreach ($timeList[$t] as $p) { + array_push($result, $p); + } + } + } + + return $result; + } + + private function threadedSort($posts, $oldFirst) { + if (! count($posts)) { + return array(); + } + + $timeList = array(); + $replyList = array(); + + foreach ($posts as $post) { + $postReplies = $this->threadedSort($post->getReplies(), $oldFirst); + + if (count($postReplies)) { + $time = array_shift($postReplies); + $replyList[$post->getId()] = $postReplies; + } else { + $time = $post->getPostedAt(); + $replyList[$post->getId()] = array(); + } + + $time = (int) $time; + if (! is_array($timeList[$time])) { + $timeList[$time] = array(); + } + array_push($timeList[$time], $post); + } + + $times = array_keys($timeList); + if ($oldFirst) { + sort($times); + } else { + rsort($times); + } + + $result = array($times[0]); + foreach ($times as $t) { + foreach ($timeList[$t] as $p) { + array_push($result, $p); + foreach ($replyList[$p->getId()] as $r) { + array_push($result, $r); + } + } + } + + return $result; + } + + public function getPostById($id) { + $this->loadPosts(); + return array_key_exists($id, $this->allPosts) ? $this->allPosts[$id] : null; + } +} + + +?> diff -Naur beta5//scripts/game/main/forums/library/substitute.inc forums//scripts/game/main/forums/library/substitute.inc --- beta5//scripts/game/main/forums/library/substitute.inc 2011-02-05 10:09:57.844335002 +0100 +++ forums//scripts/game/main/forums/library/substitute.inc 2011-02-05 10:10:03.424335002 +0100 @@ -1,59 +1,73 @@ '\[b\](.*?)\[\/b\]', + "rt" => '$1' + ), array( + "re" => '\[u\](.*?)\[\/u\]', + "rt" => '$1' + ), array( + "re" => '\[i\](.*?)\[\/i\]', + "rt" => '$1' + ), array( + "re" => '\[sep(arator)?\]', + "rt" => '
' + ), array( + "re" => '\[item\](.*?)\[\/item\]', + "rt" => '' + ), array( + "re" => '\[quote\](.*?)\[\/quote\]', + "rt" => '
$1
', + "rep" => true + ), array( + "re" => '\[quote=([^\]]+)\](.*?)\[\/quote\]', + "rt" => '
$1 said:
$2
', + "rep" => true + ), array( + "re" => '\[link=(http[^\]]+)\](.+?)\[\/link\]', + "rt" => '$2' + ), array( + "re" => '\[code\](.*?)\[\/code\]', + "rt" => '
$1
' + ) + ); function main_forums_substitute($lib) { $this->lib = $lib; $this->db = $this->lib->game->db; } - function run($text, $ec, $es) { + function run($text, $ec) { $src = array('/\\n/','/\\r/'); $dst = array("
",''); + $rsSrc = array(); $rsDst = array(); $text = utf8entities($text, ENT_NOQUOTES); - $ec = ($ec == 't'); - $es = ($es == 't'); if ($ec) { - if (is_array($this->code)) { - foreach ($this->code as $s => $d) { - array_push($src, '/'.$s.'/i'); - array_push($dst, $d); - } - } else { - $this->codes = array(); - $q = $this->db->query("SELECT * FROM f_code"); - while ($r = dbFetchArray($q)) { - $this->code[$r[0]] = $r[1]; - array_push($src, '/'.$r[0].'/i'); - array_push($dst, $r[1]); + foreach (self::$code as $c) { + if ($c['rep']) { + array_push($rsSrc, "/{$c['re']}/i"); + array_push($rsDst, $c['rt']); + } else { + array_push($src, "/{$c['re']}/i"); + array_push($dst, $c['rt']); } } } - if ($es) { - if (is_array($this->smiley)) { - foreach ($this->smiley as $s => $d) { - array_push($src, '/'.$s.'/i'); - array_push($dst, $d); - } - } else { - $this->smiley = array(); - $q = $this->db->query("SELECT * FROM f_smiley"); - while ($r = dbFetchArray($q)) { - $fn = getStatic("main/pics/smiles/icon_".$r[1].".gif"); - if (is_null($fn)) { - continue; - } - $code = "[S]"; - $this->smiley[$r[0]] = $code; - array_push($src, '/'.$r[0].'/i'); - array_push($dst, $code); + + if (count($rsSrc)) { + $i = 0; + foreach ($rsSrc as $re) { + while (preg_match($re, $text)) { + $text = preg_replace($re, $rsDst[$i], $text); } + $i ++; } } - return preg_replace($src, $dst, $text); + $rv = preg_replace($src, $dst, $text); + return $rv; } } diff -Naur beta5//scripts/game/main/forums/library.inc forums//scripts/game/main/forums/library.inc --- beta5//scripts/game/main/forums/library.inc 2011-02-05 10:09:57.844335002 +0100 +++ forums//scripts/game/main/forums/library.inc 2011-02-05 10:10:03.434335002 +0100 @@ -1,71 +1,152 @@ lib = $lib; - $this->db = $this->lib->game->db; - } + $this->game = $this->lib->game; + $this->db = $this->game->getDBAccess(); - function getVersionCategory($ver) { - $q = $this->db->query("SELECT id,description FROM f_category WHERE title='!$ver!'"); - return dbFetchHash($q); - } + $this->getForumLib = $this->db->prepare( + "SELECT l.lib_path FROM forums.t_forum f, forums.category c, forums.category_type l " + . "WHERE f.id = $1 AND c.id = f.category AND l.id = c.acl_lib", + array("id") ); + $this->getCategoryLib = $this->db->prepare( + "SELECT l.lib_path FROM forums.category c, forums.category_type l " + . "WHERE l.id = c.acl_lib AND c.id = $1", + array("id") ); + } + + public function getCategory($id, $user) { + $idx = "$id;$user"; + + if (! isset($this->categories[$idx])) { + $q = $this->getCategoryLib->execute(array("id" => $id)); + if (! ($q && dbCount($q) == 1)) { + return null; + } + list($lib) = dbFetchArray($q); + + if (! isset($this->libraries[$lib]) ) { + $this->libraries[$lib] = $this->game->getLib($lib); + } + $cat = $this->libraries[$lib]->call('getCategory', $id, $user); + + if (is_null($cat)) { + return null; + } + $this->categories[$idx] = $cat; + } - function isRead($topic, $player) { - $q = $this->db->query("SELECT * FROM f_read WHERE topic=$topic AND reader=$player"); - return $q && dbCount($q); + return $this->categories[$idx]; } - function markRead($topic, $player) { - if ($this->isRead($topic,$player)) { - return false; + public function getForum($id, $user) { + $idx = "$id;$user"; + + if (! isset($this->forums[$idx])) { + $q = $this->getForumLib->execute(array("id" => $id)); + if (! ($q && dbCount($q) == 1)) { + return null; + } + list($lib) = dbFetchArray($q); + + if (! isset($this->libraries[$lib]) ) { + $this->libraries[$lib] = $this->game->getLib($lib); + } + $f = $this->libraries[$lib]->call('getForum', $id, $user); + + if (is_null($f)) { + return null; + } + $this->forums[$idx] = $f; } - $this->db->query("DELETE FROM f_read WHERE topic=$topic AND reader=$player"); - $this->db->query("INSERT INTO f_read(topic,reader)VALUES($topic,$player)"); - return true; + + return $this->forums[$idx]; } - function markUnread($topic, $player) { - $this->db->query("DELETE FROM f_read WHERE topic=$topic AND reader<>$player"); + public function getTopic($data, $user) { + if (is_array($data)) { + $id = $data['id']; + } else { + $id = $data; + } + $idx = "$id;$user"; + + if (! isset($this->topics[$idx])) { + loader::needClasses('main/forums', 'fl_topic'); + try { + $topic = new fl_topic($this->game, $user, $data); + } catch (Exception $e) { + logText("main/forums::getTopic: failed to fetch topic #$id (user #$user)", LOG_WARNING); + return null; + } + $this->topics[$idx] = $topic; + } + + return $this->topics[$idx]; } - // Get the amount of unread topics in a forum - function getRead($fid, $uid) { - $q = $this->db->query("SELECT COUNT(*) FROM f_read r,f_topic t WHERE t.id=r.topic AND t.forum=$fid AND r.reader=$uid AND t.deleted IS NULL"); - list($nr) = dbFetchArray($q); - return $nr; + public function getPoll($id, $user) { + $idx = "$id;$user"; + + if (! isset($this->polls[$idx])) { + loader::needClasses('main/forums', 'fl_poll'); + try { + $poll = new fl_poll($this->game, $user, $id); + } catch (Exception $e) { + return null; + } + $this->polls[$idx] = $poll; + } + + return $this->polls[$idx]; } - function switchSticky($forum, $topic) { - $this->db->query("UPDATE f_topic SET sticky=NOT sticky WHERE id=$topic AND forum=$forum AND deleted IS NULL"); + public function getPost($data, $user) { + if (is_array($data)) { + $id = $data['id']; + } else { + $id = $data; + } + $idx = "$id;$user"; + + if (! isset($this->posts[$idx])) { + loader::needClasses('main/forums', 'fl_post'); + try { + $post = new fl_post($this->game, $user, $data); + } catch (Exception $e) { + logText("main/forums::getPost: failed to fetch post #$id (user #$user)", LOG_WARN); + return null; + } + $this->posts[$idx] = $post; + } + + return $this->posts[$idx]; } - function markForumRead($fid, $uid) { - $q = $this->db->query("SELECT id FROM f_topic WHERE forum=$fid AND deleted IS NULL"); - while ($r = dbFetchArray($q)) { - $this->markRead($r[0], $uid); + public function setOptions($pid, $forum, $perPage, $viewDeleted) { + if ($forum == -1) { + prefs::remove('main/F#PP#%'); + prefs::remove('main/F#VD#%'); + prefs::set('main/F#PP', $perPage); + prefs::set('main/F#VD', $viewDeleted ? 1 : 0); + } else { + prefs::set("main/F#PP#$forum", $perPage); + prefs::set("main/F#VD#$forum", $viewDeleted ? 1 : 0); } } } diff -Naur beta5//scripts/game/main/gforums/library.inc forums//scripts/game/main/gforums/library.inc --- beta5//scripts/game/main/gforums/library.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/gforums/library.inc 2011-02-05 10:10:03.384335002 +0100 @@ -0,0 +1,165 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->version = $this->game->version; + $this->db = $this->game->db; + + $this->fLib = $this->game->getLib('main/forums'); + loader::needClasses('main/forums', array('fl_category', 'fl_forum')); + + $this->getCatQuery = $this->db->prepare( + "SELECT t_string,t_is_game FROM main.gf_category WHERE category = $1", array("id") ); + $this->getCatForumsQuery = $this->db->prepare( + "SELECT id FROM forums.t_forum WHERE category = $1 ORDER BY f_order", array("category") ); + $this->getForumPrivQuery = $this->db->prepare( + "SELECT * FROM main.get_gforums_privs( $1, $2 )", array("user", "forum") ); + $this->getUserNameQ = $this->db->prepare( "SELECT name FROM account WHERE id = $1", array("user") ); + } + + function getStructure( $user ) { + $cats = array(); + + $q = $this->db->query("SELECT * FROM main.get_gf_categories($1, $2)", + $this->version->id, $this->game->name); + while ($r = dbFetchArray($q)) { + $cat = $this->fLib->call('getCategory', $r[0], $user); + if (! is_null($cat)) { + array_push($cats, $cat); + } + } + + return $cats; + } + + function getCategory( $catId , $user ) { + $q = $this->getCatQuery->execute($catId); + if (! ($q && dbCount($q) == 1)) { + return null; + } + list($str, $ig) = dbFetchArray($q); + + if (is_null($str)) { + $title = 'Legacy Worlds forums'; + $description = 'These forums are common to all versions of Legacy Worlds after Beta 5.'; + } else if ($ig == 'f') { + $txt = config::$config->versions[$str]->text; + $title = "Legacy Worlds {$txt}"; + $description = "These forums are common to all games using the Legacy Worlds " + . "{$txt} base code."; + } else { + $txt = config::$config->games[$str]->text; + $title = $txt; + $description = "These are the forums for the {$txt} game."; + } + + $cat = new fl_category( $this->game, $user, $catId, $title, $description ); + + $q = $this->getCatForumsQuery->execute($catId); + while ($r = dbFetchArray($q)) { + $forum = $this->fLib->call('getForum', $r[0], $user); + if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { + $cat->addForum( $forum ); + } + } + + return $cat; + } + + + function getForum( $forumId, $user ) { + $q = $this->getForumPrivQuery->execute($user, $forumId); + if (! ($q && dbCount($q) == 1)) { + return null; + } + $privs = dbFetchHash($q); + + try { + $forum = new fl_forum( $this->game, $forumId, $user ); + } catch (Exception $e) { + return null; + } + + $forum->setAdmin( $privs['is_admin'] == 't' ); + $forum->setMod( $privs['is_mod'] == 't' ); + $forum->setCreateTopic( $privs['can_create'] == 't' ); + $forum->setCreatePoll( $privs['can_poll'] == 't' ); + $forum->setPost( $privs['can_post'] == 't' ); + $forum->setView( $privs['can_view'] == 't' ); + + return $forum; + } + + + public function getAdmins($forum) { + $q = $this->db->query("SELECT category FROM forums.t_forum WHERE id = $1", $forum); + if (! ($q && dbCount($q) == 1)) { + return array(); + } + list($cat) = dbFetchArray($q); + + $admins = array(); + + $q = $this->db->query("SELECT account FROM gf_admin WHERE category = $1 OR category IS NULL", $cat); + if (!$q) { + return $admins; + } + while ($r = dbFetchArray($q)) { + array_push($admins, $r[0]); + } + return $admins; + } + + + public function getModerators($forum) { + $q = $this->db->query("SELECT category FROM forums.t_forum WHERE id = $1", $forum); + if (! ($q && dbCount($q) == 1)) { + return array(); + } + list($cat) = dbFetchArray($q); + + $mods = array(); + + $q = $this->db->query("SELECT account FROM gf_cat_moderator " + . "WHERE category = $1 OR category IS NULL", $cat); + if (!$q) { + return $mods; + } + while ($r = dbFetchArray($q)) { + array_push($mods, $r[0]); + } + + $q = $this->db->query("SELECT account FROM gf_forum_moderator WHERE forum = $1", $forum); + if (!$q) { + while ($r = dbFetchArray($q)) { + array_push($mods, $r[0]); + } + } + + return $mods; + } + + + public function getUsers($forum) { + return array(); + } + + + public function aclIdToName($id) { + $q = $this->getUserNameQ->execute($id); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($rv) = dbFetchArray($q); + return $rv; + } +} + +?> diff -Naur beta5//scripts/game/main/uforums/library.inc forums//scripts/game/main/uforums/library.inc --- beta5//scripts/game/main/uforums/library.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/game/main/uforums/library.inc 2011-02-05 10:10:03.374335002 +0100 @@ -0,0 +1,193 @@ +lib = $lib; + $this->game = $this->lib->game; + $this->db = $this->game->getDBAccess(); + + $this->account = $this->game->getLib('main/account'); + $this->fLib = $this->game->getLib('main/forums'); + loader::needClasses('main/forums', array('fl_category', 'fl_forum')); + + $this->getOwnCatQ = $this->db->prepare( + "SELECT main.uf_get_category($1)", array("user") ); + $this->getOtherCatsQ = $this->db->prepare( + "SELECT DISTINCT category FROM forums.t_forum WHERE id IN (" + . "SELECT forum FROM main.uf_subscription WHERE account = $1)", + array("user") ); + $this->getCatOwnerQ = $this->db->prepare( + "SELECT account FROM main.user_category WHERE category = $1", array("category") ); + $this->getOwnForumsQ = $this->db->prepare( + "SELECT id FROM forums.t_forum WHERE category = $1 ORDER BY f_order", array("category") ); + $this->getOtherForumsQ = $this->db->prepare( + "SELECT id FROM forums.t_forum" + . " WHERE category = $1 " + . " AND id IN (SELECT forum FROM main.uf_subscription WHERE account = $2)" + . " ORDER BY f_order", + array("category", "user") ); + $this->getForumPrivQ = $this->db->prepare( + "SELECT main.uf_get_user_access( $2, $1 )", array("forum", "user") ); + $this->getUserNameQ = $this->db->prepare( "SELECT name FROM account WHERE id = $1", array("user") ); + } + + function getStructure( $user ) { + $cats = array(); + + $q = $this->getOwnCatQ->execute($user); + if ( $q && dbCount($q) == 1) { + list($id) = dbFetchArray($q); + $cat = $this->fLib->call('getCategory', $id, $user); + if (! is_null($cat)) { + array_push($cats, $cat); + } + } + + $q = $this->getOtherCatsQ->execute($user); + while ($r = dbFetchArray($q)) { + $cat = $this->fLib->call('getCategory', $r[0], $user); + if (! is_null($cat)) { + array_push($cats, $cat); + } + } + + return $cats; + } + + function getCategory( $id, $user ) { + $q = $this->getCatOwnerQ->execute($id); + if (! ($q && dbCount($q) == 1) ) { + return null; + } + list($account) = dbFetchArray($q); + + if ($account == $user) { + $title = "My forums"; + $description = "These are your own forums; do with them as you please."; + } else { + $uname = $this->account->call('getUserName', $account); + $title = "$uname's forums"; + $description = "These are the personnal forums of player $uname."; + } + + $cat = new fl_category( $this->game, $user, $id, $title, $description ); + + if ($account == $user) { + $q = $this->getOwnForumsQ->execute($id); + if (! $q ) { + return null; + } + } else { + $q = $this->getOtherForumsQ->execute($id, $user); + if (! ($q && dbCount($q) ) ) { + return null; + } + } + while ($r = dbFetchArray($q)) { + $forum = $this->fLib->call('getForum', $r[0], $user); + if ($forum->canView() && ($forum->isAdmin() || ! $forum->isDeleted())) { + $cat->addForum( $forum ); + } + } + + return $cat; + } + + function getForum( $forumId, $user) { + $q = $this->getForumPrivQ->execute($forumId, $user); + if (! ($q && dbCount($q) == 1)) { + return null; + } + list($accessMode) = dbFetchArray($q); + if (is_null($accessMode)) { + return null; + } + + try { + $forum = new fl_forum( $this->game, $forumId, $user ); + } catch (Exception $e) { + return null; + } + + $forum->setAdmin( $accessMode == 'A' ); + $forum->setMod( in_array($accessMode, array('M', 'A')) ); + $forum->setCreatePoll( in_array($accessMode, array('L', 'M', 'A')) ); + $forum->setCreateTopic( in_array($accessMode, array('T', 'L', 'M', 'A')) ); + $forum->setPost( $accessMode != 'R' ); + $forum->setView( true ); + + return $forum; + } + + + public function getAdmins($forum) { + $q = $this->db->query("SELECT uc.account FROM user_category uc, forums.t_forum f " + . "WHERE f.id = $1 AND uc.category = f.category", $forum); + if (!($q && dbCount($q) == 1)) { + return array(); + } + list($admin) = dbFetchArray($q); + + return array($admin); + } + + + public function getModerators($forum) { + $q = $this->db->query("SELECT account FROM uf_subscription WHERE access_mode = 'M' AND forum = $1", + $forum); + if (!$q) { + return array(); + } + $mods = array(); + while ($r = dbFetchArray($q)) { + array_push($mods, $r[0]); + } + + return $mods; + } + + + public function getUsers($forum) { + $q = $this->db->query("SELECT uf_get_access_mode($1)", $forum); + if (!($q && dbCount($q))) { + return array(); + } + list($mode) = dbFetchArray($q); + if ($mode == 'P') { + return array(); + } + + $q = $this->db->query("SELECT account FROM uf_subscription WHERE access_mode <> 'M' AND forum = $1", + $forum); + if (!$q) { + return array(); + } + $users = array(); + while ($r = dbFetchArray($q)) { + array_push($users, $r[0]); + } + + return $users; + } + + + public function aclIdToName($id) { + $q = $this->getUserNameQ->execute($id); + if (!($q && dbCount($q) == 1)) { + return null; + } + list($rv) = dbFetchArray($q); + return $rv; + } +} + + +?> diff -Naur beta5//scripts/lib/classloader.inc forums//scripts/lib/classloader.inc --- beta5//scripts/lib/classloader.inc 2011-02-05 10:09:57.434335002 +0100 +++ forums//scripts/lib/classloader.inc 2011-03-12 14:56:22.471300053 +0100 @@ -20,6 +20,17 @@ array_push(loader::$loadedClasses, $className); } + + static function needClasses($basePath, $classes) { + $path = config::$main['scriptdir'] . "/game/$basePath/"; + if (!is_array($classes)) { + $classes = array($classes); + } + + foreach ($classes as $cn) { + self::load($path . $cn . ".inc", $cn); + } + } } ?> diff -Naur beta5//scripts/lib/config.inc forums//scripts/lib/config.inc --- beta5//scripts/lib/config.inc 2011-02-05 10:09:57.434335002 +0100 +++ forums//scripts/lib/config.inc 2011-03-12 14:56:24.031300053 +0100 @@ -123,6 +123,10 @@ static function getParam($name) { return config::$config->games['main']->params[$name]; } + + static function getMainInterface() { + return config::$config->games['main']; + } } config::load(); diff -Naur beta5//scripts/lib/db_accessor.inc forums//scripts/lib/db_accessor.inc --- beta5//scripts/lib/db_accessor.inc 2011-02-05 10:09:57.434335002 +0100 +++ forums//scripts/lib/db_accessor.inc 2011-03-12 15:30:19.021300053 +0100 @@ -32,9 +32,15 @@ $this->db->disableExceptions(); } - public function query($q) { + public function query() { $this->setNamespace(); - return $this->db->query($q); + $args = func_get_args(); + return call_user_func_array(array($this->db, "query"), $args); + } + + public function prepare($q, $params) { + $this->setNamespace(); + return db_query::getQuery($this, $q, $params); } public function begin() { @@ -45,6 +51,14 @@ return $this->db->end($rb); } + public function getNamespace() { + return $this->namespace; + } + + public function getDatabase() { + return $this->db; + } + public function needsReset() { return $this->db->needsReset(); } diff -Naur beta5//scripts/lib/db_connection.inc forums//scripts/lib/db_connection.inc --- beta5//scripts/lib/db_connection.inc 2011-02-05 10:09:57.434335002 +0100 +++ forums//scripts/lib/db_connection.inc 2011-03-12 15:39:08.891300050 +0100 @@ -229,17 +229,22 @@ return true; } - public function query($query) { - if (!$this->inTrans) { - $this->fail("query executed outside of transaction", $query); - return null; + public function query() { + if (func_num_args() == 1) { + $query = func_get_arg(0); + if (!$this->checkQuery($query)) { + return null; + } + $r = @pg_query($this->conn, $query); + } else { + $args = func_get_args(); + $query = array_shift($args); + if (!$this->checkQuery($query)) { + return null; + } + $r = call_user_func("pg_query_params", $query, $args); } - $this->queries ++; - if ($this->trace) { - l::trace("EXECUTE: $query"); - } - $r = @pg_query($this->conn, $query); if (!$r) { $cStat = pg_connection_status($this->conn); $error = pg_last_error($this->conn); @@ -264,48 +269,23 @@ } } elseif (preg_match('/^\s*insert\s+into ("?\w+"?)/i', $query, $match)) { pg_free_result($r); - - $tn = $match[1]; - if ($tn{0} == '"') { - $tn = str_replace('"', '', $tn); - } else { - $tn = strtolower($tn); - } - - $r2 = @pg_query("SELECT last_inserted('$tn')"); - if ($r2) { - $rv = pg_fetch_row($r2); - if (is_null($rv[0])) { - $r = true; - } else { - $r = $rv[0]; - } - pg_free_result($r2); - } elseif (preg_match('/deadlock/i', $error)) { - l::error("SQL: deadlock detected"); - if ($this->useExceptions) { - throw new db_deadlock_exception($error); - } - $this->error = true; - return; - } else { - $cStat = pg_connection_status($this->conn); - $error = pg_last_error($this->conn); - if ($cStat == PGSQL_CONNECTION_BAD) { - l::notice("SQL: connection is gone while fetching last ID"); - $this->__needsReset = true; - if ($this->useExceptions) { - throw new db_srv_exception($error); - } - } else { - $this->fail("failed to fetch last ID: $error", "SELECT last_inserted('$tn')"); - } - $r = null; - } + $r = $this->getLastInserted($match[1]); } return $r; } + private function checkQuery($query) { + if (!$this->inTrans) { + $this->fail("SQL: query executed outside of transaction", $query); + return false; + } + if ($this->trace) { + l::trace("EXECUTE: $query"); + } + $this->queries ++; + return true; + } + public function getGameAccess($prefix) { if (is_null($this->accessors[$prefix])) { $this->accessors[$prefix] = new db_accessor($this, $prefix); @@ -313,6 +293,50 @@ return $this->accessors[$prefix]; } + public function getConnection() { + return $this->conn; + } + + private function getLastInserted($tn) { + if ($tn{0} == '"') { + $tn = str_replace('"', '', $tn); + } else { + $tn = strtolower($tn); + } + + $r2 = @pg_query("SELECT last_inserted('$tn')"); + if ($r2) { + $rv = pg_fetch_row($r2); + if (is_null($rv[0])) { + $r = true; + } else { + $r = $rv[0]; + } + pg_free_result($r2); + } elseif (preg_match('/deadlock/i', $error)) { + l::error("SQL: deadlock detected"); + if ($this->useExceptions) { + throw new db_deadlock_exception($error); + } + $this->error = true; + $r = null; + } else { + $cStat = pg_connection_status($this->conn); + $error = pg_last_error($this->conn); + if ($cStat == PGSQL_CONNECTION_BAD) { + l::notice("SQL: connection is gone while fetching last ID"); + $this->__needsReset = true; + if ($this->useExceptions) { + throw new db_srv_exception($error); + } + } else { + $this->fail("failed to fetch last ID: $error", "SELECT last_inserted('$tn')"); + } + $r = null; + } + return $r; + } + public function needsReset() { return $this->__needsReset; } diff -Naur beta5//scripts/lib/db_query.inc forums//scripts/lib/db_query.inc --- beta5//scripts/lib/db_query.inc 1970-01-01 01:00:00.000000000 +0100 +++ forums//scripts/lib/db_query.inc 2011-02-05 10:10:03.074335002 +0100 @@ -0,0 +1,132 @@ +accessor = $accessor; + $this->query = $query; + $this->parameters = $pNames; + $this->name = $name; + + $this->database = $this->accessor->getDatabase(); + $this->connection = $this->database->getConnection(); + + $this->accessor->useNamespace(); + $r = @pg_prepare($this->connection, $this->name, $query); + + if (! $r) { + $this->accessor->getDatabase()->fail( + "SQL: Could not prepare query: " . pg_last_error($this->connection), + $this->query ); + throw new Exception('failed'); + } + + if (preg_match('/^\s*insert\s+into ("?\w+"?)/i', $this->query, $match)) { + $this->table = $match[1]; + } + } + + + public function execute() { + if ($this->counter == 0) { + $this->database->fail( + "SQL: tried to execute de-allocated query", + $this->query ); + return null; + } + if (!$this->database->checkQuery($this->query)) { + return null; + } + + $args = func_get_args(); + if (count($args) == 1 && is_array($args[0])) { + $params = $args[0]; + $p = array(); + for ($i = 0; $i < count($this->parameters); $i ++) { + if (!array_key_exists($this->parameters[$i], $params)) { + $this->database->fail( + "SQL: missing parameter '{$this->parameters[$i]}' for prepared query", + $this->query ); + return null; + } + $p[$i] = $params[$this->parameters[$i]]; + } + } elseif (count($args) == count($this->parameters)) { + $p = $args; + } else { + $this->database->fail( "SQL: invalid parameters for prepared query", $this->query ); + return null; + } + + $this->accessor->useNamespace(); + $r = @pg_execute($this->connection, $this->name, $p); + + if (! $r) { + $this->database->fail( + "SQL: could not execute prepared query: " . pg_last_error($this->connection), + $this->query ); + return null; + } + + if (!is_null($this->table)) { + pg_free_result($r); + $r = $this->database->getLastInserted($this->table); + } + + return $r; + } + + public function destroy($force = false) { + if ($this->counter == 0) { + return; + } + + if ($force) { + $this->counter = 0; + } else { + $this->counter --; + } + + if ($this->counter == 0) { + $this->accessor->useNamespace(); + $r = $this->database->query("DEALLOCATE {$this->name}"); + self::$queries[$this->name] = null; + } + } + + + public static function getQuery($accessor, $query, $pNames) { + $data = array( + "n" => $accessor->getNamespace(), + "q" => $query, + "p" => $pNames + ); + $name = "q_" . strtolower(md5(serialize($data))); + + if (is_null(self::$queries[$name])) { + try { + self::$queries[$name] = new db_query($name, $accessor, $query, $pNames); + } catch (Exception $e) { + return null; + } + } else { + self::$queries[$name]->counter ++; + } + return self::$queries[$name]; + } + +} + +?> diff -Naur beta5//scripts/lib/library.inc forums//scripts/lib/library.inc --- beta5//scripts/lib/library.inc 2011-02-05 10:09:57.434335002 +0100 +++ forums//scripts/lib/library.inc 2011-03-12 14:56:22.481300053 +0100 @@ -66,10 +66,28 @@ $fcn = $this->loadClass($this->functions[$function][1]); $this->functions[$function][2] = new $fcn($this); } - $rv = call_user_func_array(array($this->functions[$function][2], 'run'), $args); + try { + $rv = call_user_func_array(array($this->functions[$function][2], 'run'), $args); + } catch (Exception $e) { + fatalError(32, array( + "Function call '{$this->name}::$function' on game '{$this->game->name}' " + . "failed with exception:", + $e->getMessage() + )); + } } else { // Call the function instance's method - $rv = call_user_func_array(array($this->mainClass, $this->functions[$function][1]), $args); + try { + $rv = call_user_func_array( + array($this->mainClass, $this->functions[$function][1]), $args + ); + } catch (Exception $e) { + fatalError(32, array( + "Function call '{$this->name}::$function' on game '{$this->game->name}' " + . "failed with exception:", + $e->getMessage() + )); + } } return $rv; diff -Naur beta5//scripts/lib/prefs.inc forums//scripts/lib/prefs.inc --- beta5//scripts/lib/prefs.inc 2011-02-05 10:09:57.434335002 +0100 +++ forums//scripts/lib/prefs.inc 2011-02-05 10:10:03.074335002 +0100 @@ -107,6 +107,19 @@ return $v; } + static function remove($path) { + list($version, $name) = explode('/', $path); + $qs = "DELETE FROM user_preferences WHERE account={$_SESSION['userid']} AND version = '$version' AND id"; + if (strstr($name, '%') === FALSE) { + $q = dbQuery("$qs = '$name'"); + } else { + $q = dbQuery("$qs LIKE '$name'"); + } + prefs::$prefs = null; + prefs::load(); + + } + } ?> diff -Naur beta5//scripts/site/beta5/handlers/alliance.inc forums//scripts/site/beta5/handlers/alliance.inc --- beta5//scripts/site/beta5/handlers/alliance.inc 2011-02-05 10:09:57.204335002 +0100 +++ forums//scripts/site/beta5/handlers/alliance.inc 2011-03-12 15:16:23.151300053 +0100 @@ -18,8 +18,8 @@ // Pending requests "getPending", "acceptRequests", "rejectRequests", // Forums - "getForums", "newForum", "changeForum", "delForum", - "moveForum", "getForumAcl", + "getForums", "newForum", "changeForum", + "delForum", "restoreForum", "moveForum", // Ranks "getRanks", "newRank", "changeRank", "delRank" ), @@ -802,190 +802,316 @@ // ALLIANCE FORUMS MANAGEMENT //------------------------------------------- - function doGetForums($aid) { - $afl = gameAction('getAllianceForums', $aid); - $s = ""; - foreach ($afl as $id => $afd) - { - if ($s != "") - $s .= "\n"; - $s .= "$id#" . $afd['order'] . "#" . ($afd['user_post'] ? 1 : 0) . "#" . $afd['title']; - if ($afd['description'] != '') - { - $dll = split("\n", $afd['description']); - foreach ($dll as $dl) - $s .= "\n+#$dl"; + private function forumsGetLibraries() { + // Get the libraries + $this->player = input::$game->getLib('beta5/player'); + $this->alliance = input::$game->getLib('beta5/alliance'); + $this->forums = input::$game->getLib('main/forums'); + $this->aForums = input::$game->getLib('beta5/aforums'); + } + + private function doGetForums($aid, $uid) { + $out = array(); + $players = array(); + + // Get the category and output the amount of forums + $cat = array_shift($this->aForums->call('getStructure', $uid)); + array_push($out, count($cat->getForums())); + $admin = null; + + foreach ($cat->getForums() as $forum) { + // Get general information regarding the forum + $fid = $forum->getId(); + $userPrivs = $this->aForums->call('getUserPrivileges', $fid); + $topics = $forum->getTopics(); + $name = $forum->getTitle(); + + // Get information about the forum's deletion + $deleted = $forum->isDeleted() ? '1' : '0'; + $deletedAt = $forum->deletedAt(); + $deletedBy = $forum->deletedBy(); + if (! (is_null($deletedBy) || in_array($deletedBy, $players))) { + array_push($players, $deletedBy); + } + + // Output general information + array_push($out, "$fid#$deleted#$deletedBy#$deletedAt#$userPrivs#$topics#$name"); + + // Get the rank-specific access privileges for that forum + array_push($out, join('#', $forum->getUsers())); + array_push($out, join('#', $forum->getModerators())); + $admins = $forum->getAdministrators(); + + // Get the description + $description = $forum->getDescription(); + if (is_null($description) || $description == '') { + array_push($out, "0"); + } else { + $dList = explode("\n", $description); + array_push($out, count($dList)); + foreach ($dList as $dLine) { + array_push($out, $dLine); + } } } - return $s; + + // Get the rank names + $ranks = $this->alliance->call('getRanks', $aid); + array_push($out, count($ranks)); + foreach ($ranks as $rId => $rName) { + array_push($out, "$rId#" . utf8entities($rName)); + } + + // List the ranks having administrator privileges + if (is_null($admins)) { + $admins = array(); + foreach ($ranks as $rId => $rName) { + $rkp = $this->alliance->call('getRankPrivileges', $rId); + if ($rkp['forum_admin']) { + array_push($admins, $rId); + } + } + } + array_push($out, join('#', $admins)); + + // Get the player names (for forums that have been deleted) + array_push($out, count($players)); + foreach ($players as $pid) { + array_push($out, "$pid#" . utf8entities($this->player->call('getName', $pid, true))); + } + + return join("\n", $out); } - function getForums() { + public function getForums() { $pid = $_SESSION[game::sessName()]['player']; - $p = gameAction('getPlayerInfo', $pid); + $this->forumsGetLibraries(); + + // Check if the player's in an alliance + $p = $this->player->call('get', $pid); if (is_null($p['aid'])) { return "ERR#0"; } + + // Check if the player is a forums administrator $aid = $p['aid']; - $pr = gameAction('getAlliancePrivileges', $pid); + $pr = $this->alliance->call('getPrivileges', $pid); if (!$pr['forum_admin']) { return "ERR#4"; } - $_SESSION[game::sessName()]['alliance_page'] = 'FAdmin'; - return $this->doGetForums($aid); + $_SESSION[game::sessName()]['alliance_page'] = 'FAdmin'; + return $this->doGetForums($aid, $p['uid']); } - function newForum($name, $userPost, $after, $description, $acl) { - if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { - return "ERR#200"; + private function setForumPrivileges( $forum, $alliance, $acl ) { + $rl = $this->alliance->call('getRanks', $alliance); + $acla = explode('#', $acl); + $forum->clearACL(); + + foreach ($acla as $as) { + list($rank, $level) = explode('!', $as); + $level --; + if (is_null($rl[$rank]) || ($level != 0 && $level != 1)) { + continue; + } + + if ($level) { + $forum->addModerator($rank); + } else { + $forum->addUser($rank); + } } + } + + public function newForum($name, $accessMode, $after, $description, $acl) { + $this->forumsGetLibraries(); $pid = $_SESSION[game::sessName()]['player']; - $p = gameAction('getPlayerInfo', $pid); - if (is_null($p['aid'])) - return "ERR#0"; - $aid = $p['aid']; - $pr = gameAction('getAlliancePrivileges', $pid); - if (!$pr['forum_admin']) - return "ERR#4"; + $p = $this->player->call('get', $pid); - $afl = gameAction('getAllianceForums', $aid); - if (count($afl) >= 30) { - return "ERR#5"; + if (is_null($p['aid'])) { + return "ERR#0\n0"; } - $name = preg_replace('/\s+/', ' ', trim($name)); - if ($name == "" || strlen($name) < 4) - return "ERR#1"; - foreach ($afl as $fid => $fd) - if ($fd['title'] == $name) - return "ERR#2"; - - if ($after != "-1" && is_null($afl[$after])) - return "ERR#6"; - - $description = trim($description); - gameAction('newAllianceForum', $aid, $name, ($userPost == 1), $after, $description); - - $afl = gameAction('getAllianceForums', $aid); - $mId = false; - foreach ($afl as $fid => $fd) - if ($fd['title'] == $name) - { - $mId = $fid; - break; + if ($this->player->call('isOnVacation', $pid)) { + $err = 200; + } else { + $name = preg_replace('/\s+/', ' ', trim($name)); + $description = trim($description); + $cat = array_shift($this->aForums->call('getStructure', $p['uid'])); + $after = (int) $after; + + if ($name == "" || strlen($name) < 4) { + $err = 1; + } elseif ($after != -1 && is_null($cat->findForum($after))) { + $err = 6; + } else { + if ($after == -1) { + $order = 0; + } else { + $order = $cat->findForum($after)->getOrder() + 1; + } + + $rv = $this->aForums->call('create', + $pid, $p['uid'], $p['aid'], $order, + $name, $description, $accessMode + ); + if (is_object($rv)) { + $this->setForumPrivileges( $rv, $p['aid'], $acl ); + $err = null; + } else { + logText("Alliance forum creation returned $rv"); + switch ($rv) : + case 1: $err = 0; break; + case 2: $err = 4; break; + case 3: $err = 5; break; + case 4: $err = 2; break; + default: $err = 7; break; + endswitch; + } } - if (!$mId) { - return "ERR#7"; } - $rl = gameAction('getAllianceRanks', $aid); - $fread = $fmod = array(); - $acla = explode('#', $acl); - foreach ($acla as $as) - { - list($rank,$level) = explode('!', $as); - $level --; - if (is_null($rl[$rank]) || ($level != 0 && $level != 1)) - continue; - if ($level) - array_push($fmod, $rank); - else - array_push($fread, $rank); + $out = $this->doGetForums($p['aid'], $p['uid']); + if (!is_null($err)) { + $out = "ERR#$err\n$out"; } - gameAction('setForumAccess', $mId, $fread, $fmod); - - return $this->doGetForums($aid); + return $out; } - function changeForum($id, $name, $userPost, $description, $acl) { - if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { - return "ERR#200"; - } - + public function changeForum($id, $name, $accessMode, $description, $acl) { + $this->forumsGetLibraries(); $pid = $_SESSION[game::sessName()]['player']; - $p = gameAction('getPlayerInfo', $pid); - if (is_null($p['aid'])) - return "ERR#0"; - $aid = $p['aid']; - $pr = gameAction('getAlliancePrivileges', $pid); - if (!$pr['forum_admin']) - return "ERR#1"; - - $afl = gameAction('getAllianceForums', $aid); - if (is_null($afl[$id])) - return "ERR#3"; + $p = $this->player->call('get', $pid); - $name = preg_replace('/\s+/', ' ', trim($name)); - if ($name == "" || strlen($name) < 4) - return "ERR#1"; - foreach ($afl as $fid => $fd) - if ($fid != $id && $fd['name'] == $name) - return "ERR#2"; - - $description = trim($description); - gameAction('modifyAllianceForum', $id, $name, ($userPost == 1), $description); + if (is_null($p['aid'])) { + return "ERR#0\n0"; + } - $rl = gameAction('getAllianceRanks', $aid); - $fread = $fmod = array(); - $acla = explode('#', $acl); - foreach ($acla as $as) - { - list($rank,$level) = explode('!', $as); - $level --; - if (is_null($rl[$rank]) || ($level != 0 && $level != 1)) - continue; - if ($level) - array_push($fmod, $rank); - else - array_push($fread, $rank); + if ($this->player->call('isOnVacation', $pid)) { + $err = 200; + } else { + $id = (int) $id; + $name = preg_replace('/\s+/', ' ', trim($name)); + $description = trim($description); + + $rv = $this->aForums->call('modifyForum', $id, $pid, $name, $description, $accessMode); + if ($rv > 0) { + switch ($rv) : + case -1: $err = 7; break; + case -2: $err = 0; break; + case -3: $err = 4; break; + case -4: $err = 8; break; + case -5: $err = 7; break; + endswitch; + } else { + $err = null; + $this->setForumPrivileges( $this->forums->call('getForum', $id, $p['uid']), + $p['aid'], $acl ); + } } - gameAction('setForumAccess', $id, $fread, $fmod); - return $this->doGetForums($aid); + $out = $this->doGetForums($p['aid'], $p['uid']); + if (!is_null($err)) { + $out = "ERR#$err\n$out"; + } + return $out; } function delForum($id) { - if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { - return "ERR#200"; + $this->forumsGetLibraries(); + $pid = $_SESSION[game::sessName()]['player']; + $p = $this->player->call('get', $pid); + + if (is_null($p['aid'])) { + $err = 0; + $out = "0"; + } else { + if ($this->player->call('isOnVacation', $pid)) { + $err = 200; + } else { + $f = $this->forums->call('getForum', (int) $id, $p['uid']); + if (is_null($f) || $f->isDeleted()) { + $err = 8; + } elseif (! $f->isAdmin()) { + $err = 4; + } else { + $f->delete(); + $err = null; + } + } + $out = $this->doGetForums($p['aid'], $p['uid']); } + if (!is_null($err)) { + $out = "ERR#$err\n$out"; + } + return $out; + } + + function restoreForum($id) { + $this->forumsGetLibraries(); $pid = $_SESSION[game::sessName()]['player']; - $p = gameAction('getPlayerInfo', $pid); - if (is_null($p['aid'])) - return "ERR#0"; - $aid = $p['aid']; - $pr = gameAction('getAlliancePrivileges', $pid); - if (!$pr['forum_admin']) - return "ERR#1"; + $p = $this->player->call('get', $pid); - $afl = gameAction('getAllianceForums', $aid); - if (is_null($afl[$id])) { - return "ERR#8"; + if (is_null($p['aid'])) { + $err = 0; + $out = "0"; + } else { + if ($this->player->call('isOnVacation', $pid)) { + $err = 200; + } else { + $f = $this->forums->call('getForum', (int) $id, $p['uid']); + if (is_null($f)) { + $err = 9; + } elseif (! $f->isAdmin()) { + $err = 4; + } elseif (! $f->isDeleted()) { + $err = 9; + } else { + $f->restore(); + $err = null; + } + } + $out = $this->doGetForums($p['aid'], $p['uid']); } - gameAction('deleteAllianceForum', $id); - return $this->doGetForums($aid); - } - function moveForum($id, $up) { - if (gameAction('isOnVacation', $_SESSION[game::sessName()]['player'])) { - return "ERR#200"; + if (!is_null($err)) { + $out = "ERR#$err\n$out"; } + return $out; + } + function moveForum($id, $up) { + $this->forumsGetLibraries(); $pid = $_SESSION[game::sessName()]['player']; - $p = gameAction('getPlayerInfo', $pid); - if (is_null($p['aid'])) - return "ERR#0"; - $aid = $p['aid']; - $pr = gameAction('getAlliancePrivileges', $pid); - if (!$pr['forum_admin']) - return "ERR#1"; + $p = $this->player->call('get', $pid); + + if (is_null($p['aid'])) { + $err = 0; + $out = "0"; + } else { + if ($this->player->call('isOnVacation', $pid)) { + $err = 200; + } else { + $f = $this->forums->call('getForum', (int) $id, $p['uid']); + if (is_null($f) || $f->isDeleted()) { + $err = 8; + } elseif (! $f->isAdmin()) { + $err = 4; + } else { + $f->move($up == '1'); + $err = null; + } + } + $out = $this->doGetForums($p['aid'], $p['uid']); + } - $afl = gameAction('getAllianceForums', $aid); - if (!is_null($afl[$id])) - gameAction('moveAllianceForum', $id, ($up == "1")); - return $this->doGetForums($aid); + if (!is_null($err)) { + $out = "ERR#$err\n$out"; + } + return $out; } function getForumAcl($id) { diff -Naur beta5//scripts/site/beta5/handlers/comms.inc forums//scripts/site/beta5/handlers/comms.inc --- beta5//scripts/site/beta5/handlers/comms.inc 2011-02-05 10:09:57.204335002 +0100 +++ forums//scripts/site/beta5/handlers/comms.inc 2011-03-12 15:21:17.911300054 +0100 @@ -8,6 +8,50 @@ 'init' => "makeCommsTooltips();\ninitPage();" ); + /** This method dumps containers, categories and forums to the output data. + * The dump's format is the following: + * + * type # id # nElements # topics # unread + * name + * + * With: + * "type" being either "C" for categories and containers or "F" for forums + * "id" being the object's identifier + * "nElements" is the number of sub-elements the object contains + * "topics" and "unread" are the amount of topics and unread topics + * "name" is the object's title + * + * \parameter $result a reference to the output data + * \parameter $container the object to be dumped + */ + private function dumpForums( &$result, $container ) { + $res = array(); + + if ( $container instanceof fl_forum ) { + array_push($res, 'F'); + $contents = array(); + } else { + array_push($res, 'C'); + if ( $container instanceof fl_category ) { + $contents = $container->getForums(); + } else { + $contents = $container->getCategories(); + } + } + + array_push( $res, $container->getId() ); + array_push( $res, count($contents) ); + array_push( $res, $container->getTopics() ); + array_push( $res, $container->getUnread() ); + + array_push( $result, join('#', $res) ); + array_push( $result, utf8entities($container->getTitle())); + + foreach ($contents as $c) { + $this->dumpForums( &$result, $c ); + } + } + public function getCommsData() { // Get the data $data = $this->game->action('getCommsOverview', $_SESSION[game::sessName()]['player']); @@ -16,33 +60,20 @@ $result = array(); array_push($result, count($data['folders']['CUS']) . "#" . count($data['forums']['general']) . "#" . count($data['forums']['alliance'])); - - // Messages in default folders - $dFld = array('IN', 'INT', 'OUT'); - foreach ($dFld as $f) { - array_push($result, join('#', $data['folders'][$f])); - } - - // Custom folders foreach ($data['folders']['CUS'] as $id => $folder) { $folder[2] = utf8entities($folder[2]); array_unshift($folder, $id); array_push($result, join('#', $folder)); } + // Forums - foreach ($data['forums']['general'] as $cat) { - array_push($result, "{$cat['id']}#{$cat['type']}#" . count($cat['forums']) - . "#" . utf8entities($cat['title'])); - - foreach ($cat['forums'] as $f) { - $f[3] = utf8entities($f[3]); - array_push($result, join('#', $f)); - } - } - foreach ($data['forums']['alliance'] as $f) { - $f[3] = utf8entities($f[3]); - array_push($result, join('#', $f)); + $forums = gameAction('getForums', $pid); + if (is_null($forums)) { + array_push($result, "C#/#0#0#0"); + array_push($result, ""); + } else { + $this->dumpForums( &$result, $forums ); } return join("\n", $result); diff -Naur beta5//scripts/site/beta5/handlers/diplomacy.inc forums//scripts/site/beta5/handlers/diplomacy.inc --- beta5//scripts/site/beta5/handlers/diplomacy.inc 2011-02-05 10:09:57.204335002 +0100 +++ forums//scripts/site/beta5/handlers/diplomacy.inc 2011-02-05 10:10:02.884335002 +0100 @@ -1,7 +1,12 @@ array('getInformation'), @@ -9,9 +14,6 @@ ); function getAllianceRanking($tag) { - if (! $this->rkLib) { - $this->rkLib = input::$game->getLib('main/rankings'); - } $rt = $this->rkLib->call('getType', "a_general"); $r = $this->rkLib->call('get', $rt, $tag); if (!$r) { @@ -20,27 +22,33 @@ return $r; } - function getInformation() - { + function getInformation() { $out = array(); $pid = $_SESSION[game::sessName()]['player']; - $pinf = gameAction('getPlayerInfo', $pid); - if (!is_null($pinf['arid'])) - { - $ainf = gameAction('getAllianceInfo', $pinf['arid']); + + // Get the libraries + $this->rkLib = input::$game->getLib('main/rankings'); + $this->player = input::$game->getLib('beta5/player'); + $this->alliance = input::$game->getLib('beta5/alliance'); + + $pinf = $this->player->call('get', $pid); + + // Player has requested to join an alliance + if (!is_null($pinf['arid'])) { + $ainf = $this->alliance->call('get', $pinf['arid']); $s = "1#" . $ainf['nplanets'] . '#' . $ainf['avgx'] . '#' . $ainf['avgy']; list($points,$ranking) = $this->getAllianceRanking($ainf['tag']); $s .= "#$ranking#$points"; array_push($out, $s); array_push($out, utf8entities($pinf['alliance_req'])); array_push($out, utf8entities($pinf['aname'])); - $alinf = gameAction('getPlayerName', $ainf['leader']); + $alinf = $this->player->call('getName', $ainf['leader']); array_push($out, utf8entities($alinf)); - } - elseif (!is_null($pinf['aid'])) - { - $ainf = gameAction('getAllianceInfo', $pinf['aid']); - $pr = gameAction('getAlliancePrivileges', $pid); + + // Player is a member of an alliance + } elseif (!is_null($pinf['aid'])) { + $ainf = $this->alliance->call('get', $pinf['aid']); + $pr = $this->alliance->call('getPrivileges', $pid); $s = "2#" . $ainf['nplanets'] . '#' . $ainf['avgx'] . '#' . $ainf['avgy']; list($points,$ranking) = $this->getAllianceRanking($ainf['tag']); @@ -49,31 +57,36 @@ array_push($out, $s); array_push($out, utf8entities($pinf['alliance'])); array_push($out, utf8entities($pinf['aname'])); - if (!$pr['is_leader']) - { - array_push($out, utf8entities(gameAction('getPlayerName', $ainf['leader']))); - if (is_null($pinf['a_grade'])) + + // Get the player's rank if he isn't the leader + if (!$pr['is_leader']) { + array_push($out, utf8entities($this->player->call('getName', $ainf['leader']))); + if (is_null($pinf['a_grade'])) { array_push($out, "-"); - else - { - $rkl = gameAction('getAllianceRanks', $pinf['aid']); + } else { + $rkl = $this->alliance->call('getRanks', $pinf['aid']); array_push($out, $rkl[$pinf['a_grade']]); } } - $fl = gameAction('getAllianceForumsComplete', $pinf['aid']); - foreach ($fl as $fd) - { - $fid = $fd['id']; - if (!(in_array($fid, $pr['f_read']) || in_array($fid, $pr['f_mod']))) + // Get the list of alliance forums + $aForums = input::$game->getLib('beta5/aforums'); + $cat = array_shift($aForums->call('getStructure', $pinf['uid'])); + foreach ($cat->getForums() as $forum) { + if ($forum->isDeleted()) { continue; - $tot = $fd['topics']; - $unread = $tot - gameAction('getReadTopics', $fid, $pid); - array_push($out, "$fid#$tot#$unread#".utf8entities($fd['title'])); + } + $fid = $forum->getId(); + $tot = $forum->getTopics(); + $unread = $forum->getUnread(); + $name = utf8entities($forum->getTitle()); + array_push($out, "$fid#$tot#$unread#$name"); } - } - else + + // Player is not a member of any alliance and has not requested to join one + } else { array_push($out, "0"); + } $pm = gameAction('getAllMessages', $pid, 'IN'); $pmn = gameAction('getNewMessages', $pid, 'IN'); diff -Naur beta5//scripts/site/beta5/handlers/forums.inc forums//scripts/site/beta5/handlers/forums.inc --- beta5//scripts/site/beta5/handlers/forums.inc 2011-02-05 10:09:57.204335002 +0100 +++ forums//scripts/site/beta5/handlers/forums.inc 2011-03-12 14:56:16.341300049 +0100 @@ -1,9 +1,7 @@ output = "forums"; } +*/ + /* Timeout in seconds for the forums' session data */ + const SESSION_TIMEOUT = 60; + + /* AJAX engine data */ + public $needsAuth = true; + public $ajax = array( + "init" => "main = new ForumsLayout();", + "func" => array( + // Menu functions + "showMenu", "hideMenu", "getMenu", + "menuOpen", "menuClose", + // Category / forum view functions + "getView", "categoryRead", "forumOptions", "forumRead", + "restoreTopics", "deleteTopics", "changeTopicsLevel", + "setTopicsLevel", "setTopicsLock", "moveTopics", + // Topic view functions + "getTopic", "loadPostContents" + )); + + /* Method that loads the required libraries */ + private function loadLibraries() { + $this->player = input::$game->getLib('beta5/player'); + + $this->forums = input::$game->getLib('main/forums'); + $this->gForums = input::$game->getLib('main/gforums'); + $this->uForums = input::$game->getLib('main/uforums'); + $this->aForums = input::$game->getLib('beta5/aforums'); + } + + +/***********************************************************************************************************************/ +/** SESSION CODE *******************************************************************************************************/ +/***********************************************************************************************************************/ + + /* This method reads the menu's status from the session */ + private function getMenuSession() { + if (! array_key_exists( 'forums_menu', $_SESSION[game::sessName()] )) { + $_SESSION[game::sessName()]['forums_menu'] = array(); + } + return $_SESSION[game::sessName()]['forums_menu']; + } + + /* This method stores the menu's status */ + private function storeMenuSession( $value ) { + $_SESSION[game::sessName()]['forums_menu'] = $value; + } + + /* This method initialises the main forums session and removes old data */ + private function initMainSession() { + + if (is_null( $_SESSION[game::sessName()]['forums_data'] )) { + $_SESSION[game::sessName()]['forums_data'] = array(); + return; + } + + // Clean up old data + $now = time(); + $nSession = array(); + foreach ($_SESSION[game::sessName()]['forums_data'] as $key => $data) { + if (!is_array( $data )) { + continue; + } + if ($data['ts'] - $now < self::SESSION_TIMEOUT) { + $nSession[$key] = $data; + } + } + $_SESSION[game::sessName()]['forums_data'] = $nSession; + } + + /* This method is used by specific handlers to read from the session */ + public function getSessionData($key) { + $this->initMainSession(); + if (is_array($_SESSION[game::sessName()]['forums_data'][$key]) + && array_key_exists( 'data', $_SESSION[game::sessName()]['forums_data'][$key])) { + $_SESSION[game::sessName()]['forums_data'][$key]['ts'] = time(); + return $_SESSION[game::sessName()]['forums_data'][$key]['data']; + } + return null; + } + + /* This method is used by specific handlers to write into the session */ + public function setSessionData($key, $data) { + $this->initMainSession(); + $_SESSION[game::sessName()]['forums_data'][$key] = array( + "ts" => time(), + "data" => $data + ); + } + + +/***********************************************************************************************************************/ +/** MENU HANDLER *******************************************************************************************************/ +/***********************************************************************************************************************/ + + /* Returns a boolean indicating whether the menu should be displayed */ + private function getMenuVisibility() { + if (! array_key_exists( 'display_forums_menu', $_SESSION[game::sessName()] )) { + $_SESSION[game::sessName()]['display_forums_menu'] = true; + } + return $_SESSION[game::sessName()]['display_forums_menu']; + } + + /* Sets the value for the menu's visibility */ + private function setMenuVisibility( $value ) { + $_SESSION[game::sessName()]['display_forums_menu'] = (boolean) $value; + } + + /* This method generates the output for a menu entry */ + private function makeMenuEntry( &$output, $command, $isLeaf, $isOpen, + $displayType, $text, $id = '', $unread = 0 ) { + + // The first line indicates the type of menu entry, as well as the display type, + // amount of unread topics and, if the entry is a submenu, whether it's open or not + $txt = $isLeaf ? "L" : "N"; + $txt .= "#$displayType#$unread"; + if (! $isLeaf) { + $txt .= "#" . ($isOpen ? "1" : "0") . "#$id"; + } + array_push( $output, $txt ); + + // The two next lines contain the command and text / command ID + array_push( $output, $command ); + array_push( $output, $text ); + } + + /* This method outputs the "separator" indicator */ + private function makeMenuSeparator( &$output ) { + array_push( $output, "S" ); + } + + /* This method outputs the "end of menu" indicator */ + private function makeEndOfMenu( &$output ) { + array_push( $output, "E" ); + } + + /* This method generates the menu's data */ + private function dumpMenu( &$output, $structure ) { + + // Handle the root of the tree + if ($structure instanceof fl_container && $structure->getId() == '/') { + // This is the root node + $this->makeMenuEntry( &$output, "V#C#/", false, true, "C", "overview", '/' ); + + // Add "Latest messages" entry + $this->makeMenuEntry( &$output, "L#C#/", true, false, "C", "latest" ); + // Add the "Search forums" entry + $this->makeMenuEntry( &$output, "SF", true, false, "C", "search" ); + + // Add the moderation tools submenu + // FIXME + + // Add the submenus + $this->makeMenuSeparator( &$output ); + foreach ($structure->getCategories() as $cat) { + $this->dumpMenu( &$output, $cat, $menuData ); + } + + $this->makeEndOfMenu( &$output ); + return; + } + + // Handle containers + if ($structure instanceof fl_container) { + $id = $structure->getId(); + $menuData = $this->getMenuSession(); + + // Create the node + $this->makeMenuEntry( &$output, "V#C#$id", false, $menuData[$id], + "T", $structure->getTitle(), $id, $structure->getUnread()); + + // Return if the menu's closed + if (! $menuData[$id]) { + return; + } + + // Add "Latest messages" entry + $this->makeMenuEntry( &$output, "L#C#$id", true, false, "C", "latest" ); + + // Special stuff to be added here + // FIXME + + // Add the submenus + $catList = $structure->getCategories(); + if (count($catList)) { + $this->makeMenuSeparator( &$output ); + foreach ($catList as $cat) { + $this->dumpMenu( &$output, $cat, $menuData ); + } + } + + $this->makeEndOfMenu( &$output ); + return; + } + + // Standard categories + if ($structure instanceof fl_category) { + $id = $structure->getId(); + $menuData = $this->getMenuSession(); + + // Create the node + $this->makeMenuEntry( &$output, "V#C#$id", false, $menuData[$id], + "T", $structure->getTitle(), $id, $structure->getUnread()); + + // Return if the menu's closed + if (! $menuData[$id]) { + return; + } + + // Add "Latest messages" entry + $this->makeMenuEntry( &$output, "L#C#$id", true, false, "C", "latest" ); + + // Special stuff to be added here + // FIXME + + // Add the forums + $fList = $structure->getForums(); + if (count($fList)) { + $this->makeMenuSeparator( &$output ); + foreach ($fList as $f) { + if ( ! $f->canView() || $f->isDeleted() ) { + continue; + } + $this->makeMenuEntry( &$output, "V#F#" . $f->getId(), + true, false, "T", $f->getTitle(), '', $f->getUnread() ); + } + } + + $this->makeEndOfMenu( &$output ); + return; + } + } + + /* AJAX method that shows the menu */ + public function showMenu() { + $this->setMenuVisibility(true); + return $this->getMenu(); + } + + /* AJAX method that hides the menu */ + public function hideMenu() { + $this->setMenuVisibility(false); + } + + /* AJAX method that returns the forums' menu */ + public function getMenu($previousMD5 = null) { + $pid = $_SESSION[game::sessName()]['player']; + $this->loadLibraries(); + + // Get the forums' structure + $structure = input::$game->action('getForums', $pid); + + // Dump the whole thing + $result = array(); + $this->dumpMenu( &$result, $structure ); + + // Check the MD5 sum + $md5 = md5(serialize($result)); + if ($md5 !== $previousMD5) { + array_unshift($result, $md5); + return join("\n", $result); + } + + return "-"; + } + + /* AJAX method that opens a path in the menu */ + public function menuOpen($toID) { + $intID = (int) $toID; + if ( in_array($toID, array('G', 'U', 'MT')) || (string) $intID == $toID ) { + if ( (string) $intID == $toID ) { + $toID = (string) $intID; + } + $menuData = $this->getMenuSession(); + $menuData[$toID] = true; + $this->storeMenuSession($menuData); + } + + return $this->getMenu(); + } + + /* AJAX method that closes a path in the menu */ + public function menuClose($toID) { + $intID = (int) $toID; + if ( in_array($toID, array('G', 'U', 'MT')) || (string) $intID == $toID ) { + if ( (string) $intID == $toID ) { + $toID = (string) $intID; + } + $menuData = $this->getMenuSession(); + $menuData[$toID] = false; + $this->storeMenuSession($menuData); + } + + return $this->getMenu(); + } + + +/***********************************************************************************************************************/ +/** CATEGORY / FORUM VIEW **********************************************************************************************/ +/***********************************************************************************************************************/ + + /* AJAX method used to refresh a view page */ + public function getView($whichView, $md5) { + $handler = input::$game->getLib('beta5/forums/view'); + $pid = $_SESSION[game::sessName()]['player']; + $handler->call('forumCommand', $pid, $whichView); + return $handler->call('getData', $md5); + } + + /* AJAX method that marks all of a category's forums as read */ + public function categoryRead($category, $whichView) { + $handler = input::$game->getLib('beta5/forums/view'); + $pid = $_SESSION[game::sessName()]['player']; + $handler->call('forumCommand', $pid, $whichView); + $handler->call('categoryRead', $category); + return $handler->call('getData', $md5); + } + + /* AJAX method that set forums options */ + public function forumOptions($forum, $toAll, $perPage, $viewDeleted, $md5) { + $forum = (int) $forum; + $toAll = ($toAll == '1'); + $perPage = ($perPage > 0 && $perPage % 10 == 0 && $perPage / 10 < 6) ? (int)$perPage : 20; + $viewDeleted = $viewDeleted; + $pid = $_SESSION[game::sessName()]['player']; + + $this->loadLibraries(); + $this->forums->call('setOptions', $pid, $toAll ? -1 : $forum, $perPage, $viewDeleted); + + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + return $handler->call('getData', $md5); + } + + /* AJAX method that set forums options */ + public function forumRead($forum) { + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + $handler->call('forumRead'); + return $handler->call('getData'); + } + + /* AJAX method that restores a set of topics */ + public function restoreTopics($forum, $topics) { + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + $handler->call('restoreTopics', explode('#', $topics)); + return $handler->call('getData'); + } + + /* AJAX method that deletes a set of topics */ + public function deleteTopics($forum, $topics) { + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + $handler->call('deleteTopics', explode('#', $topics)); + return $handler->call('getData'); + } + + /* AJAX method that changes the sticky level for a set of topics */ + public function changeTopicsLevel($forum, $topics, $change) { + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + $handler->call('changeTopicsLevel', explode('#', $topics), $change); + return $handler->call('getData'); + } + + /* AJAX method that sets the sticky level for a set of topics */ + public function setTopicsLevel($forum, $topics, $level) { + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + $handler->call('setTopicsLevel', explode('#', $topics), $level); + return $handler->call('getData'); + } + + /* AJAX method that sets the lock for a set of topics */ + public function setTopicsLock($forum, $topics, $lock) { + $lock = ($lock == '1'); + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + $handler->call('setTopicsLock', explode('#', $topics), $lock); + return $handler->call('getData'); + } + + /* AJAX method that moves a set of topics to another forum */ + public function moveTopics($forum, $topics, $destination) { + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/view'); + $handler->call('forumCommand', $pid, "F#$forum"); + $handler->call('moveTopics', explode('#', $topics), $destination); + return $handler->call('getData'); + } + + +/***********************************************************************************************************************/ +/** TOPIC VIEW *********************************************************************************************************/ +/***********************************************************************************************************************/ + + /* AJAX method that returns a post's contents */ + public function loadPostContents($topic, $md5, $postId) { + $pid = $_SESSION[game::sessName()]['player']; + $handler = input::$game->getLib('beta5/forums/topic'); + $handler->call('initialize', $this); + $handler->call('forumCommand', $pid, "$topic#$md5"); + return $handler->call('getPostContents', $postId); + } + + +/***********************************************************************************************************************/ +/** PAGE HANDLER *******************************************************************************************************/ +/***********************************************************************************************************************/ + + /* The main page handler */ + public function handle($input) { + + // Get the command + $cmd = $input['cmd']; + if ($cmd == 'o' || $cmd == '') { + $cmd = 'V#C#/'; + } + + // Check the command + if ($cmd == 'SF') { + // Search forums + $args = array(); // FIXME + } elseif ($cmd == 'xx') { + // FIXME: other 'simple' commands + } else { + $rCmd = $cmd{0}; + $args = substr($cmd, 2); + if ($rCmd == 'V') { + // View command + $handler = input::$game->getLib('beta5/forums/view'); + } elseif ($rCmd == 'T') { + // View topic command + $handler = input::$game->getLib('beta5/forums/topic'); + } + } + + // Execute the command + $handler->call('initialize', $this); + list($mCurrent, $pageType, $pageData) = $handler->call( 'forumCommand', + $_SESSION[game::sessName()]['player'], $args ); + + $this->data = array( + "needData" => input::$IE, + "showMenu" => $this->getMenuVisibility(), + "menuCurrent" => $mCurrent, + "pageType" => $pageType + ); + if (input::$IE) { + $this->data['menuContents'] = ''; + $this->data['pageData'] = $pageData; + } else { + if ($this->data['showMenu']) { + $this->data['menuContents'] = $this->getMenu(); + } + $this->data['pageData'] = $handler->call('getData'); + } + + $this->output = "forums"; + } + } ?> diff -Naur beta5//scripts/site/beta5/output/comms.en.inc forums//scripts/site/beta5/output/comms.en.inc --- beta5//scripts/site/beta5/output/comms.en.inc 2011-02-05 10:09:57.294335002 +0100 +++ forums//scripts/site/beta5/output/comms.en.inc 2011-03-12 14:56:19.761300052 +0100 @@ -15,9 +15,7 @@

Forums

-

General forums

-

-
+
Help diff -Naur beta5//scripts/site/beta5/output/forums.en.inc forums//scripts/site/beta5/output/forums.en.inc --- beta5//scripts/site/beta5/output/forums.en.inc 2011-02-05 10:09:57.294335002 +0100 +++ forums//scripts/site/beta5/output/forums.en.inc 2011-02-05 10:10:02.974335002 +0100 @@ -1,5 +1,6 @@ @@ -68,5 +69,19 @@ $sp = $args['sp']; $args = $args['d']; include("forums/en/$sp.inc"); +*/ ?> +
+
##
+
+ +
+
+ +
+
 
diff -Naur beta5//scripts/ticks.php forums//scripts/ticks.php --- beta5//scripts/ticks.php 2011-02-05 10:09:57.904335002 +0100 +++ forums//scripts/ticks.php 2011-03-12 15:13:57.671300053 +0100 @@ -20,8 +20,8 @@ $__loader = array( 'log', 'classloader', 'version', 'game', 'tick', 'config', - 'db_connection', 'db_accessor', 'db_copy', 'db', - 'pcheck', 'library', 'tick_manager', + 'db_connection', 'db_accessor', 'db_copy' , 'db_query', + 'db', 'pcheck' , 'library', 'tick_manager', ); require_once("loader.inc"); diff -Naur beta5//site/index.php forums//site/index.php --- beta5//site/index.php 2011-02-05 10:09:57.164335002 +0100 +++ forums//site/index.php 2011-02-05 10:10:02.844335002 +0100 @@ -4,8 +4,8 @@ $__loader = array( 'log', 'classloader', 'version', 'game', 'tick', 'config', - 'db_connection', 'db_accessor', 'db', - 'library', 'actions', 'data_tree', + 'db_connection', 'db_accessor', 'db_query', + 'db', 'library', 'actions', 'data_tree', 'input', 'ajax', 'handler', 'engine', 'resource', 'tracking', 'session', 'account', 'prefs', 'output' diff -Naur beta5//site/static/beta5/js/pg_alliance-en.js forums//site/static/beta5/js/pg_alliance-en.js --- beta5//site/static/beta5/js/pg_alliance-en.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_alliance-en.js 2011-03-12 15:12:38.321300050 +0100 @@ -1037,54 +1037,105 @@ // FORUMS ADMINISTRATION PAGE //-------------------------------------------------- -function drawForumList() -{ +function drawForumList() { var str = ''; str += ''; str += '

Alliance Forums

 
 
'; document.getElementById('alpmain').innerHTML = str; - if (faForums.length < 30) - drawCreateForumLink(); - if (faForums.length == 0) + + if (faForums.length < 30) { + document.getElementById('crforum').innerHTML = 'Create a forum'; + } + + if (faForums.length == 0) { drawTextNoForums(); - else + } else { drawRealForumList(); + } } -function drawCreateForumLink() -{ - var str = ''; - str += 'Create a forum'; - document.getElementById('crforum').innerHTML = str; -} - -function drawTextNoForums() -{ +function drawTextNoForums() { document.getElementById('falist').innerHTML = '

No forums have been defined for the alliance.

'; } -function drawRealForumList() -{ - var i, str = ''; +function drawRealForumList() { + var i, str = '
'; + + var getFRankText = function (id) { + var r = faRanks['r' + id]; + return r == '-' ? 'Default member' : r; + } + + str += ''; + for (i = 0; i < faForums.length; i++) { + str += ''; - for (i=0;i/g, '>') + ' ('; - str += 'Edit - '; - if (i > 0) - str += 'Move up - '; - if (i < faForums.length - 1) - str += 'Move down - '; - str += 'Delete)'; - if (faForums[i].description != '') - str += '

' + faForums[i].description.replace(/&/g, '&').replace(//g, '>').replace(/\n/g,"
") + '

'; str += '
'; } @@ -1092,85 +1143,96 @@ document.getElementById('falist').innerHTML = str; } -function cheatAlert() -{ - alert('Possible cheating detected.'); -} - -function confirmDeleteForum(name) -{ - var str = 'You are about to delete the following forum:\n' + name + '\n'; - str += 'The forum\'s topics will be lost and you will not be able to recover them.\nPlease confirm.'; +function confirmDeleteForum(forum) { + var str = 'You are about to delete the following forum:\n ' + forum.name + '\n'; + if (forum.topics > 0) { + str += 'The forum contains ' + + (forum.topics > 1 ? (formatNumber(forum.topics.toString()) + ' topics') : 'a single topic') + + '.\n'; + } + str += 'Please confirm.'; return confirm(str); } -function alertMaximumFCount() -{ +function alertMaximumFCount() { alert('The alliance has reached its maximum possible count of forums.\nYou will be taken back to the list.'); } -function drawForumEditor() -{ +function drawForumEditor() { document.getElementById('falist').innerHTML = ' '; var str; - if (faEditing.id) - { + if (faEditing.id) { f = forumById(faEditing.id); str = "'" + f.name.replace(/&/g, '&').replace(//g, '>') + "' forum"; - } - else + } else { str = "Create a forum"; + } document.getElementById('fattl').innerHTML = '

' + str + '

'; - str = '
Name & descriptionUser access mode
' + + faForums[i].name.replace(/&/g, '&').replace(//g, '>') + + ' ('; + if (faForums[i].isDeleted) { + str += 'deleted by ' + faPlayers['p' + faForums[i].deletedBy] + ' at ' + + formatDate(faForums[i].deletedAt) + ' - Restore'; + } else { + str += 'Edit - '; + if (i > 0) { + str += 'Move up - '; + } + if (i < faForums.length - 1) { + str += 'Move down - '; + } + str += 'Delete'; + } + str += ")"; + + if (faForums[i].description != '' || faForums[i].users.length || faForums[i].mods.length) { + + var nbr = false; + str += '

'; + + if (faForums[i].description != '') { + str += faForums[i].description.replace(/&/g, '&').replace(//g, '>').replace(/\n/g,"
"); + nbr = true; + } + + if (faForums[i].users.length) { + str += (nbr ? '
' : '') + 'Users: '; + for (var ri in faForums[i].users) { + str += (ri > 0 ? ' - ' : '') + getFRankText(faForums[i].users[ri]); + } + nbr = true; + } + + if (faForums[i].mods.length) { + str += (nbr ? '
' : '') + 'Moderators: '; + for (var ri in faForums[i].mods) { + str += (ri > 0 ? ' - ' : '') + getFRankText(faForums[i].mods[ri]); + } + nbr = true; + } + + str += '

'; + } - str += '
Name & descriptionNew threads
'; - if (faForums[i].userPost) - str += 'Everyone'; - else - str += 'Moderators only'; + switch (faForums[i].accessMode) { + case 'L': + str += 'Full access'; + break; + case 'T': + str += 'No polls'; + break; + case 'P': + str += 'Replies only'; + break; + case 'M': + str += 'Read only'; + break; + } str += '
'; - str += ''; + } + + str += '' + + '' + + '' + + '' + + '
Forum name:'; + str = '' + + '' + + ''; - str += ''; - str += ''; - str += ''; - str += ''; - str += ''; - str += '
Forum name:' + + '
User access mode:'; + + var aModes = { + M: 'Read only', + P: 'Replies only', + T: 'Topic creation', + L: 'Complete access' + }; + for (var i in aModes) { + str += ' '; + } str += '
New threads:'; - str += '
Description:
'; - str += ' 

Forum access

(Loading access list data ...)

'; + if (!faEditing.id) { + str += '
Initial position: 
Description:
' + + ' 

Forum access

 
'; document.getElementById('falist').innerHTML = str; + + document.getElementById('fname').value = faEditing.name; + document.getElementById('fdesc').value = faEditing.description; document.getElementById('feok').disabled = true; - if (!faEditing.id) + if (!faEditing.id) { updateFPosSelector(); + } + drawFAccessManager(); } -function updateFPosSelector() -{ - var i, str = ''; document.getElementById('faeipos').innerHTML = str; } -function drawFAccessManager() -{ +function drawFAccessManager() { var lnp = new Array(), lmd = new Array(), lrd = new Array(), ml, i, sc=0; for (i=0;i 0 ? "\n" : "") + lines.shift(); + } + + return this; + }; + + this.copyFrom = function (ori) { + this.id = ori.id; + this.isDeleted = ori.isDeleted; + this.deletedBy = ori.deletedBy; + this.deletedAt = ori.deletedAt; + this.accessMode = ori.accessMode; + this.topics = ori.topics; + this.name = ori.name; + this.description = ori.description + + // Users + for (var i in ori.users) { + this.users.push(ori.users[i]); + } + + // Moderators + for (var i in ori.mods) { + this.mods.push(ori.users[i]); + } + + return this; + }; } -function ForumACL(id,priv,name) -{ + +function ForumACL(id, priv, name) { this.id = id; this.priv = priv; this.name = name; this.selected = false; } + function forumsReceived(data) { if (amPage != 'FAdmin') { return; } - faForums = new Array(); if (data.indexOf("ERR#") == 0) { - alertForum(parseInt((data.split('#'))[1], 10)); - puTimer = setTimeout('x_getForums(forumsReceived)', 180000); - return; - } else if (data != "") { - parseForumList(data); + var lines = data.split('\n'); + var el = lines.shift(); + data = lines.join('\n'); + + alertForum(parseInt((el.split('#'))[1], 10)); } + parseForumList(data); if (!faEditing) { drawForumList(); @@ -1292,15 +1354,19 @@ alertMaximumFCount(); forumEditCancel(); } else if (!faEditing.id) { - if (faNewPos != -1 && !forumById(faNewPos)) + if (faNewPos != -1 && !forumById(faNewPos)) { faNewPos = -1; + } updateFPosSelector(); } else if (faEditing.id) { - var f = forumById(faEditing.id); - if (!f) { + var f = forumById(faEditing.id); + if (!f || f.isDeleted) { alertForumDeleted(); forumEditCancel(); - } else if (faOriginal.name != f.name || faOriginal.userPost != f.userPost || faOriginal.description != f.description) { + } else if (faOriginal.name != f.name + || faOriginal.accessMode != f.accessMode + || faOriginal.description != f.description) { + alertForumChanged(); updateFEditor(); } else { @@ -1310,111 +1376,119 @@ puTimer = setTimeout('x_getForums(forumsReceived)', 180000); } -function parseForumList(data) -{ - var dl = data.split('\n'); - var st = 0, i = 0, cf = 0; - var a; - while (i b.order ? 1 : -1)')); + i = lines.shift(); + faAdmins = (i == '') ? (new Array()) : i.split('#'); + + var pCount = parseInt(lines.shift(), 10); + faPlayers = { }; + for (i = 0; i < pCount; i ++) { + var rLine = lines.shift().split('#'); + faPlayers['p' + rLine.shift()] = rLine.join('#'); + } } -function moveForum(id,up) -{ + +function moveForum(id, up) { x_moveForum(id, up ? 1 : 0, forumsReceived); } -function forumById(id) -{ - var i; - for (i=0;i b.name.toLowerCase() ? 1 : -1')); + faAccess.push(new ForumACL(rId, pr, rn)); } - if (faEditing.id) + faAccess.sort(new Function('a','b','return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1')); + + if (faEditing.id) { faOriACL = makeForumACLString(); - drawFAccessManager(); - updateFEditor(); + } } + function setFAccessLevel(level) { var i,cc=0; @@ -1432,33 +1506,36 @@ } } -function updateFEditor() -{ - var ok, i; +function updateFEditor() { + var ok, i; ok = (faEditing.name.length >= 4); - if (faEditing.id) + if (faEditing.id) { ok = ok && ( faEditing.name != faOriginal.name || faEditing.description != faOriginal.description - || faEditing.userPost != faOriginal.userPost || faOriACL != makeForumACLString() + || faEditing.accessMode != faOriginal.accessMode || faOriACL != makeForumACLString() ); + } document.getElementById('feok').disabled = !ok; } -function makeForumACLString() -{ - var a = new Array(), i; - for (i=0;i topic' + (tot > 1 ? 's' : ''); - if (n == 0) - return str; - str += ' (' + formatNumber(n) + ' unread)'; - return str; +function makeTopicsText(tot, n) { + if (tot == 0) { + return "empty forum"; + } + + var str = '' + formatNumber(tot.toString()) + ' topic' + (tot > 1 ? 's' : ''); + if (n == 0) { + return str; + } + str += ' (' + formatNumber(n.toString()) + ' unread)'; + return str; } diff -Naur beta5//site/static/beta5/js/pg_comms.js forums//site/static/beta5/js/pg_comms.js --- beta5//site/static/beta5/js/pg_comms.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_comms.js 2011-03-12 15:09:43.961300049 +0100 @@ -12,20 +12,40 @@ this.name = name; } -function Category(id, type, name) -{ - this.id = id; - this.type = type; - this.name = name; - this.forums = new Array(); -} -function Forum(id, nTopics, nUnread, name) -{ - this.id = id; - this.nTopics = nTopics; - this.nUnread = nUnread; - this.name = name; + +function ForumsEntity(inputData) { + var iLine = inputData.shift().split('#'); + var nElements; + + this.type = iLine.shift(); + this.id = iLine.shift(); + nElements = parseInt(iLine.shift(), 10); + this.topics = parseInt(iLine.shift(), 10); + this.unread = parseInt(iLine.shift(), 10); + this.name = inputData.shift(); + this.contents = new Array(); + this.output = function (depth) { + var str = ''; + + if (this.id != '/') { + for (var i = 0; i < depth; i++) { + str += '      '; + } + str += '' + this.name + ': ' + + makeTopicsText(this.topics, this.unread); + } + + for (var i = 0; i < this.contents.length; i ++) { + str += '
' + this.contents[i].output(depth + 1); + } + + return str; + }; + + for (var i = 0; i < nElements; i ++) { + this.contents.push( new ForumsEntity(inputData) ); + } } @@ -35,110 +55,55 @@ } -function commsDataReceived(data) -{ - var i, l = data.split('\n'); - var a = l.shift().split('#'); - var nCustom = parseInt(a[0],10), nGenCats = parseInt(a[1],10), nAForums = parseInt(a[2],10); +function commsDataReceived(data) { + var i, a, nCustom; + var l = data.split('\n'); // Default folders dFolders = new Array(); - for (i=0;i<3;i++) - { + for (i=0;i<3;i++) { a = l.shift().split('#'); dFolders.push(new Folder('', a[0], a[1], '')); } // Custom folders + nCustom = parseInt(l.shift(), 10); cFolders = new Array(); - for (i=0;i' + cFolders[i].name + ': ' + makeMessagesText(cFolders[i].tMsg, cFolders[i].nMsg); + for (i=0;i' + cFolders[i].name + ': ' + + makeMessagesText(cFolders[i].tMsg, cFolders[i].nMsg); a.push(s); } document.getElementById('cflist').innerHTML = a.join('
') + '
'; } - // General forums - a = new Array(); - for (i=0;i' + name + ''; - for (j=0;j' + forums[j].name + ': '; - s += makeTopicsText(forums[j].nTopics, forums[j].nUnread); - } - a.push(s); - }} - document.getElementById('gforums').innerHTML = a.join('

'); - - // Alliance forums - if (aForums.length == 0) - document.getElementById('aforums').innerHTML = ' '; - else - { - a = new Array(); - for (j=0;j' + aForums[j].name + ': '; - s += makeTopicsText(aForums[j].nTopics, aForums[j].nUnread); - a.push(s); - } - document.getElementById('aforums').innerHTML = '

' + allianceForums + '

' + a.join('
') + '

'; - } + // Forums + document.getElementById('forums').innerHTML = '

' + forums.output(-1) + '

'; } diff -Naur beta5//site/static/beta5/js/pg_forums-en.js forums//site/static/beta5/js/pg_forums-en.js --- beta5//site/static/beta5/js/pg_forums-en.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_forums-en.js 2011-02-05 10:10:02.214335002 +0100 @@ -1,53 +1,98 @@ -function confirmDelete() -{ - var i = countSelected(); - if (i == 0) - { - alert('Please select the topic(s) you want to delete.'); - return false; - } - return confirm('Please confirm you want to delete ' + (i > 1 ? ('these ' + i + ' topics') : 'this topic') + '.'); -} - -function confirmSticky() -{ - var i = countSelected(); - if (i == 0) - { - alert('Please select the topic(s) you want to switch to/from sticky.'); - return false; - } - return confirm('Please confirm you want to switch ' + (i > 1 ? ('these ' + i + ' topics') : 'this topic') + ' to/from sticky.'); -} - -function confirmMove() -{ - var i = countSelected(); - if (i == 0) - { - alert('Please select the topic(s) you want to move.'); - return false; - } - - var e = document.getElementById('mdest'); - if (e.options[e.selectedIndex].value == '') - { - alert('Please select the forum to which the topic'+(i>1?'s':'')+' must be moved.'); - return false; - } - - return confirm( - 'Please confirm you want to move the selected topic' + (i>1?'s':'') + '\nto the "' - + e.options[e.selectedIndex].text + '" forum.' - ); -} - -function confirmDTopic() -{ - return confirm('Deleting this post will delete the whole topic. Please confirm.'); -} - -function confirmDPost() -{ - return confirm('Please confirm you want to delete this post.'); -} +/* Text for menu commands */ +MenuItem.commandText = { + overview: "Overview", + latest: "Latest messages", + search: "Search forums" +}; + +/* Overview pseudo-category title & description */ +CategoryView.ovTitle = "Forums overview"; +CategoryView.ovDescription = "This page gives you a global view of all of the forums you have access to."; + +/* Text for layout-level commands */ +ForumsLayout.menuText = ['S','h','o','w',' ','m','e','n','u']; +ForumsLayout.hideMenu = "Hide"; +ForumsLayout.menuTitle = "Forums"; + +/* Text for the category view */ +CategoryView.empty = 'There are no forums in this category.'; +CategoryView.headers = { + name: 'Forum name', + topics: 'Topics', + posts: 'Posts', + lastPost: 'Last modification' +}; +CategoryView.forumIcon = { + read: "This forums's topics have been read", + unread: "Some topics in this forum haven't been read" +}; +CategoryView.deletedAt = "Forum deleted at "; +CategoryView.by = " by "; +CategoryView.noPosts = 'Empty forum'; +CategoryView.markRead = 'Mark forums as read'; + +/* Text for the forum view */ +ForumView.markRead = 'Mark topics as read'; +ForumView.adminsHdr = 'Administrators'; +ForumView.modsHdr = 'Moderators'; +ForumView.usersHdr = 'Users'; +ForumView.empty = 'There are no topics in this forum.'; +ForumView.details = ' More details ... '; +ForumView.showDetails = 'Show details'; +ForumView.hideDetails = 'Hide details'; +ForumView.displayOptions = 'Display options'; +ForumView.newTopic = 'New topic'; +ForumView.modTools = 'Moderation tools'; +ForumView.previousPage = 'Previous page'; +ForumView.nextPage = 'Next page'; +ForumView.pageSelHdr = 'Jump to page '; +ForumView.headers = { + topic: 'Topic', + replies: 'Replies', + fPost: 'First post', + lPost: 'Last update' +}; +ForumView.movedTo = 'Moved to '; +ForumView.applyTo = 'Apply to '; +ForumView.thisForum = 'this forum'; +ForumView.allForums = 'all forums'; +ForumView.perPage = 'Topics / page:'; +ForumView.ok = 'Ok'; +ForumView.cancel = 'Cancel'; +ForumView.displayDeleted = 'Display deleted topics:'; +ForumView.deletedAt = 'Topic deleted at '; +ForumView.hideModTools = 'Hide moderation tools'; +ForumView.pleaseSelect = 'Please select at least one topic.'; +ForumView.deletedTopics = 'Deleted topics: '; +ForumView.restoreTopics = 'restore'; +ForumView.selectedTopics = 'Selected topics: '; +ForumView.deleteTopics = 'delete'; +ForumView.stickyLevel = 'sticky level: '; +ForumView.decreaseStickyLevel = 'decrease'; +ForumView.increaseStickyLevel = 'increase'; +ForumView.setTo = 'set to '; +ForumView.normalPost = 'not sticky'; +ForumView.lock = 'lock'; +ForumView.unlock = 'unlock'; +ForumView.moveTo = 'Move topics to '; + + +/* Topic view: topic not found */ +TopicView.notFound = { + title: "Topic not found", + text: "The topic you were looking for is unavailable, either because it doesn't exist anymore or " + + "because you don't have access to the forum it is in." +}; +/* Topic view: deleted topic */ +TopicView.deleted = { + header: "This topic was deleted at ", + by: " by " +}; +/* Topic view: show / hide post contents */ +TopicView.close = "Hide post contents"; +TopicView.open = "Show post contents"; +/* Topic view, misc text */ +TopicView.posted = "Posted "; +TopicView.loading = "Loading, please wait ..."; +TopicView.loadError = "An error occurred while loading this post :-("; +TopicView.edited = "Edited at "; diff -Naur beta5//site/static/beta5/js/pg_forums.js forums//site/static/beta5/js/pg_forums.js --- beta5//site/static/beta5/js/pg_forums.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_forums.js 2011-02-05 10:10:02.204335002 +0100 @@ -1,10 +1,1607 @@ -function countSelected() -{ - var n = 0, i = 0, e; - while (e = document.getElementById('msel' + i)) - { - n += e.checked ? 1 : 0; - i++; +var pageContents; +var useGIFs; + + +MenuItem = function (lines) { + + // Parse the data + var fields = lines.shift().split('#'); + + this.isNode = (fields.shift() == 'N'); + this.isCommand = (fields.shift() == 'C'); + this.unread = parseInt(fields.shift(), 10); + this.isOpen = this.isNode ? (fields.shift() == '1') : false; + this.id = this.isNode ? fields.shift() : null; + this.cmdLink = lines.shift(); + this.text = lines.shift(); + this.entries = (this.isNode && this.isOpen) ? (new Array()) : null; + + if (this.isCommand) { + this.text = MenuItem.commandText[this.text]; } - return n; -} + + // Add menu entries + while (this.isNode && this.isOpen && lines[0] != 'E') { + if (lines[0] == 'S') { + // Separators + this.entries.push(null); + lines.shift(); + } else { + // Entries + this.entries.push(new MenuItem(lines)); + } + } + if (this.isNode && this.isOpen) { + lines.shift(); + } + + this.draw = function (output, depth) { + var rd = (typeof depth == 'undefined') ? -1 : depth; + + var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle"'; + var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; + var smallCell = ''; + var str = ''; + + if (depth > 0) { + for (var i = 0; i < depth; i ++) { + str += smallCell + ' '; + } + } + + // 'Open'/'Close' command for submenus or empty cell + str += smallCell; + if (depth > -1 && this.isNode) { + if (this.isOpen) { + str += '-'; + } else { + str += '+'; + } + } else { + str += ' '; + } + str += ''; + + // Menu entry's text + str += '' + + (MenuItem.current == this.cmdLink ? '' : '') + + ((this.unread > 0 && ! (this.isNode && this.isOpen)) ? '' : '') + + this.text + + ((this.unread > 0 && ! (this.isNode && this.isOpen)) ? (' (' + this.unread + ')') : '') + + (MenuItem.current == this.cmdLink ? '' : '') + + ''; + + // Output this line + output.push(str); + + // Submenus + var sepPrev = false; + for (var i in this.entries) { + var e = this.entries[i]; + if (e) { + if (e.isNode && e.isOpen && !sepPrev) { + output.push(' '); + } + e.draw(output, rd + 1); + if (e.isNode && e.isOpen) { + output.push(' '); + sepPrev = true; + } else { + sepPrev = false; + } + } else if (!sepPrev) { + output.push(' '); + sepPrev = true; + } + } + }; +}; + + +function parseNames( lines ) { + var line, cnt; + var res = { }; + + cnt = parseInt( lines.shift(), 10 ); + for (var i = 0; i < cnt; i ++) { + line = lines.shift().split('#'); + res[line[0]] = line[1]; + } + + return res; +}; + + +TopicView = function () { + + var me = this; + var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; + var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle'; + + var pageUpdater = null; + var pageMD5 = null; + var topicId = null; + var locked = false; + var parents = []; + var posts = []; + var pOrder = { + ln: null, + lo: null, + tn: null, + to: null + }; + var opts = { + perPage: 50, + vDeleted: false, + threaded: false, + order: false, + openPosts: 1 + }; + var title; + var topicStatus; + var currentUser; + var isMod; + var canPost; + var hasPoll; + var users; + var nPages; + var cPage; + var queue; + + var parseInitialContents, displayNotFound, displayDeleted, displayTopicLayout, drawPageChanger, + displayPage, initLoaderQueue, loadPost, postLoaded; + + parseInitialContents = function (data) { + var line = data.shift(); + + if (line == 'MEH') { + displayNotFound(); + return false; + } + + if (line.indexOf('DELETED#') == 0) { + var deletedAt, nParents; + line = line.split('#'); + deletedAt = line[1]; + nParents = parseInt(line[2], 10); + displayDeleted(data, deletedAt, nParents); + return false; + } + + line = line.split('#'); + var nParents = parseInt(line[1], 10); + + title = data.shift(); + line = data.shift().split('#'); + currentUser = line.shift(); + isMod = (line.shift() == 1); + canPost = (line.shift() == 1); + hasPoll = (line.shift() == 1); + + for (var i = 0; i < nParents; i ++) { + line = data.shift().split('#'); + parents.push({ + id: line.shift(), + name: line.join('') + }); + } + + pOrder.ln = data.shift().split('#'); + pOrder.lo = data.shift().split('#'); + pOrder.tn = data.shift().split('#'); + pOrder.to = data.shift().split('#'); + + for (var i = 0; i < pOrder.ln.length; i ++) { + var p = { }; + line = data.shift().split('#'); + p.id = line.shift(); + p.depth = parseInt(line.shift(), 10); + p.author = line.shift(); + p.postedAt = line.shift(); + p.unread = (line.shift() == 1); + p.lcTime = line.shift(); + p.lcAuthor = line.shift(); + p.title = data.shift(); + p.loading = false; + posts[p.id] = p; + } + + users = parseNames( data ); + + line = data.shift().split('#'); + opts.perPage = parseInt(line.shift(), 10); + opts.vDeleted = (line.shift() == 1); + opts.threaded = (line.shift() == 1); + opts.order = (line.shift() == 1); + opts.openPosts = parseInt(line.shift(), 10); + + for (var i in posts) { + switch (opts.openPosts) { + case 0: posts[i].open = false; + break; + case 1: posts[i].open = posts[i].unread; + break; + case 2: posts[i].open = true; + break; + } + } + + initLoaderQueue(); + + var m = pOrder.ln.length % opts.perPage; + nPages = (pOrder.ln.length - m) / opts.perPage + (m ? 1 : 0); + cPage = 0; + displayTopicLayout(); + }; + + displayNotFound = function () { + document.getElementById('f-page').innerHTML = '

' + TopicView.notFound.title + '

' + + TopicView.notFound.text + '

'; + }; + + displayDeleted = function (data, deletedAt, nParents) { + var str = '
' + + '

' + data.shift() + '

'; + for (var i = 0; i < nParents; i ++) { + var l = data.shift().split('#'); + var pid = l.shift(); + + str += (i > 0 ? ' > ' : '') + '' + (pid == '/' ? ForumsLayout.menuTitle : l.join('')) + ''; + } + str += '

' + TopicView.deleted.header + '' + formatDate(deletedAt) + + '' + TopicView.deleted.by + '' + + 'some guy (FIXME)' + '.

'; + document.getElementById('f-page').innerHTML = str; + }; + + displayTopicLayout = function () { + var str = '

' + + title + '

'; + for (var i in parents) { + str += (i > 0 ? ' > ' : '') + '' + + (parents[i].id == '/' ? ForumsLayout.menuTitle : parents[i].name) + ''; + } + + str += ''; + str += drawPageChanger(); + + // FIXME: display tools + + if (hasPoll) { + // FIXME: display poll + } + + str += ''; + str += drawPageChanger(); + str += '
 
'; + + document.getElementById('f-page').innerHTML = str; + displayPage(); + }; + + drawPageChanger = function () { + if (nPages > 1) { + var st = 'border-width: 0px; margin: 0px; vertical-align: middle; '; + var str = '' + + (cPage > 0 ? '' : '') + + '<-- ' + ForumView.previousPage + (cPage > 0 ? '' : '') + + '' + + '
' + ForumView.pageSelHdr + + '
' + + ((cPage' : '') + + ForumView.nextPage + '-->' + ((cPage' : '') + ''; + return str; + } + return ''; + }; + + displayPage = function () { + var low = cPage * opts.perPage; + var high = Math.min(low + opts.perPage, pOrder.ln.length); + var str = '\n\n'; + var order; + + if (opts.threaded) { + order = opts.order ? pOrder.to : pOrder.tn; + } else { + order = opts.order ? pOrder.lo : pOrder.ln; + } + + for (var i = low; i < high; i ++) { + var post = posts[order[i]]; + str += '\n'; + if (opts.threaded && post.depth > 0) { + var sz = post.depth * 10; + str += ' '; + } + str += ' '; + } + document.getElementById('topics-display').innerHTML = str + '\n'; + + displayPosts(); + }; + + displayPosts = function () { + for (var i in posts) { + var post = posts[i]; + var el = document.getElementById("tp-post-" + post.id); + if (el) { + el.innerHTML = displayPost(post); + } + } + }; + + displayPost = function (post) { + var color = post.unread ? '#FFFFFF' : '#5F5F5F'; + var margin = post.open ? (post.depth == 0 ? '0px 0px 10px 0px' : '10px 0px') : '0px'; + var onClick = ' onClick="pageContents.togglePost(' + post.id + ')"'; + var str = '' + + ''; + + if (post.open) { + str += ''; + if (post.lcTime != post.postedAt) { + str += ''; + } + } + + return str + '
' + (post.open ? TopicView.close : TopicView.open) + '' + + post.title + '
' + TopicView.posted + '' + formatDate(post.postedAt) + '' + + CategoryView.by + '' + users[post.author] + '
' + + (post.loaded ? post.contents : + ('

' + TopicView.loading + '

')) + + '
' + TopicView.edited + + formatDate(post.lcTime) + CategoryView.by + '' + + users[post.lcAuthor] + '
'; + }; + + initLoaderQueue = function () { + var order; + + if (opts.threaded) { + order = opts.order ? pOrder.to : pOrder.tn; + } else { + order = opts.order ? pOrder.lo : pOrder.ln; + } + + var openPosts = [], fpPosts = [], rest = []; + for (var i in order) { + var post = posts[order[i]]; + if (post.open) { + openPosts.push(post); + } else if (i < opts.perPage) { + fpPosts.push(post); + } else { + rest.push(post); + } + } + + queue = openPosts.concat(fpPosts.concat(rest)); + loadPost(); + }; + + postLoaded = function (data) { + if (queue.length == 0) { + return; + } + + if (data.indexOf('-#') == 0) { + data = data.split('#'); + if (queue[0].id != data[1]) { + loadPost(); + return; + } + queue[0].loading = false; + queue[0].contents = '

' + TopicView.loadError + '

'; + } else { + data = data.split('\n'); + var l = data.shift().split('#'); + if (queue[0].id != l[1]) { + loadPost(); + return; + } + queue[0].loading = false; + queue[0].contents = data.join('\n'); + } + queue[0].loaded = true; + + var el = document.getElementById("tp-post-" + queue[0].id); + if (el) { + el.innerHTML = displayPost(queue[0]); + } + + queue.shift(); + loadPost(); + }; + + loadPost = function () { + if (queue.length == 0 || queue[0].loading) { + return; + } + + queue[0].loading = true; + x_loadPostContents(topicId, pageMD5, queue[0].id, function (data) { + postLoaded(data); + }); + }; + + this.parse = function (data) { + if (data.indexOf('\n') == -1) { + // IE, first update + data = data.split('#'); + topicId = data.shift(); + pageMD5 = data.shift(); + this.update(); + return; + } + + data = data.split('\n'); + var l = data.shift().split('#'); + topicId = l.shift(); + pageMD5 = l.shift(); + if (parseInitialContents(data)) { + pageUpdater = window.setTimeout("pageContents.update()", 10000); + } + locked = false; + }; + + this.update = function () { + if (locked) { + return; + } + pageUpdater = null; + locked = true; + x_getTopic(topicId, pageMD5, function (data) { pageContents.parse(data); }); + }; + + this.togglePost = function (postId) { + var post = posts[postId]; + post.open = ! post.open; + var el = document.getElementById("tp-post-" + post.id); + if (el) { + el.innerHTML = displayPost(post); + } + }; + + this.setFieldValues = function () { }; +}; + + +ForumView = function () { + var me = this; + + var pageUpdater = null; + var locked = false; + var pageMD5 = null; + var viewId = null; + var players = []; + + var nPages = 0; + var cPage = -1; + var perPage = 10; + + var topics = []; + var isMod = false; + var canPost = false; + var vDeleted = false; + var unread = false; + var admins = null; + var mods = null; + var users = null; + var hasMoveTargets = false; + var moveTargets = {}; + var description = ''; + var title = ''; + var parents = null; + + var showDetails = false; + var showModTools = false; + var modSelAll = false; + var showOptions = false; + var options = { }; + var modTools = { }; + var selTopicsStr = ''; + + var mainParser, parseTopic, displayContents, drawControls, drawModTools, setFieldValues, getSelectedTopics; + + mainParser = function (lines) { + var line = lines.shift().split('#'); + var nt, ndl, npe, na, nm, nu, nmt; + + forumId = line.shift(); + nt = parseInt(line.shift(), 10); + unread = (line.shift() == '1'); + isMod = (line.shift() == '1'); + canPost = (line.shift() == '1'); + perPage = parseInt(line.shift(), 10); + vDeleted = (line.shift() == '1'); + ndl = parseInt(line.shift(), 10); + npe = parseInt(line.shift(), 10); + na = parseInt(line.shift(), 10); + nm = parseInt(line.shift(), 10); + nu = parseInt(line.shift(), 10); + nmt = isMod ? parseInt(line.shift(), 10) : 0; + hasMoveTargets = (nmt > 0); + title = lines.shift(); + + var dc = new Array(); + for (var i = 0; i < ndl; i ++) { + dc.push(lines.shift()); + } + description = dc.join('
'); + + parents = new Array(); + for (var i = 0; i < npe; i ++) { + line = lines.shift().split('#'); + parents.push({ + id: line.shift(), + name: line.join('#') + }); + } + + admins = new Array(); + for (var i = 0; i < na; i ++) { + admins.push(lines.shift()); + } + mods = new Array(); + for (var i = 0; i < nm; i ++) { + mods.push(lines.shift()); + } + users = new Array(); + for (var i = 0; i < nu; i ++) { + users.push(lines.shift()); + } + + moveTargets = {}; + for (var i = 0; i < nmt; i ++) { + line = lines.shift().split('#'); + var ln = line.shift(); + moveTargets[ln] = line.join('#'); + } + + selTopicsStr = ''; + for (var i in topics) { + if (topics[i].selected) { + selTopicsStr += '#' + topics[i].id + '#'; + } + } + + topics = new Array(); + for (var i = 0; i < nt; i ++) { + topics.push(parseTopic(lines)); + } + + if (nt == 0) { + cPage = -1; + } else { + nPages = Math.ceil(nt / perPage); + if (cPage < 0) { + cPage = 0; + } else if (cPage >= nPages) { + cPage = nPages - 1; + } + } + }; + + parseTopic = function (lines) { + var topic = { }; + var line = lines.shift().split('#'); + + topic.id = line.shift(); + topic.movedTo = line.shift(); + topic.unread = (line.shift() == '1'); + topic.sticky = parseInt(line.shift(), 10); + topic.nReplies = line.shift(); + topic.fpTime = line.shift(); + topic.fpAuthor = line.shift(); + topic.lcTime = line.shift(); + topic.lcAuthor = line.shift(); + topic.isLocked = (line.shift() == '1'); + topic.hasPoll = (line.shift() == '1'); + topic.isDeleted = (line.shift() == '1'); + topic.deletedAt = line.shift(); + topic.deletedBy = line.shift(); + topic.title = lines.shift(); + topic.mToForum = (topic.movedTo == '') ? null : lines.shift(); + topic.selected = (selTopicsStr.indexOf( '#' + topic.id + '#') != -1); + + return topic; + }; + + getSelectedTopics = function (deleted) { + var rv = new Array(); + for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { + if (topics[i].movedTo == '' && topics[i].selected && topics[i].isDeleted == deleted) { + rv.push(topics[i].id); + } + } + return rv; + }; + + drawControls = function () { + // Main controls + var str = '' + + '' + + ''; + + // Page control + if (topics.length && nPages > 1) { + var st = 'border-width: 0px; margin: 0px; vertical-align: middle; '; + str += ''; + } + + str += '
' + + '
' + + '' + + ForumView.displayOptions + ''; + + if (canPost) { + str += ' - ' + ForumView.newTopic + ''; + } + if (isMod) { + str += ' - ' + + ForumView.modTools + ''; + } + + // Display options + str += '
' + + '' + ForumView.displayOptions + '
' + + '' + + ''; + if (isMod) { + str += ''; + } + str += '
' + ForumView.applyTo + + '' + +' ' + + ' ' + + '' + + '' + ForumView.ok + + '
' + + ForumView.perPage + '' + + '' + + '' + ForumView.cancel + + '
'; + + // Moderation tools + if (isMod) { + str += '
' + + '' + ForumView.modTools + '' + + '
' + + '
'; + } + str += '
' + + (cPage > 0 ? '' : '') + + '<-- ' + ForumView.previousPage + (cPage > 0 ? '' : '') + + '' + + '
' + ForumView.pageSelHdr + + '
' + + ((cPage' : '') + + ForumView.nextPage + '-->' + ((cPage' : '') + '
'; + return str; + }; + + displayContents = function () { + // Draw the header + var str = '
'; + var hasDetails = (description != '' || admins.length || mods.length || users.length); + if (unread || hasDetails) { + // "Mark as read" link + str += '
'; + if (hasDetails) { + str += '' + + (showDetails ? ForumView.hideDetails : ForumView.showDetails) + + '' + (unread ? ' - ' : ''); + } + if (unread) { + str += '' + + ForumView.markRead + ''; + } + str += '
'; + } + // Title and parent categories + str += '

' + title + '

'; + for (var i in parents) { + str += (i > 0 ? ' > ' : '') + '' + + (parents[i].id == '/' ? ForumsLayout.menuTitle : parents[i].name) + + ''; + } + str += '
'; + // Details (description & ACL) + if (hasDetails) { + var needsBR = false; + str += '
' + ForumView.details + ''; + + if (description != '') { + str += description; + needsBR = true; + } + + if (admins.length) { + str += (needsBR ? '
' : '') + '' + ForumView.adminsHdr + ': ' + + admins.join(' - '); + needsBR = true; + } + + if (mods.length) { + str += (needsBR ? '
' : '') + '' + ForumView.modsHdr + ': ' + + mods.join(' - '); + needsBR = true; + } + + if (users.length) { + str += (needsBR ? '
' : '') + '' + ForumView.usersHdr + ': ' + + users.join(' - '); + } + + str += '
'; + } + str += ''; + + str += drawControls(); + if (topics.length) { + // Topics list + str += ''; + } + str += '
' + + ''; + + for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { + str += '\n'; + if (topics[i].isDeleted) { + str += ''; + } else { + str += '' + + '' + + ''; + } + str += ''; + } + + str += '
' + + '' + + ' ' + ForumView.headers.topic + + '' + ForumView.headers.replies + + '' + ForumView.headers.fPost + + '' + ForumView.headers.lPost + '
' + + '' + + '' + + '' + + ((topics[i].isLocked || topics[i].hasPoll) ? '
' : '') + + (topics[i].isLocked ? ('') : '') + + (topics[i].hasPoll ? ('') : '') + + ((topics[i].isLocked || topics[i].hasPoll) ? '
' : '') + '
' + + (topics[i].isDeleted ? '' : ('')) + + topics[i].title + (topics[i].isDeleted ? '' : '') + + (topics[i].movedTo != '' ? ('
 -> ' + ForumView.movedTo + + '' + + topics[i].mToForum + '') : '') + + '
' + + ForumView.deletedAt + formatDate(topics[i].deletedAt) + CategoryView.by + + '' + players[topics[i].deletedBy] + '' + + formatNumber(topics[i].nReplies) + '' + + formatDate(topics[i].fpTime) + CategoryView.by + '' + + players[topics[i].fpAuthor] + '' + + formatDate(topics[i].lcTime) + CategoryView.by + '' + + players[topics[i].lcAuthor] + '
'; + } else { + str += '
' + + ForumView.empty + '
'; + + document.getElementById('f-page').innerHTML = str; + setFieldValues(); + }; + + hideOptions = function () { + var e = document.getElementById('f-options'); + if (!e) { + return; + } + showOptions = false; + e.style.display = 'none'; + document.getElementById('f-std-ctrl').style.display = 'block'; + }; + + drawModTools = function () { + var cnt = 0, sel = 0, dSel = 0, nSel = 0, hasLocked = false, hasUnlocked = false, + minStL = 11, maxStL = -1; + if (cPage != -1) { + for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { + if (topics[i].movedTo != '') { + continue; + } + document.getElementById('mt-sel-' + topics[i].id).checked = topics[i].selected; + if (topics[i].selected) { + if (topics[i].isDeleted) { + dSel ++; + } else { + nSel ++; + hasLocked = hasLocked || topics[i].isLocked; + hasUnlocked = hasUnlocked || ! topics[i].isLocked; + } + minStL = (minStL > topics[i].sticky) ? topics[i].sticky : minStL; + maxStL = (maxStL < topics[i].sticky) ? topics[i].sticky : maxStL; + sel ++; + } + cnt ++; + } + document.getElementById('mt-sel-all').checked = modSelAll = (sel == cnt); + } + + var str = '' + ForumView.hideModTools + + '
'; + if (!(dSel || nSel)) { + str += '
' + ForumView.pleaseSelect; + } else { + if (nSel) { + str += '
' + ForumView.selectedTopics + + '' + + ForumView.deleteTopics + ' - ' + ForumView.stickyLevel; + + // Sticky level management + if (maxStL < 10) { + str += '' + + ForumView.increaseStickyLevel + ' / '; + } + if (minStL > 0) { + str += '' + + ForumView.decreaseStickyLevel + ' / '; + } + + str += ForumView.setTo + ''; + + // Lock / Unlock + if (hasLocked) { + str += ' - ' + + ForumView.unlock + ''; + } + if (hasUnlocked) { + str += ' - ' + + ForumView.lock + ''; + } + + // Move to + if (hasMoveTargets) { + str += '
' + ForumView.moveTo + ''; + } + } + if (dSel) { + str += '
' + ForumView.deletedTopics + + '' + + ForumView.restoreTopics + ''; + } + } + + document.getElementById('f-mod-tools-contents').innerHTML = str; + }; + + setFieldValues = function () { + var e = document.getElementById('f-options'); + if (!e) { + return; + } + + // Options block + document.getElementById('f-apply-to-this').checked = (options.toAll == 0); + document.getElementById('f-apply-to-all').checked = (options.toAll == 1); + document.getElementById('f-per-page').selectedIndex = (options.pp / 10) - 1; + + // Moderation tools + if (! isMod) { + return; + } + document.getElementById('f-disp-del').checked = (options.dd == 1); + + if (cPage != -1) { + for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { + document.getElementById('mt-sel-' + topics[i].id).style.display = + ((showModTools && topics[i].movedTo == '') ? 'block' : 'none'); + } + document.getElementById('mt-sel-all').style.display = (showModTools ? 'block' : 'none'); + } + + drawModTools(); + }; + + // Page update: parser & updater + this.parse = function (data) { + if (data != '-') { + if (data.indexOf('\n') == -1) { + // IE, first update + viewId = data; + this.update(); + return; + } + + var lines = data.split('\n'); + pageMD5 = lines.shift(); + viewId = lines.shift(); + + mainParser(lines); + players = parseNames(lines); + + displayContents(); + } + + pageUpdater = window.setTimeout("pageContents.update()", 10000); + locked = false; + }; + this.update = function () { + if (locked) { + return; + } + pageUpdater = null; + locked = true; + x_getView(viewId, pageMD5, function (data) { pageContents.parse(data); }); + }; + this.setFieldValues = function () { setFieldValues(); }; + + // Displaying details + this.toggleDetails = function () { + var e = document.getElementById('f-details'); + if (!e) { + return; + } + showDetails = ! showDetails; + document.getElementById('f-toggle-details').innerHTML + = (showDetails ? ForumView.hideDetails : ForumView.showDetails) + e.style.display = showDetails ? 'block' : 'none'; + }; + + // "Display options" box management + this.displayOptions = function () { + var e = document.getElementById('f-options'); + if (!e) { + return; + } + options = { + toAll: 0, + pp: perPage, + dd: (isMod && vDeleted) ? 1 : 0 + }; + setFieldValues(); + showOptions = true; + e.style.display = 'block'; + document.getElementById('f-std-ctrl').style.display = 'none'; + }; + this.optionsOk = function () { + hideOptions(); + if (locked) { + window.setTimeout("pageContents.optionsOk()", 250); + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + x_forumOptions(forumId, options.toAll, options.pp, options.dd, pageMD5, function (data) { + pageContents.parse(data); }); + }; + this.optionsCancel = function () { hideOptions(); }; + this.setOption = function (name, value) { options[name] = value; }; + + // Moderation tools + this.displayModTools = function () { + var e = document.getElementById('f-mod-tools'); + if (!e) { + return; + } + showModTools = true; + setFieldValues(); + e.style.display = 'block'; + document.getElementById('f-std-ctrl').style.display = 'none'; + }; + this.hideModTools = function () { + var e = document.getElementById('f-mod-tools'); + if (!e) { + return; + } + showModTools = false; + setFieldValues(); + e.style.display = 'none'; + document.getElementById('f-std-ctrl').style.display = 'block'; + }; + this.toggleTopic = function (index) { + topics[index].selected = !topics[index].selected; + drawModTools(); + }; + this.toggleSelAll = function () { + modSelAll = !modSelAll; + for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { + if (topics[i].movedTo != '') { + continue; + } + topics[i].selected = modSelAll; + } + drawModTools(); + }; + this.restoreSelected = function () { + if (locked) { + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(true); + x_restoreTopics(forumId, tlist.join('#'), function (data) { pageContents.parse(data); }); + }; + this.deleteSelected = function () { + if (locked) { + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(false); + x_deleteTopics(forumId, tlist.join('#'), function (data) { pageContents.parse(data); }); + }; + this.increaseSticky = function () { + if (locked) { + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(false); + x_changeTopicsLevel(forumId, tlist.join('#'), 1, function (data) { pageContents.parse(data); }); + }; + this.decreaseSticky = function () { + if (locked) { + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(false); + x_changeTopicsLevel(forumId, tlist.join('#'), -1, function (data) { pageContents.parse(data); }); + }; + this.setSticky = function (value) { + var e = document.getElementById('mt-set-level'); + if (e) { e.disabled = true; } + e = document.getElementById('mt-set-level'); if (e) { e.disabled = true; } + e = document.getElementById('mt-move-to'); if (e) { e.disabled = true; } + + if (locked) { + window.setTimeout("pageContents.setSticky(" + value + ")", 250); + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(false); + x_setTopicsLevel(forumId, tlist.join('#'), value, function (data) { pageContents.parse(data); }); + }; + this.lockTopics = function () { + if (locked) { + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(false); + x_setTopicsLock(forumId, tlist.join('#'), 1, function (data) { pageContents.parse(data); }); + }; + this.unlockTopics = function () { + if (locked) { + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(false); + x_setTopicsLock(forumId, tlist.join('#'), 0, function (data) { pageContents.parse(data); }); + }; + this.moveTo = function (dest) { + e = document.getElementById('mt-set-level'); if (e) { e.disabled = true; } + e = document.getElementById('mt-move-to'); if (e) { e.disabled = true; } + + if (locked) { + window.setTimeout("pageContents.moveTo(" + dest + ")", 250); + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + + var tlist = getSelectedTopics(false); + x_moveTopics(forumId, tlist.join('#'), dest, function (data) { pageContents.parse(data); }); + }; + + // Page management + this.nextPage = function () { this.jumpToPage(cPage + 1); }; + this.prevPage = function () { this.jumpToPage(cPage - 1); }; + this.jumpToPage = function (value) { + cPage = parseInt(value, 10); + displayContents(); + }; + + // Mark topics as read + this.markRead = function () { + if (locked) { + window.setTimeout("pageContents.markRead()", 250); + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + x_forumRead(forumId, function (data) { pageContents.parse(data); }); + }; +}; + + +CategoryView = function () { + + var me = this; + var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; + var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle"'; + + var pageUpdater = null; + var pageMD5 = null; + var viewId = null; + var contents = null; + var locked = false; + var players = []; + + var parseContents, parseForum, displayContents, displayForums; + + parseContents = function (lines) { + var line = lines.shift().split('#'); + var obj = {}; + var cc, dc; + + obj.id = line.shift(); + obj.isCategory = (line.shift() == 'F'); + obj.hasUnread = (line.shift() == '1'); + cc = parseInt(line.shift(), 10); + dc = parseInt(line.shift(), 10); + + if (obj.isCategory) { + obj.typeName = lines.shift(); + } + obj.title = lines.shift(); + + var da = new Array(); + for (var i = 0; i < dc; i ++) { + da.push(lines.shift()); + } + obj.description = da.join('
'); + + if (obj.id == '/') { + obj.title = CategoryView.ovTitle; + obj.description = CategoryView.ovDescription; + } + + obj.contents = new Array(); + for (var i = 0; i < cc; i ++) { + obj.contents.push( obj.isCategory ? parseForum(lines) : parseContents(lines) ); + } + + return obj; + }; + parseForum = function (lines) { + var line = lines.shift().split('#'); + var obj = { }; + var dc; + + obj.id = line.shift(); + dc = parseInt(line.shift(), 10); + obj.isDeleted = (line.shift() == '1'); + obj.deletedAt = line.shift(); + obj.deletedBy = line.shift(); + obj.topics = line.shift(); + obj.posts = line.shift(); + obj.isUnread = (line.shift() == '1'); + + if (parseInt(obj.posts, 10) > 0) { + line = lines.shift().split('#'); + obj.lastAuthor = line.shift(); + obj.lastTimestamp = line.shift(); + } + + obj.title = lines.shift(); + var desc = new Array(); + for (var i = 0; i < dc; i ++) { + desc.push(lines.shift()); + } + obj.description = desc.join('
'); + + return obj; + }; + displayContents = function (obj, depth) { + var d = (typeof depth == 'undefined') ? 0 : depth; + var mw = d * 8; + var htag = 'h' + (d + 1); + + var str = ''; + if (d > 0) { + str += ''; + } + + var rText = ''; + if (obj.isCategory) { + rText = '
' + obj.typeName; + if (obj.hasUnread) { + rText += '
' + CategoryView.markRead + ''; + } + rText += '
'; + } + + str += '' + rText + '
<' + htag + '>' + obj.title + '' + + (obj.description != '' ? ('

' + obj.description + '

') : '') + + '
' + + ' 
'; + + if (! obj.isCategory) { + for (var i in obj.contents) { + str += displayContents(obj.contents[i], d + 1); + } + } else { + str += displayForums(obj); + } + + return str; + }; + displayForums = function (obj) { + if (obj.contents.length == 0) { + return '

' + CategoryView.empty + '

'; + } + + var str = '' + + ''; + + for (var i in obj.contents) { + var forum = obj.contents[i]; + str += '' + + ''; + if (forum.isDeleted) { + str += ''; + } else { + str += ''; + } + str += ''; + } + + str += '
 ' + + CategoryView.headers.name + '' + CategoryView.headers.topics + + '' + CategoryView.headers.posts + + '' + CategoryView.headers.lastPost + + '
'
+				+ CategoryView.forumIcon[forum.isUnread ? 'unread' : 'read']
+				+ '' + forum.title + '' + + (forum.description != '' ? ('
' + forum.description) : '') + '
' + + CategoryView.deletedAt + '' + formatDate(forum.deletedAt) + '' + + CategoryView.by + '' + players[forum.deletedBy] + '' + + formatNumber(forum.topics) + + '' + + formatNumber(forum.posts) + + ''; + if (forum.posts != 0) { + str += formatDate(forum.lastTimestamp) + '
' + CategoryView.by + + '' + players[forum.lastAuthor] + ''; + } else { + str += CategoryView.noPosts; + } + str += '
'; + return str; + }; + + this.parse = function (data) { + if (data != '-') { + if (data.indexOf('\n') == -1) { + // IE, first update + viewId = data; + this.update(); + return; + } + + var lines = data.split('\n'); + pageMD5 = lines.shift(); + viewId = lines.shift(); + + contents = parseContents(lines); + players = parseNames(lines); + + document.getElementById('f-page').innerHTML = displayContents(contents); + } + + pageUpdater = window.setTimeout("pageContents.update()", 10000); + locked = false; + }; + + this.update = function () { + if (locked) { + return; + } + pageUpdater = null; + locked = true; + x_getView(viewId, pageMD5, function (data) { pageContents.parse(data); }); + }; + + this.markRead = function (id) { + if (locked) { + window.setTimeout("pageContents.markRead('" + id + "')", 250); + return; + } + locked = true; + if (pageUpdater) { + window.clearTimeout(pageUpdater); + pageUpdater = null; + } + x_categoryRead(id, viewId, function (data) { pageContents.parse(data); }); + }; + + this.setFieldValues = function () { }; +}; + + + +ForumsLayout = function () { + + var init = document.getElementById('f-params').innerHTML.split('#'); + var me = this; + + // Read the page's parameters + var menuVisible = (init.shift() == '1'); + var needData = (init.shift() == '1'); + var pageType = init.shift(); + + // Read the current menu entry + MenuItem.current = document.getElementById('f-menu-current').innerHTML; + + // Initialize other private variables here + var menuMD5 = ''; + var menuTree = null; + var menuUpdater = null; + var menuFDisabled = false; + var page = null; + + // Initialize private methods here + var parseMenuData, drawLayout, drawMenu; + parseMenuData = function(data) { + menuUpdater = null; + if (! menuVisible) { + menuFDisabled = false; + return; + } + + var lines = data.split('\n'); + var line = lines.shift(); + + if (line != '-') { + menuMD5 = line; + menuTree = new MenuItem(lines); + drawMenu(); + } + menuFDisabled = false; + menuUpdater = window.setTimeout("main.updateMenu()", 5000); + }; + drawMenu = function () { + if (! menuTree) { + menuVisible = false; + drawLayout(); + return; + } + + var tst = ' style="width: 100%; margin: 0px; padding: 0px; border-width: 0px"'; + var cst = ' style="margin: 0px; padding: 0px; border-width: 0px; vertical-align: middle"'; + var str = '' + ForumsLayout.menuTitle + '' + ForumsLayout.hideMenu + + '' + ' '; + + var x = new Array(); + menuTree.draw(x); + + str += x.join('') + ''; + document.getElementById('f-menu').innerHTML = str; + }; + drawLayout = function () { + var mContents; + if (document.getElementById('f-page')) { + mContents = document.getElementById('f-page').innerHTML; + } else { + mContents = ' '; + } + + var mainMargin; + var str = '
(loading menu)'; + mainMargin = 326; + } else { + str += ';text-align:center; width: 16px">' + + ForumsLayout.menuText.join('
') + '
'; + mainMargin = 42; + } + str += '
 
'; + document.getElementById('f-contents').innerHTML = str; + document.getElementById('f-page').innerHTML = mContents; + if (mContents != ' ') { + pageContents.setFieldValues(); + } + }; + + // Public methods + this.updateMenu = function() { + menuFDisabled = true; + x_getMenu(menuMD5, parseMenuData); + }; + this.hideMenu = function() { + if (menuFDisabled || !menuVisible) { + if (menuFDisabled) { + window.setTimeout("main.hideMenu()", 250); + } + return; + } + if (menuUpdater) { + window.clearTimeout(menuUpdater); + } + menuUpdater = menuMD5 = menuTree = null; + menuVisible = false; + x_hideMenu(function () {}); + drawLayout(); + }; + this.showMenu = function() { + if (menuFDisabled || menuVisible) { + if (menuFDisabled) { + window.setTimeout("main.showMenu()", 250); + } + return; + } + menuFDisabled = true; + menuVisible = true; + drawLayout(); + x_showMenu(parseMenuData); + }; + this.menuOpen = function(id) { + if (menuFDisabled || !menuVisible) { + if (menuFDisabled) { + window.setTimeout("main.menuOpen('" + id + "')", 250); + } + return; + } + menuFDisabled = true; + if (menuUpdater) { + window.clearTimeout(menuUpdater); + menuUpdater = null; + } + x_menuOpen(id, parseMenuData); + }; + this.menuClose = function(id) { + if (menuFDisabled || !menuVisible) { + if (menuFDisabled) { + window.setTimeout("main.menuClose('" + id + "')", 250); + } + return; + } + menuFDisabled = true; + if (menuUpdater) { + window.clearTimeout(menuUpdater); + menuUpdater = null; + } + x_menuClose(id, parseMenuData); + }; + + useGIFs = needData; + pageContents = new ForumsLayout.pages[pageType]; + drawLayout(); + if (needData) { + if (menuVisible) { + x_getMenu(parseMenuData); + } + } else { + if (menuVisible) { + parseMenuData(document.getElementById('f-menu-init').innerHTML); + } + } + pageContents.parse(document.getElementById('f-page-init').innerHTML); +}; + +ForumsLayout.pages = { + vCat: CategoryView, + vForum: ForumView, + vTopic: TopicView +}; diff -Naur beta5//site/static/beta5/js/pg_overview-en.js forums//site/static/beta5/js/pg_overview-en.js --- beta5//site/static/beta5/js/pg_overview-en.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_overview-en.js 2011-03-12 15:10:45.561300049 +0100 @@ -84,26 +84,7 @@ str += '

Fleets

Total fleet power: '+formatNumber(flOverview[0])+'

'; str += '

Money

Daily Profit: €'+formatNumber(moOverview[2])+'

'; - str += '

Forums

'; - var i,j,k,a=new Array(); - for (i=0;i1 ? 's' : ''))); - a.push(''+name+': ' + j); - }} - - if (aForums.length) - { - k = 0; - for (j=0;j1 ? 's' : ''))); - a.push('Alliance forums: ' + j); - } - str += a.join('
') + '

'; + str += '

Forums

' + forums.output(-1, false) + '

'; str += '

Planets

'+formatNumber(unOverview[0])+' planets

'; str += '

Next ticks

'; @@ -188,30 +169,7 @@ str += 'Daily Profit: €'+formatNumber(moOverview[2])+'
'; str += 'More details...

'; - str += '

Forums

'; - var j,a=new Array(),s; - for (i=0;i (view)'; - for (j=0;j' + forums[j].name + ': '; - s += makeTopicsText(forums[j].nTopics, forums[j].nUnread); - } - a.push(s); - }} - - if (aForums.length) - { - s = 'Alliance Forums (view)'; - for (j=0;j' + aForums[j].name + ': '; - s += makeTopicsText(aForums[j].nTopics, aForums[j].nUnread); - } - a.push(s); - } - str += a.join('

') + '

'; + str += '

Forums

' + forums.output(-1, true) + '

'; str += '

Universe

'+formatNumber(unOverview[0])+' planets';// ('; str += /*formatNumber(unOverview[2]) + ' at the same prot. level)*/'
'; @@ -240,6 +198,40 @@ } +function __forumsOutput(depth, complete) { + var str = ''; + + if (this.id != '/') { + for (var i = 0; i < depth; i++) { + str += '      '; + } + str += '' + this.name + ': '; + if (complete) { + str += '' + this.topics + ' topic' + (this.unread > 1 ? 's' : ''); + if (this.unread > 0) { + str += ' (' + this.unread + ' unread)'; + } + } else { + if (this.unread == 0) { + str += 'no unread topics'; + } else { + str += '' + this.unread + ' unread topic' + (this.unread > 1 ? 's' : ''); + } + } + } + + for (var i = 0; i < this.contents.length; i ++) { + if (this.contents[i].type == 'F' && ! complete) { + continue; + } + + str += '
' + this.contents[i].output(depth + 1, complete); + } + + return str; +} + + function confirmBreakProtection() { return confirm('You are about to break away from Peacekeeper protection.\n' + 'Anyone will be able to attack your planets afterwards.\n' diff -Naur beta5//site/static/beta5/js/pg_overview.js forums//site/static/beta5/js/pg_overview.js --- beta5//site/static/beta5/js/pg_overview.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_overview.js 2011-03-12 15:08:00.801300049 +0100 @@ -1,5 +1,5 @@ var dFolders, cFolders; -var genForums, aForums, allianceId; +var forums; var plOverview, flOverview, moOverview, nResearch; var unOverview,stDiff,ticks,tUpdate,rankings; var complete, protection, updateTimer; @@ -110,24 +110,25 @@ this.name = name; } -function Category(id, type, name) -{ - this.id = id; - this.type = type; - this.name = name; - this.forums = new Array(); -} +function ForumsEntity(inputData) { + var iLine = inputData.shift().split('#'); + var nElements; + + this.type = iLine.shift(); + this.id = iLine.shift(); + nElements = parseInt(iLine.shift(), 10); + this.topics = parseInt(iLine.shift(), 10); + this.unread = parseInt(iLine.shift(), 10); + this.name = inputData.shift(); + this.contents = new Array(); + this.output = __forumsOutput; -function Forum(id, nTopics, nUnread, name) -{ - this.id = id; - this.nTopics = nTopics; - this.nUnread = nUnread; - this.name = name; + for (var i = 0; i < nElements; i ++) { + this.contents.push( new ForumsEntity(inputData) ); + } } - function initPage() { overviewReceived(document.getElementById('init-data').value); } @@ -140,55 +141,26 @@ } -function parseComms(l) -{ - var i, a = l.shift().split('#'); - var nCustom = parseInt(a[0],10), nGenCats = parseInt(a[1],10), nAForums = parseInt(a[2],10); - allianceId = a[3]; +function parseComms(l) { + var i, a, nCustom; // Default folders dFolders = new Array(); - for (i=0;i<3;i++) - { + for (i=0;i<3;i++) { a = l.shift().split('#'); dFolders.push(new Folder('', a[0], a[1], '')); } // Custom folders + nCustom = parseInt(l.shift(), 10); cFolders = new Array(); - for (i=0;i>=<:88673=$>?=;9741*'# 9&0 +6!$'3,812233458@,>#09FEEDCCBA@ 19J%tRB(B (H@SX .^ 9I]!9 <a5Ej0B$H'Hd"RFtԘŊ!8dHd/.# +0XсF$XE!Ĉ&N@" +MkJLeD; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/forum_read.png forums//site/static/beta5/pics/forum_read.png --- beta5//site/static/beta5/pics/forum_read.png 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/forum_read.png 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,3 @@ +PNG + + IHDR szzbKGD pHYs  tIME  0)No޸IDATXk$EU]fr]"ěG,( ^]y%EmDZ`~B)T4M)(`&I)% 6 $1>Pͫ7o38FJֺ|џcٶ_nܸٶ=e 0ΕHt7,ӓ{10իi-IB@#ܧ0FK'RJֺ\__$β,Ak8Zc3s`ss(nGQD$y_vfu]¶mc6Bs^J ;3^]YYy=cvvvH4r]n} Wj[>JY`@e0$28yh4b4M֭[xo9<`vww1Ơ&"jZ6ߟhbV_JqB| 6Rpq~0<`۶^[< ?jg}ͶIENDB` \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/forum_unread.gif forums//site/static/beta5/pics/forum_unread.gif --- beta5//site/static/beta5/pics/forum_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/forum_unread.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,3 @@ +GIF89a  ""#'*2*+3+56/804:45>7?=8>;B<=>EABHBCDLEGFHIOKLMOPRUUV}XXWXYZ҃Ն]^սu^`Շ`n׍؈dגgؚeחeםעlڤjچبۧحkزlnopڸ݂u߽q܁x݄t߇݂yxyy߅||~ߕߘߝߠߥߨ! +,  H*\ȰÇ#JxWIT+^n5T(L*H+񪵪Ӥ=g0B%<"N#BdAdZXJGnРC5z(cӪ]˶-AmA#4hΚ1S 1_^ZO+ S`ʤ)R2Xqj!D|HH )Ll0@1|SǔȡǏ F.i┊Pd ,Za#"MQ@h=$Vg͖\%+KVHF@g,3'"9h0q!lQ +(T@t CQ@a`RT\Ak(܉( ; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/forum_unread.png forums//site/static/beta5/pics/forum_unread.png --- beta5//site/static/beta5/pics/forum_unread.png 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/forum_unread.png 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,8 @@ +PNG + + IHDR szzbKGD pHYs  tIME  0*fIDATXkU?w~lv7شm4mQ}4hѪG+jAD`)>E}C}ZXmKjִI!M7;;sgwM_TȁÝ9̜mؿlSPM1aM/ý$#_QSXp~[ ̓g1_aŮcv+Wz_zN,qqC5l HDJ@$ R%E`0{ ^*8&Ȉ[r]E HcI#Ch.lAAj +04p{&Mfx?@VhR'Q?2V +=}6cF8-O#Wt6T薜)%'?==i >nmX' !1IrJ}:OW<Nalps`v3#v|",bC AkZ1 nng~6wR_@ "!*MnT <Eh,x%W@s +!\8ӄ$Tj6\@ӧX=`1]4sz2h'֍$":>jjUAwW +Fit!6KC ̱uzSK/*BGkGX^(_l4/f:Pdu4)I-7cg۹^NEfuˆ|O*QΰA[n~,;'k@}`8|* $A8RM1LmQr{[@0 0l,00-rfw/ y|;q_G*ye))CMO^0ԭ_-Lh(U!4EDS/u;jd:q!PYoШb;eFu|x{[PQOH=YjW +T6z{Z~(7rC;d.fpsnD'aiw6v(mb-سց;Z@ .IENDB` \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/post_cn.gif forums//site/static/beta5/pics/post_cn.gif --- beta5//site/static/beta5/pics/post_cn.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/post_cn.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,9 @@ +GIF89a "$), -!0$3%8*:,;-<.>.?/?0@1A2C4E4F5G6G7H8I9J9L:K:M;N<O=P>Q?R@S@TAUBUBVCWDXEYFZG[H\H^I]I_J^J`K_KaL`LbMfKcNdOePfQjOgQiRhRlQiSnRkToSpSrUsVnX pYuXr[vYs\t]w^{]v^ y` ~`|ca{bhj ljkmlnmno opq rqtsuvvwwxy z{||$}~'(!)"*#+$,-..5/012233445<6789:ABCDEFGHIJKQWRXSTUVWX_`abh«cìd¬jĭeíkŮfįlŰmƱnDzoɳp̷{лѼӿ‹ÌčŔȖɗʘ˙͛̚͡΢ϣХҦԮ! +,  H*\ȰCY"J!,W8 фh1K@9rڨS;i;yF +t#C816e"gE- KpCGQYhKR4bDO?w^'N4%@ ;j";EBʔgQ@MA?~ pL) +e +*R<=) > K4J +UnS:5R`E#($ ;l@VhaFZ|+l; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/post_co.gif forums//site/static/beta5/pics/post_co.gif --- beta5//site/static/beta5/pics/post_co.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/post_co.gif 2011-02-05 10:10:02.714335002 +0100 @@ -0,0 +1,6 @@ +GIF89a ...666:::===>>>???@@@BBBDDDEEEHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmppprrrssstttuuuvvvwwwxxxzzz|||}}}~~~! +,  H*\Ç  bH"D#bA@TBe(O0QRGGG118B /j4IXhWĉ'O0D,Zd +&iȲ' HpA$֜ +ƐOA[Q 0@X )H\ +0~` 0 $`c 6Tj#zF TXAB"4VLaĂ`!A `tA_WP1eAZ!DY@)le8\Uax<3``AZtE}t"R c TЁ_ՅM&4"RH nаbhhYPU8 4=Fzq#$R0`mp+*bW2oGeDC<|HqD!a!uop!j QiR4]=G4lG; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/post_on.gif forums//site/static/beta5/pics/post_on.gif --- beta5//site/static/beta5/pics/post_on.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/post_on.gif 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,11 @@ +GIF89a ))& ( , 2%5(7)9+:,;-<.>.?/?0@1A2C4E4F5G6G7H8I9J9L:M;N<O=Q?R@TATAUBVCWDXE\CYFZG[H\H^I_J`KaLbMcNgLdOcO ePfQjOhRlQnRoSlUpSqTlVrUsVpYqZqZ xZwZv]t]w^{]v^ u^w_ +za~`{b{b}d}df~efi ljmknno p +q qrsrttuuvwwxyy z{||$}~,},~&'!)"*#+$,--./001122933:44;55<66=77>8?9@@:AGBCDEFGHIJPKQLRSTUVWY_`fab«cìd¬jįlŰmƱnDzoʴq˵r̶s̷{ι}Ϻ~čŔ̠̚͡ϣХҦӧԮ! +,  H*\Ç r)c 1d 1pd9p±#4$D =ziFL˅WT$)RDuH(:mҀJ PfTƂsRN 1e(RڼX𠮍B Ug>JEԞ>ȱ'd9X\ifcƒ(t͓*"VXaA@! HdP=ybPϭ[עǀ6$)Q@zPzx]x +Bt @}A;#a3 lPB LJ)4E@[D"0(2q@P,2 +)FyA\}c +B@ ` - M 2<[T((A2WB # I@a 0qƊYA(PX: U_@ApB-0>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~! +,  H*\PÇ&|H2cĄQ"A)T޼qf41(Q +9rqC3e2^;v$Ȗ5hR³=zC VpaE1[R?}RCꕌ*b?Z$HP@xhJi̘0yJ1DPB$pqXĈ&t,KRN0D(aʝ:YG _E (RLsL6i.V$IHaPD,c4ez7D*UiC /&ʓ0l$[$T |p 1 kA ?Todbx8 1PcA N@%p @ =70aD0%I$ " +(pC:ÖD!lG&Z\ 50 2p<FhyI'\Y)<&;Ipιg* auıX"ʤ +5cZ&Y2fCI~d*J$`F"Hzp (\X +D4F# bmZzlID<"bRp +&u%@o {$Wl?; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_locked.gif forums//site/static/beta5/pics/topic_locked.gif --- beta5//site/static/beta5/pics/topic_locked.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_locked.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,5 @@ +GIF89a  + + +  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~! +,  H*\ȰÇ#JԫV($,x),,? 4S jxL9%,?>n<:Cퟀ,MW-UX.Xf% :&V HǘxDT HjxfAD 6 $쑢)[n`;ž1rEXᗻE͗tClt]{׌Hn zZGqojIENDB` \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_poll.gif forums//site/static/beta5/pics/topic_poll.gif --- beta5//site/static/beta5/pics/topic_poll.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_poll.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,9 @@ +GIF89a m + + +  !!!###$$$&&&---///111222666777888;;;<<<===>>>@@@AAABBBCCCEEEFFFGGGHHHIIIJJJLLLMMMNNNPPPRRRSSSTTTUUUWWWYYY]]]___```aaabbbcccdddeeekkklllmmmnnnooorrrssswwwzzz! +,  + ;NM?/Ilh[D HjfYBGidWAŏ'PSOK(Đ +-ejd_- Jΐ *agb],ۏA^f]+ + pP q=[AD#B߼\xQ̰ #"8ე /6PE7$"$:F4m?EAo SPrܡcB*4P`挖!fRXFP^$`dL*3>2 +@8p`")A+V 8p˘3k6; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_poll.png forums//site/static/beta5/pics/topic_poll.png --- beta5//site/static/beta5/pics/topic_poll.png 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_poll.png 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,7 @@ +PNG + + IHDR szzbKGD pHYs  tIME  s( IDATXOk$UuuWuPiB4 H!!,EA0 rE ѕ&n Ջzﺘ&L'a\n{^!jzT*"1o܏^{UwK 0c9kEQifK:sGD0|\^^lR^V?oW(_Ͽ;;;lllb|&"\knIJU( +)k8PGQoc""RXk  Uu=eYL&XZV5"uqqި^J "F=KKKooo 28n1O}sz4M"2u{Q`0*Z,( +ʲ,& f +we7ywvvv}:[{AA|/"ov:<-sDi ߆aBX5$I*2o۵Zfkk-}VVVT*HcN}F %쨛M CD&j: ^dVyVD&c?cVwQVyXq]@(`ceI$ Gq`0 MqLTިi | +<>s<ϛHk-m9)~k-nNCY5U}2 1*sss*eYrR14MI7৻;?|rIENDB` \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s0_read.gif forums//site/static/beta5/pics/topic_s0_read.gif --- beta5//site/static/beta5/pics/topic_s0_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s0_read.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,4 @@ +GIF89a x'''(((***---000111222555666777<<<>>>???CCCDDDGGGIIIJJJKKKLLLMMMNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +, /=CB?:73-,)('$! +1SUQLJGCD850+)&$ 7SOOJHE>>,,  3XWVUUSSMMKKDDAA<841-7vuupsptq޼ 7kά! C9ܩsNEXoN8nAaƓQXG6rƜ&F-ᄔF6mF 5>E9N7rdӰ!K3# 9hֈb֬iR3f|!q5ĭ][٥i׶}wv+^s3eΜ!3&kxPiҠ1C -t r6[f 0ZFb9oԴL7͚ڦO&|ܴA!2fƈseC% q) jC*=}#`(KY`V +TPP8DHaDC;Åꀁ!h 6$"`@ Ɍ4h8H ; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s0_unread.gif forums//site/static/beta5/pics/topic_s0_unread.gif --- beta5//site/static/beta5/pics/topic_s0_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s0_unread.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,2 @@ +GIF89a v'#($*&-(0+1,2-506172<6>8?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! +, -;A@=851+*'&%" /QSOJHEAB63.)'$" 5QMMHFC<<** 1VUTSSQQKKIIBB??:62/+5tssnqnroڴf6iʤs C7ԙSNEX/Μ7l1aƓQ85p”&F-݄F6m̹4>EGΜ6pTp!K3{8f҈b֬Y 2dtq5ĭ][٥i׶}wt+^s-2cʔKkxgΘ!#F,t 25;&/XBRr8mд<LڦO&|ج11d€ReC% uq)E jC(=m#]^({eKW`V RHN0FAB9Å`!` 2Ѐ$P bɌ4h8H ; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s10_read.gif forums//site/static/beta5/pics/topic_s10_read.gif --- beta5//site/static/beta5/pics/topic_s10_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s10_read.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,12 @@ +GIF89a  + + + '''(((***---000111222555666777<<<===>>>???CCCDDDGGGIIIJJJKKKLLLNNNOOOPPPQQQRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffjjjkkklllmmmnnnpppqqqrrrtttzzz}}}! +,  H*\ȰÇ#*d'I 2\HÇ 4X 2htre +#D~QC +#8?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKLMMNOOPPQRTTUVWWXXYYZZ[[]_ñaɶdнhijllmmnopqqrsttuuvvwwxxyyzz{{||}}~! +,  H*\ȰÇ#*d'I 2\HÇ 4X 2htre +#D~QC +#>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +, .962,+('&#  +0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhL @E0٤Y(ʙCM:2<$a(TECi&5hp-k8qؼLL2^Ftj',eϦ]K-Էqֽא:#KV2f̌FK7U|StV4hΔ3Zl)nQ7Z%#˗," `IgN7i^F ֮aR>~m؜Y̘2b|1ų!u,fݻ嵡ɾ&/}E\GrS<M(FQ?9a9\`d0 8P (H<#"; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s1_unread.gif forums//site/static/beta5/pics/topic_s1_unread.gif --- beta5//site/static/beta5/pics/topic_s1_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s1_unread.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,3 @@ +GIF89a w'#($*&-(0+1,2-506172<6>8?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! +, .962,+('&#  +0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhL @E0٤Y(ʙCM:2<$a(TECi&5hp-k8qؼLL2^Ftj',eϦ]K-Էqֽא:#KV2f̌FK7U|StV4hΔ3Zl)nQ7Z%#˗," `IgN7i^F ֮aR>~m؜Y̘2b|1ų!u,fݻ嵡ɾ&/}E\GrS<M(FQ?9a9\`d0 8P (H<#"; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s2_read.gif forums//site/static/beta5/pics/topic_s2_read.gif --- beta5//site/static/beta5/pics/topic_s2_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s2_read.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,7 @@ +GIF89a w'''(((***---000111222555666777<<<>>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +, .962,+('&#  +0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhQ aIF PdC=JM:2<$a(FԢi&5hp-uh}EټLL2^Ft + ,eϦ]K-ηtέ{7!u"K,X3d̘#o +jQ4ϔ3Zl)'fKV3'4/Efwkװ o6lάQxfL1aِl:e]ax{P +~U`__x͇h0ODDJ qE18?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! +, .962,+('&#  +0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32NhQ aIF PdC=JM:2<$a(FԢi&5hp-uh}EټLL2^Ft + ,eϦ]K-ηtέ{7!u"K,X3d̘#o +jQ4ϔ3Zl)'fKV3'4/Efwkװ o6lάQxfL1aِl:e]ax{P +~U`__x͇h0ODDJ qE1>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +, .962,+('&#  +0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32Nh"ъDaIF PdC!*gNR:2<$a(FTB]猚^zTq$ML2^FtV,fѪ%VۤrҵWo!u"KCt_R6Dɘ13F -! I9웱k9Sf̘/ZjqbnRKF̗/YDZΜ8nҼDcޯaB#}ڰ9F1eĄb hC)Hw;eKlC)!}_t]pQ_}XV SLDO4GQDC:Çp!d(6,"`@ 0 Ɏ<㏈; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s3_unread.gif forums//site/static/beta5/pics/topic_s3_unread.gif --- beta5//site/static/beta5/pics/topic_s3_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s3_unread.gif 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,3 @@ +GIF89a w'#($*&-(0+1,2-506172<6>8?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmpqqrsttuuvvwwxxyyzz{{||}}~! +, .962,+('&#  +0RTPKIFBC74/*(%# 6RNNIGD==++ 2WVUTTRRLLJJCC@@;730,6uttoroq6oܨ1f /% c:)R>9tഉS愡SnTI$8oc"ʛ1|32Nh"ъDaIF PdC!*gNR:2<$a(FTB]猚^zTq$ML2^FtV,fѪ%VۤrҵWo!u"KCt_R6Dɘ13F -! I9웱k9Sf̘/ZjqbnRKF̗/YDZΜ8nҼDcޯaB#}ڰ9F1eĄb hC)Hw;eKlC)!}_t]pQ_}XV SLDO4GQDC:Çp!d(6,"`@ 0 Ɏ<㏈; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s4_read.gif forums//site/static/beta5/pics/topic_s4_read.gif --- beta5//site/static/beta5/pics/topic_s4_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s4_read.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,5 @@ +GIF89a  '''(((***+++,,,---000111222555666777999;;;<<<>>>???CCCDDDFFFGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +,  H*\ȰÇ#*aI$CA#,R 1 2P/`\"ɓ >r̈ႅ:hАB˂@hRe +#GpҠ !:̠<Ȍ C /^`bɓ%K顣n  $>}=}ࡃ'6. +!DHA)S^@3SoV-?}"ۚ13}Y*;yG:8#y0:43g4ug3`)GOqs GdG|G}G F! "X  4(tg` @_|4ALQq&dmzm(ma + AxlM4f|zԑbu1oA9$e yXopL$_g4؆mNZ[tE8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMMNNOOPPQRTUVWWXXYYZ[[_ñaɶdλgнhijllmopqqrsttuuvvwwxxyyzz{{||}}~! +,  H*\ȰÇ#*aI$CA#,R 1 2P/`\"ɓ >r̈ႅ:hАB˂@hRe +#GpҠ !:̠<Ȍ C /^`bɓ%K顣n  $>}=}ࡃ'6. +!DHA)S^@3SoV-?}"ۚ13}Y*;yG:8#y0:43g4ug3`)GOqs GdG|G}G F! "X  4(tg` @_|4ALQq&dmzm(ma + AxlM4f|zԑbu1oA9$e yXopL$_g4؆mNZ[tE>>???CCCDDDFFFGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +,  H*\ȰÇ#*a $CA#,R 1 2P._X >r̈ႅ:hАB˂@dBEʓ#GpҠ !:̠<ĄC˗.]\Rʼn%J顣n  +>}ٳo<|ܙsN5. +4( A)S?#SoVt ?|y"ۚ13ڏ}V$;`?"H6- : @:w _pZ@k;v!Gk p@`z|dnp@XA7b(fhq1oa +$޶We@uIpdmAzQlA4 ~akuXVyd͠_{Gtpḻƍ-InHWqW ͉A#kcA0x\hESH6P8F1D: [ P`DVk"; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s5_unread.gif forums//site/static/beta5/pics/topic_s5_unread.gif --- beta5//site/static/beta5/pics/topic_s5_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s5_unread.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,6 @@ +GIF89a   +'#($*&+',(-(0+1,2-50617293:4<6>8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMNNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijkllmopqqrsttuuvvwwxxyyzz{{||}}~! +,  H*\ȰÇ#*a $CA#,R 1 2P._X >r̈ႅ:hАB˂@dBEʓ#GpҠ !:̠<ĄC˗.]\Rʼn%J顣n  +>}ٳo<|ܙsN5. +4( A)S?#SoVt ?|y"ۚ13ڏ}V$;`?"H6- : @:w _pZ@k;v!Gk p@`z|dnp@XA7b(fhq1oa +$޶We@uIpdmAzQlA4 ~akuXVyd͠_{Gtpḻƍ-InHWqW ͉A#kcA0x\hESH6P8F1D: [ P`DVk"; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s6_read.gif forums//site/static/beta5/pics/topic_s6_read.gif --- beta5//site/static/beta5/pics/topic_s6_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s6_read.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,8 @@ +GIF89a  '''(((***+++,,,---...000111222555666777999;;;<<<>>>???CCCDDDFFFGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +,  H*\ȰÇ#*ԑ䉓%Fq /X8a 4T`0bdB "AxؠE +!>pB˂CprJ%Jp޸sE +#>?ʐCV0`h +&Mң.4O ?}=~䩓GΝ61 )ThP!B)S@DgA!SoV= @~##ۚ13F`- 6=y# %|l=aq[ z + F|A%R + xAm0nHA!|yԷmr@Fx`@Ȋ, AQqF )޶01gr +mtQ$snA76 kwxGZ*d٠_{v衘rnƎ=In#[ ݩmcA3F_t;VTAR@DIaj@ k$@ P`Df"; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s6_unread.gif forums//site/static/beta5/pics/topic_s6_unread.gif --- beta5//site/static/beta5/pics/topic_s6_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s6_unread.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,9 @@ +GIF89a   +'#($*&+',(-(.)0+1,2-50617293;5<6>8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMNOOPPQQRTUVWWXXYYZ[[_ñaɶdнhijllmopqqrsttuuvvwwxxyyzz{{||}}~! +,  H*\ȰÇ#*ԑ䉓%Fq /X8a 4T`0bdB "AxؠE +!>pB˂CprJ%Jp޸sE +#>?ʐCV0`h +&Mң.4O ?}=~䩓GΝ61 )ThP!B)S@DgA!SoV= @~##ۚ13F`- 6=y# %|l=aq[ z + F|A%R + xAm0nHA!|yԷmr@Fx`@Ȋ, AQqF )޶01gr +mtQ$snA76 kwxGZ*d٠_{v衘rnƎ=In#[ ݩmcA3F_t;VTAR@DIaj@ k$@ P`Df"; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s7_read.gif forums//site/static/beta5/pics/topic_s7_read.gif --- beta5//site/static/beta5/pics/topic_s7_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s7_read.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,4 @@ +GIF89a  '''(((***+++,,,---...000111222555666777999<<<>>>???CCCDDDGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +,  H*\ȰÇ#*q $DQCF -T(A 0P._X!?tА :hАB˂AdBE$Hp֨3 !:Ġ=ĄC˗.]\Rʼn%K񱣮  +>}ٳo<|ܙsN5/ 4( A)S?#'A SoVt ?|y#ۚ13ڏFf, 5s;xG:G?y h 3g1׃k8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmnopqqrsttuuvvwwxxyyzz{{||}}~! +,  H*\ȰÇ#*q $DQCF -T(A 0P._X!?tА :hАB˂AdBE$Hp֨3 !:Ġ=ĄC˗.]\Rʼn%K񱣮  +>}ٳo<|ܙsN5/ 4( A)S?#'A SoVt ?|y#ۚ13ڏFf, 5s;xG:G?y h 3g1׃k>>???CCCDDDGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}! +,  H*\ȰÇ#*qI%DQCF -T(A 0P/`\"!?tА :hАB˂AhRe +$Hp֨3 !:Ġ=ƈ C /^`b&L񱣮  $>}=}ࡃ'6/ !DHA)S^@3'A SoV-?}#ۚ13B ,2sP$4c9@ ؅APxs8?9Cs?t@vAwAxByBzC{D|D}E~EFGGHHIIJJKKLMNOOPPQRTUVWWXXYYZ[[_ñaɶdнhijllmoppqqrsttuuvvwwxxyyzz{{||}}~! +,  H*\ȰÇ#*qI%DQCF -T(A 0P/`\"!?tА :hАB˂AhRe +$Hp֨3 !:Ġ=ƈ C /^`b&L񱣮  $>}=}ࡃ'6/ !DHA)S^@3'A SoV-?}#ۚ13B ,2sP$4c9@ ؅APxs>>???CCCDDDGGGIIIJJJKKKLLLNNNPPPQQQRRRSSSTTTUUUWWW^^^___```bbbcccdddfffkkklllmmmnnnoooqqqrrrtttzzz}}}! +,  H*\ȰÇ#*d'H١ㆍ1\@q 6\2gtre +#C|A#Ɗ#>pB˂EȀ%K&Lpѳ +%>AԤAC2dx… +(P0ZP"C}*TA +Hϟ:4 ihQ#F)S^DBdžA"SoV"D!S#ۚ164  J*'r @G:x(3ق0:2, "?_m@ ߀u@yqm^|w}]f)@h`A=0"∌0H@Gzq/m% n`|衇n|H-B"#E!5{am`-"HG"@9h^|2(|䁇}AALW`"ƈuA8av +vhGtikX fa|:Y\aT<I Į"AA,`bAF 4 ( , ѵf; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s9_unread.gif forums//site/static/beta5/pics/topic_s9_unread.gif --- beta5//site/static/beta5/pics/topic_s9_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s9_unread.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,10 @@ +GIF89a  +  +   +  '#($*&-(0+1,2-506172;5<6=7>8?9Cs?t@vAwAxBzC{D|D}E~EFGGHHIIJJKKLMNNOOPPQRSTUVWWXXYYZ[[\_ñaɶdнhijkllmmnppqqrsttuuvvwwxxyyzz{{||}}~! +,  H*\ȰÇ#*d'H١ㆍ1\@q 6\2gtre +#C|A#Ɗ#>pB˂EȀ%K&Lpѳ +%>AԤAC2dx… +(P0ZP"C}*TA +Hϟ:4 ihQ#F)S^DBdžA"SoV"D!S#ۚ164  J*'r @G:x(3ق0:2, "?_m@ ߀u@yqm^|w}]f)@h`A=0"∌0H@Gzq/m% n`|衇n|H-B"#E!5{am`-"HG"@9h^|2(|䁇}AALW`"ƈuA8av +vhGtikX fa|:Y\aT<I Į"AA,`bAF 4 ( , ѵf; \ No newline at end of file diff -Naur beta5//sql/13-main-forums.sql forums//sql/13-main-forums.sql --- beta5//sql/13-main-forums.sql 2011-02-05 10:09:56.244335002 +0100 +++ forums//sql/13-main-forums.sql 1970-01-01 01:00:00.000000000 +0100 @@ -1,149 +0,0 @@ --- LegacyWorlds Beta 5 --- PostgreSQL database scripts --- --- 13-main-forums.sql --- --- Tables for the forums --- --- Copyright(C) 2004-2007, DeepClone Development --- -------------------------------------------------------- - - - --- Connect to the database -\c legacyworlds legacyworlds_admin - - - --- --- Forum categories --- -CREATE TABLE main.f_category ( - id SERIAL NOT NULL PRIMARY KEY, - corder INT NOT NULL UNIQUE CHECK(corder >= 0), - title VARCHAR(64) NOT NULL UNIQUE, - description TEXT -); - -GRANT SELECT,INSERT ON TABLE main.f_category TO legacyworlds; -GRANT SELECT,UPDATE ON main.f_category_id_seq TO legacyworlds; - - --- --- Forums --- -CREATE TABLE main.f_forum ( - id SERIAL NOT NULL PRIMARY KEY, - category INT NOT NULL REFERENCES main.f_category (id) ON DELETE CASCADE, - forder INT NOT NULL DEFAULT 0 CHECK(forder >= 0), - title VARCHAR(64) NOT NULL, - description TEXT, - topics INT NOT NULL CHECK(topics >= 0), - posts INT NOT NULL CHECK(posts >= 0), - last_post BIGINT NULL, - user_post BOOLEAN NOT NULL DEFAULT TRUE, - admin_only BOOLEAN NOT NULL DEFAULT FALSE -); - -CREATE UNIQUE INDEX forum_unique ON main.f_forum (category, forder); -CREATE INDEX forum_last_post ON main.f_forum (last_post); - -GRANT SELECT,UPDATE,INSERT ON TABLE main.f_forum TO legacyworlds; -GRANT SELECT,UPDATE ON main.f_forum_id_seq TO legacyworlds; - - --- --- Topics --- -CREATE TABLE main.f_topic ( - id BIGSERIAL NOT NULL PRIMARY KEY, - forum INT NOT NULL REFERENCES main.f_forum (id) ON DELETE CASCADE, - first_post BIGINT NOT NULL, - last_post BIGINT NULL, - sticky BOOLEAN NOT NULL DEFAULT FALSE, - deleted INT NULL -); - -CREATE INDEX topic_forum ON main.f_topic (forum); -CREATE INDEX topic_fpost ON main.f_topic (first_post); -CREATE INDEX topic_lpost ON main.f_topic (last_post); - -GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE main.f_topic TO legacyworlds; -GRANT SELECT,UPDATE ON main.f_topic_id_seq TO legacyworlds; - - --- --- Posts --- -CREATE TABLE main.f_post ( - id BIGSERIAL PRIMARY KEY, - forum INT NOT NULL REFERENCES main.f_forum (id) ON DELETE CASCADE, - topic BIGINT NULL REFERENCES main.f_topic (id) ON DELETE CASCADE, - author BIGINT NOT NULL REFERENCES main.account (id), - reply_to BIGINT NULL REFERENCES main.f_post (id) ON DELETE SET NULL, - moment INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), - title VARCHAR(100) NOT NULL, - contents TEXT NOT NULL, - enable_code BOOLEAN NOT NULL DEFAULT TRUE, - enable_smileys BOOLEAN NOT NULL DEFAULT TRUE, - edited INT NULL, - edited_by BIGINT NULL REFERENCES main.account (id), - deleted INT NULL -); - -CREATE INDEX post_forum ON main.f_post (forum); -CREATE INDEX post_topic ON main.f_post (topic); -CREATE INDEX post_author ON main.f_post (author); -CREATE INDEX post_editor ON main.f_post (edited_by); -CREATE INDEX post_reply ON main.f_post (reply_to); - -ALTER TABLE main.f_forum ADD FOREIGN KEY (last_post) REFERENCES main.f_post (id) ON DELETE SET NULL; -ALTER TABLE main.f_topic ADD FOREIGN KEY (first_post) REFERENCES main.f_post (id) ON DELETE CASCADE; -ALTER TABLE main.f_topic ADD FOREIGN KEY (last_post) REFERENCES main.f_post (id) ON DELETE SET NULL; - -GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE main.f_post TO legacyworlds; -GRANT SELECT,UPDATE ON main.f_post_id_seq TO legacyworlds; - - --- --- Read topics --- -CREATE TABLE main.f_read ( - reader BIGINT NOT NULL REFERENCES main.account (id), - topic BIGINT NOT NULL REFERENCES main.f_topic (id) ON DELETE CASCADE, - PRIMARY KEY (reader, topic) -); - -GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE main.f_read TO legacyworlds; - - --- --- Smileys and forum codes --- -CREATE TABLE main.f_smiley ( - smiley VARCHAR(20) NOT NULL PRIMARY KEY, - file VARCHAR(20) NOT NULL -); -CREATE TABLE main.f_code ( - p_reg_exp VARCHAR(40) NOT NULL PRIMARY KEY, - replacement VARCHAR(80) NOT NULL -); -GRANT SELECT ON main.f_smiley TO legacyworlds; -GRANT SELECT ON main.f_code TO legacyworlds; - - --- --- Admins, mods, losers --- Not everything is useful in the current version so meh. --- -CREATE TABLE main.f_admin ( - "user" BIGINT NOT NULL REFERENCES main.account (id) PRIMARY KEY, - category INT NULL REFERENCES main.f_category (id) ON DELETE CASCADE -); -GRANT SELECT,INSERT,UPDATE,DELETE ON main.f_admin TO legacyworlds; - -CREATE TABLE main.f_moderator ( - "user" BIGINT NOT NULL REFERENCES main.account (id) PRIMARY KEY, - forum INT NULL REFERENCES main.f_forum (id) ON DELETE CASCADE -); -GRANT SELECT,INSERT,UPDATE,DELETE ON main.f_moderator TO legacyworlds; diff -Naur beta5//sql/14-main-forums.sql forums//sql/14-main-forums.sql --- beta5//sql/14-main-forums.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/14-main-forums.sql 2011-02-05 10:10:01.774335002 +0100 @@ -0,0 +1,227 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 14-main-forums.sql +-- +-- Execute the generic forums installation script, +-- then add tables for the general forums +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- Install the generic forums +\i forums/FORUMS.sql + + +-- +-- Create category type for general forums +-- +SELECT forums.add_category_type( 'main/gforums', 'en', 'General forums' ); +-- SELECT forums.add_category_type( 'main/gforums', 'fr', 'Forums généraux ' ); + + + +-- +-- The general forums' categories +-- +CREATE TABLE main.gf_category ( + category BIGINT PRIMARY KEY REFERENCES forums.category (id) ON DELETE CASCADE, + t_string VARCHAR(15) NULL, + t_is_game BOOLEAN NULL, + UNIQUE(t_string, t_is_game) +); + +GRANT SELECT,INSERT,DELETE ON main.gf_category TO legacyworlds; + + + +-- +-- The general forums' access list +-- +-- Access type: +-- - MO: moderator- (and admin-) only access, not visible to most users +-- - MP: moderator- (and admin-) only posting, standard users can't create new topics +-- - UP: users can view the forum and create topics, but can't create polls; this is the default +-- - UL: users can view the forum, create topics and create polls +-- +CREATE TABLE main.gf_forum ( + forum BIGINT PRIMARY KEY REFERENCES forums.t_forum (id) ON DELETE CASCADE, + access_type CHAR(2) NOT NULL DEFAULT 'UP' CHECK( access_type IN ('MO', 'MP', 'UP', 'UL') ) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON main.gf_forum TO legacyworlds; + +-- Trigger function & definition for main.gf_forum +CREATE OR REPLACE FUNCTION main.trgf_gf_forum_check () RETURNS TRIGGER AS $$ +BEGIN + PERFORM g.category FROM main.gf_category g, forums.forum f + WHERE f.id = NEW.forum AND g.category = f.category; + IF NOT FOUND THEN + RAISE EXCEPTION 'Forum #% is not a general forum', NEW.forum; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_gf_forum_check BEFORE INSERT OR UPDATE ON main.gf_forum + FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_forum_check(); + + +-- +-- Bans on the general forums +-- +CREATE TABLE main.gf_ban ( + account BIGINT PRIMARY KEY REFERENCES main.account (id) ON DELETE CASCADE, + until INT NOT NULL +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON main.gf_ban TO legacyworlds; + + + +-- +-- General forums administrators +-- +-- List of people who can create, remove or modify general forums. +-- An admin is either a general admin (all GF categories) or the admin for a specific category +-- +CREATE TABLE main.gf_admin ( + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + category BIGINT NULL REFERENCES main.gf_category (category) ON DELETE CASCADE, + UNIQUE( account, category ) +); + +GRANT SELECT,INSERT,DELETE ON main.gf_admin TO legacyworlds; + +-- Trigger function & definition that makes sure that you're either a general admin or a category-specific admin, +-- not both. +CREATE OR REPLACE FUNCTION main.trgf_gf_admin_check () RETURNS TRIGGER AS $$ +BEGIN + IF NEW.category IS NULL THEN + PERFORM * FROM main.gf_admin WHERE account = NEW.account AND category IS NOT NULL; + IF FOUND THEN + RAISE EXCEPTION 'User #% is already a cat-specific admin', NEW.account; + END IF; + DELETE FROM main.gf_cat_moderator WHERE account = NEW.account; + DELETE FROM main.gf_forum_moderator WHERE account = NEW.account; + ELSE + PERFORM * FROM main.gf_category WHERE category = NEW.category; + IF NOT FOUND THEN + RAISE EXCEPTION 'Category #% is not a general category', NEW.category; + END IF; + PERFORM * FROM main.gf_admin WHERE account = NEW.account AND category IS NULL; + IF FOUND THEN + RAISE EXCEPTION 'User #% is already a general admin', NEW.account; + END IF; + DELETE FROM main.gf_cat_moderator WHERE account = NEW.account AND category = NEW.category; + DELETE FROM main.gf_forum_moderator WHERE account = NEW.account AND forum IN ( + SELECT id FROM forums.forum WHERE category = NEW.category + ); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_gf_admin_check BEFORE INSERT ON main.gf_admin + FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_admin_check(); + + + + +-- +-- General forums moderators, by category +-- +-- List of people who can moderate whole categories of the general forums. +-- These people should not already be listed in the admin list for the category. +-- +CREATE TABLE main.gf_cat_moderator ( + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + category BIGINT NULL REFERENCES main.gf_category (category) ON DELETE CASCADE, + UNIQUE( account, category ) +); + +GRANT SELECT,INSERT,DELETE ON main.gf_cat_moderator TO legacyworlds; + +-- Trigger function & definition that makes sure that you're either a general mod or a category-specific mod, +-- and that you're not an admin. +CREATE OR REPLACE FUNCTION main.trgf_gf_cmod_check () RETURNS TRIGGER AS $$ +BEGIN + IF NEW.category IS NULL THEN + PERFORM * FROM main.gf_cat_moderator WHERE account = NEW.account AND category IS NOT NULL; + IF FOUND THEN + RAISE EXCEPTION 'User #% is already a cat-specific moderator', NEW.account; + END IF; + PERFORM * FROM main.gf_admin WHERE account = NEW.account AND category IS NULL; + IF FOUND THEN + RAISE EXCEPTION 'User #% is already a general administrator', NEW.account; + END IF; + DELETE FROM main.gf_forum_moderator WHERE account = NEW.account; + ELSE + PERFORM * FROM main.gf_category WHERE category = NEW.category; + IF NOT FOUND THEN + RAISE EXCEPTION 'Category #% is not a general category', NEW.category; + END IF; + PERFORM * FROM main.gf_cat_moderator WHERE account = NEW.account AND category IS NULL; + IF FOUND THEN + RAISE EXCEPTION 'User #% is already a general moderator', NEW.account; + END IF; + PERFORM * FROM main.gf_admin + WHERE account = NEW.account + AND (category = NEW.category OR category IS NULL); + IF FOUND THEN + RAISE EXCEPTION 'User #% is already an administrator for category', NEW.account; + END IF; + DELETE FROM main.gf_forum_moderator WHERE account = NEW.account AND forum IN ( + SELECT id FROM forums.forum WHERE category = NEW.category + ); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_gf_cmod_check BEFORE INSERT ON main.gf_cat_moderator + FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_cmod_check(); + + + + +-- +-- General forums moderators, by forum +-- +-- List of people who can moderate specific forums. +-- +CREATE TABLE main.gf_forum_moderator ( + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + forum BIGINT NOT NULL REFERENCES forums.t_forum (id) ON DELETE CASCADE, + UNIQUE( account, forum ) + -- FIXME: add constraint to make sure that if someone's a mod for the forum's category or an admin + -- for the category in question, that someone can't be added as a forum-specific moderator +); + +GRANT SELECT,INSERT,DELETE ON main.gf_forum_moderator TO legacyworlds; + +-- Trigger function & definition that makes sure that you're not a forums' category mod or admin. +CREATE OR REPLACE FUNCTION main.trgf_gf_fmod_check() RETURNS TRIGGER AS $$ +DECLARE + cid BIGINT; +BEGIN + SELECT INTO cid g.category FROM main.gf_category g, forums.forum f + WHERE f.id = NEW.forum AND g.category = f.category; + IF NOT FOUND THEN + RAISE EXCEPTION 'Forum #% is not a general forum', NEW.forum; + END IF; + + PERFORM * FROM main.gf_cat_moderator WHERE account = NEW.account AND (category IS NULL OR category = cid); + IF FOUND THEN + RAISE EXCEPTION 'User #% is already a moderator for forum #%', NEW.account, NEW.forum; + END IF; + + PERFORM * FROM main.gf_admin WHERE account = NEW.account AND (category IS NULL OR category = cid); + IF FOUND THEN + RAISE EXCEPTION 'User #% is already an administrator for forum #%', NEW.account, NEW.forum; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_gf_fmod_check BEFORE INSERT ON main.gf_forum_moderator + FOR EACH ROW EXECUTE PROCEDURE main.trgf_gf_fmod_check(); diff -Naur beta5//sql/15-main-gf-functions.sql forums//sql/15-main-gf-functions.sql --- beta5//sql/15-main-gf-functions.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/15-main-gf-functions.sql 2011-02-05 10:10:01.774335002 +0100 @@ -0,0 +1,205 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 15-main-gf-functions.sql +-- +-- Creates the general forums access functions +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- +-- main.init_general_forums() +-- +-- This function initialises the general forums by creating +-- a category for the main GF category. + +CREATE OR REPLACE FUNCTION main.init_general_forums() RETURNS BIGINT AS $$ +DECLARE + cid BIGINT; +BEGIN + SELECT INTO cid * FROM main.gf_category WHERE t_string IS NULL; + IF FOUND THEN + RETURN cid; + END IF; + + SELECT INTO cid forums.make_category( 'main/gforums' ); + INSERT INTO main.gf_category (category, t_string, t_is_game ) + VALUES (cid, NULL, NULL); + + RETURN cid; +END; +$$ LANGUAGE plpgsql; + + +-- +-- main.init_version_forums( version ) +-- +-- This function initialises the general forums category +-- for a specific version of LW + +CREATE OR REPLACE FUNCTION main.init_version_forums( v TEXT ) RETURNS BIGINT AS $$ +DECLARE + cid BIGINT; +BEGIN + SELECT INTO cid * FROM main.gf_category WHERE t_string = v AND NOT t_is_game; + IF FOUND THEN + RETURN cid; + END IF; + + SELECT INTO cid forums.make_category( 'main/gforums' ); + INSERT INTO main.gf_category (category, t_string, t_is_game ) + VALUES (cid, v, FALSE); + + RETURN cid; +END; +$$ LANGUAGE plpgsql; + + +-- +-- main.init_game_forums( game ) +-- +-- This function initialises the general forums category +-- for a specific LW game + +CREATE OR REPLACE FUNCTION main.init_game_forums( g TEXT ) RETURNS BIGINT AS $$ +DECLARE + cid BIGINT; +BEGIN + SELECT INTO cid * FROM main.gf_category WHERE t_string = g AND t_is_game; + IF FOUND THEN + RETURN cid; + END IF; + + SELECT INTO cid forums.make_category( 'main/gforums' ); + INSERT INTO main.gf_category (category, t_string, t_is_game ) + VALUES (cid, g, TRUE); + + RETURN cid; +END; +$$ LANGUAGE plpgsql; + + +-- +-- main.get_gf_categories( version , game ) +-- +-- Returns the list of all available general forum categories for a version and game + +CREATE OR REPLACE FUNCTION main.get_gf_categories( ver TEXT, game TEXT ) RETURNS SETOF BIGINT AS $$ + SELECT category FROM main.gf_category + WHERE t_string IS NULL + OR (t_string = $1 AND NOT t_is_game) + OR (t_string = $2 AND t_is_game) + ORDER BY category; +$$ LANGUAGE SQL; + + +-- +-- main.get_gf_list( version , game ) +-- +-- Returns the list of all available general forums for a version and game + +CREATE OR REPLACE FUNCTION main.get_gf_list( ver TEXT, game TEXT ) RETURNS SETOF BIGINT AS $$ + SELECT id FROM forums.t_forum + WHERE category IN ( SELECT * FROM main.get_gf_categories( $1, $2 ) ) + ORDER BY category, f_order; +$$ LANGUAGE SQL; + + +-- +-- main.get_gforums_privs( user , forum ) +-- +-- Returns the set of privileges an user has over a specific general forum + +CREATE OR REPLACE FUNCTION main.get_gforums_privs( aid BIGINT, fid BIGINT, OUT can_view BOOLEAN, OUT can_post BOOLEAN, OUT can_create BOOLEAN, OUT can_poll BOOLEAN, OUT is_mod BOOLEAN, OUT is_admin BOOLEAN) AS $$ +DECLARE + cid BIGINT; + r TEXT; +BEGIN + -- Get the category's ID + SELECT INTO cid category FROM forums.t_forum WHERE id = fid; + IF NOT FOUND THEN + is_admin := FALSE; + is_mod := FALSE; + can_view := FALSE; + can_post := FALSE; + can_create := FALSE; + can_poll := FALSE; + RETURN; + END IF; + + -- Check if this is a general category + PERFORM * FROM main.gf_category WHERE category = cid; + IF NOT FOUND THEN + RETURN; + END IF; + + -- Fetch category admin details + PERFORM * FROM main.gf_admin WHERE (category IS NULL OR category = cid) AND account = aid; + IF FOUND THEN + can_view := TRUE; + can_post := TRUE; + can_create := TRUE; + can_poll := TRUE; + is_mod := TRUE; + is_admin := TRUE; + RETURN; + END IF; + is_admin := FALSE; + + -- Fetch category mod details + PERFORM * FROM main.gf_cat_moderator WHERE (category IS NULL OR category = cid) AND account = aid; + IF FOUND THEN + can_view := TRUE; + can_post := TRUE; + can_create := TRUE; + can_poll := TRUE; + is_mod := TRUE; + RETURN; + END IF; + + -- Fetch forum mod details + PERFORM * FROM main.gf_forum_moderator WHERE forum = fid AND account = aid; + IF FOUND THEN + can_view := TRUE; + can_post := TRUE; + can_create := TRUE; + can_poll := TRUE; + is_mod := TRUE; + RETURN; + END IF; + is_mod := false; + + -- Check forum access + SELECT INTO r access_type FROM main.gf_forum WHERE forum = fid; + IF NOT FOUND THEN + r := 'UP'; + END IF; + + -- Check for mod-only forum + IF r = 'MO' THEN + can_view := FALSE; + can_post := FALSE; + can_create := FALSE; + can_poll := FALSE; + RETURN; + END IF; + can_view := TRUE; + + -- Check for bans + PERFORM * FROM main.gf_ban WHERE account = aid; + IF FOUND THEN + can_post := FALSE; + can_create := FALSE; + can_poll := FALSE; + RETURN; + END IF; + + -- Set can_post, can_create and can_poll depending on the access type + can_post := TRUE; + can_create := (r = 'UP' OR r = 'UL'); + can_poll := (r = 'UL'); +END; +$$ LANGUAGE plpgsql; diff -Naur beta5//sql/15-main-uforums.sql forums//sql/15-main-uforums.sql --- beta5//sql/15-main-uforums.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/15-main-uforums.sql 2011-02-05 10:10:01.774335002 +0100 @@ -0,0 +1,332 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- 15-main-uforums.sql +-- +-- Add tables and functions to manage the user forums +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Access types for forums: +-- - 'P': public +-- - 'W': password +-- - 'I': invite-only +-- +-- User access flag: +-- - 'M': moderator +-- - 'L': can start topics, post and create polls +-- - 'T': can start topics and post +-- - 'P': can post +-- - 'R': can read + + +-- +-- Create category type +-- +SELECT forums.add_category_type( 'main/uforums', 'en', 'User forums' ); + + +-- +-- main.uf_get_access_mode( forum ) +-- +-- Returns the access mode for a specific user forum + +CREATE OR REPLACE FUNCTION main.uf_get_access_mode( fid BIGINT ) RETURNS TEXT AS $$ +DECLARE + cid BIGINT; + am TEXT; +BEGIN + -- Check if the forum exists and is an user forum; if it is, get the access mode + SELECT INTO am access_mode FROM main.user_forum WHERE forum = fid; + IF NOT FOUND THEN + RETURN NULL; + END IF; + + IF am IS NULL THEN + -- Forum uses defaults, get its category + SELECT INTO cid category FROM forums.t_forum WHERE id = fid; + SELECT INTO am default_access FROM main.user_category; + END IF; + + RETURN am; +END; +$$ LANGUAGE plpgsql; + + +-- +-- main.uf_get_user_access( user, forum ) +-- +-- Returns the user access mode for a specific user and forum combination + +CREATE OR REPLACE FUNCTION main.uf_get_user_access( aid BIGINT, fid BIGINT ) RETURNS TEXT AS $$ +DECLARE + fua TEXT; + r RECORD; +BEGIN + -- Read the category's data + SELECT INTO r c.account AS fo, c.user_access AS am FROM main.user_category c, forums.t_forum f + WHERE c.category = f.category AND f.id = fid; + + -- Try reading the subscription value + SELECT INTO fua access_mode FROM main.uf_subscription WHERE account = aid; + IF NOT FOUND THEN + -- No subscription - check if we're the owner + IF r.fo = aid THEN + RETURN 'A'; + END IF; + RETURN NULL; + END IF; + + -- We got a subscription value + IF fua IS NOT NULL THEN + RETURN fua; + END IF; + + -- Check if the forum exists and get its default user access + SELECT INTO fua user_access FROM main.user_forum WHERE forum = fid; + IF NOT FOUND THEN + RETURN NULL; + END IF; + + -- If we had a default user access mode, return it + IF fua IS NOT NULL THEN + RETURN fua; + END IF; + + -- Return the category's default access mode + RETURN r.am; +END; +$$ LANGUAGE plpgsql; + + +-- +-- main.uf_get_category( user ) +-- +-- Gets the ID of the user's forums category. If it doesn't exist, create it. + +CREATE OR REPLACE FUNCTION main.uf_get_category( aid BIGINT ) RETURNS BIGINT AS $$ +DECLARE + cid BIGINT; +BEGIN + SELECT INTO cid category FROM main.user_category WHERE account = aid; + IF NOT FOUND THEN + cid := forums.make_category( 'main/uforums' ); + INSERT INTO main.user_category (category, account) VALUES (cid, aid); + END IF; + RETURN cid; +END; +$$ LANGUAGE plpgsql; + + +-- +-- main.uf_create_forum( user, forum_order, forum_title, forum_description, user_access, access_mode, password ) +-- +-- Creates an user forum. +-- Return values: +-- any positive value: identifier of the new forum +-- -1: the user has 20 forums already +-- -2: invalid access modes +-- -3: a forum with the same name already exists +-- -4: failure in the generic forums code + +CREATE OR REPLACE FUNCTION main.uf_create_forum( aid BIGINT, fo INT, ttl TEXT, dsc TEXT, ua TEXT, am TEXT, pass TEXT) RETURNS BIGINT AS $$ +DECLARE + cid BIGINT; + fid BIGINT; + n INT; + cam TEXT; +BEGIN + -- Get the user's category and check if it's possible to create the user's new forum + cid := main.uf_get_category( aid ); + SELECT INTO n COUNT(*) FROM forums.t_forum WHERE category = cid AND deleted IS NULL; + IF n = 20 THEN + RETURN -1; + END IF; + + -- Check the access mode + SELECT INTO cam default_access FROM main.user_category WHERE category = cid; + IF (am = 'W' AND pass IS NULL) OR (am IS NULL AND cam = 'W' AND pass IS NULL) THEN + RETURN -2; + END IF; + + -- Check the forum's title + PERFORM * FROM forums.t_forum WHERE category = cid AND title = ttl; + IF FOUND THEN + RETURN -3; + END IF; + + -- Create the forum + fid := forums.make_forum( cid, fo, ttl, dsc ); + IF fid IS NULL THEN + RETURN -4; + END IF; + IF (am = 'W' OR (am IS NULL AND cam = 'W')) THEN + INSERT INTO main.user_forum (forum, access_mode, password, user_access) VALUES ( fid, am, pass, ua ); + ELSE + INSERT INTO main.user_forum (forum, access_mode, user_access) VALUES ( fid, am, ua ); + END IF; + + RETURN fid; +END; +$$ LANGUAGE plpgsql; + + + + +-- +-- Category / user mapping +-- +-- If the account gets disabled (QUIT, INAC), we need to mark the forums for deletion. +-- If the account gets re-enabled (STD), we need to restore the forums. +-- If the account gets kicked, we need to delete the forums immediately. + +CREATE TABLE main.user_category ( + account BIGINT PRIMARY KEY REFERENCES main.account (id) ON DELETE CASCADE, + category BIGINT NOT NULL UNIQUE REFERENCES forums.category (id) ON DELETE CASCADE, + default_access CHAR(1) NOT NULL DEFAULT 'I' CHECK( default_access IN ('P', 'I') ), + user_access CHAR(1) NOT NULL DEFAULT 'T' CHECK( user_access IN ('M', 'L', 'T', 'P', 'R') ) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON main.user_category TO legacyworlds; + +-- Trigger function & definition for the account table that allows handling the users' categories. +CREATE OR REPLACE FUNCTION main.trgf_account_user_forums() RETURNS TRIGGER AS $$ +DECLARE + cid BIGINT; +BEGIN + -- No status change, proceed + IF NEW.status = OLD.status THEN + RETURN NEW; + END IF; + + -- Get the user's category + SELECT INTO cid category FROM main.user_category WHERE account = NEW.id; + IF NOT FOUND THEN + RETURN NEW; + END IF; + + -- Check the new status + IF NEW.status = 'INAC' OR NEW.status = 'QUIT' THEN + PERFORM forums.delete_forums( cid, NEW.id ); + ELSIF NEW.status = 'STD' THEN + PERFORM forums.restore_forums( cid ); + ELSIF NEW.status = 'KICKED' THEN + DELETE FROM forums.category WHERE id = cid; + END IF; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_account_user_forums BEFORE UPDATE ON main.account + FOR EACH ROW EXECUTE PROCEDURE main.trgf_account_user_forums(); + + + +-- +-- User forums +-- + +CREATE TABLE main.user_forum ( + forum BIGINT PRIMARY KEY REFERENCES forums.t_forum (id) ON DELETE CASCADE, + access_mode CHAR(1) DEFAULT NULL CHECK( access_mode IS NULL + OR access_mode IN ('P', 'W', 'I') ), + password VARCHAR(64) DEFAULT NULL, + user_access CHAR(1) DEFAULT NULL CHECK( user_access IS NULL + OR user_access IN ('M', 'L', 'T', 'P', 'R') ), + CHECK( (main.uf_get_access_mode(forum) = 'W' AND password IS NOT NULL) + OR (main.uf_get_access_mode(forum) <> 'W' AND password IS NULL) ) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON main.user_forum TO legacyworlds; + + +-- +-- Subscriptions +-- +-- An user can't subscribe to one of his own forums. +-- + +CREATE TABLE main.uf_subscription ( + forum BIGINT NOT NULL REFERENCES main.user_forum (forum) ON DELETE CASCADE, + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + access_mode CHAR(1) DEFAULT NULL CHECK( access_mode IS NULL + OR access_mode IN ('M', 'L', 'T', 'P', 'R') ), + PRIMARY KEY( forum, account ) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON main.uf_subscription TO legacyworlds; + +-- Trigger function & definition +CREATE OR REPLACE FUNCTION main.trgf_uf_subscription_check() RETURNS TRIGGER AS $$ +DECLARE + fo BIGINT; +BEGIN + IF TG_OP = 'INSERT' THEN + SELECT INTO fo account FROM main.user_category c, forums.t_forum f + WHERE c.category = f.category AND f.id = NEW.forum; + IF FOUND AND fo = NEW.account THEN + RETURN NULL; + END IF; + ELSIF TG_OP = 'UPDATE' THEN + NEW.forum = OLD.forum; + NEW.account = OLD.account; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_uf_subscription_check BEFORE INSERT OR UPDATE ON main.uf_subscription + FOR EACH ROW EXECUTE PROCEDURE main.trgf_uf_subscription_check(); + + +-- +-- Invites +-- +-- An user can only be invited to an invite-only user forum. +-- An user can be invited if he's not already a subscriber to the forum. +-- An user can't be invited to one of his own forums. +-- + +CREATE TABLE main.uf_invite ( + forum BIGINT NOT NULL REFERENCES main.user_forum (forum) ON DELETE CASCADE, + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + sent_on INT NOT NULL DEFAULT UNIX_TIMESTAMP( NOW() ), + PRIMARY KEY( forum, account ) +); + +GRANT SELECT,INSERT,DELETE ON main.uf_invite TO legacyworlds; + +-- Trigger function & definition +CREATE OR REPLACE FUNCTION main.trgf_uf_invite_check() RETURNS TRIGGER AS $$ +DECLARE + m TEXT; + fo BIGINT; +BEGIN + -- Check the forum's access mode + m := main.uf_get_access_mode( NEW.forum ); + IF m IS NULL OR m <> 'I' THEN + RETURN NULL; + END IF; + -- Check for a subscription by the user + PERFORM * FROM main.uf_subscription WHERE forum = NEW.forum AND account = NEW.account; + IF FOUND THEN + RETURN NULL; + END IF; + -- Check for an existing invite for the user + PERFORM * FROM main.uf_invite WHERE forum = NEW.forum AND account = NEW.account; + IF FOUND THEN + RETURN NULL; + END IF; + -- Check if the user is actually the forum's owner + SELECT INTO fo account FROM main.user_category c, forums.t_forum f + WHERE c.category = f.category AND f.id = NEW.forum; + IF FOUND AND fo = NEW.account THEN + RETURN NULL; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER trg_uf_invite_check BEFORE INSERT ON main.uf_invite + FOR EACH ROW EXECUTE PROCEDURE main.trgf_uf_invite_check(); diff -Naur beta5//sql/18-main-functions.sql forums//sql/18-main-functions.sql --- beta5//sql/18-main-functions.sql 2011-02-05 10:09:56.244335002 +0100 +++ forums//sql/18-main-functions.sql 2011-02-05 10:10:01.774335002 +0100 @@ -27,8 +27,13 @@ -- -- Function that registers a game -- -CREATE OR REPLACE FUNCTION main.register_game (version TEXT, game_name TEXT) RETURNS VOID AS $$ +CREATE OR REPLACE FUNCTION main.register_game (ver TEXT, gn TEXT) RETURNS VOID AS $$ +BEGIN INSERT INTO main.ranking_game (ranking, game) - SELECT id, $2 FROM main.ranking_def - WHERE version = $1; -$$ LANGUAGE SQL; + SELECT id, gn FROM main.ranking_def + WHERE version = ver; + PERFORM main.init_general_forums(); + PERFORM main.init_version_forums( ver ); + PERFORM main.init_game_forums( gn ); +END; +$$ LANGUAGE plpgsql; diff -Naur beta5//sql/19-main-values.sql forums//sql/19-main-values.sql --- beta5//sql/19-main-values.sql 2011-02-05 10:09:56.244335002 +0100 +++ forums//sql/19-main-values.sql 2011-03-12 15:05:03.511300054 +0100 @@ -77,49 +77,6 @@ \. -COPY main.f_smiley FROM STDIN; -:-?\\) smile -[:;]-?p razz -:-?D lol -[:;]-> biggrin -;-?\\) wink -;-?D mrgreen -:-?\\( sad -:evil: evil -:smile: smile -:happy: smile -:wink: wink -:sad: sad -:unhappy: sad -:'\\( cry -:cry: cry -:crying: cry -:grin: biggrin -:lol: lol -:tongue: razz -:rofl: mrgreen -[:;]-?\\| neutral -:neutral: neutral -\. - - -COPY main.f_code FROM STDIN; -\\[b\\](.*?)\\[\\/b\\] $1 -\\[u\\](.*?)\\[\\/u\\] $1 -\\[i\\](.*?)\\[\\/i\\] $1 -\\[sep(arator)?\\]


-\\[item\\](.*?)\\[\\/item\\]
  • $1
-\\[quote\\](.*?)\\[\\/quote\\]
$1
-\\[quote=([^\\]]+)\\](.*?)\\[\\/quote\\]
$1 said:
$2
-\\[link=(http[^\\]]+)\\](.+?)\\[\\/link\\] $2 -\\[code\\](.*?)\\[\\/code\\]
$1
-<\\/li><\\/ul>\\s*(\\s*)*
  • -\\[manual\\](.*?)\\[\\/manual\\] $1 -\\[manual=(\\w+)(#\\w+)?\\](.*?)\\[\\/manual\\] $3 -\\[topic=(\\d+)\\](.*?)\\[\\/topic\\] $2 -\. - - -- Connect to the database in USER mode \c legacyworlds legacyworlds diff -Naur beta5//sql/beta5/00-beta5.sql forums//sql/beta5/00-beta5.sql --- beta5//sql/beta5/00-beta5.sql 2011-02-05 10:09:56.184335002 +0100 +++ forums//sql/beta5/00-beta5.sql 2011-02-05 10:10:01.744335002 +0100 @@ -44,3 +44,6 @@ 'for a long term estimate of the bets players'' accomplishments.'); SELECT add_ranking_description('beta5', 'p_idr', 'en', 'Inflicted Damage Ranking', 'This ranking represents the amount of damage a player has inflicted on other players'' fleets.'); + + +SELECT forums.add_category_type( 'beta5/aforums', 'en', 'Alliance forums' ); diff -Naur beta5//sql/beta5/structure/01-alliance.sql forums//sql/beta5/structure/01-alliance.sql --- beta5//sql/beta5/structure/01-alliance.sql 2011-02-05 10:09:56.174335002 +0100 +++ forums//sql/beta5/structure/01-alliance.sql 2011-03-12 14:59:24.651300052 +0100 @@ -20,12 +20,15 @@ successor BIGINT REFERENCES player (id), democracy BOOLEAN NOT NULL DEFAULT FALSE, default_grade BIGINT NOT NULL, + f_category BIGINT NOT NULL DEFAULT forums.make_category('beta5/forums') + REFERENCES forums.category (id), enable_tt CHAR(1) NOT NULL DEFAULT 'N' CHECK(enable_tt IN ('N', 'S', 'R')) ); CREATE INDEX alliance_leader ON alliance (leader); CREATE INDEX alliance_successor ON alliance (successor); CREATE INDEX alliance_def_grade ON alliance (default_grade); +CREATE INDEX alliance_f_category ON alliance (f_category); GRANT SELECT,INSERT,UPDATE,DELETE ON alliance TO legacyworlds; GRANT SELECT,UPDATE ON alliance_id_seq TO legacyworlds; diff -Naur beta5//sql/beta5/structure/02-alliance-forums.sql forums//sql/beta5/structure/02-alliance-forums.sql --- beta5//sql/beta5/structure/02-alliance-forums.sql 2011-02-05 10:09:56.174335002 +0100 +++ forums//sql/beta5/structure/02-alliance-forums.sql 2011-03-12 15:02:40.271300051 +0100 @@ -10,91 +10,268 @@ -- -------------------------------------------------------- -CREATE TABLE af_forum ( - id SERIAL PRIMARY KEY, - alliance INT NOT NULL REFERENCES alliance(id) ON DELETE CASCADE, - forder INT NOT NULL CHECK(forder >= 0), - title VARCHAR(64) NOT NULL, - description TEXT, - topics INT NOT NULL DEFAULT 0 CHECK(topics >= 0), - posts INT NOT NULL DEFAULT 0 CHECK(posts >= 0), - last_post BIGINT, - user_post BOOLEAN NOT NULL DEFAULT TRUE, - UNIQUE(alliance, forder), - UNIQUE(alliance, title) -); - -CREATE INDEX af_forum_last_post ON af_forum (last_post); - -GRANT SELECT,INSERT,UPDATE,DELETE ON af_forum TO legacyworlds; -GRANT SELECT,UPDATE ON af_forum_id_seq TO legacyworlds; - - - -CREATE TABLE af_topic ( - id BIGSERIAL NOT NULL PRIMARY KEY, - forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, - first_post BIGINT NOT NULL, - last_post BIGINT, - sticky BOOLEAN NOT NULL DEFAULT FALSE -); +-- +-- Access types for alliance forums +-- +-- - M: only mods can post, create topics and polls +-- - P: only mods can create topics and polls, users can post +-- - T: only mods can create polls, users can create topics and post +-- - L: users can create polls or topics and post +-- -CREATE INDEX af_topic_forum ON af_topic (forum); -CREATE INDEX af_topic_first_post ON af_topic (first_post); -CREATE INDEX af_topic_last_post ON af_topic (last_post); - -GRANT SELECT,INSERT,UPDATE,DELETE ON af_topic TO legacyworlds; -GRANT SELECT,UPDATE ON af_topic_id_seq TO legacyworlds; +-- +-- Alliance forums +-- -CREATE TABLE af_post ( - id BIGSERIAL NOT NULL PRIMARY KEY, - forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, - topic BIGINT REFERENCES af_topic (id) ON DELETE CASCADE, - author BIGINT NOT NULL REFERENCES player (id), - reply_to BIGINT REFERENCES af_post (id) ON DELETE NO ACTION, - moment INT NOT NULL DEFAULT INT4(EXTRACT(EPOCH FROM NOW())), - title VARCHAR(100) NOT NULL, - contents TEXT NOT NULL, - enable_code BOOLEAN NOT NULL DEFAULT TRUE, - enable_smileys BOOLEAN NOT NULL DEFAULT TRUE, - edited INT, - edited_by BIGINT REFERENCES player(id) +CREATE TABLE alliance_forum ( + forum BIGINT PRIMARY KEY REFERENCES forums.t_forum (id) ON DELETE CASCADE, + alliance INT NOT NULL REFERENCES alliance(id) ON DELETE CASCADE, + access_mode CHAR(1) NOT NULL DEFAULT('T') CHECK( access_mode IN ('M', 'P', 'T', 'L') ), + UNIQUE( alliance, forum ) ); -CREATE INDEX af_post_forum ON af_post (forum); -CREATE INDEX af_post_topic ON af_post (topic); -CREATE INDEX af_post_author ON af_post (author); -CREATE INDEX af_post_reply_to ON af_post (reply_to); -CREATE INDEX af_post_edited_by ON af_post (edited_by); - -ALTER TABLE af_forum ADD FOREIGN KEY (last_post) REFERENCES af_post (id) ON DELETE SET NULL; -ALTER TABLE af_topic ADD FOREIGN KEY (first_post) REFERENCES af_post (id) ON DELETE CASCADE; -ALTER TABLE af_topic ADD FOREIGN KEY (last_post) REFERENCES af_post (id) ON DELETE SET NULL; +GRANT SELECT, INSERT, UPDATE ON alliance_forum TO legacyworlds; -GRANT SELECT,INSERT,UPDATE,DELETE ON af_post TO legacyworlds; -GRANT SELECT,UPDATE ON af_post_id_seq TO legacyworlds; +-- +-- Alliance ranks / forum access +-- -CREATE TABLE af_read ( - reader BIGINT NOT NULL REFERENCES player (id), - topic BIGINT NOT NULL REFERENCES af_topic (id) ON DELETE CASCADE, - PRIMARY KEY (reader, topic) +CREATE TABLE al_rank_forum ( + rank BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, + forum INT NOT NULL REFERENCES alliance_forum (forum) ON DELETE CASCADE, + is_mod BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY ( rank, forum ) ); -CREATE INDEX af_read_topic ON af_read (topic); -GRANT SELECT,INSERT,DELETE ON af_read TO legacyworlds; - +CREATE INDEX al_rank_forum_forum ON al_rank_forum (forum); +GRANT SELECT,INSERT,DELETE,UPDATE ON al_rank_forum TO legacyworlds; -CREATE TABLE algr_forums ( - grade BIGINT NOT NULL REFERENCES alliance_grade (id) ON DELETE CASCADE, - forum INT NOT NULL REFERENCES af_forum (id) ON DELETE CASCADE, - is_mod BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (grade, forum) -); - -CREATE INDEX algr_forums_forum ON algr_forums (forum); -GRANT SELECT,INSERT,DELETE,UPDATE ON algr_forums TO legacyworlds; +-- +-- create_alliance_forum( player, alliance, order, title, description, access_mode ) +-- +-- Creates a new forum in an alliance. Return values: +-- any positive value: identifier of the new forum +-- -1: alliance not found +-- -2: the player doesn't have the necessary privileges to create the forum +-- -3: the alliance already has 30 forums +-- -4: a forum with the same title already exists +-- -5: invalid access mode +-- -6: failure to create the generic forum + +CREATE OR REPLACE FUNCTION create_alliance_forum( pid BIGINT, aid BIGINT, fo INT, ttl TEXT, dsc TEXT, am TEXT) RETURNS BIGINT AS $$ +DECLARE + arec RECORD; + prec RECORD; + rid BIGINT; + rrec RECORD; + nf BIGINT; +BEGIN + -- Check the access mode + IF am NOT IN ('M','P','T','L') THEN + RETURN -5; + END IF; + + -- Get the alliance's record + SELECT INTO arec default_grade, f_category, leader FROM alliance WHERE id = aid; + IF NOT FOUND THEN + RETURN -1; + END IF; + + -- Get the player's record + SELECT INTO prec alliance, a_grade FROM player WHERE id = pid AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW())) + AND alliance = aid AND a_status = 'IN '; + IF NOT FOUND THEN + RETURN -2; + END IF; + + -- Get the player's rank + rid := CASE prec.a_grade IS NULL + WHEN TRUE THEN arec.default_grade + ELSE prec.a_grade + END; + SELECT INTO rrec forum_admin FROM alliance_grade WHERE id = rid; + IF NOT FOUND THEN + RETURN -2; + END IF; + + -- Check if the player has access + IF NOT (pid = arec.leader OR rrec.forum_admin) THEN + RETURN -2; + END IF; + + -- Check the amount of forums the alliance has + SELECT INTO nf COUNT(*) FROM alliance_forum WHERE alliance = aid; + IF nf >= 30 THEN + RETURN -3; + END IF; + + -- Check the forum's title + PERFORM * FROM forums.t_forum WHERE category = arec.f_category AND title = ttl; + IF FOUND THEN + RETURN -4; + END IF; + + -- Create the forum + nf := forums.make_forum( arec.f_category, fo, ttl, dsc ); + IF nf IS NULL THEN + RETURN -6; + END IF; + INSERT INTO alliance_forum (forum, alliance, access_mode) VALUES (nf, aid, am); + RETURN nf; +END; +$$ LANGUAGE plpgsql; + + +-- +-- modify_alliance_forum( player, forum, title, description, access_mode ) +-- +-- This function tries to modify an existing alliance forum. Return values: +-- 0 success +-- -1 invalid access mode +-- -2 the player is not in the alliance +-- -3 the player is not a forum administrator for the alliance +-- -4 forum not found + +CREATE OR REPLACE FUNCTION modify_alliance_forum( pid BIGINT, fid BIGINT, ttl TEXT, dsc TEXT, am TEXT ) RETURNS INT AS $$ +DECLARE + prec RECORD; + arec RECORD; + rid BIGINT; +BEGIN + -- Check the access mode + IF am NOT IN ('M','P','T','L') THEN + RETURN -1; + END IF; + + -- Get the player's alliance and rank + SELECT INTO prec alliance, a_grade FROM player WHERE id = pid AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW())) + AND alliance IS NOT NULL AND a_status = 'IN '; + IF NOT FOUND THEN + RETURN -2; + END IF; + + -- Check the player's privileges + SELECT INTO arec default_grade, leader FROM alliance WHERE id = prec.alliance; + IF (arec.leader <> pid) THEN + rid := CASE prec.a_grade IS NULL + WHEN TRUE THEN arec.default_grade + ELSE prec.a_grade + END; + PERFORM id FROM alliance_grade WHERE id = rid AND forum_admin; + IF NOT FOUND THEN + RETURN -3; + END IF; + END IF; + + -- Make sure the forum exists and hasn't been deleted + PERFORM f.id,af.alliance FROM forums.t_forum f, alliance_forum af + WHERE af.alliance = prec.alliance AND f.id = af.forum AND f.deleted IS NULL; + IF NOT FOUND THEN + RETURN -4; + END IF; + + -- Update both the forum and the access table + UPDATE forums.t_forum SET title = ttl, description = dsc WHERE id = fid; + UPDATE alliance_forum SET access_mode = am WHERE forum = fid; + RETURN 0; +END; +$$ LANGUAGE plpgsql; + + +-- +-- get_aforums_privs( player, forum ) +-- +-- Returns the set of privileges a player has over a specific alliance forum + +CREATE OR REPLACE FUNCTION get_aforums_privs( pid BIGINT, fid BIGINT, OUT can_view BOOLEAN, OUT can_post BOOLEAN, OUT can_create BOOLEAN, OUT can_poll BOOLEAN, OUT is_mod BOOLEAN, OUT is_admin BOOLEAN) AS $$ +DECLARE + cid BIGINT; + rid BIGINT; + arec RECORD; + b BOOLEAN; +BEGIN + -- Initialise all privileges to FALSE + is_admin := FALSE; + is_mod := FALSE; + can_view := FALSE; + can_post := FALSE; + can_create := FALSE; + can_poll := FALSE; + + -- Checks whether it's an alliance forum and get the associated data + SELECT INTO arec a.id, a.leader, a.default_grade, af.access_mode + FROM alliance a, alliance_forum af + WHERE af.forum = fid AND a.id = af.alliance; + IF NOT FOUND THEN + RETURN; + END IF; + + -- If the player is the leader, set all privileges to TRUE and return + IF arec.leader = pid THEN + is_admin := TRUE; + is_mod := TRUE; + can_view := TRUE; + can_post := TRUE; + can_create := TRUE; + can_poll := TRUE; + RETURN; + END IF; + + -- Checks whether the player is an actual member of the alliance and get his rank + SELECT INTO rid a_grade FROM player + WHERE id = pid AND (quit IS NULL OR quit > UNIX_TIMESTAMP(NOW())) + AND alliance = arec.id AND a_status = 'IN '; + IF NOT FOUND THEN + RETURN; + ELSIF rid IS NULL THEN + rid := arec.default_grade; + END IF; + + -- Checks whether the player is a forums admin for the alliance + SELECT INTO b forum_admin FROM alliance_grade WHERE id = rid; + IF NOT FOUND THEN + RETURN; + ELSIF b THEN + is_admin := TRUE; + is_mod := TRUE; + can_view := TRUE; + can_post := TRUE; + can_create := TRUE; + can_poll := TRUE; + RETURN; + END IF; + + -- Get the access level for this forum/rank combination + SELECT INTO b is_mod FROM al_rank_forum WHERE rank = rid AND forum = fid; + IF NOT FOUND THEN + RETURN; + ELSIF b THEN + is_mod := TRUE; + can_view := TRUE; + can_post := TRUE; + can_create := TRUE; + can_poll := TRUE; + END IF; + + -- We can view; can we post? + can_view := TRUE; + IF arec.access_mode = 'M' THEN + RETURN; + END IF; + + -- We can post; can we create topics? + can_post := TRUE; + IF arec.access_mode = 'P' THEN + RETURN; + END IF; + + -- We can create topics; can we create polls? + can_create := TRUE; + can_poll := (arec.access_mode = 'L'); +END; +$$ LANGUAGE plpgsql; diff -Naur beta5//sql/forums/00-schema.sql forums//sql/forums/00-schema.sql --- beta5//sql/forums/00-schema.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/00-schema.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,18 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/00-schema.sql +-- +-- Initialises the schema for the forums +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Connect to the database +\c legacyworlds legacyworlds_admin + +-- Create the forums schema +CREATE SCHEMA forums; +GRANT USAGE ON SCHEMA forums TO legacyworlds; + diff -Naur beta5//sql/forums/01-forums.sql forums//sql/forums/01-forums.sql --- beta5//sql/forums/01-forums.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/01-forums.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,77 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/01-forums.sql +-- +-- Categories, forums, topics and posts +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + + +-- +-- Create the table of forum category types +-- +CREATE TABLE forums.category_type ( + id SERIAL PRIMARY KEY, + lib_path VARCHAR(24) NOT NULL UNIQUE +); + +GRANT SELECT ON forums.category_type TO legacyworlds; +SELECT register_serial_table('forums', 'category_type'); + + + +-- +-- Create the table of translations for category types +-- +CREATE TABLE forums.cat_type_text ( + id INT NOT NULL REFERENCES forums.category_type (id) ON DELETE CASCADE, + lang VARCHAR(4) NOT NULL REFERENCES main.lang (txt) ON DELETE CASCADE, + name VARCHAR(32) NOT NULL, + PRIMARY KEY( id, lang ) +); + +CREATE INDEX i_forums_cat_type_lang ON forums.cat_type_text (lang); + +GRANT SELECT ON forums.cat_type_text TO legacyworlds; + + +-- +-- Create the category table to store forum groups +-- +CREATE TABLE forums.category ( + id BIGSERIAL PRIMARY KEY, + acl_lib INT NOT NULL REFERENCES forums.category_type (id) ON DELETE CASCADE +); + +CREATE INDEX i_forums_category_acl_lib ON forums.category (acl_lib); + +GRANT SELECT,INSERT,DELETE ON forums.category TO legacyworlds; +GRANT SELECT,UPDATE ON forums.category_id_seq TO legacyworlds; + +SELECT register_serial_table('forums', 'category'); + + +-- +-- Create the forums table +-- +CREATE TABLE forums.t_forum ( + id BIGSERIAL PRIMARY KEY, + category BIGINT NOT NULL REFERENCES forums.category (id) ON DELETE CASCADE, + f_order INT NOT NULL CHECK( f_order >= 0 ), + title VARCHAR(64) NOT NULL, + description TEXT, + deleted INT NULL, + deleted_by BIGINT NULL REFERENCES main.account (id) ON DELETE SET NULL, + UNIQUE( category, f_order ), + UNIQUE( category, title ) +); + +CREATE INDEX i_forum_deleted_by ON forums.t_forum (deleted_by); + +GRANT SELECT,INSERT,UPDATE,DELETE ON forums.t_forum TO legacyworlds; +GRANT SELECT,UPDATE ON forums.t_forum_id_seq TO legacyworlds; + +SELECT register_serial_table('forums', 't_forum'); diff -Naur beta5//sql/forums/01-signatures.sql forums//sql/forums/01-signatures.sql --- beta5//sql/forums/01-signatures.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/01-signatures.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,31 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/01-signatures.sql +-- +-- Signature storage table +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Create the signature storage table +-- +CREATE TABLE forums.signature ( + id BIGSERIAL PRIMARY KEY, + account BIGINT NOT NULL REFERENCES main.account(id) ON DELETE CASCADE, + sig_set INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + sig_unset INT, + signature TEXT NOT NULL, + enable_code BOOLEAN NOT NULL, + enable_smileys BOOLEAN NOT NULL, + CHECK( sig_unset IS NULL OR sig_set < sig_unset ) +); + +CREATE UNIQUE INDEX i_forums_signature ON forums.signature (account, sig_set); + +GRANT SELECT,INSERT,UPDATE,DELETE ON forums.signature TO legacyworlds; +GRANT SELECT,UPDATE ON forums.signature_id_seq TO legacyworlds; + +SELECT register_serial_table('forums', 'signature'); diff -Naur beta5//sql/forums/02-topics.sql forums//sql/forums/02-topics.sql --- beta5//sql/forums/02-topics.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/02-topics.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,92 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/02-topics.sql +-- +-- Topics, posts and post texts +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Create the topics table +-- +CREATE TABLE forums.t_topic ( + id BIGSERIAL PRIMARY KEY, + forum BIGINT NOT NULL REFERENCES forums.t_forum (id) ON DELETE CASCADE, + sticky_level INT2 NOT NULL DEFAULT 0 CHECK( sticky_level >= 0 AND sticky_level <= 10 ), + moved_from BIGINT NULL REFERENCES forums.t_forum (id) ON DELETE SET NULL, + deleted INT NULL, + deleted_by BIGINT NULL REFERENCES main.account (id) ON DELETE CASCADE, + locked BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE INDEX i_topic_forum ON forums.t_topic (forum); +CREATE INDEX i_topic_moved_from ON forums.t_topic (moved_from); +CREATE INDEX i_topic_deleted_by ON forums.t_topic (deleted_by); + +GRANT SELECT,INSERT,UPDATE,DELETE ON forums.t_topic TO legacyworlds; +GRANT SELECT,UPDATE ON forums.t_topic_id_seq TO legacyworlds; + +SELECT register_serial_table('forums', 't_topic'); + + +-- +-- Create the post table +-- + +CREATE TABLE forums.t_post ( + id BIGSERIAL PRIMARY KEY, + topic BIGINT NOT NULL REFERENCES forums.t_topic (id) ON DELETE CASCADE, + depth INT NOT NULL DEFAULT 0, + reply_to BIGINT REFERENCES forums.t_post (id), + signature BIGINT REFERENCES forums.signature (id) ON DELETE SET NULL, + deleted INT, + deleted_by BIGINT REFERENCES main.account (id) ON DELETE CASCADE +); + +CREATE INDEX i_post_topic ON forums.t_post (topic); +CREATE INDEX i_post_reply_to ON forums.t_post (reply_to); +CREATE INDEX i_post_signature ON forums.t_post (signature); +CREATE INDEX i_deleted_by ON forums.t_post (deleted_by); + +GRANT SELECT,INSERT,UPDATE,DELETE ON forums.t_post TO legacyworlds; +GRANT SELECT,UPDATE ON forums.t_post_id_seq TO legacyworlds; + +SELECT register_serial_table('forums', 't_post'); + + +-- +-- Create the table to store post texts +-- + +CREATE TABLE forums.post_text ( + post BIGINT NOT NULL REFERENCES forums.t_post (id) ON DELETE CASCADE, + moment INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + author BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + title VARCHAR(100) NOT NULL, + contents TEXT NOT NULL, + enable_code BOOLEAN NOT NULL, + enable_smileys BOOLEAN NOT NULL, + PRIMARY KEY( post, moment ) +); + +CREATE INDEX i_post_text_author ON forums.post_text (author); + +GRANT SELECT,INSERT,DELETE ON forums.post_text TO legacyworlds; + + +-- +-- Create a table that stores the last time an user read some topic +-- +CREATE TABLE forums.topic_read ( + topic BIGINT NOT NULL REFERENCES forums.t_topic (id) ON DELETE CASCADE, + read_by BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + read_at INT NOT NULL DEFAULT UNIX_TIMESTAMP(NOW()), + PRIMARY KEY( topic, read_by ) +); + +CREATE INDEX i_topic_read_by ON forums.topic_read (read_by); + +GRANT SELECT,INSERT,UPDATE,DELETE ON forums.topic_read TO legacyworlds; diff -Naur beta5//sql/forums/03-polls.sql forums//sql/forums/03-polls.sql --- beta5//sql/forums/03-polls.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/03-polls.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,55 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/03-polls.sql +-- +-- Forum polls, poll options and votes +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- Create the polls table +-- +CREATE TABLE forums.poll ( + topic BIGINT PRIMARY KEY REFERENCES forums.t_topic (id) ON DELETE CASCADE, + title VARCHAR(64) NOT NULL, + closed BOOLEAN NOT NULL DEFAULT FALSE, + deleted INT NULL, + deleted_by BIGINT NULL REFERENCES main.account(id) ON DELETE SET NULL +); + +CREATE INDEX i_poll_deleted_by ON forums.poll (deleted_by); + +GRANT SELECT,INSERT,UPDATE,DELETE ON forums.poll TO legacyworlds; + + +-- +-- Table for a poll's options +-- +CREATE TABLE forums.poll_option ( + id BIGSERIAL PRIMARY KEY, + poll BIGINT NOT NULL REFERENCES forums.t_topic (id) ON DELETE CASCADE, + po_order INT NOT NULL CHECK( po_order >= 0 ), + title VARCHAR(64) NOT NULL, + UNIQUE( poll, po_order ), + UNIQUE( poll, title ) +); + +GRANT SELECT,INSERT,UPDATE,DELETE ON forums.poll_option TO legacyworlds; +GRANT SELECT,UPDATE ON forums.poll_option_id_seq TO legacyworlds; + +SELECT register_serial_table('forums', 'poll_option'); + + +-- +-- Table for a poll's votes +-- +CREATE TABLE forums.poll_vote ( + vote BIGINT NOT NULL REFERENCES forums.poll_option (id) ON DELETE CASCADE, + account BIGINT NOT NULL REFERENCES main.account (id) ON DELETE CASCADE, + PRIMARY KEY( vote, account ) +); + +GRANT SELECT,INSERT,DELETE ON forums.poll_vote TO legacyworlds; diff -Naur beta5//sql/forums/10-views.sql forums//sql/forums/10-views.sql --- beta5//sql/forums/10-views.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/10-views.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,73 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/10-views.sql +-- +-- Defines a few views corresponding to some common +-- queries +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- +-- View for individual posts +-- + +CREATE VIEW forums.post + AS SELECT p.id AS id, t.forum AS forum, p.topic AS topic, t1.author AS author, + p.reply_to AS reply_to, p.signature AS signature, p.deleted AS deleted, + p.deleted_by AS deleted_by, p.depth AS depth, t1.moment AS post_moment, + t2.moment AS last_change, t2.author AS last_author, + t2.contents AS contents, t2.title AS title, + t2.enable_code AS enable_code, t2.enable_smileys AS enable_smileys + FROM forums.t_post p, forums.t_topic t, forums.post_text t1, forums.post_text t2 + WHERE t1.post = p.id AND t1.moment = (SELECT MIN(moment) FROM forums.post_text WHERE post = p.id) + AND t2.post = p.id AND t2.moment = (SELECT MAX(moment) FROM forums.post_text WHERE post = p.id) + AND t.id = p.topic; + +GRANT SELECT ON forums.post TO legacyworlds; + + +-- +-- View for forum topics +-- + +CREATE VIEW forums.topic_fp_lp + AS SELECT t.id, t.forum, MIN(p.post_moment) AS fp_moment, + MAX(p.last_change) AS lc_moment, COUNT(p.*) AS posts + FROM forums.t_topic t + LEFT JOIN forums.post p ON (p.topic = t.id AND ( + (t.deleted IS NULL AND p.deleted IS NULL) + OR (t.deleted IS NOT NULL AND p.deleted = t.deleted) )) + GROUP BY t.id, t.forum, t.deleted; + + +CREATE VIEW forums.topic + AS SELECT t.*, tt.posts AS posts, + fp.title, fp.id AS first_post, fp.author AS fp_author, fp.post_moment AS fp_moment, + lp.author AS lc_author, lp.last_change AS lc_moment + FROM forums.t_topic t + LEFT JOIN forums.topic_fp_lp tt ON (t.id = tt.id) + LEFT JOIN forums.post fp ON (fp.topic = t.id AND fp.post_moment = tt.fp_moment) + LEFT JOIN forums.post lp ON (lp.topic = t.id AND lp.last_change = tt.lc_moment) + ORDER BY sticky_level DESC, lc_moment DESC; + +GRANT SELECT ON forums.topic TO legacyworlds; + + +-- +-- View for forums +-- + +CREATE VIEW forums.forum + AS SELECT f.id AS id, f.category AS category, f.f_order AS f_order, + f.title AS title, f.description AS description, + f.deleted AS deleted, f.deleted_by AS deleted_by, + (SELECT COUNT(*) FROM forums.t_topic WHERE forum = f.id AND deleted IS NULL) AS topics, + (SELECT COUNT(*) FROM forums.post WHERE forum = f.id AND deleted IS NULL) AS posts + FROM forums.t_forum f + ORDER BY category, f_order; + +GRANT SELECT ON forums.forum TO legacyworlds; + diff -Naur beta5//sql/forums/11-access-functions.sql forums//sql/forums/11-access-functions.sql --- beta5//sql/forums/11-access-functions.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/11-access-functions.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,869 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/11-access-functions.sql +-- +-- Functions to access the forums and topics +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- -------------------------------------------------------- +-- LIST OF ALL REQUIRED FUNCTIONS +-- -------------------------------------------------------- +-- +-- Functions listed with a '*' have been implemented. +-- Functions listed with a '!' need modifications. +-- +-- +-- GENERAL CLEAN-UP +-- +-- forum_cleanup() * +-- Causes a general clean-up by deleting old stuff +-- that had been marked for deletion earlier. +-- +-- +-- SIGNATURE MANAGEMENT +-- +-- set_signature( account, set_all, new_signature, enable_code, enable_smileys ) * +-- Sets a new signature for the account +-- +-- get_signature( account, timestamp ) * +-- Returns the complete data for the account's +-- signature at the specified timestamp. +-- +-- +-- CATEGORIES AND FORUMS MANAGEMENT +-- +-- add_category_type( library_path , language , description ) * +-- Creates a category type with the specified library as +-- its handler if it doesn't exist, then add the description. +-- +-- make_category( access_library ) * +-- Creates a category and returns its ID +-- +-- make_forum( category, f_order, title, description ) * +-- Creates a forum and returns its ID +-- +-- move_up( forum ) * +-- Moves a forum up in the list of forums in a category +-- +-- move_down( forum ) * +-- Moves a forum down in the list of forums in a category +-- +-- delete_forum( forum , administrator ) * +-- Delete a forum and its contents +-- +-- restore_forum( forum ) * +-- Restores a deleted forum to its previous state +-- +-- delete_forums( category, administrator ) +-- Deletes all forums in a category +-- +-- restore_forums( category ) +-- Restore all forums in a category +-- +-- get_last_post( forum ) * +-- Returns the data from the last post in the specified forum +-- +-- get_read_topics( forum , user ) * +-- Returns the amount of topics an user has read since they +-- were last updated +-- +-- mark_forum_read( forum, read_by ) * +-- Marks all of a forum's topics as read by some user +-- +-- +-- TOPICS AND POSTS MANAGEMENT +-- +-- mark_topic_read( topic, read_by ) * +-- Marks a topic as read by some user +-- +-- create_topic( forum, author, sticky_level, title, contents, enable_code, enable_smileys, sig ) * +-- Creates a new topic in a forum and returns its +-- ID +-- +-- move_topic( topic, forum, user ) * +-- Moves a topic from a forum to another +-- +-- add_reply( reply_to, author, title, contents, enable_code, enable_smileys, signature ) * +-- Posts a reply to a post +-- +-- edit_post( post, author, contents, enable_code, enable_smileys, change_signature, signature ) * +-- Modifies a post +-- +-- delete_post( post, moderator ) * +-- Marks a post as deleted; delete the topic if the +-- post is the topic's "main" post +-- +-- restore_post( post ) * +-- Restores a deleted post or, if that post was the +-- first of a topic, restore the whole topic +-- +-- +-- FORUM POLLS +-- +-- create_poll( topic, title ) * +-- Adds a new poll to the database and returns the +-- new row +-- +-- create_option( poll, order, title ) * +-- Adds a poll option at the specified order and +-- returns the new row +-- +-- delete_option( poll, order ) * +-- Removes a poll option +-- +-- move_opt_up( poll, order ) * +-- Moves a poll option up in the list and returns a +-- boolean to indicate success or failure +-- +-- move_opt_down( poll, order ) * +-- Moves a poll option down in the list and returns a +-- boolean to indicate success or failure +-- +-- set_vote( user, poll, option) * +-- Sets the vote on a forum poll +-- + + + + +-- -------------------------------------------------------- +-- GENERAL CLEAN-UP +-- -------------------------------------------------------- + +-- +-- forum_cleanup() +-- +-- Causes a general clean-up by deleting old stuff that had been marked for deletion earlier. + +CREATE OR REPLACE FUNCTION forums.forum_cleanup() RETURNS VOID AS $$ +DECLARE + crec RECORD; + frec RECORD; + fod INT; + nts INT; +BEGIN + -- Start by locking the tables + LOCK TABLE forums.category, forums.t_forum, forums.t_topic, forums.t_post IN ACCESS EXCLUSIVE MODE; + + -- Now get the categories containing forums to be deleted + nts := UNIX_TIMESTAMP( NOW() ) - 28 * 24 * 3600; + FOR crec IN SELECT DISTINCT category FROM forums.t_forum + WHERE deleted IS NOT NULL AND deleted <= nts + LOOP + -- For each category, go through the list of forums + fod := 0; + FOR frec IN SELECT id,deleted FROM forums.t_forum WHERE category = crec.category + LOOP + -- This forum is to be deleted; remove it + IF (frec.deleted IS NOT NULL AND frec.deleted <= nts) THEN + DELETE FROM forums.t_forum WHERE id = frec.id; + fod := fod + 1; + + -- This forum is not to be deleted, however its order must be fixed + ELSIF (fod > 1) THEN + UPDATE forums.t_forum SET f_order = f_order - fod WHERE id = frec.id; + END IF; + END LOOP; + END LOOP; + + -- Delete topics and posts that were not part of forums to be deleted + DELETE FROM forums.t_topic WHERE deleted IS NOT NULL AND deleted <= nts; + DELETE FROM forums.t_post WHERE deleted IS NOT NULL AND deleted <= nts; +END; +$$ LANGUAGE plpgsql; + + + + +-- -------------------------------------------------------- +-- SIGNATURE MANAGEMENT +-- -------------------------------------------------------- + +-- +-- forums.set_signature( account, set_all, new_signature, enable_code, enable_smileys ) +-- +-- Sets a new signature for the account + +CREATE OR REPLACE FUNCTION forums.set_signature ( a BIGINT, s BOOLEAN, t TEXT, ec BOOLEAN, es BOOLEAN ) RETURNS VOID AS $$ +DECLARE + sid BIGINT; +BEGIN + -- Mark the previous signature as "ended" + UPDATE forums.signature + SET sig_unset = UNIX_TIMESTAMP(NOW()) - 1 + WHERE account = a AND sig_unset IS NULL; + + -- If there *is* a new signature, insert it + IF NOT(t IS NULL OR t = '') THEN + INSERT INTO forums.signature (account, signature, enable_code, enable_smileys) + VALUES ( a, t, ec, es ); + sid := last_inserted('signature'); + ELSE + sid := NULL; + END IF; + + -- Change all of the posts' signatures to the new one and delete + -- the old signatures if needed. + IF s THEN + UPDATE forums.t_post + SET signature = sid + WHERE author = a; + IF sid IS NOT NULL THEN + DELETE FROM forums.signature + WHERE account = a AND id <> sid; + ELSE + DELETE FROM forums.signature + WHERE account = a; + END IF; + END IF; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.get_signature( account, timestamp ) +-- +-- Returns the complete data for the account's signature at the specified timestamp. + +CREATE OR REPLACE FUNCTION forums.get_signature ( a BIGINT, ts INT ) RETURNS forums.signature AS $$ + SELECT * FROM forums.signature + WHERE account = $1 AND sig_set <= $2 + AND (sig_unset IS NULL OR sig_unset >= $2); +$$ LANGUAGE SQL; + + + + +-- -------------------------------------------------------- +-- FORUMS AND CATEGORIES MANAGEMENT +-- -------------------------------------------------------- + +-- +-- forums.add_category_type( library_path , language , description ) +-- +-- Creates a category type with the specified library as its handler if it doesn't exist, then add the description. + +CREATE OR REPLACE FUNCTION forums.add_category_type ( lp TEXT, lg TEXT, dsc TEXT ) RETURNS VOID AS $$ +DECLARE + ct INT; +BEGIN + SELECT INTO ct id FROM forums.category_type WHERE lib_path = lp; + IF NOT FOUND THEN + INSERT INTO forums.category_type (lib_path) VALUES (lp); + ct := last_inserted( 'category_type' ); + END IF; + + INSERT INTO forums.cat_type_text (id, lang, name) VALUES (ct, lg, dsc); +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.make_category( access_library ) +-- +-- Creates a category and returns its ID + +CREATE OR REPLACE FUNCTION forums.make_category ( al TEXT ) RETURNS BIGINT AS $$ +DECLARE + ct INT; +BEGIN + SELECT INTO ct id FROM forums.category_type WHERE lib_path = al; + IF NOT FOUND THEN + RETURN NULL; + END IF; + + INSERT INTO forums.category (acl_lib) VALUES ( ct ); + RETURN last_inserted( 'category' ); +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.make_forum( category, f_order, title, description ) +-- +-- Creates a forum and returns its ID + +CREATE OR REPLACE FUNCTION forums.make_forum( cid BIGINT, fo INT, tt TEXT, dsc TEXT ) + RETURNS BIGINT AS $$ +DECLARE + rec RECORD; + fid BIGINT; + rfo INT; +BEGIN + IF fo IS NULL THEN + SELECT INTO rfo MAX(f_order) + 1 FROM forums.t_forum WHERE category = cid; + ELSE + rfo := fo; + FOR rec IN SELECT id FROM forums.t_forum + WHERE category = cid AND f_order >= rfo + ORDER BY f_order DESC + FOR UPDATE + LOOP + UPDATE forums.t_forum SET f_order = f_order + 1 WHERE id = rec.id; + END LOOP; + END IF; + + INSERT INTO forums.t_forum (category, f_order, title, description) + VALUES (cid, rfo, tt, dsc); + fid := last_inserted( 't_forum' ); + RETURN fid; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.move_up( forum ) +-- +-- Moves a forum up in the list of forums in a category + +CREATE OR REPLACE FUNCTION forums.move_up ( fid BIGINT ) RETURNS BOOLEAN AS $$ +DECLARE + cid BIGINT; + fo INT; + mfo INT; +BEGIN + SELECT INTO fo COUNT(*) FROM forums.t_forum WHERE id = fid; + IF fo = 0 THEN + RETURN FALSE; + END IF; + SELECT INTO cid category FROM forums.t_forum WHERE id = fid; + + PERFORM * FROM forums.t_forum WHERE category = cid FOR UPDATE; + SELECT INTO fo f_order FROM forums.t_forum WHERE id = fid; + IF fo = 0 THEN + RETURN FALSE; + END IF; + + SELECT INTO mfo MAX(f_order) + 1 FROM forums.t_forum + WHERE category = (SELECT category FROM forums.t_forum WHERE id = fid); + + UPDATE forums.t_forum SET f_order = mfo WHERE id = fid; + UPDATE forums.t_forum SET f_order = fo WHERE category = cid AND f_order = fo - 1; + UPDATE forums.t_forum SET f_order = fo - 1 WHERE id = fid; + + RETURN TRUE; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.move_down( forum ) +-- +-- Moves a forum down in the list of forums in a category + +CREATE OR REPLACE FUNCTION forums.move_down ( fid BIGINT ) RETURNS BOOLEAN AS $$ +DECLARE + cid BIGINT; + fo INT; + mfo INT; +BEGIN + SELECT INTO fo COUNT(*) FROM forums.t_forum WHERE id = fid; + IF fo = 0 THEN + RETURN FALSE; + END IF; + SELECT INTO cid category FROM forums.t_forum WHERE id = fid; + + PERFORM * FROM forums.t_forum WHERE category = cid FOR UPDATE; + SELECT INTO fo f_order FROM forums.t_forum WHERE id = fid; + SELECT INTO mfo MAX(f_order) FROM forums.t_forum + WHERE category = (SELECT category FROM forums.t_forum WHERE id = fid); + IF fo = mfo THEN + RETURN FALSE; + END IF; + + UPDATE forums.t_forum SET f_order = mfo + 1 WHERE id = fid; + UPDATE forums.t_forum SET f_order = fo WHERE category = cid AND f_order = fo + 1; + UPDATE forums.t_forum SET f_order = fo + 1 WHERE id = fid; + + RETURN TRUE; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.delete_forum( forum , administrator ) +-- +-- Delete a forum and its contents + +CREATE OR REPLACE FUNCTION forums.delete_forum( fid BIGINT, aid BIGINT ) RETURNS VOID AS $$ +DECLARE + cts INT; +BEGIN + -- Check if the forum exists and hasn't been deleted + PERFORM * FROM forums.t_forum WHERE id = fid AND deleted IS NULL; + IF NOT FOUND THEN + RETURN; + END IF; + + -- Marks the forum and its contents as deleted + cts := UNIX_TIMESTAMP( NOW () ); + UPDATE forums.t_forum SET deleted = cts, deleted_by = aid WHERE id = fid; + -- FIXME Argh! f_order! headache! + UPDATE forums.t_post SET deleted = cts, deleted_by = aid + WHERE topic IN (SELECT id FROM forums.t_topic WHERE forum = fid AND deleted IS NULL) + AND deleted IS NULL; + UPDATE forums.t_topic SET deleted = cts, deleted_by = aid + WHERE forum = fid AND deleted IS NULL; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.restore_forum( forum ) +-- +-- Restores a deleted forum to its previous state + +CREATE OR REPLACE FUNCTION forums.restore_forum( fid BIGINT ) RETURNS VOID AS $$ +DECLARE + ts INT; +BEGIN + SELECT INTO ts deleted FROM forums.t_forum + WHERE id = fid AND deleted IS NOT NULL; + IF NOT FOUND THEN + RETURN; + END IF; + + -- Marks the forum and its contents as deleted + UPDATE forums.t_forum SET deleted = NULL, deleted_by = NULL WHERE id = fid; + -- FIXME Argh! f_order! headache! + UPDATE forums.t_post SET deleted = NULL, deleted_by = NULL + WHERE topic IN (SELECT id FROM forums.t_topic WHERE forum = fid AND deleted = ts) + AND deleted = ts; + UPDATE forums.t_topic SET deleted = NULL, deleted_by = NULL + WHERE forum = fid AND deleted = ts; +END; +$$ LANGUAGE plpgsql; + +-- +-- forums.delete_forums( category, administrator ) +-- +-- Deletes all forums in a category + +CREATE OR REPLACE FUNCTION forums.delete_forums( cid BIGINT, aid BIGINT ) RETURNS VOID AS $$ +DECLARE + f RECORD; +BEGIN + FOR f IN SELECT id FROM forums.t_forum WHERE category = cid AND deleted IS NULL + LOOP + PERFORM forums.delete_forum( f.id, aid ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.restore_forums( category ) +-- +-- Restore all forums in a category + +CREATE OR REPLACE FUNCTION forums.restore_forums( cid BIGINT ) RETURNS VOID AS $$ +DECLARE + f RECORD; +BEGIN + FOR f IN SELECT id FROM forums.t_forum WHERE category = cid AND deleted IS NOT NULL + LOOP + PERFORM forums.restore_forum( f.id ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.get_last_post( forum ) +-- +-- Returns the data from the last post in the specified forum + +CREATE OR REPLACE FUNCTION forums.get_last_post( fid BIGINT ) RETURNS forums.post AS $$ + SELECT * FROM forums.post + WHERE forum = $1 AND deleted IS NULL + AND last_change = (SELECT MAX(last_change) FROM forums.post WHERE forum = $1 AND deleted IS NULL); +$$ LANGUAGE SQL; + + +-- +-- forums.get_read_topics( forum , user ) +-- +-- Returns the amount of topics an user has read since they were last updated + +CREATE OR REPLACE FUNCTION forums.get_read_topics( fid BIGINT, aid BIGINT ) RETURNS BIGINT AS $$ + SELECT COUNT(DISTINCT p.topic) + FROM forums.post p + LEFT JOIN forums.topic_read r ON (p.topic = r.topic AND p.last_change <= r.read_at) + WHERE p.forum = $1 AND r.read_by = $2 AND p.deleted IS NULL; +$$ LANGUAGE SQL; + + +-- +-- forums.mark_forum_read( forum, read_by ) +-- +-- Marks all of a forum's topics as read by some user + +CREATE OR REPLACE FUNCTION forums.mark_forum_read( fid BIGINT, aid BIGINT ) RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + FOR rec IN SELECT id FROM forums.t_topic WHERE deleted IS NULL AND forum = fid FOR UPDATE + LOOP + PERFORM forums.mark_topic_read( rec.id, aid ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + + +-- -------------------------------------------------------- +-- TOPICS AND POSTS MANAGEMENT +-- -------------------------------------------------------- + +-- +-- forums.mark_topic_read( topic, read_by ) +-- +-- Marks a topic as read by some user + +CREATE OR REPLACE FUNCTION forums.mark_topic_read ( tid BIGINT, rid BIGINT ) RETURNS VOID AS $$ +DECLARE + rr RECORD; +BEGIN + SELECT INTO rr * FROM forums.topic_read WHERE topic = tid AND read_by = rid FOR UPDATE; + IF NOT FOUND THEN + INSERT INTO forums.topic_read (topic, read_by) VALUES (tid, rid); + ELSE + UPDATE forums.topic_read SET read_at = UNIX_TIMESTAMP(NOW()) + WHERE topic = tid AND read_by = rid; + END IF; +END; +$$ LANGUAGE plpgsql; + + + +-- +-- forums.create_topic( forum, author, sticky_level, title, contents, enable_code, enable_smileys, sig ) +-- +-- Creates a new topic in a forum and returns its ID + +CREATE OR REPLACE FUNCTION forums.create_topic( + fid BIGINT, aid BIGINT, sl INT, + ttl TEXT, c TEXT, ec BOOLEAN, + es BOOLEAN, sig BIGINT ) + RETURNS BIGINT AS $$ +DECLARE + tid BIGINT; + pid BIGINT; +BEGIN + INSERT INTO forums.t_topic (forum, sticky_level) VALUES (fid, sl); + SELECT INTO tid last_inserted('t_topic'); + + INSERT INTO forums.t_post (topic, signature) VALUES (tid, sig); + SELECT INTO pid last_inserted('t_post'); + + INSERT INTO forums.post_text (post, author, title, contents, enable_code, enable_smileys) + VALUES (pid, aid, ttl, c, ec, es); + INSERT INTO forums.topic_read (topic, read_by) VALUES (tid, aid); + + RETURN tid; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.move_topic( topic, forum, user ) * +-- +-- Moves a topic from a forum to another + +CREATE OR REPLACE FUNCTION forums.move_topic( tid BIGINT, did BIGINT, aid BIGINT ) RETURNS VOID AS $$ +DECLARE + cfid BIGINT; +BEGIN + SELECT INTO cfid forum FROM forums.t_topic WHERE id = tid; + IF NOT FOUND THEN + RETURN; + END IF; + + UPDATE forums.t_topic SET moved_from = cfid, forum = did WHERE id = tid; + DELETE FROM forums.topic_read WHERE topic = tid; + PERFORM forums.mark_topic_read( tid, aid ); +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.add_reply( reply_to, author, title, contents, enable_code, enable_smileys, signature ) +-- +-- Posts a reply to a post + +CREATE OR REPLACE FUNCTION forums.add_reply ( + rid BIGINT, aid BIGINT, ttl TEXT, + c TEXT, ec BOOLEAN, es BOOLEAN, + sig BIGINT ) + RETURNS BIGINT AS $$ +DECLARE + rr RECORD; + pid BIGINT; +BEGIN + SELECT INTO rr * FROM forums.t_post WHERE id = rid AND deleted IS NULL; + IF NOT FOUND THEN + RETURN -1; + END IF; + + INSERT INTO forums.t_post (topic, reply_to, signature, depth) + VALUES (rr.topic, rid, sig, rr.depth + 1); + SELECT INTO pid last_inserted('t_post'); + + INSERT INTO forums.post_text (post, author, title, contents, enable_code, enable_smileys) + VALUES (pid, aid, ttl, c, ec, es); + + PERFORM forums.mark_topic_read( rr.topic, aid ); + + RETURN pid; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.edit_post( post, author, contents, enable_code, enable_smileys, change_signature, signature ) +-- +-- Modifies a post + +CREATE OR REPLACE FUNCTION forums.edit_post ( + pid BIGINT, aid BIGINT, ttl TEXT, + c TEXT, ec BOOLEAN, es BOOLEAN, + chsig BOOLEAN, sig BIGINT ) + RETURNS VOID AS $$ +DECLARE + tid BIGINT; +BEGIN + PERFORM * FROM forums.t_post WHERE id = pid AND deleted IS NULL; + IF NOT FOUND THEN + RETURN; + END IF; + + INSERT INTO forums.post_text (post, author, title, contents, enable_code, enable_smileys) + VALUES (pid, aid, ttl, c, ec, es); + IF chsig THEN + UPDATE forums.t_post SET signature = sig WHERE id = pid; + END IF; + + SELECT INTO tid topic FROM forums.t_post WHERE id = pid; + PERFORM forums.mark_topic_read( tid, aid ); +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.delete_post( post, moderator ) +-- +-- Marks a post as deleted; delete the topic if the post is the topic's "main" post + +CREATE OR REPLACE FUNCTION forums.delete_post ( pid BIGINT, mid BIGINT ) RETURNS VOID AS $$ +DECLARE + ppid BIGINT; + cts INT; +BEGIN + SELECT INTO ppid reply_to FROM forums.t_post WHERE id = pid AND deleted IS NULL; + IF NOT FOUND THEN + RETURN; + END IF; + + cts := UNIX_TIMESTAMP(NOW()); + IF ppid IS NULL THEN + -- Delete the topic + SELECT INTO ppid topic FROM forums.t_post WHERE id = pid; + UPDATE forums.t_topic SET deleted = cts, deleted_by = mid WHERE id = ppid; + UPDATE forums.poll SET deleted = cts, deleted_by = mid WHERE topic = ppid; + UPDATE forums.t_post SET deleted = cts, deleted_by = mid + WHERE topic = ppid AND deleted IS NULL; + ELSE + -- Delete the post and reparent replies; + UPDATE forums.t_post SET deleted = cts, deleted_by = mid WHERE id = pid; + UPDATE forums.t_post SET reply_to = ppid, depth = depth - 1 WHERE reply_to = pid; + END IF; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.restore_post( post ) +-- +-- Restores a deleted post or, if that post was the first of a topic, restore the whole topic + +CREATE OR REPLACE FUNCTION forums.restore_post ( pid BIGINT ) RETURNS VOID AS $$ +DECLARE + r RECORD; +BEGIN + SELECT INTO r reply_to, deleted, topic FROM forums.t_post + WHERE id = pid AND deleted IS NOT NULL; + IF NOT FOUND THEN + RETURN; + END IF; + + IF r.reply_to IS NULL THEN + -- Restore the topic + UPDATE forums.t_post SET deleted = NULL, deleted_by = NULL + WHERE topic = r.topic AND deleted = r.deleted; + UPDATE forums.t_topic SET deleted = NULL, deleted_by = NULL + WHERE id = r.topic; + UPDATE forums.poll SET deleted = NULL, deleted_by = NULL + WHERE topic = r.topic AND deleted = r.deleted; + ELSE + -- Restore the post + UPDATE forums.t_post SET deleted = NULL, deleted_by = NULL + WHERE id = pid; + END IF; +END; +$$ LANGUAGE plpgsql; + + + + +-- -------------------------------------------------------- +-- FORUM POLLS +-- -------------------------------------------------------- + +-- +-- forums.create_poll( topic, title ) +-- +-- Adds a new poll to the database and returns the new row + +CREATE OR REPLACE FUNCTION forums.create_poll( t_id BIGINT, ttl TEXT ) RETURNS forums.poll AS $$ +DECLARE + rv forums.poll; +BEGIN + INSERT INTO forums.poll (topic, title) VALUES (t_id, ttl); + SELECT INTO rv * FROM forums.poll WHERE topic = t_id; + RETURN rv; +EXCEPTION WHEN unique_violation OR foreign_key_violation THEN + rv.topic := NULL; + rv.title := NULL; + RETURN rv; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.create_option( poll, order, title ) +-- +-- Adds a poll option at the specified order and returns the new row + +CREATE OR REPLACE FUNCTION forums.create_option( p_id BIGINT, oo INT, ttl TEXT ) RETURNS forums.poll_option AS $$ +DECLARE + rv forums.poll_option; + rec RECORD; + noid BIGINT; +BEGIN + FOR rec IN SELECT * FROM forums.poll_option + WHERE poll = p_id AND po_order >= oo + ORDER BY po_order DESC + FOR UPDATE + LOOP + UPDATE forums.poll_option SET po_order = po_order + 1 WHERE id = rec.id; + END LOOP; + + INSERT INTO forums.poll_option (poll, po_order, title) VALUES (p_id, oo, ttl); + SELECT INTO noid last_inserted('poll_option'); + + SELECT INTO rv * FROM forums.poll_option WHERE id = noid; + RETURN rv; + +EXCEPTION WHEN foreign_key_violation OR unique_violation THEN + rv.id := NULL; + rv.poll := NULL; + RETURN rv; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.delete_option( poll, order ) +-- +-- Removes a poll option + +CREATE OR REPLACE FUNCTION forums.delete_option( p_id BIGINT, oo INT ) RETURNS VOID AS $$ +DECLARE + rec RECORD; +BEGIN + PERFORM * FROM forums.poll_option WHERE poll = p_id AND po_order > oo FOR UPDATE; + DELETE FROM forums.poll_option WHERE poll = p_id AND po_order = oo; + FOR rec IN SELECT id FROM forums.poll_option WHERE poll = p_id AND po_order > oo ORDER BY po_order ASC + LOOP + UPDATE forums.poll_option SET po_order = po_order - 1 WHERE id = rec.id; + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.move_opt_up( poll, order ) +-- +-- Moves a poll option up in the list and returns a boolean to indicate success or failure + +CREATE OR REPLACE FUNCTION forums.move_opt_up( p_id BIGINT, oo INT ) RETURNS BOOLEAN AS $$ +DECLARE + c BIGINT; +BEGIN + IF oo = 0 THEN + RETURN FALSE; + END IF; + + SELECT INTO c COUNT(*) FROM forums.poll_option WHERE poll = p_id; + IF NOT FOUND OR oo >= c THEN + RETURN FALSE; + END IF; + + UPDATE forums.poll_option SET po_order = c WHERE poll = p_id AND po_order = oo - 1; + UPDATE forums.poll_option SET po_order = po_order - 1 WHERE poll = p_id AND po_order = oo; + UPDATE forums.poll_option SET po_order = oo WHERE poll = p_id AND po_order = c; + RETURN TRUE; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.move_opt_down( poll, order ) +-- +-- Moves a poll option down in the list and returns a boolean to indicate success or failure + +CREATE OR REPLACE FUNCTION forums.move_opt_down( p_id BIGINT, oo INT ) RETURNS BOOLEAN AS $$ +DECLARE + c BIGINT; +BEGIN + SELECT INTO c COUNT(*) FROM forums.poll_option WHERE poll = p_id; + IF NOT FOUND OR oo >= c - 1 THEN + RETURN FALSE; + END IF; + + UPDATE forums.poll_option SET po_order = c WHERE poll = p_id AND po_order = oo + 1; + UPDATE forums.poll_option SET po_order = po_order + 1 WHERE poll = p_id AND po_order = oo; + UPDATE forums.poll_option SET po_order = oo WHERE poll = p_id AND po_order = c; + RETURN TRUE; +END; +$$ LANGUAGE plpgsql; + + +-- +-- forums.set_vote( user, poll, option ) +-- +-- Sets the vote on a forum poll + +CREATE OR REPLACE FUNCTION forums.set_vote( u_id BIGINT, p_id BIGINT, o_id BIGINT ) RETURNS VOID AS $$ +DECLARE + p BIGINT; +BEGIN + -- Delete the previous vote + DELETE FROM forums.poll_vote + WHERE account = u_id + AND vote IN (SELECT id FROM forums.poll_option WHERE poll = p_id); + + IF o_id IS NOT NULL THEN + -- Add the new vote if that is possible + SELECT INTO p poll FROM forums.poll_option WHERE id = o_id; + IF NOT FOUND OR p <> p_id THEN + RETURN; + END IF; + INSERT INTO forums.poll_vote (vote, account) VALUES (o_id, u_id); + END IF; +END; +$$ LANGUAGE plpgsql; diff -Naur beta5//sql/forums/FORUMS.sql forums//sql/forums/FORUMS.sql --- beta5//sql/forums/FORUMS.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/forums/FORUMS.sql 2011-02-05 10:10:01.764335002 +0100 @@ -0,0 +1,19 @@ +-- LegacyWorlds Beta 5 +-- PostgreSQL database scripts +-- +-- forums/FORUMS.sql +-- +-- Install the forums' generic tables and code +-- +-- Copyright(C) 2004-2007, DeepClone Development +-- -------------------------------------------------------- + + +-- Execute the forums' installation scripts +\i forums/00-schema.sql +\i forums/01-forums.sql +\i forums/01-signatures.sql +\i forums/02-topics.sql +\i forums/03-polls.sql +\i forums/10-views.sql +\i forums/11-access-functions.sql diff -Naur beta5//sql/INSTALL.sql forums//sql/INSTALL.sql --- beta5//sql/INSTALL.sql 2011-02-05 10:09:56.244335002 +0100 +++ forums//sql/INSTALL.sql 2011-03-12 15:03:56.721300053 +0100 @@ -10,16 +10,20 @@ \i 00-init.sql \i 01-inheritance.sql + \i 10-main.sql \i 11-main-enums.sql \i 12-main-tables.sql \i 13-main-donations.sql -\i 13-main-forums.sql +-- \i 13-main-forums.sql \i 13-main-links.sql \i 13-main-manual.sql \i 13-main-proxy.sql +\i 14-main-forums.sql +\i 15-main-gf-functions.sql \i 18-main-functions.sql \i 19-main-values.sql + \i 25-ctf-maps.sql \i 25-predefined-alliances.sql \i 30-beta5.sql diff -Naur beta5//sql/tools/destroy_alliance_forums.sql forums//sql/tools/destroy_alliance_forums.sql --- beta5//sql/tools/destroy_alliance_forums.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/tools/destroy_alliance_forums.sql 2011-02-05 10:10:01.714335002 +0100 @@ -0,0 +1,11 @@ +ALTER TABLE alliance DROP COLUMN f_category; + +-- grep 'CREATE TABLE' beta5/structure/02-alliance-forums.sql | sed -e 's/CREATE/DROP/' -e 's/ ($/ CASCADE;/' >>tools/destroy_alliance_forums.sql +-- grep 'CREATE OR REPLACE FUNCTION' beta5/structure/02-alliance-forums.sql | sed -e 's/CREATE OR REPLACE/DROP/' -e 's/).*$/) CASCADE;/' >> tools/destroy_alliance_forums.sql + +DROP TABLE alliance_forum CASCADE; +DROP TABLE al_rank_forum CASCADE; + +DROP FUNCTION create_alliance_forum( pid BIGINT, aid BIGINT, fo INT, ttl TEXT, dsc TEXT, am TEXT) CASCADE; +DROP FUNCTION modify_alliance_forum( pid BIGINT, fid BIGINT, ttl TEXT, dsc TEXT, am TEXT ) CASCADE; +DROP FUNCTION get_aforums_privs( pid BIGINT, fid BIGINT, OUT can_view BOOLEAN, OUT can_post BOOLEAN, OUT can_create BOOLEAN, OUT can_poll BOOLEAN, OUT is_mod BOOLEAN, OUT is_admin BOOLEAN) CASCADE; diff -Naur beta5//sql/tools/destroy_forums.sql forums//sql/tools/destroy_forums.sql --- beta5//sql/tools/destroy_forums.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/tools/destroy_forums.sql 2011-02-05 10:10:01.714335002 +0100 @@ -0,0 +1,39 @@ +DROP SCHEMA forums CASCADE; + +SET search_path="b5",public; +\i tools/destroy_alliance_forums.sql + +SET search_path="b5m0",public; +\i tools/destroy_alliance_forums.sql + +SET search_path=public; +-- grep 'CREATE TABLE' 1[45]*.sql | awk -F: '{print $2}' | sed -e 's/CREATE/DROP/' -e 's/ ($/ CASCADE;/' >>tools/destroy_forums.sql +-- grep 'CREATE OR REPLACE FUNCTION' 1[45]*.sql | awk -F: '{print $2}' | sed -e 's/CREATE OR REPLACE/DROP/' -e 's/).*$/) CASCADE;/' >> tools/destroy_forums.sql + +DROP TABLE main.gf_category CASCADE; +DROP TABLE main.gf_forum CASCADE; +DROP TABLE main.gf_ban CASCADE; +DROP TABLE main.gf_admin CASCADE; +DROP TABLE main.gf_cat_moderator CASCADE; +DROP TABLE main.gf_forum_moderator CASCADE; +DROP TABLE main.user_category CASCADE; +DROP TABLE main.user_forum CASCADE; +DROP TABLE main.uf_subscription CASCADE; +DROP TABLE main.uf_invite CASCADE; +DROP FUNCTION main.trgf_gf_forum_check () CASCADE; +DROP FUNCTION main.trgf_gf_admin_check () CASCADE; +DROP FUNCTION main.trgf_gf_cmod_check () CASCADE; +DROP FUNCTION main.trgf_gf_fmod_check() CASCADE; +DROP FUNCTION main.init_general_forums() CASCADE; +DROP FUNCTION main.init_version_forums( v TEXT ) CASCADE; +DROP FUNCTION main.init_game_forums( g TEXT ) CASCADE; +DROP FUNCTION main.get_gf_categories( ver TEXT, game TEXT ) CASCADE; +DROP FUNCTION main.get_gf_list( ver TEXT, game TEXT ) CASCADE; +DROP FUNCTION main.get_gforums_privs( aid BIGINT, fid BIGINT, OUT can_view BOOLEAN, OUT can_post BOOLEAN, OUT can_create BOOLEAN, OUT can_poll BOOLEAN, OUT is_mod BOOLEAN, OUT is_admin BOOLEAN) CASCADE; +DROP FUNCTION main.uf_get_access_mode( fid BIGINT ) CASCADE; +DROP FUNCTION main.uf_get_user_access( aid BIGINT, fid BIGINT ) CASCADE; +DROP FUNCTION main.uf_get_category( aid BIGINT ) CASCADE; +DROP FUNCTION main.uf_create_forum( aid BIGINT, fo INT, ttl TEXT, dsc TEXT, ua TEXT, am TEXT, pass TEXT) CASCADE; +DROP FUNCTION main.trgf_account_user_forums() CASCADE; +DROP FUNCTION main.trgf_uf_subscription_check() CASCADE; +DROP FUNCTION main.trgf_uf_invite_check() CASCADE; diff -Naur beta5//sql/tools/make_alliance_forums.sql forums//sql/tools/make_alliance_forums.sql --- beta5//sql/tools/make_alliance_forums.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/tools/make_alliance_forums.sql 2011-02-05 10:10:01.714335002 +0100 @@ -0,0 +1,115 @@ +ALTER TABLE alliance ADD COLUMN f_category BIGINT NOT NULL DEFAULT forums.make_category('beta5/aforums') REFERENCES forums.category (id); +CREATE INDEX alliance_f_category ON alliance (f_category); +\i beta5/structure/02-alliance-forums.sql + + + +CREATE OR REPLACE FUNCTION get_player_uid( pid BIGINT ) RETURNS BIGINT AS $$ +DECLARE + i BIGINT; +BEGIN + SELECT INTO i userid FROM player WHERE id = pid; + RETURN i; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION upgrade_alf_replies(opid BIGINT, npid BIGINT) RETURNS VOID AS $$ +DECLARE + rep RECORD; + nrid BIGINT; +BEGIN + -- For each reply + FOR rep IN SELECT * FROM af_post WHERE reply_to = opid + LOOP + -- Post the reply + SELECT INTO nrid forums.add_reply( npid, get_player_uid(rep.author), rep.title, rep.contents, + rep.enable_code, rep.enable_smileys, NULL ); + UPDATE forums.post_text SET moment = rep.moment WHERE post = nrid; + + -- Check for edited post + IF rep.edited IS NOT NULL THEN + INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) + VALUES (nrid, rep.edited, get_player_uid(rep.edited_by), rep.title, rep.contents, + rep.enable_code, rep.enable_smileys); + END IF; + + -- Handle replies + PERFORM upgrade_alf_replies( rep.id, nrid ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION upgrade_alf_contents(ofid INT, nfid BIGINT) RETURNS VOID AS $$ +DECLARE + top RECORD; + fpost RECORD; + ntid BIGINT; + fpid BIGINT; +BEGIN + RAISE NOTICE 'Importing alliance forum #% (as #%)', ofid, nfid; + + -- For each topic + FOR top IN SELECT * FROM af_topic WHERE forum = ofid + LOOP + -- Create it + SELECT INTO fpost * FROM af_post WHERE id = top.first_post; + SELECT INTO ntid forums.create_topic( + nfid, get_player_uid(fpost.author), CASE top.sticky WHEN true THEN 10 ELSE 0 END, + fpost.title, fpost.contents, fpost.enable_code, fpost.enable_smileys, NULL + ); + + -- Get the first post and fix its timestamp + SELECT INTO fpid id FROM forums.t_post WHERE topic = ntid; + UPDATE forums.post_text SET moment = fpost.moment WHERE post = fpid; + + -- Add an entry if it has been edited + IF fpost.edited IS NOT NULL THEN + INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) + VALUES (fpid, fpost.edited, get_player_uid(fpost.edited_by), fpost.title, fpost.contents, + fpost.enable_code, fpost.enable_smileys); + END IF; + + PERFORM upgrade_alf_replies( fpost.id, fpid ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION upgrade_alliance( aid INT, cid BIGINT ) RETURNS VOID AS $$ +DECLARE + af RECORD; + arf RECORD; + nfid BIGINT; +BEGIN + FOR af IN SELECT * FROM af_forum WHERE alliance = aid + LOOP + SELECT INTO nfid forums.make_forum( cid, af.forder, af.title, af.description ); + INSERT INTO alliance_forum VALUES ( nfid, aid, CASE af.user_post WHEN TRUE THEN 'T' ELSE 'P' END); + FOR arf IN SELECT * FROM algr_forums WHERE forum = af.id + LOOP + INSERT INTO al_rank_forum VALUES (arf.grade, nfid, arf.is_mod); + END LOOP; + PERFORM upgrade_alf_contents( af.id, nfid ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION upgrade_alliance_forums() RETURNS VOID AS $$ +DECLARE + al RECORD; +BEGIN + FOR al IN SELECT * FROM alliance + LOOP + RAISE NOTICE 'Upgrading forums for alliance #% [%]', al.id, al.tag; + PERFORM upgrade_alliance( al.id, al.f_category ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + +SELECT upgrade_alliance_forums(); + +DROP FUNCTION upgrade_alliance_forums() ; +DROP FUNCTION upgrade_alliance( INT, BIGINT ) ; +DROP FUNCTION upgrade_alf_contents( INT, BIGINT) ; +DROP FUNCTION get_player_uid( BIGINT ) ; diff -Naur beta5//sql/tools/make_forums.sql forums//sql/tools/make_forums.sql --- beta5//sql/tools/make_forums.sql 1970-01-01 01:00:00.000000000 +0100 +++ forums//sql/tools/make_forums.sql 2011-02-05 10:10:01.714335002 +0100 @@ -0,0 +1,168 @@ +\i 14-main-forums.sql +\i 15-main-gf-functions.sql +\i 15-main-uforums.sql + +SELECT forums.add_category_type( 'beta5/aforums', 'en', 'Alliance forums' ); + + +CREATE OR REPLACE FUNCTION upgrade_gen_replies(opid BIGINT, npid BIGINT) RETURNS VOID AS $$ +DECLARE + rep RECORD; + nrid BIGINT; +BEGIN + -- For each reply + FOR rep IN SELECT * FROM main.f_post WHERE reply_to = opid AND deleted IS NULL + LOOP + -- Post the reply + SELECT INTO nrid forums.add_reply( npid, rep.author, rep.title, rep.contents, + rep.enable_code, rep.enable_smileys, NULL ); + UPDATE forums.post_text SET moment = rep.moment WHERE post = nrid; + + -- Check for edited post + IF rep.edited IS NOT NULL THEN + INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) + VALUES (nrid, rep.edited, rep.edited_by, rep.title, rep.contents, + rep.enable_code, rep.enable_smileys); + END IF; + + -- Handle replies + PERFORM upgrade_gen_replies( rep.id, nrid ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION upgrade_gen_forum(ofid INT, nfid BIGINT) RETURNS VOID AS $$ +DECLARE + top RECORD; + fpost RECORD; + ntid BIGINT; + fpid BIGINT; +BEGIN + RAISE NOTICE 'Importing general forum #% (as #%)', ofid, nfid; + + -- For each topic + FOR top IN SELECT * FROM main.f_topic WHERE forum = ofid AND deleted IS NULL + LOOP + -- Create it + SELECT INTO fpost * FROM main.f_post WHERE id = top.first_post; + SELECT INTO ntid forums.create_topic( + nfid, fpost.author, CASE top.sticky WHEN true THEN 10 ELSE 0 END, + fpost.title, fpost.contents, fpost.enable_code, fpost.enable_smileys, NULL + ); + + -- Get the first post and fix its timestamp + SELECT INTO fpid id FROM forums.t_post WHERE topic = ntid; + UPDATE forums.post_text SET moment = fpost.moment WHERE post = fpid; + + -- Add an entry if it has been edited + IF fpost.edited IS NOT NULL THEN + INSERT INTO forums.post_text (post, moment, author, title, contents, enable_code, enable_smileys) + VALUES (fpid, fpost.edited, fpost.edited_by, fpost.title, fpost.contents, + fpost.enable_code, fpost.enable_smileys); + END IF; + + PERFORM upgrade_gen_replies( fpost.id, fpid ); + END LOOP; +END; +$$ LANGUAGE plpgsql; + + +CREATE OR REPLACE FUNCTION upgrade_forums() RETURNS VOID AS $$ +DECLARE + mcid BIGINT; + cid_v BIGINT; + cid_b5 BIGINT; + cid_m5 BIGINT; + fid BIGINT; +BEGIN + -- Main forums + SELECT INTO mcid main.init_general_forums(); + -- Announcements + SELECT INTO fid forums.make_forum( mcid, 0, 'Announcements', + 'Updates on the game''s progress, new versions, etc...'); + INSERT INTO main.gf_forum VALUES ( fid, 'MP' ); + PERFORM upgrade_gen_forum( 1, fid ); + -- The Pub + SELECT INTO fid forums.make_forum( mcid, 1, 'The Pub', 'Discuss whatever''s on your mind in here.'); + INSERT INTO main.gf_forum VALUES ( fid, 'UL' ); + PERFORM upgrade_gen_forum( 3, fid ); + -- M&A + SELECT INTO fid forums.make_forum( mcid, 2, 'Malcontents and Anarchists', + 'Forum in which public executions take place.'); + INSERT INTO main.gf_forum VALUES ( fid, 'MP' ); + PERFORM upgrade_gen_forum( 4, fid ); + -- Admin policies + SELECT INTO fid forums.make_forum( mcid, 3, '[ADMIN] Main moderators/admins board', + 'Forum only available to Legacy Worlds administrators and moderators.'); + INSERT INTO main.gf_forum VALUES ( fid, 'MO' ); + PERFORM upgrade_gen_forum( 12, fid ); + + + -- Beta 5 *version* forums + SELECT INTO cid_v main.init_version_forums('beta5'); + -- New / Improved features + SELECT INTO fid forums.make_forum( cid_v, 0, 'New / Improved features', + 'Your ideas about how to improve the game and our response to these ideas.' ); + PERFORM upgrade_gen_forum( 2, fid); + -- Bugs and Problems + SELECT INTO fid forums.make_forum( cid_v, 1, 'Bugs and Problems', + 'Whine about what you think is wrong with Beta 5 in this forum.' ); + PERFORM upgrade_gen_forum( 7, fid); + -- Help + SELECT INTO fid forums.make_forum( cid_v, 2, 'Help', 'Ask the staff and other players for advice.' ); + PERFORM upgrade_gen_forum( 8, fid); + + + -- Beta 5 *game* forums + SELECT INTO cid_b5 main.init_game_forums('beta5'); + -- General Discussion + SELECT INTO fid forums.make_forum( cid_b5, 0, 'General Discussion', + 'Discuss what''s going on in the Beta 5 universe.' ); + PERFORM upgrade_gen_forum( 5, fid ); + -- Alliance Recruitment + SELECT INTO fid forums.make_forum( cid_b5, 1, 'Alliance Recruitment', + 'Advertise for your alliance and recruit new members through this forum.' ); + PERFORM upgrade_gen_forum( 6, fid ); + -- Marketplace Advertisement + SELECT INTO fid forums.make_forum( cid_b5, 2, 'Marketplace Advertisement', + 'Advertise for items you''ve put on sale in the marketplace or technologies you''re willing to provide through diplomacy.' ); + PERFORM upgrade_gen_forum( 9, fid ); + + + -- Beta 5 *match* forums + SELECT INTO cid_m5 main.init_game_forums('b5match'); + -- General Discussion + SELECT INTO fid forums.make_forum( cid_m5, 0, 'General Discussion', + 'Discuss what''s going on in the Beta 5 match.' ); + PERFORM upgrade_gen_forum( 11, fid ); + -- Alliance Recruitment + SELECT INTO fid forums.make_forum( cid_m5, 1, 'Alliance Recruitment', + 'Advertise for your alliance and recruit new members through this forum.' ); + -- Marketplace Advertisement + SELECT INTO fid forums.make_forum( cid_m5, 2, 'Marketplace Advertisement', + 'Advertise for items you''ve put on sale in the marketplace or technologies you''re willing to provide through diplomacy.' ); + + + -- Add admins and mods + INSERT INTO main.gf_admin (account) VALUES (1); + INSERT INTO main.gf_admin (account) VALUES (2); + INSERT INTO main.gf_admin (account) VALUES (3); + INSERT INTO main.gf_admin (account) VALUES (4); + INSERT INTO main.gf_cat_moderator (account) VALUES (7); + INSERT INTO main.gf_cat_moderator (account) VALUES (8); +END; +$$ LANGUAGE plpgsql; + +SELECT upgrade_forums(); + +DROP FUNCTION upgrade_forums() ; +DROP FUNCTION upgrade_gen_forum(ofid INT, nfid BIGINT) ; +DROP FUNCTION upgrade_gen_replies(BIGINT, BIGINT) ; + +SET search_path=b5,main,public; +\i tools/make_alliance_forums.sql +SET search_path=b5m0,main,public; +\i tools/make_alliance_forums.sql +SET search_path=public; + +VACUUM ANALYZE;