예제 #1
0
 /**
  * Perform a fresh installation of the esoTalk database. Create the table structure and insert default data.
  *
  * @param array $info An array of information gathered from the installation form.
  * @return void
  */
 public function install($info)
 {
     // Create the table structure.
     $this->structure(true);
     // Create the administrator member.
     $member = array("username" => $info["adminUser"], "email" => $info["adminEmail"], "password" => $info["adminPass"], "account" => "Administrator", "confirmed" => true);
     ET::memberModel()->create($member);
     // Set the session's userId and user information variables to the administrator, so that all entities
     // created below will be created by the administrator user.
     ET::$session->userId = 1;
     ET::$session->user = ET::memberModel()->getById(1);
     // Create the moderator group.
     ET::groupModel()->create(array("name" => "Moderator", "canSuspend" => true));
     // Create the General Discussion channel.
     $id = ET::channelModel()->create(array("title" => "General Discussion", "slug" => slug("General Discussion")));
     ET::channelModel()->setPermissions($id, array(GROUP_ID_GUEST => array("view" => true), GROUP_ID_MEMBER => array("view" => true, "reply" => true, "start" => true), 1 => array("view" => true, "reply" => true, "start" => true, "moderate" => true)));
     // Create the Staff Only channel.
     $id = ET::channelModel()->create(array("title" => "Staff Only", "slug" => slug("Staff Only")));
     ET::channelModel()->setPermissions($id, array(1 => array("view" => true, "reply" => true, "start" => true, "moderate" => true)));
     // Set the flood control config setting to zero so that we can create two conversations in a row.
     ET::$config["esoTalk.conversation.timeBetweenPosts"] = 0;
     // Create a welcome conversation.
     ET::conversationModel()->create(array("title" => "Welcome to " . $info["forumTitle"] . "!", "content" => "[b]Welcome to " . $info["forumTitle"] . "![/b]\n\n" . $info["forumTitle"] . " is powered by [url=http://esotalk.org]esoTalk[/url], the simple, fast, free web-forum.\n\nFeel free to edit or delete this conversation. Otherwise, it's time to get posting!\n\nAnyway, good luck, and we hope you enjoy using esoTalk.", "channelId" => 1));
     // Create a helpful private conversation with the administrator.
     ET::conversationModel()->create(array("title" => "Pssst! Want a few tips?", "content" => "Hey {$info["adminUser"]}, congrats on getting esoTalk installed!\n\nCool! Your forum is now good-to-go, but you might want to customize it with your own logo, design, and settings—so here's how.\n\n[h]Changing the Logo[/h]\n\n1. Go to the [url=" . C("esoTalk.baseURL") . "admin/settings]Forum Settings[/url] section of your administration panel.\n2. Select 'Show an image in the header' for the 'Forum header' setting.\n3. Find and select the image file you wish to use.\n4. Click 'Save Changes'. The logo will automatically be resized so it fits nicely in the header.\n\n[h]Changing the Appearance[/h]\n\n1. Go to the [url=" . C("esoTalk.baseURL") . "admin/appearance]Appearance[/url] section of your administration panel.\n2. Choose colors for the header, page background, or select a background image. (More skins will be available soon.)\n3. Click 'Save Changes', and your forum's appearance will be updated!\n\n[h]Managing Channels[/h]\n\n'Channels' are a way to categorize conversations in your forum. You can create as many or as few channels as you like, nest them, and give them custom permissions.\n\n1. Go to the [url=" . C("esoTalk.baseURL") . "admin/channels]Channels[/url] section of your administration panel.\n2. Click 'Create Channel' and fill out a title, description, and select permissions to add a new channel.\n3. Drag and drop channels to rearrange and nest them.\n\n[h]Getting Help[/h]\n\nIf you need help, come and give us a yell at the [url=http://esotalk.org/forum]esoTalk Support Forum[/url]. Don't worry—we don't bite!", "channelId" => 1), array(array("type" => "member", "id" => 1)));
     // All done!
 }
예제 #2
0
 public function setup($oldVersion = "")
 {
     // For the Profiles plugin, we need two tables.
     // The first, profile_field, stores information about the custom profile fields that
     // the administrator has defined.
     $structure = ET::$database->structure();
     $structure->table("profile_field")->column("fieldId", "int(11) unsigned", false)->column("name", "varchar(31)", false)->column("description", "varchar(255)")->column("type", "enum('text','textarea','select','radios','checkboxes','member')", "text")->column("options", "text")->column("showOnPosts", "tinyint(1)", 0)->column("hideFromGuests", "tinyint(1)", 0)->column("searchable", "tinyint(1)", 0)->column("position", "int(11)", 0)->key("fieldId", "primary")->exec(false);
     // The second, profile_data, stores the actual values of these fields that users
     // have entered in their profiles.
     $structure->table("profile_data")->column("memberId", "int(11) unsigned", false)->column("fieldId", "int(11) unsigned", false)->column("data", "text")->key(array("memberId", "fieldId"), "primary")->exec(false);
     // If this is the first installation of the Profiles plugin (e.g. on a fresh
     // esoTalk installation,) set up some default fields for the administrator.
     if (!$oldVersion) {
         $this->createDefaultFields();
     } elseif (version_compare($oldVersion, "1.0.0g4", "<")) {
         $this->createDefaultFields();
         $model = ET::getInstance("profileFieldModel");
         $result = ET::SQL()->select("memberId, preferences")->from("member")->exec();
         while ($row = $result->nextRow()) {
             ET::memberModel()->expand($row);
             if (!empty($row["preferences"]["about"])) {
                 $model->setData($row["memberId"], 1, $row["preferences"]["about"]);
             }
             if (!empty($row["preferences"]["location"])) {
                 $model->setData($row["memberId"], 2, $row["preferences"]["location"]);
             }
         }
     }
     return true;
 }
 /**
  * Deny all members.
  *
  * @return void
  */
 public function action_denyAll()
 {
     if (!$this->validateToken()) {
         return;
     }
     ET::memberModel()->delete(array("confirmed" => 0));
     $this->message(T("message.changesSaved"), "success autoDismiss");
     $this->redirect(URL("admin/unapproved"));
 }
 /**
  * Deny a member; delete their account.
  *
  * @param int $memberId The ID of the member to deny.
  * @return void
  */
 public function action_deny($memberId)
 {
     // Get this member's details. If it doesn't exist or is already approved, show an error.
     if (!($member = ET::memberModel()->getById((int) $memberId)) or $member["confirmed"]) {
         $this->redirect(URL("admin/unapproved"));
         return;
     }
     ET::memberModel()->deleteById($memberId);
     $this->message(T("message.changesSaved"), "success autoDismiss");
     $this->redirect(URL("admin/unapproved"));
 }
예제 #5
0
 public function handler_conversationController_formatPostForTemplate($sender, &$formatted, $post, $conversation)
 {
     if ($post["deleteTime"]) {
         return;
     }
     if (!C("plugin.Reputation.showReputationPublic")) {
         return;
     }
     // Show reputation points next to username on every post
     $memberRepo = ET::memberModel()->getById($post["memberId"]);
     $postMemberReputation = "+ " . $memberRepo["reputationPoints"] . " RP";
     $postMemberReputation = "<a href='" . URL("reputation") . "' class = 'time' title='Reputation Points'>{$postMemberReputation}</a>";
     $formatted["info"][] = $postMemberReputation;
 }
