"Text", "ForumPosters" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers, NoOne', 'LoggedInUsers')", "CanAttachFiles" => "Boolean", ); static $has_one = array( "Moderator" => "Member", "Category" => "ForumCategory" ); static $many_many = array( 'Moderators' => 'Member', 'PosterGroups' => 'Group' ); static $defaults = array( "ForumPosters" => "LoggedInUsers" ); /** * Number of posts to include in the thread view before pagination takes effect. * * @var int */ static $posts_per_page = 8; /** * When migrating from older versions of the forum it used post ID as the url token * as of forum 1.0 we now use ThreadID. If you want to enable 301 redirects from post to thread ID * set this to true * * @var bool */ static $redirect_post_urls_to_thread = false; /** * Can this user view the thread. * * @return bool */ function canView() { return (parent::canView() || $this->isAdmin()); } /** * Can the user edit this thread - The settings and configuration in the thread. * Not the individual posts. Individual posts is controlled by canCreate * * @return bool */ function canEdit() { return $this->isAdmin(); } /** * Can the user post threads to this forum * * @return bool */ function canPost() { if($this->ForumPosters == "Anyone" || $this->isAdmin()) return true; if($this->ForumPosters == "NoOne") return false; if($member = Member::currentUser()) { if($this->ForumPosters == "LoggedInUsers") return true; if($groups = $this->PosterGroups()) { foreach($groups as $group) { if($member->inGroup($group)) return true; } } } return false; } /** * Can we attach files to topics/posts inside this forum? * * @return bool Set to TRUE if the user is allowed to, to FALSE if they're * not */ function canAttach() { return $this->CanAttachFiles ? true : false; } /** * Add default records to database * * This function is called whenever the database is built, after the * database tables have all been created. */ public function requireDefaultRecords() { parent::requireDefaultRecords(); $code = "ACCESS_FORUM"; if(!$forumGroup = DataObject::get_one("Group", "\"Group\".\"Code\" = 'forum-members'")) { $group = new Group(); $group->Code = 'forum-members'; $group->Title = "Forum Members"; $group->write(); Permission::grant( $group->ID, $code ); DB::alteration_message(_t('Forum.GROUPCREATED','Forum Members group created'),"created"); } else if(DB::query("SELECT * FROM \"Permission\" WHERE \"GroupID\" = '$forumGroup->ID' AND \"Code\" LIKE '$code'")->numRecords() == 0 ) { Permission::grant($forumGroup->ID, $code); } if(!$category = DataObject::get_one("ForumCategory")) { $category = new ForumCategory(); $category->Title = _t('Forum.DEFAULTCATEGORY', 'General'); $category->write(); } if(!DataObject::get_one("ForumHolder")) { $forumholder = new ForumHolder(); $forumholder->Title = "Forums"; $forumholder->URLSegment = "forums"; $forumholder->Content = "
"._t('Forum.WELCOMEFORUMHOLDER','Welcome to SilverStripe Forum Module! This is the default ForumHolder page. You can now add forums.')."
"; $forumholder->Status = "Published"; $forumholder->write(); $forumholder->publish("Stage", "Live"); DB::alteration_message(_t('Forum.FORUMHOLDERCREATED','ForumHolder page created'),"created"); $forum = new Forum(); $forum->Title = _t('Forum.TITLE','General Discussion'); $forum->URLSegment = "general-discussion"; $forum->ParentID = $forumholder->ID; $forum->Content = ""._t('Forum.WELCOMEFORUM','Welcome to SilverStripe Forum Module! This is the default Forum page. You can now add topics.')."
"; $forum->Status = "Published"; $forum->CategoryID = $category->ID; $forum->write(); $forum->publish("Stage", "Live"); DB::alteration_message(_t('Forum.FORUMCREATED','Forum page created'),"created"); } // update the forumpostersgroupID if($this->ForumPostersGroupID > 0) { $this->PosterGroups()->add(DataObject::get_by_id('Group', $this->ForumPostersGroupID)); $this->ForumPostersGroupID == 0; $this->write(); DB::alteration_message(_t('Forum.FORUMPOSTERSGROUPMIGRATED','Forum posters group migrated'),"created"); } } /** * Returns a FieldSet with which to create the CMS editing form * * @return FieldSet The fields to be displayed in the CMS. */ function getCMSFields() { Requirements::javascript("forum/javascript/ForumAccess.js"); Requirements::css("forum/css/Forum_CMS.css"); $fields = parent::getCMSFields(); $fields->addFieldToTab("Root.Access", new HeaderField(_t('Forum.ACCESSPOST','Who can post to the forum?'), 2)); $fields->addFieldToTab("Root.Access", new OptionsetField("ForumPosters", "", array( "Anyone" => _t('Forum.READANYONE', 'Anyone'), "LoggedInUsers" => _t('Forum.READLOGGEDIN', 'Logged-in users'), "OnlyTheseUsers" => _t('Forum.READLIST', 'Only these people (choose from list)'), "NoOne" => _t('Forum.READNOONE', 'Nobody. Make Forum Read Only') ))); $fields->addFieldToTab("Root.Access", new TreeMultiselectField("PosterGroups", _t('Forum.GROUPS',"Groups"))); $fields->addFieldToTab("Root.Access", new OptionsetField("CanAttachFiles", _t('Forum.ACCESSATTACH','Can users attach files?'), array( "1" => _t('Forum.YES','Yes'), "0" => _t('Forum.NO','No') ))); $fields->addFieldToTab("Root.Category", new HasOneCTFWithDefaults( $this, 'Category', 'ForumCategory', array( 'Title' => 'Title' ), 'getCMSFields_forPopup', "\"ForumHolderID\"={$this->ParentID}", null, null, array("ForumHolderID" => $this->ParentID) ) ); // TagField comes in it's own module. // If it's installed, use it to select moderators for this forum if(class_exists('TagField')) { $fields->addFieldToTab('Root.Content.Moderators', new TagField( 'Moderators', _t('MODERATORS', 'Moderators for this forum'), null, 'Forum', 'Email' //need to use emails here because user nicknames are: // (1) not unique // (2) can have spaces (default tag separator is a space) ) ); } else { $fields->addFieldToTab('Root.Content.Moderators', new LiteralField('ModeratorWarning', 'Please install the TagField module to manage moderators for this forum.
')); } return $fields; } /** * Return true if user is an "admin" of this forum or is a moderator. * The user can either have ADMIN permissions {@link Permission} or be * a moderator of this forum (their member ID is in the Moderators * many many relation ID list). * * @see ForumRole->isModeratingForum() * * @return boolean */ function isAdmin() { if(!Member::currentUserID()) return false; $member = Member::currentUser(); $isModerator = ($member) ? $member->isModeratingForum($this) : false; return (Permission::check('ADMIN') || $isModerator) ? true : false; } /** * Create breadcrumbs * * @param int $maxDepth Maximal lenght of the breadcrumb navigation * @param bool $unlinked Set to TRUE if the breadcrumb should consist of * links, otherwise FALSE. * @param bool $stopAtPageType Currently not used * @param bool $showHidden Set to TRUE if also hidden pages should be * displayed * @return string HTML code to display breadcrumbs */ public function Breadcrumbs($maxDepth = null,$unlinked = false, $stopAtPageType = false,$showHidden = false) { $page = $this; $nonPageParts = array(); $parts = array(); $controller = Controller::curr(); $params = $controller->getURLParams(); $SQL_id = $params['ID']; if(is_numeric($SQL_id)) { $topic = DataObject::get_by_id("ForumThread", $SQL_id); if($topic) { $nonPageParts[] = Convert::raw2xml($topic->getTitle()); } } while($page && (!$maxDepth || sizeof($parts) < $maxDepth)) { if($showHidden || $page->ShowInMenus || ($page->ID == $this->ID)) { if($page->URLSegment == 'home') $hasHome = true; if($nonPageParts) { $parts[] = "Link() . "\">" . Convert::raw2xml($page->Title) . ""; } else { $parts[] = (($page->ID == $this->ID) || $unlinked) ? Convert::raw2xml($page->Title) : ("Link() . "\">" . Convert::raw2xml($page->Title) . ""); } } $page = $page->Parent; } return implode(" » ", array_reverse(array_merge($nonPageParts,$parts))); } /** * Helper Method from the template includes. Uses $ForumHolder so in order for it work * it needs to be included on this page * * @return ForumHolder */ function getForumHolder() { return $this->Parent(); } /** * Get the latest posting of the forum. For performance the forum ID is stored on the * {@link Post} object as well as the {@link Forum} object * * @return Post */ function getLatestPost() { return DataObject::get_one('Post', "\"Post\".\"ForumID\" = '$this->ID'", true, "\"Post\".\"ID\" DESC"); } /** * Get the number of total topics (threads) in this Forum * * @return int Returns the number of topics (threads) */ function getNumTopics() { return (int)DB::query(" SELECT count(\"ID\") FROM \"ForumThread\" WHERE \"ForumID\" = $this->ID")->value(); } /** * Get the number of total posts * * @return int Returns the number of posts */ function getNumPosts() { return (int)DB::query(" SELECT COUNT(*) FROM \"Post\" WHERE \"Post\".\"ForumID\" = $this->ID")->value(); } /** * Get the number of distinct authors * * @return int Returns the number of distinct authors */ function getNumAuthors() { return DB::query(" SELECT COUNT(DISTINCT \"AuthorID\") FROM \"Post\" WHERE \"Post\".\"ForumID\" = $this->ID")->value(); } /** * Returns the topics (first posting of each thread) for this forum * @return DataObjectSet */ function getTopics() { if(Member::currentUser()==$this->Moderator() && is_numeric($this->ID)) { $statusFilter = "(\"PostList\".\"Status\" IN ('Moderated', 'Awaiting')"; } else { $statusFilter = "\"PostList\".\"Status\" = 'Moderated'"; } if(isset($_GET['start']) && is_numeric($_GET['start'])) $limit = Convert::raw2sql($_GET['start']) . ", 30"; else $limit = 30; return DataObject::get( "ForumThread", "\"ForumThread\".\"ForumID\" = $this->ID AND \"ForumThread\".\"IsGlobalSticky\" = 0 AND \"ForumThread\".\"IsSticky\" = 0 AND $statusFilter", "max(\"PostList\".\"Created\") DESC, max(\"PostList\".\"ID\") DESC", "INNER JOIN \"Post\" AS \"PostList\" ON \"PostList\".\"ThreadID\" = \"ForumThread\".\"ID\"", $limit ); } /** * Return the Sticky Threads * @return DataObjectSet */ function getStickyTopics() { $standard = DataObject::get( "ForumThread", "\"ForumThread\".\"ForumID\" = $this->ID AND \"ForumThread\".\"IsSticky\" = 1", "MAX(\"PostList\".\"Created\") DESC", "INNER JOIN \"Post\" AS \"PostList\" ON \"PostList\".\"ThreadID\" = \"ForumThread\".\"ID\"" ); // We have to join posts through their forums to their holders, and then restrict the holders to just the parent of this forum. $global = DataObject::get( "ForumThread", "\"ForumThread\".\"IsGlobalSticky\" = 1", "MAX(\"PostList\".\"Created\") DESC", "INNER JOIN \"Post\" AS \"PostList\" ON \"PostList\".\"ThreadID\" = \"ForumThread\".\"ID\"" ); if($global) { $global->merge($standard); $global->sort('PostList.Created'); return $global; } return $standard; } } /** * The forum controller class * * @package forum */ class Forum_Controller extends Page_Controller { static $allowed_actions = array( 'AdminFormFeatures', 'deleteattachment', 'deletepost', 'doAdminFormFeatures', 'doPostMessageForm', 'editpost', 'markasspam', 'PostMessageForm', 'reply', 'show', 'starttopic', 'subscribe', 'unsubscribe', 'rss' ); function init() { parent::init(); if(Director::redirected_to()) return; if(!$this->canView()) { $messageSet = array( 'default' => _t('Forum.LOGINDEFAULT','Enter your email address and password to view this forum.'), 'alreadyLoggedIn' => _t('Forum.LOGINALREADY','I\'m sorry, but you can\'t access this forum until you\'ve logged in. If you want to log in as someone else, do so below'), 'logInAgain' => _t('Forum.LOGINAGAIN','You have been logged out of the forums. If you would like to log in again, enter a username and password below.') ); Security::permissionFailure($this, $messageSet); return; } // Log this visit to the ForumMember if they exist $member = Member::currentUser(); if($member) { $member->LastViewed = date("Y-m-d H:i:s"); $member->write(); } Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery.js"); Requirements::javascript("forum/javascript/forum.js"); Requirements::javascript("forum/javascript/jquery.MultiFile.js"); Requirements::themedCSS('Forum'); RSSFeed::linkToFeed($this->Parent()->Link("rss/forum/$this->ID"), sprintf(_t('Forum.RSSFORUM',"Posts to the '%s' forum"),$this->Title)); RSSFeed::linkToFeed($this->Parent()->Link("rss"), _t('Forum.RSSFORUMS','Posts to all forums')); // Icky hack to set this page ShowInCategories so we can determine if we need to show in category mode or not. $holderPage = $this->Parent; if($holderPage) $this->ShowInCategories = $holderPage->ShowInCategories; // Set the back url if(isset($_SERVER['REQUEST_URI'])) { Session::set('BackURL', $_SERVER['REQUEST_URI']); } else { Session::set('BackURL', $this->Link()); } } /** * A convenience function which provides nice URLs for an rss feed on this forum. */ function rss() { $this->redirect($this->Parent()->Link("rss/forum/$this->ID"), 301); } /** * Is OpenID support available? * * This method checks if the {@link OpenIDAuthenticator} is available and * registered. * * @return bool Returns TRUE if OpenID is available, FALSE otherwise. */ function OpenIDAvailable() { return $this->Parent()->OpenIDAvailable(); } /** * Subscribe a user to a thread given by an ID. * * Designed to be called via AJAX so return true / false * * @return bool */ function subscribe() { if(Member::currentUser() && !ForumThread_Subscription::already_subscribed($this->urlParams['ID'])) { $obj = new ForumThread_Subscription(); $obj->ThreadID = (int) $this->urlParams['ID']; $obj->MemberID = Member::currentUserID(); $obj->LastSent = date("Y-m-d H:i:s"); $obj->write(); die('1'); } return false; } /** * Unsubscribe a user from a thread by an ID * * Designed to be called via AJAX so return true / false * * @return bool */ function unsubscribe() { $member = Member::currentUser(); if(!$member) Security::permissionFailure($this, _t('LOGINTOUNSUBSCRIBE', 'To unsubscribe from that thread, please log in first.')); if(ForumThread_Subscription::already_subscribed($this->urlParams['ID'], $member->ID)) { DB::query(" DELETE FROM \"ForumThread_Subscription\" WHERE \"ThreadID\" = '". Convert::raw2sql($this->urlParams['ID']) ."' AND \"MemberID\" = '$member->ID'"); die('1'); } return false; } /** * Mark a post as spam. Deletes any posts or threads created by that user * and removes their user account from the site * * Must be logged in and have the correct permissions to do marking */ function markasspam() { if($this->isAdmin() && isset($this->urlParams['ID'])) { $post = DataObject::get_by_id('Post', $this->urlParams['ID']); if($post) { // send spam feedback if needed if(class_exists('SpamProtectorManager')) { SpamProtectorManager::send_feedback($post, 'spam'); } // some posts do not have authors if($author = $post->Author()) { $SQL_id = Convert::raw2sql($author->ID); // delete all threads and posts from that user $posts = DataObject::get('Post', "\"AuthorID\" = '$SQL_id'"); if($posts) { foreach($posts as $post) { if($post->isFirstPost()) { // post was the start of a thread, Delete the whole thing $post->Thread()->delete(); } else { if($post->ID) { $post->delete(); } } } } // delete the authors account $author->delete(); } else { $post->delete(); } } } return (Director::is_ajax()) ? true : $this->redirect($this->Link()); } /** * Get posts to display. This method assumes an URL parameter "ID" which contains the thread ID. * * @return DataObjectSet Posts */ function Posts($order = "ASC") { $SQL_id = Convert::raw2sql($this->urlParams['ID']); $numPerPage = Forum::$posts_per_page; if(isset($_GET['showPost']) && !isset($_GET['start'])) { $allIDs = DB::query("SELECT \"ID\" FROM \"Post\" WHERE \"ThreadID\" = '$SQL_id' ORDER BY \"Created\"")->column(); if($allIDs) { $foundPos = array_search($_GET['showPost'], $allIDs); $_GET['start'] = floor($foundPos / $numPerPage) * $numPerPage; } } if(!isset($_GET['start'])) $_GET['start'] = 0; return DataObject::get("Post", "\"ThreadID\" = '$SQL_id'", "\"Created\" $order" , "", (int)$_GET['start'] . ", $numPerPage"); } /** * Get the usable BB codes * * @return DataObjectSet Returns the usable BB codes * @see BBCodeParser::usable_tags() */ function BBTags() { return BBCodeParser::usable_tags(); } /** * Section for dealing with reply / edit / create threads form * * @return Form Returns the post message form */ function PostMessageForm($addMode = false, $post = false) { $thread = false; if($post) $thread = $post->Thread(); else if(isset($this->urlParams['ID'])) $thread = DataObject::get_by_id('ForumThread', $this->urlParams['ID']); // Check to see that the user has create forum thread rights if(!$this->canPost()) { $messageSet = array( 'default' => _t('Forum.LOGINTOPOST','You\'ll need to login before you can post to that forum. Please do so below.'), 'alreadyLoggedIn' => _t('Forum.LOGINTOPOSTLOGGEDIN','I\'m sorry, but you can\'t post to this forum until you\'ve logged in. If you want to log in as someone else, do so below. If you\'re logged in and you still can\'t post, you don\'t have the correct permissions to post.'), 'logInAgain' => _t('Forum.LOGINTOPOSTAGAIN','You have been logged out of the forums. If you would like to log in again to post, enter a username and password below.'), ); Security::permissionFailure($this, $messageSet); return false; } $fields = new FieldSet( ($post && $post->isFirstPost() || !$thread) ? new TextField("Title", _t('Forum.FORUMTHREADTITLE', 'Title')) : new ReadonlyField('Title', _t('Forum.FORUMTHREADTITLE', 'Title'), 'Re:'. $thread->Title), new TextareaField("Content", _t('Forum.FORUMREPLYCONTENT', 'Content')), new LiteralField("BBCodeHelper", ""), new CheckboxField("TopicSubscription", _t('Forum.SUBSCRIBETOPIC','Subscribe to this topic (Receive email notifications when a new reply is added)'), ($thread) ? $thread->getHasSubscribed() : false) ); if($thread) $fields->push(new HiddenField('ThreadID', 'ThreadID', $thread->ID)); if($post) $fields->push(new HiddenField('ID', 'ID', $post->ID)); // Check if we can attach files to this forum's posts if($this->canAttach()) { $fields->push(new FileField("Attachment", _t('Forum.ATTACH', 'Attach file'))); } // If this is an existing post check for current attachments and generate // a list of the uploaded attachments if($post && $attachmentList = $post->Attachments()) { if($attachmentList->exists()) { $attachments = "