/**
  * Toggle the user's subscription to a channel.
  *
  * @param int $channelId The ID of the channel to toggle subscription to.
  * @return void
  */
 public function action_subscribe($channelId = "")
 {
     if (!ET::$session->user or !$this->validateToken()) {
         return;
     }
     // If we don't have permission to view this channel, don't proceed.
     if (!ET::channelModel()->hasPermission((int) $channelId, "view")) {
         return;
     }
     // Work out if we're already unsubscribed or not, and switch to the opposite of that.
     $channel = ET::SQL()->select("unsubscribed, lft, rgt")->from("channel c")->from("member_channel mc", "mc.channelId = c.channelId AND mc.memberId = :userId", "left")->bind(":userId", ET::$session->userId)->where("c.channelId", (int) $channelId)->exec()->firstRow();
     // Get all the child channels of this channel.
     $rows = ET::SQL()->select("channelId")->from("channel")->where("lft >= :lft")->bind(":lft", $channel["lft"])->where("rgt <= :rgt")->bind(":rgt", $channel["rgt"])->exec()->allRows();
     $channelIds = array();
     foreach ($rows as $row) {
         $channelIds[] = $row["channelId"];
     }
     // Write to the database.
     ET::channelModel()->setStatus($channelIds, ET::$session->userId, array("unsubscribed" => !$channel["unsubscribed"]));
     // Normally, redirect back to the channel list.
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         redirect(URL("channels"));
     }
     // Otherwise, set a JSON var.
     $this->json("unsubscribed", !$channel["unsubscribed"]);
     $this->render();
 }
Пример #2
0
 public function action_create()
 {
     // Include the file needed to create the sitemap.
     include "lib/Sitemap.php";
     $sitemap = new Sitemap(C("esoTalk.baseURL"));
     $sitemap->setPath(PATH_ROOT . "/");
     $sitemap->addItem("", "1.0", "hourly", 'now');
     $result = ET::SQL()->select("ch.channelId")->select("ch.slug")->from("channel ch")->orderBy("ch.channelId ASC")->exec();
     $channels = $result->allRows("channelId");
     foreach ($channels as $channel) {
         if (!in_array($channel["slug"], C("plugin.Sitemap.channels"))) {
             $sitemap->addItem("conversations/" . $channel["slug"], C("plugin.Sitemap.priority3"), C("plugin.Sitemap.frequency3"), 'now');
             $result = ET::SQL()->select("c.conversationId")->select("c.title")->select("c.channelId")->select("c.sticky")->select("lastPostTime")->from("conversation c")->where("c.channelId = :channelId")->where("private", 0)->orderBy("c.conversationId ASC")->bind(":channelId", $channel["channelId"])->exec();
             $conversations = $result->allRows();
             foreach ($conversations as $conversation) {
                 $url = conversationURL($conversation["conversationId"], $conversation["title"]);
                 if ($conversation["sticky"]) {
                     $sitemap->addItem($url, C("plugin.Sitemap.priority2"), C("plugin.Sitemap.frequency2"), $conversation["lastPostTime"]);
                 } else {
                     $sitemap->addItem($url, C("plugin.Sitemap.priority1"), C("plugin.Sitemap.frequency1"), $conversation["lastPostTime"]);
                 }
             }
         }
     }
     $sitemap->createSitemapIndex("http://www.bitcoinclub.nl/", 'now');
 }
 /**
  * Initialize the admin controller. Construct a menu to show all admin panels.
  *
  * @return void
  */
 public function init()
 {
     // If the user isn't an administrator, kick them out.
     if (!ET::$session->isAdmin()) {
         $this->redirect(URL("user/login?return=" . urlencode($this->selfURL)));
     }
     parent::init();
     // Construct the menus for the side bar.
     $this->defaultMenu = ETFactory::make("menu");
     $this->menu = ETFactory::make("menu");
     $this->defaultMenu->add("dashboard", "<a href='" . URL("admin/dashboard") . "'><i class='icon-dashboard'></i> " . T("Dashboard") . "</a>");
     $this->defaultMenu->add("settings", "<a href='" . URL("admin/settings") . "'><i class='icon-cog'></i> " . T("Forum Settings") . "</a>");
     $this->defaultMenu->add("appearance", "<a href='" . URL("admin/appearance") . "'><i class='icon-eye-open'></i> " . T("Appearance") . "</a>");
     $this->defaultMenu->add("channels", "<a href='" . URL("admin/channels") . "'><i class='icon-tags'></i> " . T("Channels") . "</a>");
     $this->defaultMenu->add("members", "<a href='" . URL("members") . "'><i class='icon-group'></i> " . T("Members") . "</a>");
     $this->defaultMenu->add("plugins", "<a href='" . URL("admin/plugins") . "'><i class='icon-puzzle-piece'></i> " . T("Plugins") . "</a>");
     $this->defaultMenu->highlight(ET::$controllerName);
     $this->menu->highlight(ET::$controllerName);
     // If new registrations require admin approval, add the 'unapproved' admin page with a count.
     if (C("esoTalk.registration.requireConfirmation") == "approval") {
         $count = ET::SQL()->select("COUNT(1)")->from("member")->where("confirmed", 0)->exec()->result();
         $this->menu->add("unapproved", "<a href='" . URL("admin/unapproved") . "'><i class='icon-lock'></i> " . T("Unapproved") . " <span class='badge'>" . $count . "</span></a>");
     }
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         $this->pushNavigation("admin", "administration", URL($this->selfURL));
     }
     $this->addJSFile("core/js/admin.js");
     $this->addCSSFile("core/skin/admin.css");
     $this->trigger("initAdmin", array($this->menu, $this->defaultMenu));
 }
 public function insertAttachments($attachments, $keys)
 {
     $inserts = array();
     foreach ($attachments as $id => $attachment) {
         $inserts[] = array_merge(array($id, $attachment["name"], $attachment["secret"]), array_values($keys));
     }
     ET::SQL()->insert("attachment")->setMultiple(array_merge(array("attachmentId", "filename", "secret"), array_keys($keys)), $inserts)->exec();
 }