예제 #6
0
 public function handler_conversationModel_addReplyAfter($sender, $conversation, $postId, $content)
 {
     // Only continue if this is the first post.
     if ($conversation["countPosts"] > 1) {
         return;
     }
     // We get all members who have starred the post author and have no unread posts in the conversation.
     $sql = ET::SQL()->from("member_member mm2", "mm2.memberId2=:userId AND mm2.memberId1=m.memberId AND mm2.follow=1 AND mm2.memberId1!=:userId", "inner")->from("member_conversation co", "co.conversationId=:conversationId AND co.type='member' AND co.id=m.memberId", "left")->where("co.lastRead IS NULL OR co.lastRead>=:posts")->bind(":conversationId", $conversation["conversationId"])->bind(":posts", $conversation["countPosts"] - 1)->bind(":userId", ET::$session->userId);
     $members = ET::memberModel()->getWithSQL($sql);
     $data = array("conversationId" => $conversation["conversationId"], "postId" => $postId, "title" => $conversation["title"]);
     $emailData = array("content" => $content);
     foreach ($members as $member) {
         // Check if this member is allowed to view this conversation before sending them a notification.
         $sql = ET::SQL()->select("conversationId")->from("conversation c")->where("conversationId", $conversation["conversationId"]);
         ET::conversationModel()->addAllowedPredicate($sql, $member);
         if (!$sql->exec()->numRows()) {
             continue;
         }
         ET::activityModel()->create("postMember", $member, ET::$session->user, $data, $emailData);
     }
 }
예제 #7
0
// Copyright 2011 Toby Zerner, Simon Zerner
// This file is part of esoTalk. Please see the included license file for usage information.
if (!defined("IN_ESOTALK")) {
    exit;
}
/**
 * Shows a summary of the members allowed in a conversation.
 * For example: Toby, Administrators, and 2 others can view this conversation.
 *
 * @package esoTalk
 */
