/** * 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(); }
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(); }
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"; } }
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"); }
/** * 複数タグ文字列で検索 * @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; }
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"]); } }
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(); }
/** * 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(); }
/** * 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); }
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)); } }
/** * 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; }
/** * 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(); }
/** * 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); } }
/** * 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; }
/** * 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(); } }
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"); }
/** * 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(); }
protected function fulltextQuery($search) { return ET::SQL()->from("attachment")->where("filename LIKE :search")->bind(":search", "%" . $search . "%"); }
/** * 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(); }