Пример #5
0
 public function validateSlug($slug)
 {
     if (!strlen($slug)) {
         return "empty";
     }
     if (ET::SQL()->select("COUNT(pageId)")->from("page")->where("slug=:slug")->bind(":slug", $slug)->exec()->result() > 0) {
         return "channelSlugTaken";
     }
 }
Пример #6
0
 public function handler_conversationController_conversationIndexDefault($sender, &$conversation)
 {
     $sender->addCSSFile($this->resource("views.css"));
     if ($conversation["startMemberId"] == ET::$session->userId) {
         return;
     }
     $conversation["views"]++;
     ET::SQL()->update("conversation")->set("views", "views + 1", false)->where("conversationId", $conversation["conversationId"])->exec();
 }
 /**
  * Show a sheet containing a list of groups. Pretty simple, really!
  *
  * @return void
  */
 public function action_index()
 {
     ET::activityModel()->markNotificationsAsRead('unapproved');
     $sql = ET::SQL();
     $sql->where("confirmed", 0);
     $sql->orderBy("m.memberId desc");
     $members = ET::memberModel()->getWithSQL($sql);
     $this->data("members", $members);
     $this->render("admin/unapproved");
 }
Пример #8
0
 /**
  * 複数タグ文字列で検索
  * @param type $keys
  * @return type
  */
 public function getTagsIds($keys)
 {
     if (is_array($keys) && count($keys)) {
         // タグID,タグテキストの配列を取得
         $result = ET::SQL()->select("distinct tagsId")->from("tags")->where("tagText IN (:keys)")->bind(":keys", $keys)->exec()->allRows();
         $ids = array();
         if (count($result)) {
             foreach ($result as $r) {
                 $ids[] = $r["tagsId"];
             }
         }
         return $ids;
     }
 }
 /**
  * Show the administrator dashboard view.
  *
  * @return void
  */
 public function action_index()
 {
     $this->title = T("Dashboard");
     // Work out a UNIX timestamp of one week ago.
     $oneWeekAgo = time() - 60 * 60 * 24 * 7;
     // Create an array of statistics to show on the dashboard.
     $statistics = array("<a href='" . URL("members") . "'>" . T("Members") . "</a>" => number_format(ET::SQL()->select("COUNT(*)")->from("member")->exec()->result()), T("Conversations") => number_format(ET::SQL()->select("COUNT(*)")->from("conversation")->exec()->result()), T("Posts") => number_format(ET::SQL()->select("COUNT(*)")->from("post")->exec()->result()), T("New members in the past week") => number_format(ET::SQL()->select("COUNT(*)")->from("member")->where(":time<joinTime")->bind(":time", $oneWeekAgo)->exec()->result()), T("New conversations in the past week") => number_format(ET::SQL()->select("COUNT(*)")->from("conversation")->where(":time<startTime")->bind(":time", $oneWeekAgo)->exec()->result()), T("New posts in the past week") => number_format(ET::SQL()->select("COUNT(*)")->from("post")->where(":time<time")->bind(":time", $oneWeekAgo)->exec()->result()));
     // Determine if we should show the welcome sheet.
     if (!C("esoTalk.admin.welcomeShown")) {
         $this->data("showWelcomeSheet", true);
         ET::writeConfig(array("esoTalk.admin.welcomeShown" => true));
     }
     $this->data("statistics", $statistics);
     $this->render("admin/dashboard");
 }
 public function getReputationMembers()
 {
     $result = ET::SQL()->select("username")->select("memberId")->select("reputationPoints")->from("member")->orderBy("reputationPoints DESC")->exec()->allRows();
     //Assign ranks to all members based on reputation points
     $rank = 1;
     foreach ($result as $k => $v) {
         $results[$k]["rank"] = $rank;
         $results[$k]["avatar"] = avatar($v, "thumb");
         $results[$k]["username"] = $result[$k]["username"];
         $results[$k]["memberId"] = $result[$k]["memberId"];
         $results[$k]["reputationPoints"] = $result[$k]["reputationPoints"];
         $rank++;
     }
     return $results;
 }