$conversation = $data["conversation"];
$names = array();
$avatars = array();
$model = ET::memberModel();
// Go through the list of members/groups allowed and construct an array of formatted names.
foreach ($conversation["membersAllowedSummary"] as $member) {
    // If this entity is a member, add their name as a link to their profile. Also add an avatar to be
    // displayed at the start of the list.
    if ($member["type"] == "member") {
        $names[] = "<span class='name'>" . memberLink($member["id"], $member["name"]) . "</span>";
        if (count($avatars) < 3) {
            $avatars[] = avatar($member + array("memberId" => $member["id"]), "thumb");
        }
    } else {
        $names[] = "<span class='name'>" . groupLink($member["name"]) . "</span>";
    }
}
// If there are specific names, output the list!
if (count($names)) {
예제 #8
0
 /**
  * Show the settings page with the change password or email pane.
  *
  * @return void
  */
 public function password()
 {
     $member = $this->profile("password");
     // Construct the form.
     $form = ETFactory::make("form");
     $form->action = URL("settings/password");
     // If the form was submitted...
     if ($form->validPostBack("save")) {
         $update = array();
         // Are we setting a new password?
         if ($password = $form->getValue("password")) {
             // Do the passwords entered match?
             if ($password != $form->getValue("confirm")) {
                 $form->error("confirm", T("message.passwordsDontMatch"));
             } else {
                 $update["password"] = $password;
             }
         }
         // Are we setting a new email?
         if ($email = $form->getValue("email")) {
             $update["email"] = $email;
         }
         // Did they enter the correct "current password"?
         if (!ET::memberModel()->checkPassword($form->getValue("currentPassword"), ET::$session->user["password"])) {
             $form->error("currentPassword", T("message.incorrectPassword"));
         }
         // If no preliminary errors occurred, and we have stuff to update, we can go ahead and call the model.
         if (!$form->errorCount() and count($update)) {
             // Update the stuff we need to with the model.
             $model = ET::memberModel();
             $model->updateById(ET::$session->userId, $update);
             // If the model encountered errors, pass them along to the form.
             if ($model->errorCount()) {
                 $form->errors($model->errors());
             } else {
                 $this->message(T("message.changesSaved"), "success");
                 $this->redirect(URL("settings"));
             }
         }
     }
     $this->data("form", $form);
     $this->renderProfile("settings/password");
 }
예제 #9
0
 /**
  * Log in the member with the specified username and password, and optionally set a persistent login cookie.
  *
  * @param string $username The username.
  * @param string $password The password.
  * @param bool $remember Whether or not to set a persistent login cookie.
  * @return bool true on success, false on failure.
  */
 public function login($name, $password, $remember = false)
 {
     // Get the member with this username or email.
     $sql = ET::SQL()->where("m.username=:username OR m.email=:email")->bind(":username", $name)->bind(":email", $name);
     $member = reset(ET::memberModel()->getWithSQL($sql));
     // Check that the password is correct.
     if (!$member or !ET::memberModel()->checkPassword($password, $member["password"])) {
         $this->error("password", "incorrectLogin");
         return false;
     }
     // Process the login.
     $return = $this->processLogin($member);
     // Set a persistent login "remember me" cookie?
     // We use this implementation: http://jaspan.com/improved_persistent_login_cookie_best_practice
     if ($return === true and $remember) {
         // Generate a new series identifier, and a token.
         $series = md5(generateRandomString(32));
         $token = $this->createPersistentToken($this->userId, $series);
         // Set the cookie.
         $this->setCookie("persistent", $this->userId . $series . $token, time() + C("esoTalk.cookie.expire"));
     }
     return $return;
 }
예제 #10
0
<?php 
echo avatar($member);
?>

<div id='memberInfo'>

<h1 id='memberName'><?php 
echo name($member["username"]);
?>
</h1>

<?php 
// Online indicator.
if (empty($member["preferences"]["hideOnline"])) {
    $lastAction = ET::memberModel()->getLastActionInfo($member["lastActionTime"], $member["lastActionDetail"]);
    if ($lastAction) {
        echo "<" . (!empty($lastAction[1]) ? "a href='{$lastAction[1]}'" : "span") . " class='online' title='" . T("Online") . ($lastAction[0] ? " (" . sanitizeHTML($lastAction[0]) . ")" : "") . "'><i class='icon-circle'></i></" . (!empty($lastAction[1]) ? "a" : "span") . ">";
    }
}
?>

<?php 
// Output the email if the viewer is an admin.
if (ET::$session->isAdmin()) {
    ?>
<p class='subText'><?php 
    echo sanitizeHTML($member["email"]);
    ?>
</p><?php 
}
 /**
  * Format post data into an array which can be used to display the post template view (conversation/post).
  *
  * @param array $post The post data.
  * @param array $conversation The details of the conversation which the post is in.
  * @return array A formatted array which can be used in the post template view.
  */
 public function formatPostForTemplate($post, $conversation)
 {
     $canEdit = ET::postModel()->canEditPost($post, $conversation);
     $avatar = avatar($post);
     // Construct the post array for use in the post view (conversation/post).
     $formatted = array("id" => "p" . $post["postId"], "title" => memberLink($post["memberId"], $post["username"]), "avatar" => (!$post["deleteTime"] and $avatar) ? "<a href='" . URL(memberURL($post["memberId"], $post["username"])) . "'>{$avatar}</a>" : false, "class" => $post["deleteTime"] ? array("deleted") : array(), "info" => array(), "controls" => array(), "body" => !$post["deleteTime"] ? $this->displayPost($post["content"]) : false, "footer" => array(), "data" => array("id" => $post["postId"], "memberid" => $post["memberId"]));
     $date = smartTime($post["time"], true);
     // Add the date/time to the post info as a permalink.
     $formatted["info"][] = "<a href='" . URL(postURL($post["postId"])) . "' class='time' title='" . _strftime(T("date.full"), $post["time"]) . "' data-timestamp='" . $post["time"] . "'>" . (!empty($conversation["searching"]) ? T("Show in context") : $date) . "</a>";
     // If the post isn't deleted, add a lot of stuff!
     if (!$post["deleteTime"]) {
         // Add the user's online status / last action next to their name.
         if (empty($post["preferences"]["hideOnline"])) {
             $lastAction = ET::memberModel()->getLastActionInfo($post["lastActionTime"], $post["lastActionDetail"]);
             if ($lastAction[0]) {
                 $lastAction[0] = " (" . sanitizeHTML($lastAction[0]) . ")";
             }
             if ($lastAction) {
                 array_unshift($formatted["info"], "<" . (!empty($lastAction[1]) ? "a href='{$lastAction[1]}'" : "span") . " class='online' title='" . T("Online") . "{$lastAction[0]}'><i class='icon-circle'></i></" . (!empty($lastAction[1]) ? "a" : "span") . ">");
             }
         }
         // Show the user's group type.
         $formatted["info"][] = "<span class='group'>" . memberGroup($post["account"], $post["groups"]) . "</span>";
         $formatted["class"][] = "group-" . $post["account"];
         foreach ($post["groups"] as $k => $v) {
             if ($k) {
                 $formatted["class"][] = "group-" . $k;
             }
         }
         // If the post has been edited, show the time and by whom next to the controls.
         if ($post["editMemberId"]) {
             $formatted["controls"][] = "<span class='editedBy'>" . sprintf(T("Edited %s by %s"), "<span title='" . _strftime(T("date.full"), $post["editTime"]) . "' data-timestamp='" . $post["editTime"] . "'>" . relativeTime($post["editTime"], true) . "</span>", memberLink($post["editMemberId"], $post["editMemberName"])) . "</span>";
         }
         // If the user can reply, add a quote control.
         if ($conversation["canReply"]) {
             $formatted["controls"][] = "<a href='" . URL(conversationURL($conversation["conversationId"], $conversation["title"]) . "/?quote=" . $post["postId"] . "#reply") . "' title='" . T("Quote") . "' class='control-quote'><i class='icon-quote-left'></i></a>";
         }
         // If the user can edit the post, add edit/delete controls.
         if ($canEdit) {
             $formatted["controls"][] = "<a href='" . URL("conversation/editPost/" . $post["postId"]) . "' title='" . T("Edit") . "' class='control-edit'><i class='icon-edit'></i></a>";
             $formatted["controls"][] = "<a href='" . URL("conversation/deletePost/" . $post["postId"] . "?token=" . ET::$session->token) . "' title='" . T("Delete") . "' class='control-delete'><i class='icon-remove'></i></a>";
         } elseif (!$conversation["locked"] && !ET::$session->isSuspended() && $post["memberId"] == ET::$session->userId && (!$post["deleteMemberId"] || $post["deleteMemberId"] == ET::$session->userId) && C("esoTalk.conversation.editPostTimeLimit") == "reply") {
             $formatted["controls"][] = "<span title='" . sanitizeHTML(T("message.cannotEditSinceReply")) . "' class='control-edit disabled'><i class='icon-edit'></i></span>";
             $formatted["controls"][] = "<span title='" . sanitizeHTML(T("message.cannotEditSinceReply")) . "' class='control-delete disabled'><i class='icon-remove'></i></span>";
         }
     } else {
         // Add the "deleted by" information.
         if ($post["deleteMemberId"]) {
             $formatted["controls"][] = "<span>" . sprintf(T("Deleted %s by %s"), "<span title='" . _strftime(T("date.full"), $post["deleteTime"]) . "' data-timestamp='" . $post["deleteTime"] . "'>" . relativeTime($post["deleteTime"], true) . "</span>", memberLink($post["deleteMemberId"], $post["deleteMemberName"])) . "</span>";
         }
         // If the user can edit the post, add a restore control.
         if ($canEdit) {
             $formatted["controls"][] = "<a href='" . URL("conversation/restorePost/" . $post["postId"] . "?token=" . ET::$session->token) . "' title='" . T("Restore") . "' class='control-restore'><i class='icon-reply'></i></a>";
         }
     }
     $this->trigger("formatPostForTemplate", array(&$formatted, $post, $conversation));
     return $formatted;
 }
예제 #12
0
 /**
  * Log in the member with the specified username and password, and optionally set a persistent login cookie.
  *
  * @param string $username The username.
  * @param string $password The password.
  * @param bool $remember Whether or not to set a persistent login cookie.
  * @return bool true on success, false on failure.
  */
 public function login($name, $password, $remember = false)
 {
     $return = $this->trigger("login", array($name, $password, $remember));
     if (count($return)) {
         return reset($return);
     }
     // Get the member with this username or email.
     $sql = ET::SQL()->where("m.username=:username OR m.email=:email")->bind(":username", $name)->bind(":email", $name);
     $member = reset(ET::memberModel()->getWithSQL($sql));
     // Check that the password is correct.
     if (!$member or !ET::memberModel()->checkPassword($password, $member["password"])) {
         $this->error("password", "incorrectLogin");
         return false;
     }
     // Process the login.
     $return = $this->processLogin($member);
     // Set a persistent login "remember me" cookie?
     if ($return === true and $remember) {
         $this->setRememberCookie($this->userId);
     }
     return $return;
 }
 /**
  * Format post data into an array which can be used to display the post template view (conversation/post).
  *
  * @param array $post The post data.
  * @param array $conversation The details of the conversation which the post is in.
  * @return array A formatted array which can be used in the post template view.
  */
 protected function formatPostForTemplate($post, $conversation)
 {
     $canEdit = $this->canEditPost($post, $conversation);
     $avatar = avatar($post["memberId"], $post["avatarFormat"]);
     // Construct the post array for use in the post view (conversation/post).
     $formatted = array("id" => "p" . $post["postId"], "title" => memberLink($post["memberId"], $post["username"]), "avatar" => (!$post["deleteMemberId"] and $avatar) ? "<a href='" . URL(memberURL($post["memberId"], $post["username"])) . "'>{$avatar}</a>" : false, "class" => $post["deleteMemberId"] ? array("deleted") : array(), "info" => array(), "controls" => array(), "body" => !$post["deleteMemberId"] ? $this->displayPost($post["content"]) : false, "data" => array("id" => $post["postId"], "memberid" => $post["memberId"]));
     // If the post was within the last 24 hours, show a relative time (eg. 2 hours ago.)
     if (time() - $post["time"] < 24 * 60 * 60) {
         $date = relativeTime($post["time"], true);
     } else {
         $date = date("M j", $post["time"]);
     }
     // Add the date/time to the post info as a permalink.
     $formatted["info"][] = "<a href='" . URL(postURL($post["postId"])) . "' class='time' title='" . date(T("date.full"), $post["time"]) . "'>" . (!empty($conversation["searching"]) ? T("Context") : $date) . "</a>";
     // If the post isn't deleted, add a lot of stuff!
     if (!$post["deleteMemberId"]) {
         // Add the user's online status / last action next to their name.
         $lastAction = ET::memberModel()->getLastActionInfo($post["lastActionTime"], $post["lastActionDetail"]);
         if ($lastAction[0]) {
             $lastAction[0] = " (" . sanitizeHTML($lastAction[0]) . ")";
         }
         if ($lastAction) {
             array_unshift($formatted["info"], "<" . (!empty($lastAction[1]) ? "a href='{$lastAction[1]}'" : "span") . " class='online' title='" . T("Online") . "{$lastAction[0]}'>" . T("Online") . "</" . (!empty($lastAction[1]) ? "a" : "span") . ">");
         }
         // Show the user's group type.
         $formatted["info"][] = "<span class='group'>" . memberGroup($post["account"], $post["groups"]) . "</span>";
         // If the post has been edited, show the time and by whom next to the controls.
         if ($post["editMemberId"]) {
             $formatted["controls"][] = "<span class='editedBy'>" . sprintf(T("Edited %s by %s"), "<span title='" . date(T("date.full"), $post["editTime"]) . "'>" . relativeTime($post["editTime"], true) . "</span>", $post["editMemberName"]) . "</span>";
         }
         // If the user can reply, add a quote control.
         if ($conversation["canReply"]) {
             $formatted["controls"][] = "<a href='" . URL(conversationURL($conversation["conversationId"], $conversation["title"]) . "/?quote=" . $post["postId"] . "#reply") . "' title='" . T("Quote") . "' class='control-quote'>" . T("Quote") . "</a>";
         }
         // If the user can edit the post, add edit/delete controls.
         if ($canEdit) {
             $formatted["controls"][] = "<a href='" . URL("conversation/editPost/" . $post["postId"]) . "' title='" . T("Edit") . "' class='control-edit'>" . T("Edit") . "</a>";
             $formatted["controls"][] = "<a href='" . URL("conversation/deletePost/" . $post["postId"] . "?token=" . ET::$session->token) . "' title='" . T("Delete") . "' class='control-delete'>" . T("Delete") . "</a>";
         }
     } else {
         // Add the "deleted by" information.
         if ($post["deleteMemberId"]) {
             $formatted["controls"][] = "<span>" . sprintf(T("Deleted %s by %s"), "<span title='" . date(T("date.full"), $post["deleteTime"]) . "'>" . relativeTime($post["deleteTime"], true) . "</span>", $post["deleteMemberName"]) . "</span>";
         }
         // If the user can edit the post, add a restore control.
         if ($canEdit) {
             $formatted["controls"][] = "<a href='" . URL("conversation/restorePost/" . $post["postId"] . "?token=" . ET::$session->token) . "' title='" . T("Restore") . "' class='control-restore'>" . T("Restore") . "</a>";
         }
     }
     $this->trigger("formatPostForTemplate", array(&$formatted, $post, $conversation));
     return $formatted;
 }
 /**
  * Display a list of conversations, optionally filtered by channel(s) and a search string.
  *
  * @return void
  */
 function index($channelSlug = false)
 {
     // Add the default gambits to the gambit cloud: gambit text => css class to apply.
     $gambits = array(T("gambit.active last ? hours") => "gambit-activeLastHours", T("gambit.active last ? days") => "gambit-activeLastDays", T("gambit.active today") => "gambit-activeToday", T("gambit.author:") . T("gambit.member") => "gambit-author", T("gambit.contributor:") . T("gambit.member") => "gambit-contributor", T("gambit.dead") => "gambit-dead", T("gambit.has replies") => "gambit-hasReplies", T("gambit.has >10 replies") => "gambit-replies", T("gambit.locked") => "gambit-locked", T("gambit.more results") => "gambit-more", T("gambit.order by newest") => "gambit-orderByNewest", T("gambit.order by replies") => "gambit-orderByReplies", T("gambit.random") => "gambit-random", T("gambit.reverse") => "gambit-reverse", T("gambit.sticky") => "gambit-sticky");
     // Add some more personal gambits if there is a user logged in.
     if (ET::$session->user) {
         $gambits += array(T("gambit.contributor:") . T("gambit.myself") => "gambit-contributorMyself", T("gambit.author:") . T("gambit.myself") => "gambit-authorMyself", T("gambit.draft") => "gambit-draft", T("gambit.muted") => "gambit-muted", T("gambit.private") => "gambit-private", T("gambit.starred") => "gambit-starred", T("gambit.unread") => "gambit-unread");
     }
     list($channelInfo, $currentChannels, $channelIds, $includeDescendants) = $this->getSelectedChannels($channelSlug);
     // Now we need to construct some arrays to determine which channel "tabs" to show in the view.
     // $channels is a list of channels with the same parent as the current selected channel(s).
     // $path is a breadcrumb trail to the depth of the currently selected channel(s).
     $channels = array();
     $path = array();
     // Work out what channel we will use as the "parent" channel. This will be the last item in $path,
     // and its children will be in $channels.
     $curChannel = false;
     // If channels have been selected, use the first of them.
     if (count($currentChannels)) {
         $curChannel = $channelInfo[$currentChannels[0]];
     }
     // If the currently selected channel has no children, or if we're not including descendants, use
     // its parent as the parent channel.
     if ($curChannel and $curChannel["lft"] >= $curChannel["rgt"] - 1 or !$includeDescendants) {
         $curChannel = @$channelInfo[$curChannel["parentId"]];
     }
     // If no channel is selected, make a faux parent channel.
     if (!$curChannel) {
         $curChannel = array("lft" => 0, "rgt" => PHP_INT_MAX, "depth" => -1);
     }
     // Now, finally, go through all the channels and add ancestors of the "parent" channel to the $path,
     // and direct children to the list of $channels. Make sure we don't include any channels which
     // the user has unsubscribed to.
     foreach ($channelInfo as $channel) {
         if ($channel["lft"] > $curChannel["lft"] and $channel["rgt"] < $curChannel["rgt"] and $channel["depth"] == $curChannel["depth"] + 1 and empty($channel["unsubscribed"])) {
             $channels[] = $channel;
         } elseif ($channel["lft"] <= $curChannel["lft"] and $channel["rgt"] >= $curChannel["rgt"]) {
             $path[] = $channel;
         }
     }
     // Store the currently selected channel in the session, so that it can be automatically selected
     // if "New conversation" is clicked.
     if (!empty($currentChannels)) {
         ET::$session->store("channelId", $currentChannels[0]);
     }
     // Get the search string request value.
     $searchString = R("search");
     // Last, but definitely not least... perform the search!
     $search = ET::searchModel();
     $conversationIDs = $search->getConversationIDs($channelIds, $searchString, count($currentChannels));
     $results = $search->getResults($conversationIDs);
     // Were there any errors? Show them as messages.
     if ($search->errorCount()) {
         $this->messages($search->errors(), "warning dismissable");
     } else {
         $words = array();
         foreach ($search->fulltext as $term) {
             if (preg_match_all('/"(.+?)"/', $term, $matches)) {
                 $words[] = $matches[1];
                 $term = preg_replace('/".+?"/', '', $term);
             }
             $words = array_unique(array_merge($words, explode(" ", $term)));
         }
         ET::$session->store("highlight", $words);
     }
     // Pass on a bunch of data to the view.
     $this->data("results", $results);
     $this->data("showViewMoreLink", $search->areMoreResults());
     $this->data("channelPath", $path);
     $this->data("channelTabs", $channels);
     $this->data("currentChannels", $currentChannels);
     $this->data("channelInfo", $channelInfo);
     $this->data("channelSlug", $channelSlug ? $channelSlug : "all");
     $this->data("searchString", $searchString);
     $this->data("fulltextString", implode(" ", $search->fulltext));
     $this->data("gambits", $gambits);
     // If we're loading the page in full...
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         // Update the user's last action.
         ET::memberModel()->updateLastAction("search");
         // Construct a canonical URL and add to the breadcrumb stack.
         $slugs = array();
         foreach ($currentChannels as $channel) {
             $slugs[] = $channelInfo[$channel]["slug"];
         }
         $url = "conversations/" . urlencode(($k = implode(" ", $slugs)) ? $k : "all") . ($searchString ? "?search=" . urlencode($searchString) : "");
         $this->pushNavigation("conversations", "search", URL($url));
         $this->canonicalURL = URL($url, true);
         // Add a link to the RSS feed in the bar.
         $this->addToMenu("meta", "feed", "<a href='" . URL(str_replace("conversations/", "conversations/index.atom/", $url)) . "' id='feed'>" . T("Feed") . "</a>");
         // Construct a list of keywords to use in the meta tags.
         $keywords = array();
         foreach ($channelInfo as $c) {
             if ($c["depth"] == 0) {
                 $keywords[] = strtolower($c["title"]);
             }
         }
         // Add meta tags to the header.
         $this->addToHead("<meta name='keywords' content='" . sanitizeHTML(($k = C("esoTalk.meta.keywords")) ? $k : implode(",", $keywords)) . "'>");
         list($lastKeyword) = array_splice($keywords, count($keywords) - 1, 1);
         $this->addToHead("<meta name='description' content='" . sanitizeHTML(($d = C("esoTalk.meta.description")) ? $d : sprintf(T("forumDescription"), C("esoTalk.forumTitle"), implode(", ", $keywords), $lastKeyword)) . "'>");
         // If this is not technically the homepage (if it's a search page) the we don't want it to be indexed.
         if ($searchString) {
             $this->addToHead("<meta name='robots' content='noindex, noarchive'>");
         }
         // Add JavaScript language definitions and variables.
         $this->addJSLanguage("Starred", "Unstarred", "gambit.member", "gambit.more results", "Filter conversations", "Jump to last");
         $this->addJSVar("searchUpdateInterval", C("esoTalk.search.updateInterval"));
         $this->addJSVar("currentSearch", $searchString);
         $this->addJSVar("currentChannels", $currentChannels);
         $this->addJSFile("js/lib/jquery.cookie.js");
         $this->addJSFile("js/autocomplete.js");
         $this->addJSFile("js/search.js");
         // Add an array of channels in the form slug => id for the JavaScript to use.
         $channels = array();
         foreach ($channelInfo as $id => $c) {
             $channels[$id] = $c["slug"];
         }
         $this->addJSVar("channels", $channels);
         // Get a bunch of statistics...
         $queries = array("post" => ET::SQL()->select("COUNT(*)")->from("post")->get(), "conversation" => ET::SQL()->select("COUNT(*)")->from("conversation")->get(), "member" => ET::SQL()->select("COUNT(*)")->from("member")->get());
         $sql = ET::SQL();
         foreach ($queries as $k => $query) {
             $sql->select("({$query}) AS {$k}");
         }
         $stats = $sql->exec()->firstRow();
         // ...and show them in the footer.
         foreach ($stats as $k => $v) {
             $stat = Ts("statistic.{$k}", "statistic.{$k}.plural", number_format($v));
             if ($k == "member") {
                 $stat = "<a href='" . URL("members") . "'>{$stat}</a>";
             }
             $this->addToMenu("statistics", "statistic-{$k}", $stat, array("before" => "statistic-online"));
         }
         $this->render("conversations/index");
     } elseif ($this->responseType === RESPONSE_TYPE_VIEW) {
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_AJAX) {
         $this->json("channels", $this->getViewContents("channels/tabs", $this->data));
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_JSON) {
         $this->json("results", $results);
         $this->render();
     }
 }
 /**
  * Send private conversation invitation notifications to a list of members. A notification will only
  * be sent if this is the first time a member has been added to the conversation, to prevent intentional
  * email spamming.
  *
  * @param array $conversation The conversation to that we're sending out notifications for.
  * @param array $memberIds A list of member IDs to send the notifications to.
  * @param bool $notifyAll If set to true, all members will be notified regardless of if they have been
  * 		added to this conversation before.
  * @return void
  */
 protected function privateAddNotification($conversation, $memberIds, $notifyAll = false, $content = null)
 {
     $memberIds = (array) $memberIds;
     // Remove the currently logged in user from the list of member IDs.
     if (($k = array_search(ET::$session->userId, $memberIds)) !== false) {
         unset($memberIds[$k]);
     }
     if (!count($memberIds)) {
         return;
     }
     // Get the member details for this list of member IDs.
     $sql = ET::SQL()->from("member_conversation s", "s.conversationId=:conversationId AND s.type='member' AND s.id=m.memberId", "left")->bind(":conversationId", $conversation["conversationId"])->where("m.memberId IN (:memberIds)")->bind(":memberIds", $memberIds);
     // Only get members where the member_conversation row doesn't exist (implying that this is the first time
     // they've been added to the conversation.)
     if (!$notifyAll) {
         $sql->where("s.id IS NULL");
     }
     $members = ET::memberModel()->getWithSQL($sql);
     $data = array("conversationId" => $conversation["conversationId"], "title" => $conversation["title"]);
     $emailData = array("content" => $content);
     // Create the "privateAdd" activity which will send out a notification and an email if appropriate.
     // Also get IDs of members who would like to automatically follow this conversation.
     $followIds = array();
     foreach ($members as $member) {
         ET::activityModel()->create("privateAdd", $member, ET::$session->user, $data, $emailData);
         if (!empty($member["preferences"]["starPrivate"])) {
             $followIds[] = $member["memberId"];
         }
     }
     // Follow the conversation for the appropriate members.
     if (!empty($followIds)) {
         $this->setStatus($conversation["conversationId"], $followIds, array("starred" => true));
     }
 }
 /**
  * Display a list of conversations, optionally filtered by channel(s) and a search string.
  *
  * @return void
  */
 public function action_index($channelSlug = false)
 {
     if (!$this->allowed()) {
         return;
     }
     list($channelInfo, $currentChannels, $channelIds, $includeDescendants) = $this->getSelectedChannels($channelSlug);
     // Now we need to construct some arrays to determine which channel "tabs" to show in the view.
     // $channels is a list of channels with the same parent as the current selected channel(s).
     // $path is a breadcrumb trail to the depth of the currently selected channel(s).
     $channels = array();
     $path = array();
     // Work out what channel we will use as the "parent" channel. This will be the last item in $path,
     // and its children will be in $channels.
     $curChannel = false;
     // If channels have been selected, use the first of them.
     if (count($currentChannels)) {
         $curChannel = $channelInfo[$currentChannels[0]];
     }
     // If the currently selected channel has no children, or if we're not including descendants, use
     // its parent as the parent channel.
     if ($curChannel and $curChannel["lft"] >= $curChannel["rgt"] - 1 or !$includeDescendants) {
         $curChannel = @$channelInfo[$curChannel["parentId"]];
     }
     // If no channel is selected, make a faux parent channel.
     if (!$curChannel) {
         $curChannel = array("lft" => 0, "rgt" => PHP_INT_MAX, "depth" => -1);
     }
     // Now, finally, go through all the channels and add ancestors of the "parent" channel to the $path,
     // and direct children to the list of $channels. Make sure we don't include any channels which
     // the user has unsubscribed to.
     foreach ($channelInfo as $channel) {
         if ($channel["lft"] > $curChannel["lft"] and $channel["rgt"] < $curChannel["rgt"] and $channel["depth"] == $curChannel["depth"] + 1 and empty($channel["unsubscribed"])) {
             $channels[] = $channel;
         } elseif ($channel["lft"] <= $curChannel["lft"] and $channel["rgt"] >= $curChannel["rgt"]) {
             $path[] = $channel;
         }
     }
     // Store the currently selected channel in the session, so that it can be automatically selected
     // if "New conversation" is clicked.
     if (!empty($currentChannels)) {
         ET::$session->store("searchChannelId", $currentChannels[0]);
     }
     // Get the search string request value.
     $searchString = R("search");
     // Last, but definitely not least... perform the search!
     $search = ET::searchModel();
     $conversationIDs = $search->getConversationIDs($channelIds, $searchString, count($currentChannels) or !ET::$session->userId);
     // If this page was originally accessed at conversations/markAsRead/all?search=whatever (the
     // markAsRead method simply calls the index method), then mark the results as read.
     if ($this->controllerMethod == "markasread" and ET::$session->userId) {
         ET::conversationModel()->markAsRead($conversationIDs, ET::$session->userId);
     }
     $results = $search->getResults($conversationIDs);
     // Were there any errors? Show them as messages.
     if ($search->errorCount()) {
         $this->messages($search->errors(), "warning dismissable");
     } else {
         $this->highlight($search->fulltext);
     }
     // Pass on a bunch of data to the view.
     $this->data("results", $results);
     $this->data("limit", $search->limit);
     $this->data("showViewMoreLink", $search->areMoreResults());
     $this->data("channelPath", $path);
     $this->data("channelTabs", $channels);
     $this->data("currentChannels", $currentChannels);
     $this->data("channelInfo", $channelInfo);
     $this->data("channelSlug", $channelSlug = $channelSlug ? $channelSlug : "all");
     $this->data("searchString", $searchString);
     $this->data("fulltextString", implode(" ", $search->fulltext));
     // Construct a canonical URL and add to the breadcrumb stack.
     $slugs = array();
     foreach ($currentChannels as $channel) {
         $slugs[] = $channelInfo[$channel]["slug"];
     }
     $url = "conversations/" . urlencode(($k = implode(" ", $slugs)) ? $k : "all") . ($searchString ? "?search=" . urlencode($searchString) : "");
     $this->pushNavigation("conversations", "search", URL($url));
     $this->canonicalURL = URL($url, true);
     // If we're loading the page in full...
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         // Update the user's last action.
         ET::memberModel()->updateLastAction("search");
         // Add a link to the RSS feed in the bar.
         // $this->addToMenu("meta", "feed", "<a href='".URL(str_replace("conversations/", "conversations/index.atom/", $url))."' id='feed'>".T("Feed")."</a>");
         $controls = ETFactory::make("menu");
         // Mark as read controls
         if (ET::$session->user) {
             $controls->add("markAllAsRead", "<a href='" . URL("conversations/markAllAsRead/?token=" . ET::$session->token . "' id='control-markAllAsRead'><i class='icon-check'></i> " . T("Mark all as read") . "</a>"));
             $controls->add("markListedAsRead", "<a href='" . URL("conversations/{$channelSlug}/?search=" . urlencode($searchString) . "&markAsRead=1&token=" . ET::$session->token . "' id='control-markListedAsRead'><i class='icon-list'></i> " . T("Mark listed as read") . "</a>"));
         }
         // Add the default gambits to the gambit cloud: gambit text => css class to apply.
         $gambits = array("main" => array(T("gambit.sticky") => array("gambit-sticky", "icon-pushpin")), "time" => array(T("gambit.order by newest") => array("gambit-orderByNewest", "icon-list-ol"), T("gambit.active last ? hours") => array("gambit-activeLastHours", "icon-time"), T("gambit.active last ? days") => array("gambit-activeLastDays", "icon-calendar"), T("gambit.active today") => array("gambit-activeToday", "icon-asterisk"), T("gambit.dead") => array("gambit-dead", "icon-remove"), T("gambit.locked") => array("gambit-locked", "icon-lock")), "member" => array(T("gambit.author:") . T("gambit.member") => array("gambit-author", "icon-user"), T("gambit.contributor:") . T("gambit.member") => array("gambit-contributor", "icon-user")), "replies" => array(T("gambit.has replies") => array("gambit-hasReplies", "icon-comment"), T("gambit.has >10 replies") => array("gambit-replies", "icon-comments"), T("gambit.order by replies") => array("gambit-orderByReplies", "icon-list-ol")), "text" => array(T("gambit.title:") . " ?" => array("gambit-title", "icon-font")), "misc" => array(T("gambit.random") => array("gambit-random", "icon-random"), T("gambit.reverse") => array("gambit-reverse", "icon-exchange")));
         // Add some more personal gambits if there is a user logged in.
         if (ET::$session->user) {
             addToArrayString($gambits["main"], T("gambit.private"), array("gambit-private", "icon-envelope-alt"), 1);
             addToArrayString($gambits["main"], T("gambit.starred"), array("gambit-starred", "icon-star"), 2);
             addToArrayString($gambits["main"], T("gambit.draft"), array("gambit-draft", "icon-pencil"), 3);
             addToArrayString($gambits["main"], T("gambit.ignored"), array("gambit-ignored", "icon-eye-close"), 4);
             addToArrayString($gambits["time"], T("gambit.unread"), array("gambit-unread", "icon-inbox"), 0);
             addToArrayString($gambits["member"], T("gambit.author:") . T("gambit.myself"), array("gambit-authorMyself", "icon-smile"), 0);
             addToArrayString($gambits["member"], T("gambit.contributor:") . T("gambit.myself"), array("gambit-contributorMyself", "icon-smile"), 2);
         }
         $this->trigger("constructGambitsMenu", array(&$gambits));
         // Construct the gambits menu based on the above arrays.
         $gambitsMenu = ETFactory::make("menu");
         $linkPrefix = "conversations/" . $channelSlug . "/?search=" . urlencode(!empty($searchString) ? $searchString . " + " : "");
         foreach ($gambits as $section => $items) {
             foreach ($items as $gambit => $classes) {
                 $gambitsMenu->add($classes[0], "<a href='" . URL($linkPrefix . urlencode("#" . $gambit)) . "' class='{$classes[0]}' data-gambit='{$gambit}'>" . (!empty($classes[1]) ? "<i class='{$classes[1]}'></i> " : "") . "{$gambit}</a>");
             }
             end($gambits);
             if ($section !== key($gambits)) {
                 $gambitsMenu->separator();
             }
         }
         $this->data("controlsMenu", $controls);
         $this->data("gambitsMenu", $gambitsMenu);
         // Construct a list of keywords to use in the meta tags.
         $keywords = array();
         foreach ($channelInfo as $c) {
             if ($c["depth"] == 0) {
                 $keywords[] = strtolower($c["title"]);
             }
         }
         // Add meta tags to the header.
         $this->addToHead("<meta name='keywords' content='" . sanitizeHTML(($k = C("esoTalk.meta.keywords")) ? $k : implode(",", $keywords)) . "'>");
         $lastKeyword = reset(array_splice($keywords, count($keywords) - 1, 1));
         $this->addToHead("<meta name='description' content='" . sanitizeHTML(($d = C("esoTalk.meta.description")) ? $d : sprintf(T("forumDescription"), C("esoTalk.forumTitle"), implode(", ", $keywords), $lastKeyword)) . "'>");
         // If this is not technically the homepage (if it's a search page) the we don't want it to be indexed.
         if ($searchString) {
             $this->addToHead("<meta name='robots' content='noindex, noarchive'>");
         }
         // Add JavaScript language definitions and variables.
         $this->addJSLanguage("Starred", "Unstarred", "gambit.member", "gambit.more results", "Filter conversations", "Jump to last");
         $this->addJSVar("searchUpdateInterval", C("esoTalk.search.updateInterval"));
         $this->addJSVar("currentSearch", $searchString);
         $this->addJSVar("currentChannels", $currentChannels);
         $this->addJSFile("core/js/lib/jquery.cookie.js");
         $this->addJSFile("core/js/autocomplete.js");
         $this->addJSFile("core/js/search.js");
         // Add an array of channels in the form slug => id for the JavaScript to use.
         $channels = array();
         foreach ($channelInfo as $id => $c) {
             $channels[$id] = $c["slug"];
         }
         $this->addJSVar("channels", $channels);
         // Get a bunch of statistics...
         $queries = array("post" => ET::SQL()->select("COUNT(*)")->from("post")->get(), "conversation" => ET::SQL()->select("COUNT(*)")->from("conversation")->get(), "member" => ET::SQL()->select("COUNT(*)")->from("member")->get());
         $sql = ET::SQL();
         foreach ($queries as $k => $query) {
             $sql->select("({$query}) AS {$k}");
         }
         $stats = $sql->exec()->firstRow();
         // ...and show them in the footer.
         foreach ($stats as $k => $v) {
             $stat = Ts("statistic.{$k}", "statistic.{$k}.plural", number_format($v));
             if ($k == "member" and (C("esoTalk.members.visibleToGuests") or ET::$session->user)) {
                 $stat = "<a href='" . URL("members") . "'>{$stat}</a>";
             }
             $this->addToMenu("statistics", "statistic-{$k}", $stat, array("before" => "statistic-online"));
         }
         $this->render("conversations/index");
     } elseif ($this->responseType === RESPONSE_TYPE_VIEW) {
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_AJAX) {
         $this->json("channels", $this->getViewContents("channels/tabs", $this->data));
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_JSON) {
         $this->json("results", $results);
         $this->render();
     }
 }
 /**
  * 会話(テーマ)新規作成処理
  * Show the start conversation page.
  *
  * @param string $member A member's name to make the conversation private with.
  * @return void
  */
 public function action_start($member = false)
 {
     // If the user isn't logged in, redirect them to the login page.
     if (!ET::$session->user) {
         $this->redirect(SwcUtils::getSwcUrl('login'));
     }
     // If the user is suspended, show an error.
     if (ET::$session->isSuspended()) {
         $this->renderMessage("Error!", T("message.suspended"));
         return;
     }
     // Set up a form.
     $form = ETFactory::make("form");
     $form->action = URL("conversation/start");
     // Get a list of channels so that we can check to make sure a valid channel is selected.
     $channels = ET::channelModel()->get("start");
     $channelId = $form->validPostBack("content") ? ET::$session->get("channelId") : ET::$session->get("searchChannelId");
     ET::$session->store("channelId", isset($channels[$channelId]) ? $channelId : reset(array_keys($channels)));
     // Get an empty conversation.
     $model = ET::conversationModel();
     $conversation = $model->getEmptyConversation();
     $conversation["membersAllowed"] = $model->getMembersAllowed($conversation);
     $conversation["membersAllowedSummary"] = $model->getMembersAllowedSummary($conversation, $conversation["membersAllowed"]);
     $conversation["channelPath"] = $model->getChannelPath($conversation);
     // XXX: タグ入力エリア設定 新規作成フラグ設定
     $this->data("isStartFlg", 1);
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         $this->title = T("New conversation");
         // Update the user's last action to say that they're "starting a conversation".
         ET::memberModel()->updateLastAction("startingConversation");
         // Add a meta tag to the head to prevent search engines from indexing this page.
         $this->addToHead("<meta name='robots' content='noindex, noarchive'/>");
         $this->addJSFile("core/js/lib/jquery.autogrow.js");
         $this->addJSFile("core/js/scrubber.js");
         $this->addJSFile("core/js/autocomplete.js");
         //		$this->addJSFile("core/js/conversation.js");    TODO: 4 debug
         $this->addJSFile("/forum/core/js/conversation.js", "last");
         // 共通検索用js 追加
         $this->addJSFile("core/js/search.conversation.js");
         //		$this->addJSFile("/forum/core/js/search.conversation.js","last");
         $this->addJSVar("mentions", C("esoTalk.format.mentions"));
         $this->addJSLanguage("message.confirmLeave", "message.confirmDiscardPost");
         // TODO: 名前有りの場合 確認
         // If there's a member name in the querystring, make the conversation that we're starting private
         // with them and redirect.
         if ($member and ET::$session->validateToken(R("token"))) {
             ET::$session->remove("membersAllowed");
             if (!($member = ET::conversationModel()->getMemberFromName($member))) {
                 $this->message(T("message.memberDoesntExist"), "warning");
             } else {
                 ET::conversationModel()->addMember($conversation, $member);
             }
             $this->redirect(URL("conversation/start"));
         }
     }
     // If the form was submitted (validate the presence of the content field)...
     if ($form->validPostBack("content")) {
         // 新規会話(テーマ) 登録時の処理
         $model = ET::conversationModel();
         $result = $model->create(array("title" => $_POST["title"], "channelId" => ET::$session->get("channelId"), "content" => $_POST["content"]), ET::$session->get("membersAllowed"), $form->isPostBack("saveDraft"));
         if ($model->errorCount()) {
             $this->messages($model->errors(), "warning");
         }
         if ($result) {
             list($conversationId, $postId) = $result;
             // タグ情報の更新処理
             ET::tagsModel()->editPost($conversationId, 1, $form->getValue("tags"));
             ET::$session->remove("membersAllowed");
             ET::$session->remove("channelId");
             if ($this->responseType === RESPONSE_TYPE_JSON) {
                 $this->json("url", URL(conversationURL($conversationId, $form->getValue("title"))));
                 $this->json("conversationId", $conversationId);
             } else {
                 $this->redirect(URL(conversationURL($conversationId, $form->getValue("title"))));
             }
         }
     }
     // Make a form to add members allowed.
     $membersAllowedForm = ETFactory::make("form");
     $membersAllowedForm->action = URL("conversation/addMember/");
     $this->data("conversation", $conversation);
     $this->data("form", $form);
     $this->data("membersAllowedForm", $membersAllowedForm);
     $this->data("replyControls", $this->getEditControls("reply"));
     $this->render("conversation/edit");
 }
