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 @@ +<? + +/** This action returns the structure of the forums a player has access to. + * + * \parameter $player The player identifier for whom to get the forums' structure + * + * \returns null on error or a fl_container instance that contains the forums' structure. + */ + +class beta5_getForums { + + private static $names = array( + 'G' => 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 @@ +<?php + +/** Beta 5 alliance forums management library */ + +class beta5_aforums_library { + + private static $allianceAdmins = array(); + private static $rAccess = array(); + + var $index = array( ); + + public function __construct($lib) { + $this->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 @@ +<? + +class beta5_forums_topic_library { + + private $pHandler; + private $lib; + private $game; + private $topic = null; + private $deleted = false; + + public function __construct($lib) { + $this->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 @@ +<? + +class beta5_forums_view_library { + + private $pHandler; + private $lib; + private $game; + private $fStructure; + private $displayObject; + + public function __construct($lib) { + $this->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 @@ +<?php + +/* Category management class */ + +class fl_category { + + private static $getLibQuery = null; + private static $getNameQuery = null; + + private $game; + private $mainLib = null; + private $hdlLib = null; + + private $parent = null; + private $user; + private $id; + private $title; + private $description; + + private $forums = array(); + private $oForums = array(); + private $unread = null; + private $topics = null; + private $posts = null; + + public function __construct( $game, $user, $id, $title, $description = null ) { + self::initQueries(); + + $this->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 @@ +<?php + +/* Class that represents a pseudo-category that contains sub-categories */ + +class fl_container { + + private $categories = array(); + private $parent = null; + private $id; + private $title; + private $description; + + private $topics = null; + private $unread = null; + + public function __construct( $id, $title, $description ) { + $this->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 @@ +<?php + +/* Forum handling class */ + +class fl_forum { + + private static $queries = null; + + private $game; + private $db; + private $mainLib = null; + private $parent = null; + + private $user; + private $record; + private $privs; + + private $unread = null; + private $topicList = null; + + private $acl = array( + "admins" => 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 @@ +<?php + + +/* Forum poll management class */ + +class fl_poll { + + private $user; + private $game; + private $db; + + private $fromDB; + private $nbVotesQ; + private $checked; + + private $record; + private $options = array(); + + public function __construct() { + $args = func_get_args(); + + if (count($args) == 1) { + // Manual initialisation + $this->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 @@ +<?php + + +class fl_post { + + private static $getQ = null; + private static $historyQ = null; + private static $repliesQ = null; + + private $game; + private $mainLib; + private $db; + private $user; + + private $record; + private $history = null; + private $replies = null; + + + public function __construct($game, $user, $data) { + self::initQueries(); + + $this->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 @@ +<?php + +/* Topic handling class */ + +class fl_topic { + + private static $getQuery = null; + private static $restoreQuery = null; + private static $deleteQuery = null; + private static $stickyLevelQuery = null; + private static $setLockQuery = null; + private static $moveQuery = null; + private static $postsQuery = null; + + private $game; + private $db; + private $mainLib = null; + + private $user; + private $record; + private $lastRead = null; + private $allPosts = null; + + public function __construct($game, $user, $data) { + self::initQueries(); + + $this->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 @@ <?php class main_forums_substitute { - var $code = null; - var $smiley = null; + private static $code = array( + array( + "re" => '\[b\](.*?)\[\/b\]', + "rt" => '<b>$1</b>' + ), array( + "re" => '\[u\](.*?)\[\/u\]', + "rt" => '<u>$1</u>' + ), array( + "re" => '\[i\](.*?)\[\/i\]', + "rt" => '<i>$1</i>' + ), array( + "re" => '\[sep(arator)?\]', + "rt" => '<hr/>' + ), array( + "re" => '\[item\](.*?)\[\/item\]', + "rt" => '<ul style="margin: 0px"><li>$1</li></ul>' + ), array( + "re" => '\[quote\](.*?)\[\/quote\]', + "rt" => '<blockquote style="margin: 10px 5px; padding: 5px; border: 1px solid #5F5F5F; background-color: #2F2F2F">$1</blockquote>', + "rep" => true + ), array( + "re" => '\[quote=([^\]]+)\](.*?)\[\/quote\]', + "rt" => '<blockquote style="margin: 10px 5px; padding: 5px; border: 1px solid #5F5F5F; background-color: #2F2F2F"><b>$1</b> said:<br/>$2</blockquote>', + "rep" => true + ), array( + "re" => '\[link=(http[^\]]+)\](.+?)\[\/link\]', + "rt" => '<a href="$1" target="_blank">$2</a>' + ), array( + "re" => '\[code\](.*?)\[\/code\]', + "rt" => '<pre>$1</pre>' + ) + ); 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("<br/>",''); + $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 = "<img src='$fn' alt='[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 @@ <?php class main_forums_library { - var $index = array( - 'deletePost', - 'deleteTopic', - 'edit', - 'get', - 'getAdministrator', - 'getCategories', - 'getCategory', - 'getForums', - 'getModerator', - 'getPost', - 'getPosts', - 'getTopic', - 'getTopics', - 'move', - 'newTopic', - 'reply', - 'signature', - 'substitute', - 'updateLast', + private $libraries = array(); + private $categories = array(); + private $forums = array(); + private $topics = array(); + private $polls = array(); + private $posts = array(); + + private $getForumLib; + private $getCategoryLib; + + public $index = array( + 'substitute' ); - function main_forums_library($lib) { + public function __construct($lib) { $this->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 @@ +<?php + +/* General forums management library */ + +class main_gforums_library { + + var $index = array( ); + + function __construct($lib) { + $this->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 @@ +<?php + +/** User forums management library + * + * This library handles permissions for user forums. + */ + +class main_uforums_library { + + var $index = array( ); + + function __construct($lib) { + $this->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 @@ +<?php + +class db_query { + + private static $queries = array(); + + private $counter = 1; + private $table = null; + private $name; + private $accessor; + private $database; + private $connection; + private $query; + private $parameters; + private $resource; + + private function __construct($name, $accessor, $query, $pNames) { + $this->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 @@ <?php -class page_handler -{ +class page_handler { + + private $rkLib; + private $player; + private $alliance; + private $aForums; + var $needsAuth = true; var $ajax = array( 'func' => 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 @@ <?php -class page_handler -{ - var $needsAuth = true; - +class page_handler { +/* function getForum($ctype, $fid, &$cats) { $forum = null; @@ -850,8 +848,9 @@ $gfl[$fid] = $ctype; } - $txt = '%' . preg_replace('/\*/', '%', addslashes($_SESSION[game::sessName()]['forumsearch']['text'])) . '%'; - $pg = $_SESSION[game::sessName()]['forumsearch']['page']; +*/ +// $txt = '%' . preg_replace('/\*/', '%', addslashes($_SESSION[game::sessName()]['forumsearch']['text'])) . '%'; +/* $pg = $_SESSION[game::sessName()]['forumsearch']['page']; $mpp = $_SESSION[game::sessName()]['forumsearch']['perpage']; $c = $_SESSION[game::sessName()]['forumsearch']['whole']; $sfs = array('moment','title'); @@ -915,7 +914,10 @@ $fl[$cat['type'] . "#" . $f['id']] = array($f['title'],$cat['id'],$cat['title']); } - $txt = '%' . preg_replace('/\*/', '%', addslashes($_SESSION[game::sessName()]['forumsearch']['text'])) . '%'; +*/ +// $txt = '%' . preg_replace('/\*/', '%', addslashes($_SESSION[game::sessName()]['forumsearch']['text'])) . '%'; + +/* $pg = $_SESSION[game::sessName()]['forumsearch']['page']; $mpp = $_SESSION[game::sessName()]['forumsearch']['perpage']; $c = $_SESSION[game::sessName()]['forumsearch']['whole']; @@ -1067,6 +1069,474 @@ } $this->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 @@ </p> </td><td style="width:45%"> <h1>Forums</h1> - <h2>General forums</h2> - <p id="gforums"></p> - <div id="aforums"></div> + <div id="forums"></div> </td><td> <a href="manual?p=communications_page">Help</a> </td> 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 @@ <? +/* function drawForumsMenu($mode,$fList) { ?> @@ -68,5 +69,19 @@ $sp = $args['sp']; $args = $args['d']; include("forums/en/$sp.inc"); +*/ ?> +<div style="display: none; visibility: hidden;"> + <div id="f-params"><?=$args['showMenu'] ? 1 : 0?>#<?=$args['needData'] ? 1 : 0?>#<?=$args['pageType']?></div> + <div id="f-menu-current"><?=$args['menuCurrent']?></div> +<?php +if (! $args['needData']) { +?> + <div id="f-menu-init"><?=$args['menuContents']?></div> + <div id="f-page-init"><?=$args['pageData']?></div> +<?php +} +?> +</div> +<div id="f-contents"> </div> 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 = '<table cellspacing="0" cellpadding="0">'; str += '<tr><td class="div2" id="fattl"><h1>Alliance Forums</h1><td id="crforum"> </td></tr>'; str += '<tr><td colspan="2" id="falist"> </td></tr></table>'; document.getElementById('alpmain').innerHTML = str; - if (faForums.length < 30) - drawCreateForumLink(); - if (faForums.length == 0) + + if (faForums.length < 30) { + document.getElementById('crforum').innerHTML = '<a href="#" ' + alltt[120] + + ' onClick="createForum();return false">Create a forum</a>'; + } + + if (faForums.length == 0) { drawTextNoForums(); - else + } else { drawRealForumList(); + } } -function drawCreateForumLink() -{ - var str = '<a href="#" ' + alltt[120] + ' onClick="createForum();return false">'; - str += 'Create a forum</a>'; - document.getElementById('crforum').innerHTML = str; -} - -function drawTextNoForums() -{ +function drawTextNoForums() { document.getElementById('falist').innerHTML = '<p>No forums have been defined for the alliance.</p>'; } -function drawRealForumList() -{ - var i, str = '<table class="list" cellspacing="0" cellpadding="0" id="fatbl">'; +function drawRealForumList() { + var i, str = '<table class="list" cellspacing="0" cellpadding="0" id="fatbl">'; + + var getFRankText = function (id) { + var r = faRanks['r' + id]; + return r == '-' ? '<i>Default member</i>' : r; + } + + str += '<tr><th class="faname">Name & description</th><th class="fauserpost">User access mode</th></tr>'; + for (i = 0; i < faForums.length; i++) { + str += '<tr><td class="faname"><b><u>' + + faForums[i].name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') + + '</u></b> ('; + if (faForums[i].isDeleted) { + str += 'deleted by <b>' + faPlayers['p' + faForums[i].deletedBy] + '</b> at <b>' + + formatDate(faForums[i].deletedAt) + '</b> - <a href="#" onClick="restoreForum(' + + faForums[i].id + ');return false">Restore</a>'; + } else { + str += '<a ' + alltt[130] + ' href="#" onClick="editForum(' + faForums[i].id + + ');return false">Edit</a> - '; + if (i > 0) { + str += '<a ' + alltt[131] + ' href="#" onClick="moveForum(' + faForums[i].id + + ',true);return false">Move up</a> - '; + } + if (i < faForums.length - 1) { + str += '<a ' + alltt[132] + ' href="#" onClick="moveForum(' + faForums[i].id + + ',false);return false">Move down</a> - '; + } + str += '<a ' + alltt[133] + ' href="#" onClick="deleteForum(' + faForums[i].id + + ');return false">Delete</a>'; + } + str += ")"; + + if (faForums[i].description != '' || faForums[i].users.length || faForums[i].mods.length) { + + var nbr = false; + str += '<br/><p>'; + + if (faForums[i].description != '') { + str += faForums[i].description.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g,"<br/>"); + nbr = true; + } + + if (faForums[i].users.length) { + str += (nbr ? '<br/>' : '') + '<b>Users:</b> '; + for (var ri in faForums[i].users) { + str += (ri > 0 ? ' - ' : '') + getFRankText(faForums[i].users[ri]); + } + nbr = true; + } + + if (faForums[i].mods.length) { + str += (nbr ? '<br/>' : '') + '<b>Moderators:</b> '; + for (var ri in faForums[i].mods) { + str += (ri > 0 ? ' - ' : '') + getFRankText(faForums[i].mods[ri]); + } + nbr = true; + } + + str += '</p>'; + } - str += '<tr><th class="faname">Name & description</th><th class="fauserpost">New threads</th></tr>'; - for (i=0;i<faForums.length;i++) - { - str += '<tr>'; - str += '<td class="faname"><b><u>' + faForums[i].name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') + '</u></b> ('; - str += '<a ' + alltt[130] + ' href="#" onClick="editForum(' + faForums[i].id + ');return false">Edit</a> - '; - if (i > 0) - str += '<a ' + alltt[131] + ' href="#" onClick="moveForum(' + faForums[i].id + ',true);return false">Move up</a> - '; - if (i < faForums.length - 1) - str += '<a ' + alltt[132] + ' href="#" onClick="moveForum(' + faForums[i].id + ',false);return false">Move down</a> - '; - str += '<a ' + alltt[133] + ' href="#" onClick="deleteForum(' + faForums[i].id + ');return false">Delete</a>)'; - if (faForums[i].description != '') - str += '<br/><p>' + faForums[i].description.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g,"<br/>") + '</p>'; str += '</td><td class="fauserpost">'; - 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 += '</td></tr>'; } @@ -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, '<').replace(/>/g, '>') + "' forum"; - } - else + } else { str = "Create a forum"; + } document.getElementById('fattl').innerHTML = '<h1>' + str + '</h1>'; - str = '<table cellspacing="0" cellpadding="0" id="feditor">'; - str += '<tr><th class="edheader">Forum name:</th><td><input ' + alltt[140] + ' type="text" name="fedit" id="fname" size="48" maxlength="64" value="'; - str += faEditing.name.replace(/"/g, '"') + '" onKeyUp="faEditing.name=value.replace(/</g, \'<\').replace(/>/g, \'>\').replace(/&/g, \'&\');'; - str += 'updateFEditor()" onChange="faEditing.name=value;updateFEditor()" />'; + str = '<table cellspacing="0" cellpadding="0" id="feditor">' + + '<tr><th class="edheader">Forum name:</th><td><input ' + alltt[140] + + ' type="text" name="fedit" id="fname" size="48" maxlength="64"' + + ' onKeyUp="faEditing.name=value;updateFEditor()" onChange="faEditing.name=value;updateFEditor()" />' + + '</td></tr>' + + '<tr><th class="edheader">User access mode:</th><td>'; + + var aModes = { + M: 'Read only', + P: 'Replies only', + T: 'Topic creation', + L: 'Complete access' + }; + for (var i in aModes) { + str += '<input type="radio" name="fam" id="f-am-' + i + '" value="' + i + + '" onClick="faEditing.accessMode=value;updateFEditor()"' + + (faEditing.accessMode == i ? ' checked="checked"' : '') + + ' /><label for="f-am-' + i + '">' + aModes[i] + '</label> '; + } str += '</td></tr>'; - str += '<tr><th class="edheader">New threads:</th><td>'; - str += '<label><input ' + alltt[141] + ' type="radio" name="nt" value="1" onClick="faEditing.userPost=true;updateFEditor()" '; - if (faEditing.userPost) - str += 'checked="checked" '; - str += '/> Everyone</label> <label><input ' + alltt[142] + ' type="radio" name="nt" value="0" onClick="faEditing.userPost=false;updateFEditor()" '; - if (!faEditing.userPost) - str += 'checked="checked" '; - str += '/> Moderators only</label></td></tr>'; - if (!faEditing.id) - str += '<tr><th class="edheader">Initial position:</th><td ' + alltt[143] + ' id="faeipos"> </td></tr>'; - str += '<tr><th class="edheader">Description:</th><td><textarea ' + alltt[144] + ' name="fdesc" onKeyUp="faEditing.description='; - str += 'value.replace(/</g, \'<\').replace(/>/g, \'>\').replace(/&/g, \'&\');updateFEditor()"'; - str += ' onChange="faEditing.description=value;updateFEditor()"'; - str += ' rows="10" cols="48">' + faEditing.description.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') + '</textarea></td></tr>'; - str += '<td id="febut" colspan="2"><input type="button" name="feok" value="Ok" id="feok" onClick="forumEditOk();return false" ' + alltt[145] + ' />'; - str += ' <input type="button" name="feccl" value="Cancel" onClick="forumEditCancel();return false" ' + alltt[146] + ' /></td></tr>'; - str += '<tr><td colspan="2"><h3>Forum access</h3></td></tr>'; - str += '<tr><td colspan="2" id="faeaccess"><p>(Loading access list data ...)</p></td></tr>'; - str += '</table>'; + if (!faEditing.id) { + str += '<tr><th class="edheader">Initial position:</th><td ' + + alltt[143] + ' id="faeipos"> </td></tr>'; + } + + str += '<tr><th class="edheader">Description:</th><td><textarea ' + alltt[144] + + ' name="fdesc" id="fdesc" onKeyUp="faEditing.description=value;updateFEditor()"' + + ' onChange="faEditing.description=value;updateFEditor()"' + + ' rows="10" cols="48"></textarea></td></tr>' + + '<tr><td id="febut" colspan="2"><input type="button" name="feok" value="Ok" id="feok" ' + + 'onClick="forumEditOk();return false" ' + alltt[145] + ' />' + + ' <input type="button" name="feccl" value="Cancel" onClick="forumEditCancel();return false" ' + + alltt[146] + ' /></td></tr>' + + '<tr><td colspan="2"><h3>Forum access</h3></td></tr>' + + '<tr><td colspan="2" id="faeaccess"> </td></tr>' + + '</table>'; 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 = '<select name="fanewpos" onChange="faNewPos=this.options[this.selectedIndex].value">'; - str += '<option value="-1">At the beginning</option>'; - for (i=0;i<faForums.length;i++) - { +function updateFPosSelector() { + var i, str = '<select name="fanewpos" onChange="faNewPos=this.options[this.selectedIndex].value">' + + '<option value="-1">At the beginning</option>'; + for (i = 0; i < faForums.length; i ++) { str += '<option value="' + faForums[i].id + '"'; - if (faNewPos == faForums[i].id) + if (faNewPos == faForums[i].id) { str += ' selected="selected"'; + } str += '>After ' + faForums[i].name + '</option>'; } str += '</select>'; 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<faAccess.length;i++) { @@ -1268,6 +1330,8 @@ case 6: str += 'Probable bug: the forum to insert after wasn\'t transmitted.'; break; case 7: str += 'A server error has occured.'; break; case 8: str += 'This forum has already been deleted.'; break; + case 9: str += 'This forum has been cleansed and can no longer be recovered.'; break; + case 10: str += 'This forum has already been restored.'; break; case 200: str += "You can\'t modify an alliance\'s forums while in vacation mode."; break; default: str += 'An unknown error has happened.'; break; } diff -Naur beta5//site/static/beta5/js/pg_alliance.js forums//site/static/beta5/js/pg_alliance.js --- beta5//site/static/beta5/js/pg_alliance.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_alliance.js 2011-03-12 14:56:07.421300050 +0100 @@ -80,6 +80,9 @@ var faOriACL; var faNewPos; var faAccess; +var faRanks; +var faAdmins; +var faPlayers; var raRanks = new Array(); var raForums; @@ -1239,11 +1242,9 @@ // Forums administation -function switchToFAdminPage() -{ +function switchToFAdminPage() { clearUpdate(); - if (alPrivileges[5] != 1) - { + if (alPrivileges[5] != 1) { switchToMainPage(); return; } @@ -1255,36 +1256,97 @@ x_getForums(forumsReceived); } -function AllianceForum(id,order,userPost,name,description) -{ - this.id = id; - this.order = order; - this.userPost = userPost; - this.name = name; - this.description = description; +function AllianceForum() { + this.id = null; + this.accessMode = 'T'; + this.isDeleted = false; + this.deletedAt = null; + this.deletedBy = null; + this.name = ''; + this.description = ''; + this.topics = 0; + this.users = new Array(); + this.mods = new Array(); + + this.parse = function (lines) { + var line = lines.shift().split('#'); + + // General information + this.id = line.shift(); + this.isDeleted = (line.shift() == '1'); + this.deletedBy = line.shift(); + this.deletedAt = line.shift(); + this.accessMode = line.shift(); + this.topics = parseInt(line.shift(), 10); + this.name = line.join('#'); + + // Users + line = lines.shift(); + if (line != '') { + this.users = line.split('#'); + } + + // Moderators + line = lines.shift(); + if (line != '') { + this.mods = line.split('#'); + } + + // Description + var dLines = parseInt(lines.shift(), 10); + for (var i = 0; i < dLines; i ++) { + this.description += (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<dl.length) - { - if (st == 0) - { - a = dl[i].split('#'); - faForums[cf] = new AllianceForum(a.shift(),a.shift(),(a.shift()=="1"),a.join('#'),''); - st = 1; - i ++; - } - else if (st == 1) - { - if (dl[i].indexOf('+#') == 0) - { - a = dl[i].split('#'); - a.shift(); - a = a.join('#'); - if (faForums[cf].description != '') - faForums[cf].description += '\n'; - faForums[cf].description += a; - i++; - } - else - { - cf++; - st = 0; - } - } +function parseForumList(data) { + var lines = data.split('\n'); + var fCount = parseInt(lines.shift(), 10); + var i; + + faForums = new Array(); + for (i = 0; i < fCount; i ++) { + faForums.push( new AllianceForum().parse(lines) ); + } + + var rCount = parseInt(lines.shift(), 10); + faRanks = { }; + for (i = 0; i < rCount; i ++) { + var rLine = lines.shift().split('#'); + faRanks['r' + rLine.shift()] = rLine.join('#'); } - faForums.sort(new Function('a','b','return (a.order > 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<faForums.length&&faForums[i].id!=id;i++) - ; - return (i==faForums.length) ? false : faForums[i]; +function forumById(id) { + var i; + for (i = 0 ; i < faForums.length && faForums[i].id != id; i ++) ; + return (i==faForums.length) ? false : faForums[i]; } -function deleteForum(id) -{ - var f = forumById(id); - if (!(f && confirmDeleteForum(f.name))) +function deleteForum(id) { + var f = forumById(id); + if (!(f && confirmDeleteForum(f))) { return; + } x_delForum(id, forumsReceived); } -function createForum() -{ - faEditing = new AllianceForum(false,false,true,'',''); - if (faForums.length) +function restoreForum(id) { + var f = forumById(id); + if (!f) { + return; + } + x_restoreForum(id, forumsReceived); +} + +function createForum() { + faEditing = new AllianceForum(); + if (faForums.length) { faNewPos = faForums[faForums.length - 1].id; - else + } else { faNewPos = -1; + } displayForumEditor(); } -function editForum(id) -{ - var f = forumById(id); - if (!f) +function editForum(id) { + var f = forumById(id); + if (!f) { return; - faEditing = new AllianceForum(id,f.order,f.userPost,f.name,f.description); - faOriginal = new AllianceForum(id,f.order,f.userPost,f.name,f.description); + } + faEditing = new AllianceForum().copyFrom(f); + faOriginal = f; displayForumEditor(); } -function displayForumEditor() -{ +function displayForumEditor() { document.getElementById('crforum').innerHTML = ''; + computeForumACL(); drawForumEditor(); - x_getForumAcl(faEditing.id ? faEditing.id : '', forumAclReceived); } -function forumAclReceived(data) -{ +function computeForumACL() { + var feUsers, feMods, feAdmins; + feUsers = '#' + faEditing.users.join('##') + '#'; + feMods = '#' + faEditing.mods.join('##') + '#'; + feAdmins = '#' + faAdmins.join('##') + '#'; + faAccess = new Array(); - if (data != "") - { - var i, l = data.split('\n'); - for (i=0;i<l.length;i++) - { - var a = l[i].split('#'); - faAccess.push(new ForumACL(a.shift(), a.shift(), a.join('#'))); + for (var i in faRanks) { + var rId = i.substr(1, i.length - 1); + var rn = faRanks[i]; + var pr; + + if (feUsers.indexOf('#' + rId + '#') != -1) { + pr = 1; + } else if (feMods.indexOf('#' + rId + '#') != -1) { + pr = 2; + } else if (feAdmins.indexOf('#' + rId + '#') != -1) { + pr = 3; + } else { + pr = 0; } - faAccess.sort(new Function('a','b','return a.name.toLowerCase() > 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<faAccess.length;i++) - if (faAccess[i].priv != 3 && faAccess[i].priv != 0) +function makeForumACLString() { + var a = new Array(), i; + for (i = 0; i < faAccess.length; i ++) { + if (faAccess[i].priv != 3 && faAccess[i].priv != 0) { a.push(faAccess[i].id + '!' + faAccess[i].priv); - return a.join('#'); + } + } + return a.join('#'); } -function forumEditOk() -{ - if (faEditing.id) - x_changeForum(faEditing.id, faEditing.name, faEditing.userPost ? 1 : 0, faEditing.description, makeForumACLString(), forumEditCallback); - else - x_newForum(faEditing.name, faEditing.userPost ? 1 : 0, faNewPos, faEditing.description, makeForumACLString(), forumEditCallback); +function forumEditOk() { + if (faEditing.id) { + x_changeForum( faEditing.id, faEditing.name, faEditing.accessMode, faEditing.description, + makeForumACLString(), forumEditCallback ); + } else { + x_newForum( faEditing.name, faEditing.accessMode, faNewPos, faEditing.description, + makeForumACLString(), forumEditCallback ); + } } function forumEditCancel() diff -Naur beta5//site/static/beta5/js/pg_comms-en.js forums//site/static/beta5/js/pg_comms-en.js --- beta5//site/static/beta5/js/pg_comms-en.js 2011-02-05 10:09:56.434335002 +0100 +++ forums//site/static/beta5/js/pg_comms-en.js 2011-02-05 10:10:02.214335002 +0100 @@ -28,13 +28,15 @@ return str; } -function makeTopicsText(tot, n) -{ - if (tot == 0) - return "empty forum"; - var str = '<b>' + formatNumber(tot) + '</b> topic' + (tot > 1 ? 's' : ''); - if (n == 0) - return str; - str += ' (<b>' + formatNumber(n) + '</b> unread)'; - return str; +function makeTopicsText(tot, n) { + if (tot == 0) { + return "empty forum"; + } + + var str = '<b>' + formatNumber(tot.toString()) + '</b> topic' + (tot > 1 ? 's' : ''); + if (n == 0) { + return str; + } + str += ' (<b>' + formatNumber(n.toString()) + '</b> 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 += '<a href="forums?cmd=V%23' + this.type + '%23' + this.id + '">' + this.name + '</a>: ' + + makeTopicsText(this.topics, this.unread); + } + + for (var i = 0; i < this.contents.length; i ++) { + str += '<br/>' + 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<nCustom;i++) - { + for (i=0;i<nCustom;i++) { a = l.shift().split('#'); cFolders.push(new Folder(a.shift(), a.shift(), a.shift(), a.join('#'))); } // General categories & forums - genForums = new Array(); - for (i=0;i<nGenCats;i++) - { - a = l.shift().split('#'); - - var j,c,id,tp,nForums; - id = a.shift(); tp = a.shift(); - nForums = parseInt(a.shift(), 10); - c = new Category(id, tp, a.join('#')); - - for (j=0;j<nForums;j++) - { - a = l.shift().split('#'); - c.forums.push(new Forum(a.shift(),a.shift(),a.shift(),a.join('#'))); - } - - genForums.push(c); - } - - // Alliance forums - aForums = new Array(); - for (i=0;i<nAForums;i++) - { - a = l.shift().split('#'); - aForums.push(new Forum(a.shift(),a.shift(),a.shift(),a.join('#'))); - } + forums = new ForumsEntity(l); drawCommsPage(); setTimeout('x_getCommsData(commsDataReceived)', 30000); } -function drawCommsPage() -{ - var i, j, a; +function drawCommsPage() { + var i, j, a; // Default folders - for (i=0;i<3;i++) + for (i=0;i<3;i++) { document.getElementById('msg' + i).innerHTML = makeMessagesText(dFolders[i].tMsg, dFolders[i].nMsg); + } // Custom folders - if (cFolders.length == 0) + if (cFolders.length == 0) { document.getElementById('cflist').innerHTML = noCustomFolders; - else - { + } else { a = new Array(); - for (i=0;i<cFolders.length;i++) - { - var s = '<a href="message?a=f&f=C&cf=' + cFolders[i].id; - s += '" ' + comtt[0] + ' >' + cFolders[i].name + '</a>: ' + makeMessagesText(cFolders[i].tMsg, cFolders[i].nMsg); + for (i=0;i<cFolders.length;i++) { + var s = '<a href="message?a=f&f=C&cf=' + cFolders[i].id; + s += '" ' + comtt[0] + ' >' + cFolders[i].name + '</a>: ' + + makeMessagesText(cFolders[i].tMsg, cFolders[i].nMsg); a.push(s); } document.getElementById('cflist').innerHTML = a.join('<br/>') + '<br/>'; } - // General forums - a = new Array(); - for (i=0;i<genForums.length;i++) - {with(genForums[i]){ - var s = '<a href="forums?cmd=C%23G%23' + id + '" ' + comtt[1] + ' >' + name + '</a>'; - for (j=0;j<forums.length;j++) - { - s += '<br/> - <a href="forums?cmd=F%23' + type + '%23' + forums[j].id + '" ' + comtt[2] + ' >' + forums[j].name + '</a>: '; - s += makeTopicsText(forums[j].nTopics, forums[j].nUnread); - } - a.push(s); - }} - document.getElementById('gforums').innerHTML = a.join('<br/><br/>'); - - // Alliance forums - if (aForums.length == 0) - document.getElementById('aforums').innerHTML = ' '; - else - { - a = new Array(); - for (j=0;j<aForums.length;j++) - { - s = ' - <a href="forums?cmd=F%23A%23' + aForums[j].id + '" ' + comtt[3] + ' >' + aForums[j].name + '</a>: '; - s += makeTopicsText(aForums[j].nTopics, aForums[j].nUnread); - a.push(s); - } - document.getElementById('aforums').innerHTML = '<h2>' + allianceForums + '</h2><p>' + a.join('<br/>') + '</p>'; - } + // Forums + document.getElementById('forums').innerHTML = '<p>' + forums.output(-1) + '</p>'; } 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 = '<td style="width: 16px; margin: 0px; padding: 0px; border-width: 0px">'; + var str = '<tr><td' + cst + '><table' + tst + '><tr>'; + + if (depth > 0) { + for (var i = 0; i < depth; i ++) { + str += smallCell + ' </td>'; + } + } + + // 'Open'/'Close' command for submenus or empty cell + str += smallCell; + if (depth > -1 && this.isNode) { + if (this.isOpen) { + str += '<a href="#" onClick="main.menuClose(\'' + this.id + '\'); return false">-</a>'; + } else { + str += '<a href="#" onClick="main.menuOpen(\'' + this.id + '\'); return false">+</a>'; + } + } else { + str += ' '; + } + str += '</td>'; + + // Menu entry's text + str += '<td' + cst + '><a href="?cmd=' + this.cmdLink.replace(/#/g, '%23') + + '" style="text-decoration:none;font-style: normal">' + + (MenuItem.current == this.cmdLink ? '<em><u>' : '') + + ((this.unread > 0 && ! (this.isNode && this.isOpen)) ? '<b>' : '') + + this.text + + ((this.unread > 0 && ! (this.isNode && this.isOpen)) ? (' (' + this.unread + ')</b>') : '') + + (MenuItem.current == this.cmdLink ? '</u></em>' : '') + + '</a></td></tr></table></td></tr>'; + + // 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('<tr><td' + cst + '> </td></tr>'); + } + e.draw(output, rd + 1); + if (e.isNode && e.isOpen) { + output.push('<tr><td' + cst + '> </td></tr>'); + sepPrev = true; + } else { + sepPrev = false; + } + } else if (!sepPrev) { + output.push('<tr><td' + cst + '> </td></tr>'); + 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 = '<h1>' + TopicView.notFound.title + '</h1><p>' + + TopicView.notFound.text + '</p>'; + }; + + displayDeleted = function (data, deletedAt, nParents) { + var str = '<div style="padding: 0px 0px 5px 5px">' + + '<h1 style="padding: 0px">' + data.shift() + '</h1>'; + for (var i = 0; i < nParents; i ++) { + var l = data.shift().split('#'); + var pid = l.shift(); + + str += (i > 0 ? ' > ' : '') + '<a href="?cmd=V%23' + (i == nParents - 1 ? 'F' : 'C') + + '%23' + pid + '">' + (pid == '/' ? ForumsLayout.menuTitle : l.join('')) + '</a>'; + } + str += '</div><p>' + TopicView.deleted.header + '<b>' + formatDate(deletedAt) + + '</b>' + TopicView.deleted.by + '<b>' + + 'some guy (FIXME)' + '</b>.</p>'; + document.getElementById('f-page').innerHTML = str; + }; + + displayTopicLayout = function () { + var str = '<div style="padding: 0px 0px 0px 5px"><h1 style="padding: 0px">' + + title + '</h1>'; + for (var i in parents) { + str += (i > 0 ? ' > ' : '') + '<a href="?cmd=V%23' + (i == parents.length - 1 ? 'F' : 'C') + + '%23' + parents[i].id + '">' + + (parents[i].id == '/' ? ForumsLayout.menuTitle : parents[i].name) + '</a>'; + } + + str += '<table style="width: 100%; margin: 10px 0px 0px 0px; padding: 0px; border-width: 0px"><tr>'; + str += drawPageChanger(); + + // FIXME: display tools + + if (hasPoll) { + // FIXME: display poll + } + + str += '<tr><td colspan="3"' + cst + '" id="topics-display"> </td></tr>'; + str += drawPageChanger(); + str += '</table>'; + + document.getElementById('f-page').innerHTML = str; + displayPage(); + }; + + drawPageChanger = function () { + if (nPages > 1) { + var st = 'border-width: 0px; margin: 0px; vertical-align: middle; '; + var str = '<tr><td style="' + st + 'padding: 5px 0px 0px 0px; text-align:left; width: 33%">' + + (cPage > 0 ? '<a href="#" onClick="pageContents.prevPage(); return false">' : '') + + '<-- ' + ForumView.previousPage + (cPage > 0 ? '</a>' : '') + + '</td><td style="' + st + 'padding: 5px 0px 0px 0px; text-align:center">' + + '<form style="' + st + 'padding: 0px; ">' + ForumView.pageSelHdr + + '<select onChange="pageContents.jumpToPage(options[selectedIndex].value)">'; + for (var i = 0; i < nPages; i ++) { + str += '<option ' + (i == cPage ? 'selected="selected" ' : '') + 'value="' + + i + '">' + (i + 1) + '</option>'; + } + str += '</select></form></td><td style="' + st + + '; padding: 5px 0px 0px 0px; text-align: right; width: 33%">' + + ((cPage<nPages-1) ? '<a href="#" onClick="pageContents.nextPage(); return false">' : '') + + ForumView.nextPage + '-->' + ((cPage<nPages-1) ? '</a>' : '') + '</td></tr>'; + return str; + } + return ''; + }; + + displayPage = function () { + var low = cPage * opts.perPage; + var high = Math.min(low + opts.perPage, pOrder.ln.length); + var str = '\n\n<table' + tst + '>'; + 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<tr><td' + cst + '"><table' + tst + '><tr>'; + if (opts.threaded && post.depth > 0) { + var sz = post.depth * 10; + str += '<td style="width: ' + sz + 'px; padding: 0px; margin: 0px"> </td>'; + } + str += '<td style="padding: 0px; margin: 0px" id="tp-post-' + post.id + + '"> </td></tr></table></td></tr>'; + } + document.getElementById('topics-display').innerHTML = str + '\n</table>'; + + 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 = '<table style="width: 100%; margin: ' + margin + '; padding: 0px;border-collapse: ' + + 'collapse; border: 1px solid ' + color + '"><tr><td rowspan="2" style="' + + 'width: 32px; border: 1px solid ' + color + '; padding: 0px;' + + 'vertical-align: middle"' + onClick + '><img src="' + staticurl + + '/beta5/pics/post_' + (post.open ? 'o' : 'c') + (post.unread ? 'n' : 'o') + + '.gif" alt="' + (post.open ? TopicView.close : TopicView.open) + '" /></td>' + + '<th style="padding: 1px; text-align: left; border: 1px solid ' + color + '"' + onClick + '>' + + post.title + '</th></tr><tr><td style="padding: 1px; border: 1px solid ' + color + '"' + + onClick + '>' + TopicView.posted + '<b>' + formatDate(post.postedAt) + '</b>' + + CategoryView.by + '<b>' + users[post.author] + '</b></td></tr>'; + + if (post.open) { + str += '<tr><td style="padding: 10px 5px; border: 1px solid ' + color + '" colspan="2"' + + ' id="post-contents-' + post.id + '">' + + (post.loaded ? post.contents : + ('<p style="padding: 0px 5px"><i>' + TopicView.loading + '</i></p>')) + + '</td></tr>'; + if (post.lcTime != post.postedAt) { + str += '<tr><td style="padding: 1px 5px; border: 1px solid ' + color + + '; text-align: right" colspan="2"><i>' + TopicView.edited + + formatDate(post.lcTime) + CategoryView.by + '<b>' + + users[post.lcAuthor] + '</b></i></td></tr>'; + } + } + + return str + '</tr></table>'; + }; + + 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 = '<p style="padding: 10px"><i>' + TopicView.loadError + '</i></p>'; + } 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('<br/>'); + + 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 = '<tr><td style="padding: 10px 0px; border-width: 0px">' + + '<table style="width: 100%; padding: 0px 10px; border-width: 0px; margin: 0px">' + + '<tr><td style="padding: 0px; border-width: 0px; margin: 0px; text-align:center" colspan="3">' + + '<div id="f-std-ctrl" style="width: 100%; text-align: center' + + ((showModTools || showOptions) ? '; display: none' : '') + '">' + + '<a href="#" onClick="pageContents.displayOptions();return false">' + + ForumView.displayOptions + '</a>'; + + if (canPost) { + str += ' - <a href="?cmd=C%23' + forumId + '">' + ForumView.newTopic + '</a>'; + } + if (isMod) { + str += ' - <a href="#" onClick="pageContents.displayModTools();return false">' + + ForumView.modTools + '</a>'; + } + + // Display options + str += '</div><fieldset id="f-options" style="margin: 0px; padding:0px' + + (showOptions ? '; display: block' : '; display: none') + '">' + + '<legend>' + ForumView.displayOptions + '</legend><form style="padding: 0px; margin: 0px">' + + '<table style="width: 100%"><tr><td style="padding: 0px 5px; margin: 0px; ' + + 'width: 33%; text-align: right">' + ForumView.applyTo + + '</td><td style="padding: 0px; margin: 0px; text-align:left">' + +' <input type="radio" name="f-apply-to" ' + + 'value="0" id="f-apply-to-this" onClick="pageContents.setOption(\'toAll\', 0)" />' + + '<label for="f-apply-to-this">' + ForumView.thisForum + + '</label> <input type="radio" name="f-apply-to" value="1" id="f-apply-to-all" ' + + 'onClick="pageContents.setOption(\'toAll\', 1)" />' + + '<label for="f-apply-to-all">' + ForumView.allForums + '</label></td>' + + '<td style="width: 33%; padding: 0px 5px; margin: 0px; text-align:right">' + + '<a href="#" onClick="pageContents.optionsOk(); return false">' + ForumView.ok + + '</a></td></tr><tr><td style="width: 33%; padding: 0px 5px; margin: 0px; text-align:right">' + + ForumView.perPage + '</td><td style="padding: 0px; margin: 0px; text-align:left">' + + '<select id="f-per-page" ' + + 'onChange="pageContents.setOption(\'pp\', options[selectedIndex].value)">'; + for (var i = 10; i < 60; i += 10) { + str += '<option value="' + i + '">' + i + '</option>'; + } + str += '</select></td><td style="width: 33%; padding: 0px 5px; margin: 0px; text-align:right">' + + '<a href="#" onClick="pageContents.optionsCancel(); return false">' + ForumView.cancel + + '</a></td></tr>'; + if (isMod) { + str += '<tr><td style="padding: 0px 5px; margin: 0px; text-align: right"><label for="f-disp-del">' + + ForumView.displayDeleted + '</label></td><td style="padding: 0px; margin: 0px; ' + + 'text-align:left" colspan="2"><input type="checkbox" name="f-disp-del"' + + ' id="f-disp-del" value="1" onClick="pageContents.setOption(\'dd\', checked ? 1 : 0)" ' + + '/></td></tr>'; + } + str += '</table></form></fieldset>'; + + // Moderation tools + if (isMod) { + str += '<fieldset id="f-mod-tools" style="margin: 0px; padding: 5px 0px; text-align: center' + + (showModTools ? '; display: block' : '; display: none') + '">' + + '<legend>' + ForumView.modTools + '</legend>' + + '<form style="padding: 0px; margin: 0px" id="f-mod-tools-contents">' + + '</form></fieldset>'; + } + str += '</td></tr>'; + + // Page control + if (topics.length && nPages > 1) { + var st = 'border-width: 0px; margin: 0px; vertical-align: middle; '; + str += '<tr><td style="' + st + 'padding: 5px 0px 0px 0px; text-align:left; width: 33%">' + + (cPage > 0 ? '<a href="#" onClick="pageContents.prevPage(); return false">' : '') + + '<-- ' + ForumView.previousPage + (cPage > 0 ? '</a>' : '') + + '</td><td style="' + st + 'padding: 5px 0px 0px 0px; text-align:center">' + + '<form style="' + st + 'padding: 0px; ">' + ForumView.pageSelHdr + + '<select onChange="pageContents.jumpToPage(options[selectedIndex].value)">'; + for (var i = 0; i < nPages; i ++) { + str += '<option ' + (i == cPage ? 'selected="selected" ' : '') + 'value="' + + i + '">' + (i + 1) + '</option>'; + } + str += '</select></form></td><td style="' + st + + '; padding: 5px 0px 0px 0px; text-align: right; width: 33%">' + + ((cPage<nPages-1) ? '<a href="#" onClick="pageContents.nextPage(); return false">' : '') + + ForumView.nextPage + '-->' + ((cPage<nPages-1) ? '</a>' : '') + '</td></tr>'; + } + + str += '</table>'; + return str; + }; + + displayContents = function () { + // Draw the header + var str = '<div style="padding: 0px 0px 0px 5px">'; + var hasDetails = (description != '' || admins.length || mods.length || users.length); + if (unread || hasDetails) { + // "Mark as read" link + str += '<div style="float: right; text-align: right">'; + if (hasDetails) { + str += '<a href="#" onClick="pageContents.toggleDetails();' + + 'return false" id="f-toggle-details">' + + (showDetails ? ForumView.hideDetails : ForumView.showDetails) + + '</a>' + (unread ? ' - ' : ''); + } + if (unread) { + str += '<a href="#" onClick="pageContents.markRead(); return false">' + + ForumView.markRead + '</a>'; + } + str += '</div>'; + } + // Title and parent categories + str += '<h1 style="padding: 0px">' + title + '</h1>'; + for (var i in parents) { + str += (i > 0 ? ' > ' : '') + '<a href="?cmd=V%23C%23' + parents[i].id + '">' + + (parents[i].id == '/' ? ForumsLayout.menuTitle : parents[i].name) + + '</a>'; + } + str += '</div>'; + // Details (description & ACL) + if (hasDetails) { + var needsBR = false; + str += '<fieldset id="f-details" style="margin: 20px 20px' + + (showDetails ? '' : '; display: none') + + '"><legend>' + ForumView.details + '</legend>'; + + if (description != '') { + str += description; + needsBR = true; + } + + if (admins.length) { + str += (needsBR ? '<br/>' : '') + '<b>' + ForumView.adminsHdr + '</b>: ' + + admins.join(' - '); + needsBR = true; + } + + if (mods.length) { + str += (needsBR ? '<br/>' : '') + '<b>' + ForumView.modsHdr + '</b>: ' + + mods.join(' - '); + needsBR = true; + } + + if (users.length) { + str += (needsBR ? '<br/>' : '') + '<b>' + ForumView.usersHdr + '</b>: ' + + users.join(' - '); + } + + str += '</fieldset>'; + } + str += '<table style="width: 100%; margin: 20px 0px; padding: 0px; border-style:none">'; + + str += drawControls(); + if (topics.length) { + // Topics list + str += '<tr><td style="padding: 0px; margin: 0px"><form style="padding:0px;margin:0px;' + + 'width:100%"><table style="width: 100%; padding: 0px; margin: 0px; border-collapse: ' + + 'collapse"><tr><td style="width: 20px;text-align:center;vertical-align:middle">' + + '<input type="checkbox" id="mt-sel-all" name="mt-sel-all" value="1" ' + + (modSelAll ? 'checked="checked" ' : '') + (showModTools ? '' : 'style="display: none"') + + ' onClick="pageContents.toggleSelAll(); return true" />' + + '</td><td style="width: 32px"> </td>' + + '<th style="text-align: left; padding: 0px 0px 0px 5px">' + ForumView.headers.topic + + '</th><th style="width: 8%">' + ForumView.headers.replies + + '</th><th style="width: 25%">' + ForumView.headers.fPost + + '</th><th style="width: 25%">' + ForumView.headers.lPost + '</th></tr>'; + + for (var i = cPage * perPage; i < topics.length && i < (cPage + 1) * perPage; i ++) { + str += '\n<tr style="border-width: 1px 0px; border-style: solid; border-color: white"' + + '><td style="width: 20px; text-align: center; padding: 0px; vertical-align:' + + 'middle"><input type="checkbox" id="mt-sel-' + topics[i].id + '" value="1"' + + (topics[i].selected ? ' checked="checked"' : '') + + (showModTools ? '' : ' style="display: none"') + + ' onClick="pageContents.toggleTopic(' + i + '); return true" />' + + '</td><td style="width: 32px; text-align: center; padding: 0px">' + + '<img src="' + staticurl + '/beta5/pics/topic_s' + topics[i].sticky + + '_' + (topics[i].unread ? 'un' : '') + 'read.gif" style="border-width: 0px" />' + + '</td><td style="padding: 0px 0px 0px 5px">' + + ((topics[i].isLocked || topics[i].hasPoll) ? '<div style="float: right">' : '') + + (topics[i].isLocked ? ('<img src="' + staticurl + '/beta5/pics/topic_locked.' + + (useGIFs ? 'gif' : 'png') + + '" style="border-width: 0px; float: left" />') : '') + + (topics[i].hasPoll ? ('<img src="' + staticurl + '/beta5/pics/topic_poll.' + + (useGIFs ? 'gif' : 'png') + '" style="border-width: 0px" />') : '') + + ((topics[i].isLocked || topics[i].hasPoll) ? '</div>' : '') + '<div>' + + (topics[i].isDeleted ? '' : ('<a href="?cmd=T%23' + topics[i].id + '">')) + + topics[i].title + (topics[i].isDeleted ? '' : '</a>') + + (topics[i].movedTo != '' ? ('<br/> -> ' + ForumView.movedTo + + '<a href="?cmd=V%23F%23' + topics[i].movedTo + '">' + + topics[i].mToForum + '</a>') : '') + + '</div></td>'; + if (topics[i].isDeleted) { + str += '<td colspan="3" style="text-align: center; vertical-align: middle">' + + ForumView.deletedAt + formatDate(topics[i].deletedAt) + CategoryView.by + + '<b>' + players[topics[i].deletedBy] + '</b></td>'; + } else { + str += '<td style="text-align:center; vertical-align: middle">' + + formatNumber(topics[i].nReplies) + '</td>' + + '<td style="text-align:center;padding: 0px 2px;vertical-align:middle">' + + formatDate(topics[i].fpTime) + CategoryView.by + '<b>' + + players[topics[i].fpAuthor] + '</b></td>' + + '<td style="text-align:center;padding: 0px 2px;vertical-align:middle">' + + formatDate(topics[i].lcTime) + CategoryView.by + '<b>' + + players[topics[i].lcAuthor] + '</b></td>'; + } + str += '</tr>'; + } + + str += '</table>'; + } else { + str += '<tr><td style="padding: 0px; height: 48px; border-style: solid; border-color: white; ' + + 'border-width: 1px 0px; text-align: center; vertical-align: middle">' + + ForumView.empty + '</td></tr>'; + } + str += '</table>'; + + 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 = '<a href="#" onClick="pageContents.hideModTools(); return false">' + ForumView.hideModTools + + '</a><br/>'; + if (!(dSel || nSel)) { + str += '<br/>' + ForumView.pleaseSelect; + } else { + if (nSel) { + str += '<br/>' + ForumView.selectedTopics + + '<a href="#" onClick="pageContents.deleteSelected(); return false">' + + ForumView.deleteTopics + '</a> - ' + ForumView.stickyLevel; + + // Sticky level management + if (maxStL < 10) { + str += '<a href="#" onClick="pageContents.increaseSticky(); return false">' + + ForumView.increaseStickyLevel + '</a> / '; + } + if (minStL > 0) { + str += '<a href="#" onClick="pageContents.decreaseSticky(); return false">' + + ForumView.decreaseStickyLevel + '</a> / '; + } + + str += ForumView.setTo + '<select id="mt-set-level" onChange="pageContents.setSticky(' + + 'options[selectedIndex].value)"><option value="">-------</option>' + + var stText = [ForumView.normalPost, 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', + 'IX', 'X']; + for (var i = 0; i < 11; i ++) { + str += '<option value="' + i + '">' + stText[i] + '</option>'; + } + str += '</select>'; + + // Lock / Unlock + if (hasLocked) { + str += ' - <a href="#" onClick="pageContents.unlockTopics(); return false">' + + ForumView.unlock + '</a>'; + } + if (hasUnlocked) { + str += ' - <a href="#" onClick="pageContents.lockTopics(); return false">' + + ForumView.lock + '</a>'; + } + + // Move to + if (hasMoveTargets) { + str += '<br/>' + ForumView.moveTo + '<select id="mt-move-to" onChange="' + + 'pageContents.moveTo(options[selectedIndex].value)">' + + '<option value="">-------</option>'; + for (var i in moveTargets) { + str += '<option value="' + i + '">' + moveTargets[i] + '</option>'; + } + str += '</select>'; + } + } + if (dSel) { + str += '<br/>' + ForumView.deletedTopics + + '<a href="#" onClick="pageContents.restoreSelected(); return false">' + + ForumView.restoreTopics + '</a>'; + } + } + + 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('<br/>'); + + 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('<br/>'); + + return obj; + }; + displayContents = function (obj, depth) { + var d = (typeof depth == 'undefined') ? 0 : depth; + var mw = d * 8; + var htag = 'h' + (d + 1); + + var str = '<table style="width: 100%; margin: 5px 0px 2px 0px; padding: 0px; border-width: 0px"><tr>'; + if (d > 0) { + str += '<td style="width: ' + mw + 'px; margin: 0px; padding: 0px; border-width: 0px;">' + + ' </td>'; + } + + var rText = ''; + if (obj.isCategory) { + rText = '<div style="float: right; text-align: right">' + obj.typeName; + if (obj.hasUnread) { + rText += '<br/><a href="#" onClick="pageContents.markRead(\'' + + obj.id + '\'); return false">' + CategoryView.markRead + '</a>'; + } + rText += '</div>'; + } + + str += '<td' + cst + '>' + rText + '<div><' + htag + '>' + obj.title + '</' + htag + '>' + + (obj.description != '' ? ('<p>' + obj.description + '</p>') : '') + + '</div></td></tr></table>'; + + 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 '<p>' + CategoryView.empty + '</p>'; + } + + var str = '<table style="border-collapse: collapse; width: 100%; margin: 0px 0px 20px 0px; padding: 0px; ' + + 'border-style: none; border-color: white; border-width: 0px"><tr>' + + '<td style="width: 32px"> </td><th style="text-align: left">' + + CategoryView.headers.name + '</th><th style="width: 10%">' + CategoryView.headers.topics + + '</th><th style="width: 10%">' + CategoryView.headers.posts + + '</th><th style="width: 25%">' + CategoryView.headers.lastPost + + '</th></tr>'; + + for (var i in obj.contents) { + var forum = obj.contents[i]; + str += '<tr><td style="height: 32px"><img src="' + staticurl + + '/beta5/pics/forum_' + (forum.isUnread ? 'un' : '') + 'read.' + + (useGIFs ? 'gif' : 'png') + '" alt="' + + CategoryView.forumIcon[forum.isUnread ? 'unread' : 'read'] + + '" style="border-style:none;border-width: 0px" /></td>' + + '<td style="vertical-align: middle; padding: 1px 0px 2px 0px"><a href="?cmd=V%23F%23' + + forum.id + '">' + forum.title + '</a>' + + (forum.description != '' ? ('<br/>' + forum.description) : '') + '</td>'; + if (forum.isDeleted) { + str += '<td style="vertical-align: middle; text-align: center" colspan="3">' + + CategoryView.deletedAt + '<b>' + formatDate(forum.deletedAt) + '</b>' + + CategoryView.by + '<b>' + players[forum.deletedBy] + '</b></td>'; + } else { + str += '<td style="vertical-align: middle; text-align: center">' + + formatNumber(forum.topics) + + '</td><td style="vertical-align: middle; text-align: center">' + + formatNumber(forum.posts) + + '</td><td style="vertical-align: middle; text-align: center">'; + if (forum.posts != 0) { + str += formatDate(forum.lastTimestamp) + '<br/>' + CategoryView.by + + '<b>' + players[forum.lastAuthor] + '</b>'; + } else { + str += CategoryView.noPosts; + } + str += '</td>'; + } + str += '</tr>'; + } + + str += '</table>'; + 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 = '<table' + tst + '><tr><td' + tst + '><table' + tst + '><tr><td ' + cst + + '<h1>' + ForumsLayout.menuTitle + '</h1></td><td' + cst + '><a href="#" onClick="' + + 'main.hideMenu();return false">' + ForumsLayout.hideMenu + + '</a></td></tr></table></td></tr>' + '<tr><td' + tst + '> </td></tr>'; + + var x = new Array(); + menuTree.draw(x); + + str += x.join('') + '</table>'; + 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 = '<div id="f-menu" style="float: left; margin: 0px 5px; padding: 1px 10px 20px 5px; ' + + 'border-color: white; border-style: solid; border-width: 0px 1px 0px 0px'; + if (menuVisible) { + str += '; width: 300px"><i>(loading menu)</i>'; + mainMargin = 326; + } else { + str += ';text-align:center; width: 16px"><a href="#" onClick="main.showMenu();return false">' + + ForumsLayout.menuText.join('<br/>') + '</a>'; + mainMargin = 42; + } + str += '</div><div id="f-page" style="margin: 1px 5px 10px ' + mainMargin + 'px"> </div>'; + 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 += '<h2>Fleets</h2><p>Total fleet power: <b>'+formatNumber(flOverview[0])+'</b></p>'; str += '<h2>Money</h2><p>Daily Profit: <b>€'+formatNumber(moOverview[2])+'</b></p></td>'; - str += '<td colspan="2"><h2>Forums</h2><p>'; - var i,j,k,a=new Array(); - for (i=0;i<genForums.length;i++) - {with(genForums[i]){ - k = 0; - for (j=0;j<forums.length;j++) - k += parseInt(forums[j].nUnread,10); - j = (k==0? "no unread topics" : (k + ' unread topic' + (k>1 ? 's' : ''))); - a.push('<a href="forums?cmd=C%23G%23'+id+'" ' + ovett[4] + ' >'+name+'</a>: ' + j); - }} - - if (aForums.length) - { - k = 0; - for (j=0;j<aForums.length;j++) - k += parseInt(aForums[j].nUnread,10); - j = (k==0? "no unread topics" : (k + ' unread topic' + (k>1 ? 's' : ''))); - a.push('<a href="forums?cmd=C%23A%23'+allianceId+'" ' + ovett[5] + ' >Alliance forums</a>: ' + j); - } - str += a.join('<br/>') + '</p>'; + str += '<td colspan="2"><h2>Forums</h2><p>' + forums.output(-1, false) + '</p>'; str += '<h2>Planets</h2><p><b>'+formatNumber(unOverview[0])+'</b> planets</p>'; str += '<h2>Next ticks</h2><p id="ticks"> </p>'; @@ -188,30 +169,7 @@ str += 'Daily Profit: <b>€'+formatNumber(moOverview[2])+'</b><br/>'; str += '<a href="money" ' + ovett[15] + ' >More details...</a></p></td>'; - str += '<td colspan="2"><h2>Forums</h2><p>'; - var j,a=new Array(),s; - for (i=0;i<genForums.length;i++) - {with(genForums[i]){ - s = '<b>' + name + '</b> (<a href="forums?cmd=C%23G%23'+id+'" ' + ovett[16] + '>view</a>)'; - for (j=0;j<forums.length;j++) - { - s += '<br/> - <a href="forums?cmd=F%23' + type + '%23' + forums[j].id + '" ' + ovett[17] + '>' + forums[j].name + '</a>: '; - s += makeTopicsText(forums[j].nTopics, forums[j].nUnread); - } - a.push(s); - }} - - if (aForums.length) - { - s = '<b>Alliance Forums</b> (<a href="forums?cmd=C%23A%23'+allianceId+'" ' + ovett[18] + ' >view</a>)'; - for (j=0;j<aForums.length;j++) - { - s += '<br/> - <a href="forums?cmd=F%23A%23' + aForums[j].id + '" ' + ovett[17] + ' >' + aForums[j].name + '</a>: '; - s += makeTopicsText(aForums[j].nTopics, aForums[j].nUnread); - } - a.push(s); - } - str += a.join('<br/><br/>') + '</p>'; + str += '<td colspan="2"><h2>Forums</h2><p>' + forums.output(-1, true) + '</p>'; str += '<h2>Universe</h2><p><b>'+formatNumber(unOverview[0])+'</b> planets';// (<b>'; str += /*formatNumber(unOverview[2]) + '</b> at the same prot. level)*/'<br/><b>'; @@ -240,6 +198,40 @@ } +function __forumsOutput(depth, complete) { + var str = ''; + + if (this.id != '/') { + for (var i = 0; i < depth; i++) { + str += ' '; + } + str += '<a href="forums?cmd=V%23' + this.type + '%23' + this.id + '">' + this.name + '</a>: '; + if (complete) { + str += '<b>' + this.topics + '</b> topic' + (this.unread > 1 ? 's' : ''); + if (this.unread > 0) { + str += ' (<b>' + this.unread + '</b> unread)'; + } + } else { + if (this.unread == 0) { + str += 'no unread topics'; + } else { + str += '<b>' + this.unread + '</b> unread topic' + (this.unread > 1 ? 's' : ''); + } + } + } + + for (var i = 0; i < this.contents.length; i ++) { + if (this.contents[i].type == 'F' && ! complete) { + continue; + } + + str += '<br/>' + 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<nCustom;i++) - { + for (i=0;i<nCustom;i++) { a = l.shift().split('#'); cFolders.push(new Folder(a.shift(), a.shift(), a.shift(), a.join('#'))); } // General categories & forums - genForums = new Array(); - for (i=0;i<nGenCats;i++) - { - a = l.shift().split('#'); - - var j,c,id,tp,nForums; - id = a.shift(); tp = a.shift(); - nForums = parseInt(a.shift(), 10); - c = new Category(id, tp, a.join('#')); - - for (j=0;j<nForums;j++) - { - a = l.shift().split('#'); - c.forums.push(new Forum(a.shift(),a.shift(),a.shift(),a.join('#'))); - } - - genForums.push(c); - } - - // Alliance forums - aForums = new Array(); - for (i=0;i<nAForums;i++) - { - a = l.shift().split('#'); - aForums.push(new Forum(a.shift(),a.shift(),a.shift(),a.join('#'))); - } + forums = new ForumsEntity(l); } diff -Naur beta5//site/static/beta5/pics/forum_read.gif forums//site/static/beta5/pics/forum_read.gif --- beta5//site/static/beta5/pics/forum_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/forum_read.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,5 @@ +GIF89a � ��I�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +��,���� � ������������������������&9"-�3>�>=�<:8867�3=$�>?=;9741*'# 9�&�0� +6�!$'3,�81223��3458@������,>#0�9FEEDCCBA@��� 19J��%���t�R�B(����B� (H��@�S�X��� .^����� 9��I]�!9��� <�a�5Ej�0B�$H��'H�d"R�F�tԘ�Ŋ!8d�Hd��/.�#� +�0X�сF$X�E�!Ĉ&N�@�"� +MkJ�L��eD���; \ 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��� ��� ���szz����bKGD������������� pHYs����������tIME� 0)No���IDATX���k$E��U�]�����fr]�����"ě��G,(^��]�y��%�E<�E<+� ����$fe��d�����!�Nd���KW��T=���<U�q�rXӊ�n�nY����h4�0�o�ۿw�]��~MJ�X��?�m)%J)�RH)q��!ĽV��ݴ�䴢��Z���$����G�[RʗVWW?����Ƙs�,)���,�,�!�����x��DQ����(�5��}� ��˲&�TYEQ��)EQ��9EQ`���lz@ �� �cz�^��z�.�U��8::boo�<�Ͻm�e���J)��7s�0|,�@A��iz�oZ����bP�߲�s-8[�,!��Z�.#�T�A��������Bw���e���x�?᳙U0�y���<��N��N����666��Z�E�U��s���^ZZ�iccß��{�q��b�+�(�.^�b��j}~�Y^^��1���x��1p[)��������܄xƘ'ƕ�1�yNY���@>���m��DZ��`~�B)����T��4M)��(�`&�I������)%6 �$1�������>P��ͫ����7o�3�����8FJ�ֺ|��џc��ٶ�_nܸٶ=��e �0�ΕH��t�7��,�����ӓ��{�1�0�ի���i-��IB@��#��ܧ�0�F�K'���RJ���ֺ\__�$���β,�Ak��8��Zc�����3s`ss��(�nGQD�$��y��_v��f����u]��¶mc�6B�s��^J� �;3���^]YYy=�cvvvH�4�r]��n���}� �W�j[>�J�����Y���`@�e����0$�2�8�y�h4b4M���֭[�xo9��<`vww1Ơ�&�"�j�Z6��ߟh�bV_�J�qB����|������� ���6�R��p���q��~0�<`۶����^�����[���<�������?j�g}Ͷ����IEND�B`� \ 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��+��5��6��/��8��0��4��:��4��5��>��7��?��=��8��>��;��B��<��=��>��E��A��B��H��B��C��D��L��E��G��F��H��I��O��K��L��M��O��P��R��U��U��V��}��X��X��W��X��Y��Z�҃�Ն��]��^�ս��u��^��`�Շ����`��n��؈��d�ג��g�ؚ��e�ח��e�ם�ע��l����ڤ��j�چ�ب�ۧ�ح��k�ز��l��n��o��p�ڸ�݂��u�߽��q�܁��x�݄��t�߇�݂��y�������x��y��y�߅�������|��|���������~���ߕ����ߘ���������ߝ���ߠ�ߥ�������ߨ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#J�x�WI�T�+�^�n�5�T(L�*H�+����Ӥ=g�0��B%<��"�N�#Bd���A�dZXJGn���РC�5z��(cӪ]˶-Am�A�#�4hΚ1S��1_�^�Z��O+ S`ʤ�)R�2X��qj!D|HH�)Ll�0��@1|Sǔ���ȡ�Ǐ F�.i┊�����Pd �,Z��a#"M��Q��@�h=�$V���g͖\%+�KV�H�F@�g���,�3'��"9h�0q���!l���Q� +(���T@�tCQ���@a�`�RT��\�A�k(܉(��� �; \ 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��� ��� ���szz����bKGD������������� pHYs����������tIME� 0*�f���IDATX���kU�?w~��lv7��ش��m�4mQ�}4hѪ���G+jAD���`)>E}�C}����Z��XmK�j�ִI!M7;;sg�w����M_TȁÝ9̜���������mؿl�SPM1�aM�/ý�$����#�_QSX��p��~[�� ���̓��g1_a�Ůc�v���+Wz��_z��N��,qq�C�5l� �����H�D�J@��$� �R��%�E`0{���^��*8&Ȉ��[r�]�E Hc�I�#�Ch.�lA�A��j�� +0�����4�p�{&M�f���x�?@��V����h�R��'���Q���?���2�V����� +�=}��6��c����F�8�-O�#��W���t6�T薜�)%'?==���i�� ��������>�n����m�X'� ���������!��1I�rJ�}��:�O��W��<�Na�l�ps`����v���3#v|��",�b��C�A�kZ��1� nn�g�~��6�w��R_��@�"�!*M�����n��T<�Eh�,�x%�W@s��� +!\�8ӄ$�T�j�6�\�@�ӧ��X=��`1�]�4s�z2�h�'��֍�$�":>j��jUAw�W� +F�i����t!6�K��C� � ����̱uz�SK��/�*B��GkGX^(��_���l4/f:P�du�4����)I�-��7��cg۹^��NE�fuˆ���|O*������Q�ΰ��A��[��n�~,;�'��k@}`��8|��*�$A�8��RM1Lm�Qr�{�[@00l�,00-�����rf�w�/ �y�|��;��q�_������G��*�y��e�))CMO���^�0�ԭ��_-�Lh���(U!���4E�DS/�u;�jd:��q!PYo��Шb�;�eFu|x{��[P�QO��H=Y�jW +T6z�{�Z����~�(�7�rC;d�.f�ps�nD�'aiw���6��v�(m�b���-س�ց��;���Z@�.���������������IEND�B`� \ 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�@1�A2�C4E4�F5�G6�G7�H8�I9J9L:�K:M;�N<�O=�P>�Q?�R@S@TA�UB�UBVC�WD�XE�YF�ZG�[H\H^I�]I_J�^J`K�_KaL�`LbM�fK�cN�dO�ePfQjOgQiR�hRlQ�iSnR�kT�oS�pS�rU�sVnXpYuXr[vYs\t]w^�{]�v^ y`~`|c�a{b�h�j �l�j�k�m�l�n�m�n�o �o�p�q�r�q�t�s�u�v�v�w�w�x�y �z�{�|�|$�}�~��'����(��!��)��"��*��#��+��$��,��-��.��.��5��/��0��1��2��2��3��3��4��4��5��<��6��7��8��9��:��A��B��C��D��E��F��G��H��I��J��K��Q��W��R��X��S��T��U��V��W��X��_��`��a��b��h«cìd¬jĭeíkŮfįlŰmƱnDzoɳp̷{лѼ�ӿ������Ì�č�Ŕ�Ȗ�ɗ�ʘ�˙�̚�͛�͡��ϣ�Х�Ҧ�Ԯ���������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�C�Y"J���!�,W�����8 ф�h1K@�����9rڨS���;��i�;y����F��� +����t#C��81���6e��"g�E�-�� �K�p�CG�Q�Y�h�K�R4b���DO?w^����'N�4%���@;j��"��;E�B�ʔgQ�@M��A�?~��pL�)� +e +*R�<=)��� >���K4���J� +UnS�:5R�`�����<y�g�)�����ʔ�4d�ȡÆd?r�H�D�q9^ +v���NpA!��� 6�:�`�B�w�-��@0A�B 4��X 9��^}đAR��,��b��%��� +;ư� p��ْD d0��t(B *�@B�'�@C~���fW䢆8 �8��4xHB4`�SH8��R^*�pB�*�A !t�a,��+tE#���,��� ++�� L�!Tq�$�4R�r�E0�0s�1��L/�����\1I%�,�H�d� �,�L2��2J80A�Q�\cRFU�I��,#L/Z8�A�^� (sI2���+̨z.O�@�c�ҙ'�Tr�iq�1���F���(����%t\�Я���*�qJ,�xF��>�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����*\���Ç� b�H�"D���#bA@�T�Be��(O�0QR�GG�<�dђ�+ULBy�d /����K.[hB�d +�&L� ����1b�˓%`,�"�J%S�`b�L�2dƀI�BD 2��5(B!hԤ�ƌ�XA�I��*��8ȣ��6�٬Qs�@�&���l�"T��u8o�0A�����-^���TI�Q�>G��11�8�B/j4���I��X�hW�ĉ�'O����0�D�,Z���d +�&i�Ȳ'� H�pA��$�֜ +Ɛ�OA[�Q�0�@X��� ���� �)H\�� +0��~`�� �0 $�`c 6Tj#zFT��X�A�B"4��VLaĂ`�!A�`��t�A���_���WP1eAZ�!D�Y@�)��l�e8\�Ua����x<���3�`�`�AZt�E�}t�"�R� ��c� �TЁ_�Յ�M�&�4��"�RH nаbh��h��Y�PU�8�����4��=��F�zq�����#�$R�0`���mp+����*bW2��o�G�eD�C<���|���H�q��D��!a!��u�op!�j QiR4]��=�G4�l�G�; \ 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�@1�A2�C4E4�F5�G6�G7�H8�I9J9L:�M;�N<�O=�Q?�R@TA�TAUB�VC�WD�XE�\C�YF�ZG�[H\H^I�_J�`K�aL�bM�cN�gL�dO�cO ePfQjOhRlQ�nR�oS�lU�pS�qT�lVrU�sVpYqZqZ xZ�wZv]�t]w^�{]�v^ u^w_ +za�~`{b{b}d}d�f~ef�i�l�j�m�k�n�n�o �p +�q�q�r�s�r�t�t�u�u�v�w�w�x�y�y �z�{�|�|$�}�~�,�},�~&��'����!��)��"��*��#��+��$��,��-��-��.��/��0��0��1��1��2��2��9��3��3��:��4��4��;��5��5��<��6��6��=��7��7��>��8��?��9��@��@��:��A��G��B��C��D��E��F��G��H��I��J��P��K��Q��L��R��S��T��U��V��W��Y��_��`��f��a��b«cìd¬jįlŰmƱnDzoʴq˵r̶s̷{ι}Ϻ~�č�Ŕ�̚�̠�͡�ϣ�Х�Ҧ�ӧ�Ԯ���������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\��Çr)c�1�d�� 1p����d9p���±#�4��$D =z�i�FL˅W�T�$)R�Du�H(��:mҀ���J P��f�T�Ƃ���sRN� ��1e��(R�ڼX𠮍B����U�g>�JE�Ԟ�>����ȱ'�d9���X�\ifc�(�t��͓*���"�V�X�a�A@!��H�d�P=y���bPϭ[��עǀ��6$��)Q�@zPz�x���]�x�� +��Bt����� @}��A;��#a����3� lPB L�J)���4E�@[D"�0�(��2�q@�P��,2� +)��Fy�A\}�c +B@`�� -� � M��2<�[T�(�(�A2�WB#���I@a0����qƊYA�(���P��X�:� ��U�_����@�Ap�B�-�0�<�P��(B�b�2ƈ������%�5��[ ��#����A[��L��@A +�p1��bt��$x�I�8S�P�� +X�iD��2 +'�R�AX�"�4�X*k&��CC�a�+��҉%j�6��H�1�R�� 0���+���J)��!,��Bs,��P��R�*��q�BV��3��I�t���-��� +Vt�l䒌0z��-��r"b�l2�bD�#�����L�WTA��H'��@�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/post_oo.gif forums//site/static/beta5/pics/post_oo.gif --- beta5//site/static/beta5/pics/post_oo.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/post_oo.gif 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,7 @@ +GIF89a � ���---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\�P�Ç&|H�2cĄ�Q"A)Tq�f��41��(Q +�9r��qC��3e2^����;v���$Ȗ5hR��³��=z���C��� Vpa��E1[��R�?}���R�C���ꕌ�*b?Z$HP�@�xh���J�i��̘0yJ1D�P�B��$pqXĈ&t�,��K�R�N���0D�(aʝ:���Y�G� _�E��(RL�s��L6i.�V$I�H��aP�D�,�����c4ez7�D��*U��i�C/���&ʓ0l$�[$�T��|p����1�� k�A ?��To�dbx��8���1�P�cA�� N@��%�p��@� =���70a�D�0%��I$ �"��� +(�pC:��ÖD�!�l�G&Z\�� 5�0�2�p�<���Fh�y�I��'\�Y)<�&;��Ip��ι�g*���������a�uı�X��"ʤ +���5��c��Z��&Y��2�f��C�I���~��d���*J$`��F"�Hzp����(\X�� +D4�F#� bmZ�zl���ID���<�"��b�Rp� +&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�(�$�<y�t5�(p7~��mK�� ����K�hѧj�filT�'�����C�R�es�9��EJ�|y)��$�����RS6DQs���2�)��5�]����%��'F;�S���߿�a�H)۷È���BЪI�L9�/Ux �A欳g��t�2��`�R�&F��,W�D��G�/`�|�҅�,W�R}�����_�v�E+�+V�N��4�]�v�5+�T�H5��T�a�N�L���ӦL�*I�KD+_�x�҅�-[h�EO�@ҁ&���&�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_locked.png forums//site/static/beta5/pics/topic_locked.png --- beta5//site/static/beta5/pics/topic_locked.png 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_locked.png 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,6 @@ +�PNG + +��� IHDR��� ��� ����s����bKGD����̿��� pHYs����������tIME� +m�)X���IDATH��=hQ�{^��%�$�=J!H +E1�,R�[Y��D�F��hP����B�@�4)�tz"\�xz1���swo?09��Ke�y�¾���7�v������]C;u`ZZ�K鱹x������{�%iK[~����OO�ۣڜ�<�����Fg�{�h0�]�[�ش +�?����Z��܉�a&N�\`���f߭���w�Z�8G�.�Y/��2�Hm�M���D/E>,�x��),,�?��������<elBj�R�T�C��G@(����"&�WF'D��S�L~��?pe�����0X�dN/> �4��S��jx�L9%���,?>n<�:Cퟀ��,�MW�-����U���X�.X���f%�����:&V�� ���H�ǘxDT������ Hjx��f���A�D�� ��6 �$����쑢���)�[�n`;ž1r�EXᗻ�E��͗�tC�lt�]{�H��nzZ���G�q���o���j�����IEND�B`� \ 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 ��HjfYB��GidWA������ŏ'PSOK(���Đ +-ejd_- J�ΐ *agb],���ۏA^f]+��� +pP�q��=[AD�#B������\xQ����̰� #"�8��ე� /6P�E�7$��"�$:F4m�����?EAo�� S���PrܡcB�*4P`�挖!�fRX�F��P^$�`dL*3>2 � +�@8p`"�)���A����+V���8�p���˘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��� ��� ���szz����bKGD������������� pHYs����������tIME� s( ���IDATX��Ok$U��uuWu�PiB4 H�����!!�,EA�0�����r����E� ѕ&n� Ջ����zﺘ�&��L'�a\��n���{���^���!��j����z�T*��"���1�o��^�{U�wK��0���c��9���k��EQ�i�f�K��:s��GD�0|��������\^^��lR���^��V�����?��o���W�(���_��Ͽ����;;;lll���b|&"�\�knIJU�( +)�k�8��P���GQ���oc�"�"RXk��Uu�=eY�����L&��XZ�V5��"�����uqq�ި�^J �"�F=��KKK�ooo�� �2�8����n�����1�O���}�s�������z�4M������"2��u{���Q�`0��*�Z�,�( +ʲ�,�&��f +w��e�7y��wvv����v���}�:[{A��A�|/"ov:����<���-�s�D�i �߆a����BX���5���$I���*��2o�۵Z��f����k��k-�����}VVV�T*����Hc��N���}F� �%쨛M�CD&��j��:^��d�V�y�VD&�c?c�V��w���Q�V���y��Xq]���@���(��`c�eI�$���G���q�`0 M�qLT�ި��i | +<>����s<ϛH�k-��m���9���)�~k-�n�N�CY�5U}��2��� �1���*sss�*eYr�R�1�4MI��7৻��;���?�|�r�������IEND�B`� \ 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>>�,,� � �3XWV�UUSSMMKKDDAA<841�-�7vuupsp�tq�� �7kά!�� C9�ܩs�NE��X��oN�8n�Aa�ƓQ��XG�6rƜ���&F�-ᄔ�F��6m���F �5>�E9�N�7r��dӰ�!K3�#� 9hֈ�b�֬���i�R�3�f�|!q5�ĭ]��[��٥i}w��v+^�s��3eΜ!3&��k�xP�iҠ1C��-t���r��6���[f0ZFb��9oԴL7͚ڦO�&���|�ܴA�!2fƈs��eC�%��� �q)����jC*�=}#���`(���K��Y`V +TP�P8�DHa�D��C;��Åꀁ!h�� 6��$"`@ ������Ɍ4�h�8�H �; \ 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?9C<!D="G@#IB$JC%KD%LE&NF'RJ)SK)TL*UM*WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��l��l��m��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~��������������������������������!� +��,���� � �������������������-;A@=851+*'&%" �/QSOJHEAB63.)'$"���5QMMHFC<<�**���1VUT�SSQQKKIIBB??:62/�+�5tssnqn�roڴ��f��6iʤs� C7�ԙS�NE��X��/Μ7l��1a�ƓQ��8��5p���&F�-݄��F��6m�̹��4>�EGΜ6p��T�p�!K3�{��8f҈�b�֬���Y� 2�d�tq5�ĭ]��[��٥i}w��t+^�s-�2cʔ�K�k�x��gΘ!#F��,t���2��5���;&�/XBR�r�8mд<�L�ڦO�&���|�ج1��1d�R��eC�%��u��q)���EjC(�=m#]��^({��eK��W`V� RH�N0��FAB���9�Å�`�!`�� 2Ѐ$P�����b��Ɍ4�h�8�H �; \ 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��� �2h�t�re +�#D~��QC� +#<lؐ�B˂Fʀ�K�&Mp��ѳ� +%<����B֨IC �2e�x� +�(Q��.�0Z��P"C}*T��A� +�Hϟ:5"i�hQ#F�)S^����D�B���"�SoV��"D�!�c#��ۚ1�64 ���n���N���=��@���@@�Fn ��h�lA�g�2���Y�0�� D~ɯ#� ��4�P��b�k@����u�0^y��������{��}�w�*�0��8`���Ȉ$���"�W��G}��n��m{�q@r ��|衇nXP��F�SB���0�y�aG1�k�(�H!�������H*I���_�� ���y�aGu�h���6�{�"��2�eo$iP|-BX���8�2�Qs@ +)6t�g�AF_p�9WXaP0��I,�k�I�`&����`@��<�� ,�, +8��Hd���f�-B�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s10_unread.gif forums//site/static/beta5/pics/topic_s10_unread.gif --- beta5//site/static/beta5/pics/topic_s10_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s10_unread.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,12 @@ +GIF89a � �������� + + + '#($*&-(0+1,2-506172<6=7>8?9C<!D="G@#IB$JC%KD%LE&NF'OG'PH(QI(RJ)SK)TL*UM*VN+WO+^U/`W0bY1cZ1d[2f\3j`5ka5lb6mc6nd7pe8qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��M��N��O��O��P��P��Q��R��T��T��U��V��W��W��X��X��Y��Y��Z��Z��[��[��]��_ñaɶdнh��i��j��l��l��m��m��n��o��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*��d�'I��� 2\�H��Ç4X��� �2h�t�re +�#D~��QC� +#<lؐ�B˂Fʀ�K�&Mp��ѳ� +%<����B֨IC �2e�x� +�(Q��.�0Z��P"C}*T��A� +�Hϟ:5"i�hQ#F�)S^����D�B���"�SoV��"D�!�c#��ۚ1�64 ���n���N���=��@���@@�Fn ��h�lA�g�2���Y�0�� D~ɯ#� ��4�P��b�k@����u�0^y��������{��}�w�*�0��8`���Ȉ$���"�W��G}��n��m{�q@r ��|衇nXP��F�SB���0�y�aG1�k�(�H!�������H*I���_�� ���y�aGu�h���6�{�"��2�eo$iP|-BX���8�2�Qs@ +)6t�g�AF_p�9WXaP0��I,�k�I�`&����`@��<�� ,�, +8��Hd���f�-B�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s1_read.gif forums//site/static/beta5/pics/topic_s1_read.gif --- beta5//site/static/beta5/pics/topic_s1_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s1_read.gif 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,3 @@ +GIF89a � ��w����'''(((***---000111222555666777<<<>>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +��,���� � ���������������������.<BA>962,+('&# +�0RTPKIFBC74/*(%#���6RNNIGD==�++�� �2WVU�TTRRLLJJCC@@;730�,�6uttoro���q��6oܨ1�f/%�c���:)R����>9tഉS愡SnTI$�8o��c"�ʛ1�|32N��h�L �@E�0٤Y�(���ʙC�M:2�<$a(T�EC�i�&�5h�p-��k�8�qؼL��L2^Ft�j',��eϦ]K�-Էq�ֽ�א�:�#K��V�2f̌FK�7�U|St�V4hΔ3���Z�l)nQ7Z��%#�˗,"�`IgN7i^���F �֮aR���>~mY��̘2b�|1�ų!�u,�f�ݻ�����嵡���ɾ&��/�}�E\�G�r�S<�M(��FQ�?���9��a�9\`�d0�� 8���P�� ���(��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?9C<!D="G@#IB$JC%KD%LE&NF'RJ)SK)TL*UM*WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��l��l��m��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~�����������������������������!� +��,���� � ���������������������.<BA>962,+('&# +�0RTPKIFBC74/*(%#���6RNNIGD==�++�� �2WVU�TTRRLLJJCC@@;730�,�6uttoro���q��6oܨ1�f/%�c���:)R����>9tഉS愡SnTI$�8o��c"�ʛ1�|32N��h�L �@E�0٤Y�(���ʙC�M:2�<$a(T�EC�i�&�5h�p-��k�8�qؼL��L2^Ft�j',��eϦ]K�-Էq�ֽ�א�:�#K��V�2f̌FK�7�U|St�V4hΔ3���Z�l)nQ7Z��%#�˗,"�`IgN7i^���F �֮aR���>~mY��̘2b�|1�ų!�u,�f�ݻ�����嵡���ɾ&��/�}�E\�G�r�S<�M(��FQ�?���9��a�9\`�d0�� 8���P�� ���(��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}}}���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +��,���� � ���������������������.<BA>962,+('&# +�0RTPKIFBC74/*(%#���6RNNIGD==�++�� �2WVU�TTRRLLJJCC@@;730�,�6uttoro���q��6oܨ1�f/%�c���:)R����>9tഉS愡SnTI$�8o��c"�ʛ1�|32N��h���Q� a�I�F PdC�=J�M:2�<$a(FԢ���i�&�5h�p-�uh�}�EټL��L2^Ft� + ,��eϦ]K�-ηt�έ{7�!u"K�,��X3d̘#���o� +�jQ4�ϔ3���Z�l)'���f���K�V��3'��4/��E�fwkװ ���o�6lάQxfL1a����ِl�:�e���]�ax{���P +~U�`_����_x��͇h��0�OD�DJ q�E1�<�C;t�a�AL`b,�"0��8@�1 +0�����<��H �; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s2_unread.gif forums//site/static/beta5/pics/topic_s2_unread.gif --- beta5//site/static/beta5/pics/topic_s2_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s2_unread.gif 2011-02-05 10:10:02.694335002 +0100 @@ -0,0 +1,7 @@ +GIF89a � ��w����'#($*&-(0+1,2-506172<6>8?9C<!D="G@#IB$JC%KD%LE&NF'RJ)SK)TL*UM*WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��l��l��m��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~�����������������������������!� +��,���� � ���������������������.<BA>962,+('&# +�0RTPKIFBC74/*(%#���6RNNIGD==�++�� �2WVU�TTRRLLJJCC@@;730�,�6uttoro���q��6oܨ1�f/%�c���:)R����>9tഉS愡SnTI$�8o��c"�ʛ1�|32N��h���Q� a�I�F PdC�=J�M:2�<$a(FԢ���i�&�5h�p-�uh�}�EټL��L2^Ft� + ,��eϦ]K�-ηt�έ{7�!u"K�,��X3d̘#���o� +�jQ4�ϔ3���Z�l)'���f���K�V��3'��4/��E�fwkװ ���o�6lάQxfL1a����ِl�:�e���]�ax{���P +~U�`_����_x��͇h��0�OD�DJ q�E1�<�C;t�a�AL`b,�"0��8@�1 +0�����<��H �; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s3_read.gif forums//site/static/beta5/pics/topic_s3_read.gif --- beta5//site/static/beta5/pics/topic_s3_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s3_read.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,3 @@ +GIF89a � ��w����'''(((***---000111222555666777<<<>>>???CCCDDDGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +��,���� � ���������������������.<BA>962,+('&# +�0RTPKIFBC74/*(%#���6RNNIGD==�++�� �2WVU�TTRRLLJJCC@@;730�,�6uttoro���q��6oܨ1�f/%�c���:)R����>9tഉS愡SnTI$�8o��c"�ʛ1�|32N��h���"ъDa�I�F PdC��!*gNR:2�<$a(FT��B]猚�^��z��T��q�$M��L2^Ft��V,��fѪ%�V�ۤr�ҵ�Wo!u"K�Ct_R6Dɘ13F-!��I9웱k��9Sf̘/Zjq�b�nR�KF̗/YDZ��Μ8nҼDc��ޯa��B#�}�ڰ9�F�1eĄ�b�hC�)�H���w;���eKlC)��!�}��_t��]pQ_}X�V� SL�DO4�GQDC��:�Ç�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?9C<!D="G@#IB$JC%KD%LE&NF'RJ)SK)TL*UM*WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��l��l��m��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~�����������������������������!� +��,���� � ���������������������.<BA>962,+('&# +�0RTPKIFBC74/*(%#���6RNNIGD==�++�� �2WVU�TTRRLLJJCC@@;730�,�6uttoro���q��6oܨ1�f/%�c���:)R����>9tഉS愡SnTI$�8o��c"�ʛ1�|32N��h���"ъDa�I�F PdC��!*gNR:2�<$a(FT��B]猚�^��z��T��q�$M��L2^Ft��V,��fѪ%�V�ۤr�ҵ�Wo!u"K�Ct_R6Dɘ13F-!��I9웱k��9Sf̘/Zjq�b�nR�KF̗/YDZ��Μ8nҼDc��ޯa��B#�}�ڰ9�F�1eĄ�b�hC�)�H���w;���eKlC)��!�}��_t��]pQ_}X�V� SL�DO4�GQDC��:�Ç�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����*\Ȱ�Ç#*�a�I$C��A#�,R� 1���2P����/`�\�"�ɓ >r̈ႅ�:hА�B˂@�h�Re +�#GpҠ�� !:�̠���<Ȍ C/^�`�b�ɓ%K�顣n� �$��>}����=}�ࡃ'�6. +!DH�A�)S���^@����3���SoV-��?}����"��ۚ1��3���}����Y*����;y��G:�8�#���y��0��:�4���3g��4u��g3�`���)G���O��q�s���G��d���G|��G�}��G� F��! �"X� ���� ���� 4(t��g�`� ��@_|4A�L�Q�q���&d�m���z��m(m�a� +��� ������A�xl��M4�f|���zԑ�bu�1o�A9�$e� ��yX��op�L$_��g4؆�m����NZ�����[t�E<VP1�QD�FA�E��� t�"p`lL��,p�D{@�p�e����v�P@�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s4_unread.gif forums//site/static/beta5/pics/topic_s4_unread.gif --- beta5//site/static/beta5/pics/topic_s4_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s4_unread.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,6 @@ +GIF89a � ������ +'#($*&+',(-(0+1,2-50617293;5<6>8?9C<!D="F?#G@#IB$JC%KD%LE&NF'RJ)SK)TL*UM*WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�yB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��M��N��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdλgнh��i��j��l��l��m��o��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�a�I$C��A#�,R� 1���2P����/`�\�"�ɓ >r̈ႅ�:hА�B˂@�h�Re +�#GpҠ�� !:�̠���<Ȍ C/^�`�b�ɓ%K�顣n� �$��>}����=}�ࡃ'�6. +!DH�A�)S���^@����3���SoV-��?}����"��ۚ1��3���}����Y*����;y��G:�8�#���y��0��:�4���3g��4u��g3�`���)G���O��q�s���G��d���G|��G�}��G� F��! �"X� ���� ���� 4(t��g�`� ��@_|4A�L�Q�q���&d�m���z��m(m�a� +��� ������A�xl��M4�f|���zԑ�bu�1o�A9�$e� ��yX��op�L$_��g4؆�m����NZ�����[t�E<VP1�QD�FA�E��� t�"p`lL��,p�D{@�p�e����v�P@�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s5_read.gif forums//site/static/beta5/pics/topic_s5_read.gif --- beta5//site/static/beta5/pics/topic_s5_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s5_read.gif 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,5 @@ +GIF89a � ������ '''(((***+++,,,---000111222555666777999:::<<<>>>???CCCDDDFFFGGGIIIJJJKKKLLLNNNRRRSSSTTTUUUWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�a� $C��A#�,R� 1���2P�����._�X���� >r̈ႅ�:hА�B˂@�d�BEʓ#GpҠ�� !:�̠���<ĄC�˗.]�\�Rʼn%J�顣n� � +��>}�ٳ�o�<|�ܙsN�5. +4(� A�)S����?����#���SoVt ?|��y�"��ۚ1��3ڏ�}�����V�$����;���`�?"���H6��-�����:���@�:w� _p����Z@�k;v�!Gk� �p�@�`��z��|�dnp@��X�A7b�(f���h��q�1o�a� +$We��@uI�p���d�m��A�z�Q���l���A4� ~�a�kuX�Vyd�͠_{�Gt�p��ḻƍ-I�n���H�W�q���W �͉���A#k�������cA0x�\h�ESH6P8��F1D��:� �[������P��`�D�Vk���"�; \ 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?9C<!D="F?#G@#IB$JC%KD%LE&NF'RJ)SK)TL*UM*WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�yB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��N��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��k��l��l��m��o��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�a� $C��A#�,R� 1���2P�����._�X���� >r̈ႅ�:hА�B˂@�d�BEʓ#GpҠ�� !:�̠���<ĄC�˗.]�\�Rʼn%J�顣n� � +��>}�ٳ�o�<|�ܙsN�5. +4(� A�)S����?����#���SoVt ?|��y�"��ۚ1��3ڏ�}�����V�$����;���`�?"���H6��-�����:���@�:w� _p����Z@�k;v�!Gk� �p�@�`��z��|�dnp@��X�A7b�(f���h��q�1o�a� +$We��@uI�p���d�m��A�z�Q���l���A4� ~�a�kuX�Vyd�͠_{�Gt�p��ḻƍ-I�n���H�W�q���W �͉���A#k�������cA0x�\h�ESH6P8��F1D��:� �[������P��`�D�Vk���"�; \ 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����*\Ȱ�Ç#*ԑ䉓%F���q��/X�8a�4T���`0b�d�B� "Axؠ�E +!>p���B˂C�p�r�J%Jp�sE +#>�����?ʐCV�0`�h�� +�&M�ң.��4�O ?}�����=~�䩓GΝ61)ThP!B�)S����@���Dg�A!�SoV=� @~���##��ۚ1��3���F�`��-�� 6=y�#��� �%���|�l=�a��q[ �z +��� ����F|A�%��R +� ���x�A�m�0nH��A!�����|yԷ�mr@�F�x`��@Ȋ,�����A��Q�q��F)��0��1gr�� +��m���t�Q$�s��n��A7�6� ���kwxGZ*�d�٠_{���v衘r�n��Ǝ=I�n����#[������ �ݩ�����m�������cA3�F_t�;VTA�R@�DIa��j��@ k�$�@���P��`�D�f����"�; \ 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?9C<!D="F?#G@#HA$IB$JC%KD%LE&NF'RJ)SK)TL*UM*VN+WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�yB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��N��O��O��P��P��Q��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��l��l��m��o��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*ԑ䉓%F���q��/X�8a�4T���`0b�d�B� "Axؠ�E +!>p���B˂C�p�r�J%Jp�sE +#>�����?ʐCV�0`�h�� +�&M�ң.��4�O ?}�����=~�䩓GΝ61)ThP!B�)S����@���Dg�A!�SoV=� @~���##��ۚ1��3���F�`��-�� 6=y�#��� �%���|�l=�a��q[ �z +��� ����F|A�%��R +� ���x�A�m�0nH��A!�����|yԷ�mr@�F�x`��@Ȋ,�����A��Q�q��F)��0��1gr�� +��m���t�Q$�s��n��A7�6� ���kwxGZ*�d�٠_{���v衘r�n��Ǝ=I�n����#[������ �ݩ�����m�������cA3�F_t�;VTA�R@�DIa��j��@ k�$�@���P��`�D�f����"�; \ 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� �$D���QCF-T�(A���0P�����._�X���!?tА�� :hА�B˂A�d�BE�$Hp֨�3� !:�Ġ��=ĄC�˗.]�\�Rʼn�%K�� � +��>}�ٳ�o�<|�ܙsN�5/4(� A�)S����?����#'�A �SoVt ?|��y#��ۚ1��3ڏ��F���f,���5s;x��G�:��G����?����y�� ��h��3���g1׃����k�<�Z�(�<��5�r��-����d0���|�p��9� F��Q`� .�`A8�������v�q0�m���A8�& sP|�au�e�H� ���u�Ass���l�A��Z �����Ց`w��ƒL:9 ��_z�A���o���k�h���6~�G� +���k�ѤA3�ay�1G�q��#k��F����cA1x�@\h�ESHP8��GA���J� �$[�4�@���P��`�D�v����"�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s7_unread.gif forums//site/static/beta5/pics/topic_s7_unread.gif --- beta5//site/static/beta5/pics/topic_s7_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s7_unread.gif 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,5 @@ +GIF89a � ������ +'#($*&+',(-(.)0+1,2-50617293<6>8?9C<!D="G@#HA$IB$JC%KD%LE&NF'RJ)SK)TL*UM*VN+WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�yB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��L��M��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��l��l��m��n��o��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�q� �$D���QCF-T�(A���0P�����._�X���!?tА�� :hА�B˂A�d�BE�$Hp֨�3� !:�Ġ��=ĄC�˗.]�\�Rʼn�%K�� � +��>}�ٳ�o�<|�ܙsN�5/4(� A�)S����?����#'�A �SoVt ?|��y#��ۚ1��3ڏ��F���f,���5s;x��G�:��G����?����y�� ��h��3���g1׃����k�<�Z�(�<��5�r��-����d0���|�p��9� F��Q`� .�`A8�������v�q0�m���A8�& sP|�au�e�H� ���u�Ass���l�A��Z �����Ց`w��ƒL:9 ��_z�A���o���k�h���6~�G� +���k�ѤA3�ay�1G�q��#k��F����cA1x�@\h�ESHP8��GA���J� �$[�4�@���P��`�D�v����"�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s8_read.gif forums//site/static/beta5/pics/topic_s8_read.gif --- beta5//site/static/beta5/pics/topic_s8_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s8_read.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,7 @@ +GIF89a � ������ '''(((***+++,,,---...000111222555666777999<<<>>>???CCCDDDGGGHHHIIIJJJKKKLLLNNNRRRSSSTTTUUUVVVWWW^^^```bbbcccdddfffkkklllmmmnnnqqqrrrtttzzz}}}���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�q�I%D���QCF-T�(A���0P����/`�\�"��!?tА�� :hА�B˂A�h�Re +$Hp֨�3� !:�Ġ��=ƈ C/^�`�b��&L�� �$��>}����=}�ࡃ'�6/!DH�A�)S���^@����3'�A �SoV-��?}���#��ۚ1��3���B� ��,���2s������P�$4���c9�@�����A�����P���x�s<xؘ/����Q�P��}����r���y�@�`�@@�y ��~G����sd�� +����0Ȍ�����8^�z0'���+ĨY� Ee}D�sv��\m��`A7h���Ȋ���r0�Fe�`P � �|��s�ar�Ae�WD�f|���zԑ�bu�1o�AD�%e� ֡y��`���gXi�| B�y�A�mp��Mj� +*E�_l��YXA�RD�KqDD�l�D�` +"����X0��4�� +��xk�Hd�覫.B�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s8_unread.gif forums//site/static/beta5/pics/topic_s8_unread.gif --- beta5//site/static/beta5/pics/topic_s8_unread.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s8_unread.gif 2011-02-05 10:10:02.704335002 +0100 @@ -0,0 +1,8 @@ +GIF89a � ������ +'#($*&+',(-(.)0+1,2-50617293<6>8?9C<!D="G@#HA$IB$JC%KD%LE&NF'RJ)SK)TL*UM*VN+WO+^U/`W0bY1cZ1d[2f\3ka5lb6mc6nd7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�yB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��K��L��M��N��O��O��P��P��Q��R��T��U��V��W��W��X��X��Y��Y��Z��[��[��_ñaɶdнh��i��j��l��l��m��o��p��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�q�I%D���QCF-T�(A���0P����/`�\�"��!?tА�� :hА�B˂A�h�Re +$Hp֨�3� !:�Ġ��=ƈ C/^�`�b��&L�� �$��>}����=}�ࡃ'�6/!DH�A�)S���^@����3'�A �SoV-��?}���#��ۚ1��3���B� ��,���2s������P�$4���c9�@�����A�����P���x�s<xؘ/����Q�P��}����r���y�@�`�@@�y ��~G����sd�� +����0Ȍ�����8^�z0'���+ĨY� Ee}D�sv��\m��`A7h���Ȋ���r0�Fe�`P � �|��s�ar�Ae�WD�f|���zԑ�bu�1o�AD�%e� ֡y��`���gXi�| B�y�A�mp��Mj� +*E�_l��YXA�RD�KqDD�l�D�` +"����X0��4�� +��xk�Hd�覫.B�; \ No newline at end of file diff -Naur beta5//site/static/beta5/pics/topic_s9_read.gif forums//site/static/beta5/pics/topic_s9_read.gif --- beta5//site/static/beta5/pics/topic_s9_read.gif 1970-01-01 01:00:00.000000000 +0100 +++ forums//site/static/beta5/pics/topic_s9_read.gif 2011-02-05 10:10:02.684335002 +0100 @@ -0,0 +1,10 @@ +GIF89a � ������ + + +'''(((***---000111222555666777;;;<<<===>>>???CCCDDDGGGIIIJJJKKKLLLNNNPPPQQQRRRSSSTTTUUUWWW^^^___```bbbcccdddfffkkklllmmmnnnoooqqqrrrtttzzz}}}������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�d�'H�١ㆍ1\�@q� 6\����2g�t�re +#C|�A#Ɗ#>p���B˂EȀ�%K&Lp��ѳ� +%>�����AԤAC��2d�x� +(P����0Z��P"C}*T��A� +�Hϟ:4i�hQ#F�)S^����D�B�džA"�SoV��"D�!�S#��ۚ1�64 ������ �J*'r�� @�G:�x��(�3��ق��0�:�2�,���"?���_��m���@�߀���u�@�y���qm�^|��w�}�]f�)�@�h`A=0"∌���0H�@�Gz�q�/��m%�� n`�|衇n|H�-B"#�E!�5�{�a�m�`��-�"�����H�G"��@9h��^|2��(�|䁇}�A�AL�W�`"��ƈ��u���A8�av +��v�hGt�i�k�X� f��a|��:Y\a�T<��I �Į�"A�A,�`��b�A�F��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?9C<!D="G@#IB$JC%KD%LE&NF'PH(QI(RJ)SK)TL*UM*WO+^U/_V/`W0bY1cZ1d[2f\3ka5lb6mc6nd7oe7qf8rg9ti:zo=}q>s?�t@�vA�wA�xB�zC�{D�|D�}E�~E��F��G��G��H��H��I��I��J��J��K��K��L��M��N��N��O��O��P��P��Q��R��S��T��U��V��W��W��X��X��Y��Y��Z��[��[��\��_ñaɶdнh��i��j��k��l��l��m��m��n��p��p��q��q��r��s��t��t��u��u��v��v��w��w��x��x��y��y��z��z��{��{��|��|��}��}��~��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!� +���,���� � ����� H����*\Ȱ�Ç#*�d�'H�١ㆍ1\�@q� 6\����2g�t�re +#C|�A#Ɗ#>p���B˂EȀ�%K&Lp��ѳ� +%>�����AԤAC��2d�x� +(P����0Z��P"C}*T��A� +�Hϟ:4i�hQ#F�)S^����D�B�džA"�SoV��"D�!�S#��ۚ1�64 ������ �J*'r�� @�G:�x��(�3��ق��0�:�2�,���"?���_��m���@�߀���u�@�y���qm�^|��w�}�]f�)�@�h`A=0"∌���0H�@�Gz�q�/��m%�� n`�|衇n|H�-B"#�E!�5�{�a�m�`��-�"�����H�G"��@9h��^|2��(�|䁇}�A�AL�W�`"��ƈ��u���A8�av +��v�hGt�i�k�X� f��a|��:Y\a�T<��I �Į�"A�A,�`��b�A�F��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\\] <b>$1</b> -\\[u\\](.*?)\\[\\/u\\] <u>$1</u> -\\[i\\](.*?)\\[\\/i\\] <i>$1</i> -\\[sep(arator)?\\] <hr/> -\\[item\\](.*?)\\[\\/item\\] <ul><li>$1</li></ul> -\\[quote\\](.*?)\\[\\/quote\\] <blockquote class="quote">$1</blockquote> -\\[quote=([^\\]]+)\\](.*?)\\[\\/quote\\] <blockquote class="quote"><b>$1</b> said:<br/>$2</blockquote> -\\[link=(http[^\\]]+)\\](.+?)\\[\\/link\\] <a href="$1" target="_blank">$2</a> -\\[code\\](.*?)\\[\\/code\\] <pre>$1</pre> -<\\/li><\\/ul>\\s*(<br\\/>\\s*)*<ul><li> </li><li> -\\[manual\\](.*?)\\[\\/manual\\] <a href="manual" target="_blank">$1</a> -\\[manual=(\\w+)(#\\w+)?\\](.*?)\\[\\/manual\\] <a href="manual?p=$1$2" target="_blank">$3</a> -\\[topic=(\\d+)\\](.*?)\\[\\/topic\\] <a href="forums?cmd=T%23G%23$1" target="_blank">$2</a> -\. - - -- 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;