Пример #11
0
 public function handler_postModel_getPostsAfter($sender, &$posts)
 {
     $postsById = array();
     foreach ($posts as &$post) {
         $postsById[$post["postId"]] =& $post;
         $post["likes"] = array();
     }
     if (!count($postsById)) {
         return;
     }
     $result = ET::SQL()->select("postId, m.memberId, m.email, username, avatarFormat")->from("like l")->from("member m", "m.memberId=l.memberId", "left")->where("postId IN (:ids)")->bind(":ids", array_keys($postsById))->exec();
     while ($row = $result->nextRow()) {
         $postsById[$row["postId"]]["likes"][$row["memberId"]] = array("memberId" => $row["memberId"], "username" => $row["username"], "email" => $row["email"], "avatarFormat" => $row["avatarFormat"]);
     }
 }
Пример #12
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);
     }
 }
 /**
  * Toggle the user's subscription to a channel.
  *
  * @param int $channelId The ID of the channel to toggle subscription to.
  * @return void
  */
 public function subscribe($channelId = "")
 {
     if (!ET::$session->user or !$this->validateToken()) {
         return;
     }
     // If we don't have permission to view this channel, don't proceed.
     if (!ET::channelModel()->hasPermission((int) $channelId, "view")) {
         return;
     }
     // Work out if we're already unsubscribed or not, and switch to the opposite of that.
     $unsubscribed = !ET::SQL()->select("unsubscribed")->from("member_channel")->where("memberId", ET::$session->userId)->where("channelId", (int) $channelId)->exec()->result();
     // Write to the database.
     ET::channelModel()->setStatus($channelId, ET::$session->userId, array("unsubscribed" => $unsubscribed));
     // Normally, redirect back to the channel list.
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         redirect(URL("channels"));
     }
     // Otherwise, set a JSON var.
     $this->json("unsubscribed", $unsubscribed);
     $this->render();
 }