예제 #18
0
 /**
  * Send private conversation invitation notifications to a list of members. A notification will only
  * be sent if this is the first time a member has been added to the conversation, to prevent intentional
  * email spamming.
  *
  * @param array $conversation The conversation to that we're sending out notifications for.
  * @param array $memberIds A list of member IDs to send the notifications to.
  * @param bool $notifyAll If set to true, all members will be notified regardless of if they have been
  * 		added to this conversation before.
  * @return void
  */
 protected function privateAddNotification($conversation, $memberIds, $notifyAll = false)
 {
     $memberIds = (array) $memberIds;
     // Remove the currently logged in user from the list of member IDs.
     if (($k = array_search(ET::$session->userId, $memberIds)) !== false) {
         unset($memberIds[$k]);
     }
     if (!count($memberIds)) {
         return;
     }
     // Get the member details for this list of member IDs.
     $sql = ET::SQL()->from("member_conversation s", "s.conversationId=:conversationId AND s.type='member' AND s.id=m.memberId", "left")->bind(":conversationId", $conversation["conversationId"])->where("m.memberId IN (:memberIds)")->bind(":memberIds", $memberIds);
     // Only get members where the member_conversation row doesn't exist (implying that this is the first time
     // they've been added to the conversation.)
     if (!$notifyAll) {
         $sql->where("s.id IS NULL");
     }
     $members = ET::memberModel()->getWithSQL($sql);
     $data = array("conversationId" => $conversation["conversationId"], "title" => $conversation["title"]);
     foreach ($members as $member) {
         ET::activityModel()->create("privateAdd", $member, ET::$session->user, $data);
     }
 }
예제 #19
0
 /**
  * Show a form allowing the user to reset their password, following on from a link sent to them by the forgot
  * password process.
  *
  * @param string $hashString The hash stored in the member's resetPassword field, prefixed by their ID.
  * @return void
  */
 public function reset($hashString = "")
 {
     if (empty($hashString)) {
         return;
     }
     // Split the hash into the member ID and hash.
     $memberId = (int) substr($hashString, 0, strlen($hashString) - 32);
     $hash = substr($hashString, -32);
     // Find the member with this password reset token. If it's an invalid token, take them back to the email form.
     $member = reset(ET::memberModel()->get(array("m.memberId" => $memberId, "resetPassword" => md5($hash))));
     if (!$member) {
         return;
     }
     // Construct a form.
     $form = ETFactory::make("form");
     $form->action = URL("user/reset/{$hashString}");
     // If the change password form has been submitted...
     if ($form->validPostBack("submit")) {
         // Make sure the passwords match. The model will do the rest of the validation.
         if ($form->getValue("password") != $form->getValue("confirm")) {
             $form->error("confirm", T("message.passwordsDontMatch"));
         }
         if (!$form->errorCount()) {
             $model = ET::memberModel();
             $model->updateById($memberId, array("password" => $form->getValue("password"), "resetPassword" => null));
             // If there were validation errors, pass them to the form.
             if ($model->errorCount()) {
                 $form->errors($model->errors());
             } else {
                 $this->message(T("message.passwordChanged"));
                 $this->redirect(URL(""));
             }
         }
     }
     $this->data("form", $form);
     $this->render("user/setPassword");
 }