Пример #14
0
 /**
  * Common initialization for all controllers, called on every page load. This will add basic user links to
  * the "user" menu, and add core JS files and language definitions.
  *
  * If this is overridden, parent::init() should be called to maintain consistency between controllers.
  *
  * @return void
  */
 public function init()
 {
     // Check for updates to the esoTalk software, but only if we're the root admin and we haven't checked in
     // a while.
     if (ET::$session->userId == C("esoTalk.rootAdmin") and C("esoTalk.admin.lastUpdateCheckTime") + C("esoTalk.updateCheckInterval") < time()) {
         ET::upgradeModel()->checkForUpdates();
     }
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         // If the user IS NOT logged in, add the 'login' and 'sign up' links to the bar.
         if (!ET::$session->user) {
             $this->addToMenu("user", "join", "<a href='" . URL("user/join?return=" . urlencode($this->selfURL)) . "' class='link-join'>" . T("Sign Up") . "</a>");
             $this->addToMenu("user", "login", "<a href='" . URL("user/login?return=" . urlencode($this->selfURL)) . "' class='link-login'>" . T("Log In") . "</a>");
         } else {
             $this->addToMenu("user", "user", "<a href='" . URL("member/me") . "'>" . avatar(ET::$session->user, "thumb") . name(ET::$session->user["username"]) . "</a>");
             $this->addToMenu("user", "settings", "<a href='" . URL("settings") . "' class='link-settings'>" . T("Settings") . "</a>");
             if (ET::$session->isAdmin()) {
                 $this->addToMenu("user", "administration", "<a href='" . URL("admin") . "' class='link-administration'>" . T("Administration") . "</a>");
             }
             $this->addToMenu("user", "logout", "<a href='" . URL("user/logout?token=" . ET::$session->token) . "' class='link-logout'>" . T("Log Out") . "</a>");
         }
         // Get the number of members currently online and add it as a statistic.
         if (C("esoTalk.members.visibleToGuests") or ET::$session->user) {
             $online = ET::SQL()->select("COUNT(*)")->from("member")->where("UNIX_TIMESTAMP()-:seconds<lastActionTime")->bind(":seconds", C("esoTalk.userOnlineExpire"))->exec()->result();
             $stat = Ts("statistic.online", "statistic.online.plural", number_format($online));
             $stat = "<a href='" . URL("members/online") . "' class='link-membersOnline'>{$stat}</a>";
             $this->addToMenu("statistics", "statistic-online", $stat);
         }
         $this->addToMenu("meta", "copyright", "<a href='http://esotalk.org/' target='_blank'>" . T("Powered by") . " esoTalk</a>");
         // Set up some default JavaScript files and language definitions.
         $this->addJSFile("core/js/lib/jquery.js", true);
         $this->addJSFile("core/js/lib/jquery.migrate.js", true);
         $this->addJSFile("core/js/lib/jquery.misc.js", true);
         $this->addJSFile("core/js/lib/jquery.history.js", true);
         $this->addJSFile("core/js/lib/jquery.scrollTo.js", true);
         $this->addJSFile("core/js/global.js", true);
         $this->addJSLanguage("message.ajaxRequestPending", "message.ajaxDisconnected", "Loading...", "Notifications");
         $this->addJSVar("notificationCheckInterval", C("esoTalk.notificationCheckInterval"));
     }
     $this->trigger("init");
 }
 /**
  * Return a JSON array of up to 50 members whose usernames match a given string. This data can be used to
  * create a list of members in an autocomplete menu.
  *
  * @param string $input The string to match member usernames against.
  * @return void
  */
 public function autocomplete($input = "")
 {
     // Force the response type to JSON.
     $this->responseType = RESPONSE_TYPE_JSON;
     // Don't do this for strings less than three characters for performance reasons.
     if (strlen($input) < 3) {
         return;
     }
     // Construct a query to fetch matching members.
     $results = ET::SQL()->select("'member' AS type")->select("memberId AS id")->select("username AS name")->select("avatarFormat")->select("email")->from("member")->where("username LIKE :username")->bind(":username", $input . "%")->orderBy("username")->limit(50)->exec()->allRows();
     // Loop through the results and generate avatar HTML for each one.
     foreach ($results as $k => $v) {
         $results[$k]["avatar"] = avatar($v, "thumb");
         unset($results[$k]["avatarFormat"]);
         unset($results[$k]["email"]);
         // Convert spaces in the member name to non-breaking spaces.
         // (Spaces aren't usually allowed in esoTalk usernames, so this is a bit of a "hack" for
         // certain esoTalk installations that do allow them.)
         $results[$k]["name"] = str_replace(" ", " ", $results[$k]["name"]);
     }
     $this->json("results", $results);
     $this->render();
 }