예제 #20
0
 /**
  * Show the "online members" sheet.
  *
  * @return void
  */
 public function online()
 {
     // Set the title and make sure this page isn't indexed.
     $this->title = T("Online Members");
     $this->addToHead("<meta name='robots' content='noindex, noarchive'/>");
     // Construct a query to get only members who are online.
     $sql = ET::SQL()->where(time() - ET::config("esoTalk.userOnlineExpire") . "<lastActionTime")->orderBy("lastActionTime DESC");
     // Pass this query to the member model and get all of these members' data.
     $members = ET::memberModel()->getWithSQL($sql);
     $this->data("members", $members);
     $this->render("members/online");
 }
 /**
  * Set up and show the main installation data-entry form.
  *
  * @return void
  */
 public function action_info()
 {
     // Set up the form.
     $form = ETFactory::make("form");
     $form->action = URL("install/info");
     // Set some default values.
     $form->setValue("mysqlHost", "localhost");
     $form->setValue("tablePrefix", "et");
     // If we have values stored in the session, use them.
     if ($values = ET::$session->get("install")) {
         $form->setValues($values);
     }
     // Work out what the base URL is.
     $dir = substr($_SERVER["PHP_SELF"], 0, strrpos($_SERVER["PHP_SELF"], "/index.php"));
     $baseURL = "http://{$_SERVER["HTTP_HOST"]}{$dir}/";
     $form->setValue("baseURL", $baseURL);
     // Work out if we can handle friendly URLs.
     //if (!empty($_SERVER["REQUEST_URI"])) $form->setValue("friendlyURLs", true);
     // If the form was submitted...
     if ($form->isPostBack("submit")) {
         $values = $form->getValues();
         // Make sure the title isn't empty.
         if (!strlen($values["forumTitle"])) {
             $form->error("forumTitle", T("message.empty"));
         }
         // Make sure the admin's details are valid.
         if ($error = ET::memberModel()->validateUsername($values["adminUser"], false)) {
             $form->error("adminUser", T("message.{$error}"));
         }
         if ($error = ET::memberModel()->validateEmail($values["adminEmail"], false)) {
             $form->error("adminEmail", T("message.{$error}"));
         }
         if ($error = ET::memberModel()->validatePassword($values["adminPass"])) {
             $form->error("adminPass", T("message.{$error}"));
         }
         if ($values["adminPass"] != $values["adminConfirm"]) {
             $form->error("adminConfirm", T("message.passwordsDontMatch"));
         }
         /*
         // Try and connect to the database.
         try {
         	ET::$database->init($values["mysqlHost"], $values["mysqlUser"], $values["mysqlPass"], $values["mysqlDB"]);
         	ET::$database->connection();
         } catch (PDOException $e) {
         	$form->error("mysql", $e->getMessage());
         }
         */
         // Check to see if there are any conflicting tables already in the database.
         // If there are, show an error with a hidden input. If the form is submitted again with this hidden input,
         // proceed to perform the installation regardless.
         if (!$form->errorCount() and $values["tablePrefix"] != @$values["confirmTablePrefix"]) {
             // Get a list of all existing tables.
             $theirTables = array();
             $result = ET::SQL("SHOW TABLES");
             while ($table = $result->result()) {
                 $theirTables[] = $table;
             }
             // Just do a check for the member table. If it exists with this prefix, we have a conflict.
             if (in_array($values["tablePrefix"] . "_member", $theirTables)) {
                 $form->error("tablePrefix", T("message.tablePrefixConflict"));
                 $form->addHidden("confirmTablePrefix", $values["tablePrefix"], true);
             }
         }
         // If there are no errors, proceed to the installation step.
         if (!$form->errorCount()) {
             // Put all the POST data into the session and proceed to the install step.
             ET::$session->store("install", $values);
             $this->redirect(URL("install/install"));
         }
     }
     $this->data("form", $form);
     $this->render("install/info");
 }
 /**
  * Show the "online members" sheet.
  *
  * @return void
  */
 public function online()
 {
     // Check if we have permission to view the online list.
     if (!C("esoTalk.members.visibleToGuests") and !ET::$session->user) {
         $this->render404(T("message.pageNotFound"));
         return false;
     }
     // Set the title and make sure this page isn't indexed.
     $this->title = T("Online Members");
     $this->addToHead("<meta name='robots' content='noindex, noarchive'/>");
     // Construct a query to get only members who are online.
     $sql = ET::SQL()->where(time() - ET::config("esoTalk.userOnlineExpire") . "<lastActionTime")->orderBy("lastActionTime DESC");
     // Pass this query to the member model and get all of these members' data.
     $members = ET::memberModel()->getWithSQL($sql);
     // Filter out members who have opted out of being displayed on the online list.
     $hidden = 0;
     foreach ($members as $k => $member) {
         if (!empty($member["preferences"]["hideOnline"])) {
             unset($members[$k]);
             $hidden++;
         }
     }
     $this->data("members", $members);
     $this->data("hidden", $hidden);
     $this->render("members/online");
 }