Пример #16
0
 /**
  * The "contributor" gambit callback. Applies a filter to fetch only conversations which contain posts
  * by a particular member.
  *
  * @see gambitUnread for parameter descriptions.
  */
 public static function gambitContributor(&$search, $term, $negate)
 {
     // Get the ID of the member.
     $term = trim(str_replace(" ", " ", substr($term, strlen(T("gambit.contributor:")))));
     // Allow the user to refer to themselves using the "myself" keyword.
     if ($term == T("gambit.myself")) {
         $term = (int) ET::$session->userId;
     }
     // Apply the condition.
     $sql = ET::SQL()->select("DISTINCT conversationId")->from("post")->where("memberId = :contributorId")->bind(":contributorId", (int) $term);
     $search->addIDFilter($sql, $negate);
 }
Пример #17
0
 public function markNotificationsAsRead($type = null, $conversationId = null)
 {
     $query = ET::SQL()->update("activity")->set("`read`", 1)->where("memberId=:memberId")->where("`read`=0")->bind(":memberId", ET::$session->userId);
     if ($type) {
         $query->where("type=:type")->bind(":type", $type);
     }
     if ($conversationId) {
         $query->where("conversationId=:conversationId")->bind(":conversationId", $conversationId);
     }
     $query->exec();
 }
 /**
  * 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));
     }
 }
Пример #19
0
 /**
  * Show a member profile with the statistics pane.
  *
  * @param string $member The member ID.
  * @return void
  */
 public function action_statistics($member = "")
 {
     // Set up the member profile page.
     if (!($member = $this->profile($member, "statistics"))) {
         return;
     }
     // Fetch statistics about the member's posts (when they first posted, and how many different conversations
     // they've participated in.)
     $statistics = ET::SQL()->select("MIN(time)", "firstPosted")->select("COUNT(DISTINCT conversationId)", "conversationsParticipated")->from("post")->where("memberId", $member["memberId"])->exec()->firstRow();
     // Add a few more statistics (their post count, conversation count, and join time.)
     $statistics["postCount"] = $member["countPosts"];
     $statistics["conversationsStarted"] = $member["countConversations"];
     $statistics["joinTime"] = $member["joinTime"];
     // Send it off to the view.
     $this->data("statistics", $statistics);
     $this->renderProfile("member/statistics");
 }
 /**
  * Get post data so it can be used to construct a quote of a post.
  *
  * @param int $postId The ID of the post.
  * @param int $conversationId The ID of the conversation that the post is in.
  * @return array An array containing the username and the post content.
  */
 protected function getPostForQuoting($postId, $conversationId)
 {
     $result = ET::SQL()->select("username, content")->from("post p")->from("member m", "m.memberId=p.memberId", "inner")->where("p.postId=:postId")->where("p.conversationId=:conversationId")->bind(":postId", $postId)->bind(":conversationId", $conversationId)->exec();
     if (!$result->numRows()) {
         return false;
     }
     $result = $result->firstRow();
     // Convert spaces in the member name to non-breaking spaces.
     // (Spaces aren't usually allowed in esoTalk usernames, so this is a bit of a "hack" for
     // certain esoTalk installations that do allow them.)
     $result["username"] = str_replace(" ", " ", $result["username"]);
     return $result;
 }
Пример #21
0
 /**
  * Delete a member with the specified ID, along with all of their associated records.
  *
  * @param int $memberId The ID of the member to delete.
  * @param bool $deletePosts Whether or not to mark the member's posts as deleted.
  * @return void
  */
 public function deleteById($memberId, $deletePosts = false)
 {
     // Delete the member's posts if necessary.
     if ($deletePosts) {
         ET::SQL()->update("post")->set("deleteMemberId", ET::$session->userId)->set("deleteTime", time())->where("memberId", $memberId)->exec();
     }
     // Delete member and other records associated with the member.
     ET::SQL()->delete()->from("member")->where("memberId", $memberId)->exec();
     ET::SQL()->delete()->from("member_channel")->where("memberId", $memberId)->exec();
     ET::SQL()->delete()->from("member_conversation")->where("id", $memberId)->where("type", "member")->exec();
     ET::SQL()->delete()->from("member_group")->where("memberId", $memberId)->exec();
 }
Пример #22
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);
     }
 }
Пример #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
 /**
  * The "contributor" gambit callback. Applies a filter to fetch only conversations which contain posts
  * by a particular member.
  *
  * @see gambitUnread for parameter descriptions.
  */
 public static function gambitContributor(&$search, $term, $negate)
 {
     // Get the name of the member.
     $term = trim(substr($term, strlen(T("gambit.contributor:"))));
     // If the user is referring to themselves, then we already have their member ID.
     if ($term == T("gambit.myself")) {
         $q = (int) ET::$session->userId;
     } else {
         $q = "(" . ET::SQL()->select("memberId")->from("member")->where("username=:username")->bind(":username", $term)->get() . ")";
     }
     // Apply the condition.
     $sql = ET::SQL()->select("DISTINCT conversationId")->from("post")->where("memberId={$q}");
     $search->addIDFilter($sql, $negate);
 }
 /**
  * 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();
     }
 }
Пример #26
0
 public function handler_conversationModel_setDraftAfter($sender, $conversation, $memberId, $draft)
 {
     // If we're discarding the draft, delete all relevant attachments from the database.
     if ($draft === null) {
         ET::SQL()->delete()->from("attachment")->where("draftMemberId", ET::$session->userId)->where("draftConversationId", $conversation["conversationId"])->exec();
         // TODO: delete them from the filesystem as well.
     } else {
         $model = ET::getInstance("attachmentModel");
         $attachments = $model->extractFromSession(ET::$controller->controllerMethod == "start" ? "c0" : "c" . $conversation["conversationId"]);
         if (!empty($attachments)) {
             $model->insertAttachments($attachments, array("draftMemberId" => $memberId, "draftConversationId" => $conversation["conversationId"]));
         }
     }
 }
 /**
  * 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");
 }
Пример #28
0
 /**
  * Return a JSON array of up to 50 members whose usernames match a given string. This data can be used to
  * create a list of members in an autocomplete menu.
  *
  * @param string $input The string to match member usernames against.
  * @return void
  */
 public function autocomplete($input = "")
 {
     // Force the response type to JSON.
     $this->responseType = RESPONSE_TYPE_JSON;
     // Don't do this for strings less than three characters for performance reasons.
     if (strlen($input) < 3) {
         return;
     }
     // Construct a query to fetch matching members.
     $results = ET::SQL()->select("'member' AS type")->select("memberId AS id")->select("username AS name")->select("avatarFormat")->from("member")->where("username LIKE :username")->bind(":username", $input . "%")->orderBy("username")->limit(50)->exec()->allRows();
     // Loop through the results and generate avatar HTML for each one.
     foreach ($results as $k => $v) {
         $results[$k]["avatar"] = avatar($v["id"], $v["avatarFormat"], "thumb");
         unset($results[$k]["avatarFormat"]);
     }
     $this->json("results", $results);
     $this->render();
 }
Пример #29
0
 protected function fulltextQuery($search)
 {
     return ET::SQL()->from("attachment")->where("filename LIKE :search")->bind(":search", "%" . $search . "%");
 }
Пример #30
0
 /**
  * Fetch record(s) from the model's table.
  *
  * @param array $wheres An array of where conditions to match.
  * @return array A multi-dimensional array of matching rows.
  */
 public function get($wheres = array())
 {
     return ET::SQL()->select("*")->from($this->table)->where($wheres)->exec()->allRows();
 }