예제 #23
0
 /**
  * Create a post in the specified conversation.
  *
  * This function will go through the post content and notify any members who are @mentioned.
  *
  * @param int $conversationId The ID of the conversation to create the post in.
  * @param int $memberId The ID of the author of the post.
  * @param string $content The post content.
  * @param string $title The title of the conversation (so it can be added alongside the post, for fulltext purposes.)
  * @return bool|int The new post's ID, or false if there were errors.
  */
 public function create($conversationId, $memberId, $content, $title = "")
 {
     // Validate the post content.
     $this->validate("content", $content, array($this, "validateContent"));
     if ($this->errorCount()) {
         return false;
     }
     // Prepare the post details for the query.
     $data = array("conversationId" => $conversationId, "memberId" => $memberId, "time" => time(), "content" => $content, "title" => $title);
     $id = parent::create($data);
     // Update the member's post count.
     ET::SQL()->update("member")->set("countPosts", "countPosts + 1", false)->where("memberId", $memberId)->exec();
     // Update the channel's post count.
     ET::SQL()->update("channel")->set("countPosts", "countPosts + 1", false)->where("channelId", ET::SQL()->select("channelId")->from("conversation")->where("conversationId=:conversationId")->bind(":conversationId", $conversationId)->exec()->result())->exec();
     // Parse the post content for @mentions, and notify any members who were mentioned.
     if (C("esoTalk.format.mentions")) {
         $names = ET::formatter()->getMentions($content);
         if (count($names)) {
             // Get the member details from the database.
             $sql = ET::SQL()->where("m.username IN (:names)")->bind(":names", $names)->where("m.memberId != :userId")->bind(":userId", $memberId);
             $members = ET::memberModel()->getWithSQL($sql);
             $data = array("conversationId" => $conversationId, "postId" => (int) $id, "title" => $title);
             $emailData = array("content" => $content);
             $i = 0;
             foreach ($members as $member) {
                 // Only send notifications to the first 10 members who are mentioned to prevent abuse of the system.
                 if ($i++ > 10) {
                     break;
                 }
                 // Check if this member is allowed to view this conversation before sending them a notification.
                 $sql = ET::SQL()->select("conversationId")->from("conversation c")->where("conversationId", $conversationId);
                 ET::conversationModel()->addAllowedPredicate($sql, $member);
                 if (!$sql->exec()->numRows()) {
                     continue;
                 }
                 ET::activityModel()->create("mention", $member, ET::$session->user, $data, $emailData);
             }
         }
     }
     return $id;
 }
예제 #24
0
 /**
  * Show a sheet to delete a member.
  *
  * @param int $memberId The member's ID.
  * @return void
  */
 public function action_delete($memberId = "")
 {
     if (!($member = $this->getMember($memberId))) {
         return;
     }
     // If we don't have permission to delete the member, throw an error.
     if (!ET::memberModel()->canDelete($member)) {
         $this->renderMessage(T("Error"), T("message.noPermission"));
         return;
     }
     // Construct a form.
     $form = ETFactory::make("form");
     $form->action = URL("member/delete/" . $member["memberId"]);
     $redirectURL = URL(memberURL($member["memberId"], $member["username"]));
     if ($form->isPostBack("cancel")) {
         $this->redirect($redirectURL);
     }
     // If the form was submitted, delete the member and take the appropriate action upon all their posts.
     if ($form->validPostBack("delete")) {
         ET::memberModel()->deleteById($member["memberId"], $form->getValue("deletePosts"));
         $this->message(T("message.changesSaved"), "success autoDismiss");
         $this->redirect(URL("members"));
     }
     $this->data("member", $member);
     $this->data("form", $form);
     $this->render("member/delete");
 }
예제 #25
0
 /**
  * Clear the rememberToken for a user, effectively invalidating all persistence cookies.
  *
  * @param int $memberId The ID of the member to clear the rememberToken for.
  * @return void
  */
 protected function clearRememberToken($memberId)
 {
     ET::memberModel()->updateById($memberId, array("rememberToken" => null));
     // Eat the persistent login cookie. OM NOM NOM
     if ($this->getCookie("persistent")) {
         $this->setCookie("persistent", false, -1);
     }
 }