diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b473f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +featherbb/config.php +.idea/ +nbproject/ \ No newline at end of file diff --git a/bootstrap.php b/bootstrap.php new file mode 100644 index 0000000..65b08d1 --- /dev/null +++ b/bootstrap.php @@ -0,0 +1,5 @@ +feather = \Slim\Slim::getInstance(); + $this->request = $this->feather->request; + $this->model = new \FeatherBB\Plugins\Model\PrivateMessages(); + load_textdomain('private_messages', dirname(dirname(__FILE__)).'/lang/'.$this->feather->user->language.'/private-messages.mo'); + load_textdomain('featherbb', $this->feather->forum_env['FEATHER_ROOT'].'featherbb/lang/'.$this->feather->user->language.'/misc.mo'); + $this->feather->template->addTemplatesDirectory(dirname(dirname(__FILE__)).'/Views', 5)->setPageInfo(['active_page' => 'navextra1']); + $this->crumbs =array( + $this->feather->urlFor('Conversations.home') => __('PMS', 'private_messages') + ); + } + + public function index($fid = 2, $page = 1) + { + // Set default page to "Inbox" folder + $fid = !empty($fid) ? intval($fid) : 2; + $uid = intval($this->feather->user->id); + + if ($action = $this->request->post('action')) { + switch ($action) { + case 'move': + $this->move(); + break; + case 'delete': + $this->delete(); + break; + case 'read': + $this->markRead(); + break; + case 'unread': + $this->markRead(0); + break; + default: + Url::redirect($this->feather->urlFor('Conversations.home', ['inbox_id' => $this->request->post('inbox_id')])); + break; + } + } + + if ($this->inboxes = $this->model->getInboxes($this->feather->user->id)) { + if (!in_array($fid, array_keys($this->inboxes))) { + throw new Error(__('Wrong folder owner', 'private_messages'), 403); + } + } + // Page data + $num_pages = ceil($this->inboxes[$fid]['nb_msg'] / $this->feather->user['disp_topics']); + $p = (!isset($page) || $page <= 1 || $page > $num_pages) ? 1 : intval($page); + $start_from = $this->feather->user['disp_topics'] * ($p - 1); + $paging_links = Url::paginate($num_pages, $p, $this->feather->urlFor('Conversations.home', ['id' => $fid]).'/#'); + + // Make breadcrumbs + $this->crumbs[$this->feather->urlFor('Conversations.home', ['inbox_id' => $fid])] = $this->inboxes[$fid]['name']; + $this->crumbs[] = __('My conversations', 'private_messages'); + Utils::generateBreadcrumbs($this->crumbs, array( + 'link' => $this->feather->urlFor('Conversations.send'), + 'text' => __('Send', 'private_messages') + )); + + $this->generateMenu($this->inboxes[$fid]['name']); + $this->feather->template->addAsset('js', 'style/imports/common.js', array('type' => 'text/javascript')); + $this->feather->template + ->setPageInfo(array( + 'title' => array(Utils::escape($this->feather->config['o_board_title']), __('PMS', 'private_messages'), $this->inboxes[$fid]['name']), + 'admin_console' => true, + 'inboxes' => $this->inboxes, + 'current_inbox_id' => $fid, + 'paging_links' => $paging_links, + 'rightLink' => ['link' => $this->feather->urlFor('Conversations.send'), 'text' => __('Send', 'private_messages')], + 'conversations' => $this->model->getConversations($fid, $uid, $this->feather->user['disp_topics'], $start_from) + ) + ) + ->addTemplate('index.php')->display(); + } + + public function delete() + { + if (!$this->request->post('topics')) + throw new Error(__('No conv selected', 'private_messages'), 403); + + $topics = $this->request->post('topics') && is_array($this->request->post('topics')) ? array_map('intval', $this->request->post('topics')) : array_map('intval', explode(',', $this->request->post('topics'))); + + if (empty($topics)) + throw new Error(__('No conv selected', 'private_messages'), 403); + + if ( $this->request->post('delete_comply') ) + { + $uid = intval($this->feather->user->id); + $this->model->delete($topics, $uid); + + Url::redirect($this->feather->urlFor('Conversations.home'), __('Conversations deleted', 'private_messages')); + } + else + { + // Display confirm delete form + $this->feather->template + ->setPageInfo(array( + 'title' => array(Utils::escape($this->feather->config['o_board_title']), __('PMS', 'private_messages')), + 'topics' => $topics, + ) + ) + ->addTemplate('delete.php')->display(); + } + die(); + } + + public function move() + { + if (!$this->request->post('topics')) + throw new Error(__('No conv selected', 'private_messages'), 403); + + $topics = $this->request->post('topics') && is_array($this->request->post('topics')) ? array_map('intval', $this->request->post('topics')) : array_map('intval', explode(',', $this->request->post('topics'))); + + if (empty($topics)) + throw new Error(__('No conv selected', 'private_messages'), 403); + + $uid = intval($this->feather->user->id); + + if ( $this->request->post('move_comply') ) + { + $move_to = $this->request->post('move_to') ? intval($this->request->post('move_to')) : 2; + + if ( $this->model->move($topics, $move_to, $uid) ) { + Url::redirect($this->feather->urlFor('Conversations.home', ['inbox_id' => $move_to]), __('Conversations moved', 'private_messages')); + } else { + throw new Error(__('Error Move', 'private_messages'), 403); + } + } + + // Display move form + if ($inboxes = $this->model->getUserFolders($uid)) { + $this->feather->template + ->setPageInfo(array( + 'title' => array(Utils::escape($this->feather->config['o_board_title']), __('PMS', 'private_messages')), + 'topics' => $topics, + 'inboxes' => $inboxes, + ) + ) + ->addTemplate('move.php')->display(); + } else { + throw new Error('No inboxes', 404); + } + + die(); + } + + public function markRead($read = true) + { + $viewed = ($read == true) ? '1' : '0'; + + if (!$this->request->post('topics')) + throw new Error(__('No conv selected', 'private_messages'), 403); + + $topics = $this->request->post('topics') && is_array($this->request->post('topics')) ? array_map('intval', $this->request->post('topics')) : array_map('intval', explode(',', $this->request->post('topics'))); + + if (empty($topics)) + throw new Error(__('No conv selected', 'private_messages'), 403); + + $this->model->updateConversation($topics, $this->feather->user->id, ['viewed' => $viewed]); + + Url::redirect($this->feather->urlFor('Conversations.home', ['inbox_id' => $this->request->post('inbox_id')])); + } + + public function send($uid = null, $conv_id = null) + { + if ($this->feather->request->isPost()) { + // First raw validation + $data = array_merge(array( + 'username' => null, + 'subject' => null, + 'message' => null, + 'smilies' => 0, + 'preview' => null, + ), $this->feather->request->post()); + $data = array_map(array('FeatherBB\Core\Utils', 'trim'), $data); + + $conv = false; + if (!is_null($conv_id)) { + if ($conv_id < 1) { + throw new Error('Wrong conversation ID', 400); + } + if (!$conv = $this->model->getConversation($conv_id, $this->feather->user->id)) { + throw new Error('Unknown conversation ID', 400); + } + } + + // Preview message + if ($this->feather->request->post('preview')) { + // Make breadcrumbs + $this->crumbs[] = __('Reply', 'private_messages'); + $this->crumbs[] = __('Preview'); + Utils::generateBreadcrumbs($this->crumbs); + + $this->feather->hooks->fire('conversationsPlugin.send.preview'); + $msg = $this->feather->parser->parse_message($data['req_message'], $data['smilies']); + $this->feather->template->setPageInfo(array( + 'parsed_message' => $msg, + 'username' => Utils::escape($data['username']), + 'subject' => Utils::escape($data['subject']), + 'message' => Utils::escape($data['req_message']) + ))->addTemplate('send.php')->display(); + } else { + // Prevent flood + if (!is_null($data['preview']) && $this->feather->user['last_post'] != '' && ($this->feather->now - $this->feather->user['last_post']) < $this->feather->prefs->get($this->feather->user, 'post.min_interval')) { + throw new Error(sprintf(__('Flood start'), $this->feather->prefs->get($this->feather->user, 'post.min_interval'), $this->feather->prefs->get($this->feather->user, 'post.min_interval') - ($this->feather->now - $this->feather->user['last_post'])), 429); + } + + if (!$conv) { + // Validate username / TODO : allow multiple usernames + if (!$user = $this->model->isAllowed($data['username'])) { + throw new Error('You can\'t send an PM to '.($data['username'] ? $data['username'] : 'nobody'), 400); + } + + // Avoid self messages + if ($user->id == $this->feather->user->id) { + throw new Error('No self message', 403); + } + + // Validate subject + if ($this->feather->forum_settings['o_censoring'] == '1') + $data['subject'] = Utils::trim(Utils::censor($data['subject'])); + if (empty($data['subject'])) { + throw new Error('No subject or censored subject', 400); + } else if (Utils::strlen($data['subject']) > 70) { + throw new Error('Too long subject', 400); + } else if ($this->feather->forum_settings['p_subject_all_caps'] == '0' && Utils::is_all_uppercase($data['subject']) && !$this->feather->user->is_admmod) { + throw new Error('All caps subject forbidden', 400); + } + } + + // TODO : inbox full + + // Validate message + if ($this->feather->forum_settings['o_censoring'] == '1') + $data['req_message'] = Utils::trim(Utils::censor($data['req_message'])); + if (empty($data['req_message'])) { + throw new Error('No message or censored message', 400); + } else if (Utils::strlen($data['req_message']) > $this->feather->forum_env['FEATHER_MAX_POSTSIZE']) { + throw new Error('Too long message', 400); + } else if ($this->feather->forum_settings['p_subject_all_caps'] == '0' && Utils::is_all_uppercase($data['subject']) && !$this->feather->user->is_admmod) { + throw new Error('All caps message forbidden', 400); + } + + // Send ... TODO : when perms will be ready + // Check if the receiver has the PM enabled + // Check if he has reached his max limit of PM + // Block feature ? + + if (!$conv) { + $conv_data = array( + 'subject' => $data['subject'], + 'poster' => $this->feather->user->username, + 'poster_id' => $this->feather->user->id, + 'num_replies' => 0, + 'last_post' => $this->feather->now, + 'last_poster' => $this->feather->user->username); + $conv_id = $this->model->addConversation($conv_data); + } + if ($conv_id) { + $msg_data = array( + 'poster' => $this->feather->user->username, + 'poster_id' => $this->feather->user->id, + 'poster_ip' => $this->feather->request->getIp(), + 'message' => $data['req_message'], + 'hide_smilies' => $data['smilies'], + 'sent' => $this->feather->now, + ); + if ($conv) { + // Reply to an existing conversation + if ($msg_id = $this->model->addMessage($msg_data, $conv_id)) { + Url::redirect($this->feather->urlFor('Conversations.home'), sprintf(__('Reply success', 'private_messages'), $conv->subject)); + } + } else { + // Add message in conversation + add receiver (create new conversation) + if ($msg_id = $this->model->addMessage($msg_data, $conv_id, array($user->id, $this->feather->user->id))) { + Url::redirect($this->feather->urlFor('Conversations.home'), sprintf(__('Send success', 'private_messages'), $user->username)); + } + } + } else { + throw new Error('Unable to create conversation'); + } + } + } else { + $this->feather->hooks->fire('conversationsPlugin.send.display'); + // New conversation + if (!is_null($uid)) { + if ($uid < 2) { + throw new Error('Wrong user ID', 400); + } + if ($user = $this->model->getUserByID($uid)) { + $this->feather->template->setPageInfo(array('username' => Utils::escape($user->username))); + } else { + throw new Error('Unable to find user', 400); + } + } + // Reply + if (!is_null($conv_id)) { + if ($conv_id < 1) { + throw new Error('Wrong conversation ID', 400); + } + if ($conv = $this->model->getConversation($conv_id, $this->feather->user->id)) { + $inbox = DB::for_table('pms_folders')->find_one($conv->folder_id); + $this->crumbs[$this->feather->urlFor('Conversations.home', ['inbox_id' => $inbox['id']])] = $inbox['name']; + $this->crumbs[] = __('Reply', 'private_messages'); + $this->crumbs[] = $conv['subject']; + Utils::generateBreadcrumbs($this->crumbs); + return $this->feather->template->setPageInfo(array( + 'current_inbox' => $inbox, + 'conv' => $conv, + 'msg_data' => $this->model->getMessagesFromConversation($conv_id, $this->feather->user->id, 5) + ))->addTemplate('reply.php')->display(); + } else { + throw new Error('Unknown conversation ID', 400); + } + } + $this->crumbs[] = __('Send', 'private_messages'); + if(isset($user)) $this->crumbs[] = $user->username; + Utils::generateBreadcrumbs($this->crumbs); + $this->feather->template->addTemplate('send.php')->display(); + } + } + + public function reply($conv_id = null) + { + return $this->send(null, $conv_id); + } + + public function show($conv_id = null, $page = null) + { + // First checks + if ($conv_id < 1) { + throw new Error('Wrong conversation ID', 400); + } + if (!$conv = $this->model->getConversation($conv_id, $this->feather->user->id)) { + throw new Error('Unknown conversation ID', 404); + } else if ($this->model->isDeleted($conv_id, $this->feather->user->id)) { + throw new Error('The conversation has been deleted', 404); + } + + // Set conversation as viewed + if ($conv['viewed'] == 0) { + if (!$this->model->setViewed($conv_id, $this->feather->user->id)) { + throw new Error('Unable to set conversation as viewed', 500); + } + } + + $num_pages = ceil($conv['num_replies'] / $this->feather->user['disp_topics']); + $p = (!is_null($page) || $page <= 1 || $page > $num_pages) ? 1 : intval($page); + $start_from = $this->feather->user['disp_topics'] * ($p - 1); + $paging_links = Url::paginate($num_pages, $p, $this->feather->urlFor('Conversations.show', ['tid' => $conv_id]).'/#'); + + $this->inboxes = $this->model->getInboxes($this->feather->user->id); + + $this->crumbs[$this->feather->urlFor('Conversations.home', ['inbox_id' => $conv['folder_id']])] = $this->inboxes[$conv['folder_id']]['name']; + $this->crumbs[] = __('My conversations', 'private_messages'); + $this->crumbs[] = $conv['subject']; + Utils::generateBreadcrumbs($this->crumbs, array( + 'link' => $this->feather->urlFor('Conversations.reply', ['tid' => $conv['id']]), + 'text' => __('Reply', 'private_messages') + )); + $this->generateMenu($this->inboxes[$conv['folder_id']]['name']); + $this->feather->template + ->setPageInfo(array( + 'title' => array(Utils::escape($this->feather->config['o_board_title']), __('PMS', 'private_messages'), $this->model->getUserFolders($this->feather->user->id)[$conv['folder_id']]['name'], Utils::escape($conv['subject'])), + 'admin_console' => true, + 'paging_links' => $paging_links, + 'start_from' => $start_from, + 'cur_conv' => $conv, + 'rightLink' => ['link' => $this->feather->urlFor('Conversations.reply', ['tid' => $conv['id']]), 'text' => __('Reply', 'private_messages')], + 'messages' => $this->model->getMessages($conv['id'], $this->feather->user['disp_topics'], $start_from) + ) + ) + ->addTemplate('show.php')->display(); + } + + public function blocked() + { + $errors = array(); + + $username = $this->request->post('req_username') ? Utils::trim(Utils::escape($this->request->post('req_username'))) : ''; + if ($this->request->post('add_block')) + { + if ($username == $this->feather->user->username) + $errors[] = __('No block self', 'private_messages'); + + if (!($user_infos = $this->model->getUserByName($username)) || $username == __('Guest')) + $errors[] = sprintf(__('No user name message', 'private_messages'), Utils::escape($username)); + + if (empty($errors)) + { + if ($user_infos->group_id == $this->feather->forum_env['FEATHER_ADMIN']) + $errors[] = sprintf(__('User is admin', 'private_messages'), Utils::escape($username)); + elseif ($user_infos->group_id == $this->feather->forum_env['FEATHER_MOD']) + $errors[] = sprintf(__('User is mod', 'private_messages'), Utils::escape($username)); + + if ($this->model->checkBlock($this->feather->user->id, $user_infos->id)) + $errors[] = sprintf(__('Already blocked', 'private_messages'), Utils::escape($username)); + } + + if (empty($errors)) + { + $insert = array( + 'user_id' => $this->feather->user->id, + 'block_id' => $user_infos->id, + ); + + $this->model->addBlock($insert); + Url::redirect($this->feather->urlFor('Conversations.blocked'), __('Block added', 'private_messages')); + } + } + else if ($this->request->post('remove_block')) + { + $id = intval(key($this->request->post('remove_block'))); + // Before we do anything, check we blocked this user + if (!$this->model->checkBlock(intval($this->feather->user->id), $id)) + throw new Error(__('No permission'), 403); + + $this->model->removeBlock(intval($this->feather->user->id), $id); + Url::redirect($this->feather->urlFor('Conversations.blocked'), __('Block removed', 'private_messages')); + } + + Utils::generateBreadcrumbs(array( + $this->feather->urlFor('Conversations.home') => __('PMS', 'private_messages'), + __('Options'), + __('Blocked Users', 'private_messages') + )); + + $this->generateMenu('blocked'); + $this->feather->template + ->setPageInfo(array( + 'title' => array(Utils::escape($this->feather->config['o_board_title']), __('PMS', 'private_messages'), __('Blocked Users', 'private_messages')), + 'admin_console' => true, + 'errors' => $errors, + 'username' => $username, + 'required_fields' => array('req_username' => __('Add block', 'private_messages')), + 'blocks' => $this->model->getBlocked($this->feather->user->id), + ) + ) + ->addTemplate('blocked.php')->display(); + } + + public function folders() + { + $errors = array(); + + if ($this->request->post('add_folder')) + { + $folder = $this->request->post('req_folder') ? Utils::trim(Utils::escape($this->request->post('req_folder'))) : ''; + + if ($folder == '') + $errors[] = __('No folder name', 'private_messages'); + else if (Utils::strlen($folder) < 4) + $errors[] = __('Folder too short', 'private_messages'); + else if (Utils::strlen($folder) > 30) + $errors[] = __('Folder too long', 'private_messages'); + else if ($this->feather->forum_settings['o_censoring'] == '1' && Utils::censor($folder) == '') + $errors[] = __('No folder after censoring', 'private_messages'); + + // TODO: Check perms when ready + // $data = array( + // ':uid' => $panther_user['id'], + // ); + // + // if ($panther_user['g_pm_folder_limit'] != 0) + // { + // $ps = $db->select('folders', 'COUNT(id)', $data, 'user_id=:uid'); + // $num_folders = $ps->fetchColumn(); + // + // if ($num_folders >= $panther_user['g_pm_folder_limit']) + // $errors[] = sprintf($lang_pm['Folder limit'], $panther_user['g_pm_folder_limit']); + // } + + if (empty($errors)) + { + $insert = array( + 'user_id' => $this->feather->user->id, + 'name' => $folder + ); + + $this->model->addFolder($insert); + Url::redirect($this->feather->urlFor('Conversations.folders'), __('Folder added', 'private_messages')); + } + } + else if ($this->request->post('update_folder')) + { + $id = intval(key($this->request->post('update_folder'))); + var_dump($id); + + $errors = array(); + $folder = Utils::trim($this->request->post('folder')[$id]); + + if ($folder == '') + $errors[] = __('No folder name', 'private_messages'); + else if (Utils::strlen($folder) < 4) + $errors[] = __('Folder too short', 'private_messages'); + else if (Utils::strlen($folder) > 30) + $errors[] = __('Folder too long', 'private_messages'); + else if ($this->feather->forum_settings['o_censoring'] == '1' && Utils::censor($folder) == '') + $errors[] = __('No folder after censoring', 'private_messages'); + + if (empty($errors)) + { + $update = array( + 'name' => $folder, + ); + + if ($this->model->updateFolder($this->feather->user->id, $id, $update)) + Url::redirect($this->feather->urlFor('Conversations.folders'), __('Folder updated', 'private_messages')); + else + throw new Error(__('Error'), 403); + } + } + else if ($this->request->post('remove_folder')) + { + $id = intval(key($this->request->post('remove_folder'))); + // Before we do anything, check we blocked this user + if (!$this->model->checkFolderOwner($id, intval($this->feather->user->id))) + throw new Error(__('No permission'), 403); + + if ($this->model->removeFolder($this->feather->user->id, $id)) + Url::redirect($this->feather->urlFor('Conversations.folders'), __('Folder removed', 'private_messages')); + else + throw new Error(__('Error'), 403); + } + + Utils::generateBreadcrumbs(array( + $this->feather->urlFor('Conversations.home') => __('PMS', 'private_messages'), + __('Options'), + __('My Folders', 'private_messages') + )); + + $this->generateMenu('folders'); + $this->feather->template + ->setPageInfo(array( + 'title' => array(Utils::escape($this->feather->config['o_board_title']), __('PMS', 'private_messages'), __('Blocked Users', 'private_messages')), + 'admin_console' => true, + 'errors' => $errors + ) + ) + ->addTemplate('folders.php')->display(); + } + + public function generateMenu($page = '') + { + if (!isset($this->inboxes)) + $this->inboxes = $this->model->getInboxes($this->feather->user->id); + + $this->feather->template->setPageInfo(array( + 'page' => $page, + 'inboxes' => $this->inboxes, + ), 1 + )->addTemplate('menu.php'); + return $this->inboxes; + } + +} diff --git a/src/Model/PrivateMessages.php b/src/Model/PrivateMessages.php new file mode 100644 index 0000000..4a8ddec --- /dev/null +++ b/src/Model/PrivateMessages.php @@ -0,0 +1,468 @@ +feather = \Slim\Slim::getInstance(); + $this->config = $this->feather->config; + $this->user = $this->feather->user; + $this->request = $this->feather->request; + $this->hooks = $this->feather->hooks; + } + + // Get all inboxes owned by a user + public function getUserFolders($uid) + { + $result = DB::for_table('pms_folders') + ->select('name') + ->select('id') + ->where_any_is([ + ['user_id' => $uid], + ['user_id' => 1] + ]) + ->find_array(); + + $output = false; + foreach($result as $inbox) { + $output[(int) $inbox['id']] = array('name' => $inbox['name']); + } + return $output; + } + + public function getInboxes($uid) + { + if ($inboxes = $this->getUserFolders($uid)) { + foreach ($inboxes as $iid => $data) { + $inboxes[$iid]['nb_msg'] = $this->countMessages($iid, $uid); + } + } else { + throw new Error('No inbox', 404); + } + return $inboxes; + } + + // Check if current user owns the folder + public function checkFolderOwner($fid, $uid) + { + return DB::for_table('pms_folders') + ->select('name') + ->select('id') + ->where_any_is([ + ['user_id' => $uid], + ['user_id' => 1] + ]) + ->where('id' , $fid) + ->find_one(); + } + + // Get messages count from context + public function countMessages($fid, $uid) + { + $where[]['d.folder_id'] = $fid; + if ($fid == 1) + $where[]['d.viewed'] = '0'; + $result = DB::for_table('pms_conversations') + ->select('id') + ->table_alias('c') + ->inner_join('pms_data', array('c.id', '=', 'd.conversation_id'), 'd') + ->where('d.user_id', $uid) + ->where('d.deleted', 0) + ->where_any_is($where); + return $result->count(); + } + + // Get unread messages count for navbar + public static function countUnread($uid) + { + $result = DB::for_table('pms_data') + ->select('id') + ->where('user_id', $uid) + ->where('viewed', 0); + return $result->count(); + } + + public function getConversations($inboxes = null, $uid = null, $limit = 50, $start = 0) + { + $inboxes = (array) $inboxes; + $where = array(); + foreach($inboxes as $id => $inbox_id) { + $where[]['d.folder_id'] = (int) $inbox_id; + if ($inbox_id == 1) $where[]['d.viewed'] = '0'; + } + + $select = array( + 'c.id', + 'c.subject', + 'c.poster', + 'c.poster_id', + 'poster_gid' => 'u.group_id', 'u.email', + 'c.num_replies', + 'd.viewed', + 'c.last_post', + 'c.last_poster', + 'last_poster_id' => 'u2.id', + 'last_poster_gid' => 'u2.group_id', + 'c.last_post_id', + ); + $result = DB::for_table('pms_conversations') + ->select_many($select) + ->table_alias('c') + ->inner_join('pms_data', array('c.id', '=', 'd.conversation_id'), 'd') + ->left_outer_join('users', array('u.id', '=', 'c.poster_id'), 'u') + ->left_outer_join('users', array('u2.username', '=', 'c.last_poster'), 'u2', true) + ->where('d.user_id', $uid) + ->where('d.deleted', 0) + ->where_any_is($where) + ->order_by_desc('c.last_post') + ->limit($limit) + ->offset($start) + ->find_array(); + + foreach($result as $key => $conversation) { + $receivers = DB::for_table('pms_data') + ->table_alias('d') + ->select(array('d.user_id', 'u.username')) + ->left_outer_join('users', array('u.id', '=', 'd.user_id'), 'u') + ->where('d.conversation_id', $conversation['id']) + ->find_array(); + if (is_array($receivers)) { + foreach ($receivers as $receiver) { + $result[$key]['receivers'][$receiver['user_id']] = $receiver['username']; + } + } + } + return $result; + } + + // Delete one or more messages + public function delete($convers, $uid) + { + // Get the number of conversation messages and the number of replies from all conversations + $numConvers = count($convers); + $numReplies = DB::for_table('pms_conversations') + ->table_alias('c') + ->select('c.num_replies') + ->inner_join('pms_data', array('c.id', '=', 'cd.conversation_id'), 'cd') + // ->inner_join('pms_data', array('cd.user_id', '=', $uid), null, true) + ->where('cd.user_id', $uid) + ->where_in('c.id', $convers) + ->sum('c.num_replies'); + $numPms = ($numReplies + $numConvers); + + // Soft delete messages + DB::configure('id_column', array('conversation_id', 'user_id')); + DB::for_table('pms_data') + ->where('user_id', $uid) + ->where_in('conversation_id', $convers) + ->find_result_set() + ->set('deleted', 1) + ->save(); + + // Now check if anyone left in the conversation has any of these topics undeleted. If so, then we leave them. Otherwise, actually delete them. + foreach ($convers as $cid) + { + $left = DB::for_table('pms_data') + ->where('conversation_id', $cid) + ->where('deleted', 0); + + if ($left->count()) { // People are still left + continue; + } + + DB::for_table('pms_data')->where('conversation_id', $cid)->delete_many(); + DB::for_table('pms_messages')->where('conversation_id', $cid)->delete_many(); + DB::for_table('pms_conversations')->where('id', $cid)->delete_many(); + + } + } + + public function move($convers, $move_to, $uid) + { + if (!$this->checkFolderOwner($move_to, $uid)) { + throw new Error(__('Wrong folder owner', 'private_messages'), 403); + } + + DB::configure('id_column', array('conversation_id', 'user_id')); + + return DB::for_table('pms_data') + ->where('user_id', $uid) + ->where_in('conversation_id', $convers) + ->find_result_set() + ->set('folder_id', $move_to) + ->save(); + } + + // Mark a conversation as (un)read (default to true) + public function setViewed($conv_id, $uid, $viewed = 1) + { + DB::configure('id_column', array('conversation_id', 'user_id')); + + return DB::for_table('pms_data') + ->where('conversation_id', $conv_id) + ->where('user_id', $uid) + ->find_one() + ->set('viewed', $viewed) + ->save(); + } + + public function updateConversation($conv_ids, $uid, array $data) + { + DB::configure('id_column', array('conversation_id', 'user_id')); + + $conv_ids = (array) $conv_ids; + return DB::for_table('pms_data') + ->where('user_id', $uid) + ->where_in('conversation_id', $conv_ids) + ->find_result_set() + ->set($data) + ->save(); + } + + public function addConversation(array $data = array()) + { + $result = DB::for_table('pms_conversations') + ->create() + ->set($data); + $result->save(); + return $result->id(); + } + + + // Return false if the conv doesn't exist or if the user has no rights to access it + public function getConversation($conv_id = null, $uid = null) + { + $select = array( + 'c.id', + 'c.subject', + 'c.poster', + 'c.poster_id', + 'poster_gid' => 'u.group_id', 'u.email', + 'c.num_replies', + 'd.viewed', + 'c.last_post', + 'c.last_poster', + 'last_poster_id' => 'u2.id', + 'last_poster_gid' => 'u2.group_id', + 'c.last_post_id', + 'c.first_post_id', + 'd.folder_id' + ); + + $result = DB::for_table('pms_conversations') + ->select_many($select) + ->table_alias('c') + ->inner_join('pms_data', array('c.id', '=', 'd.conversation_id'), 'd') + ->left_outer_join('users', array('u.id', '=', 'c.poster_id'), 'u') + ->left_outer_join('users', array('u2.username', '=', 'c.last_poster'), 'u2', true) + ->where_any_is(array(array('c.poster_id' => $uid), + array('d.user_id' => $uid))) + ->where('c.id', $conv_id) + ->find_one(); + + return $result; + } + + public function addMessage(array $data = array(), $conv_id = null, array $uid = array()) + { + $add = DB::for_table('pms_messages') + ->create() + ->set($data) + ->set('conversation_id', $conv_id); + $add->save(); + + $update_data = ['last_post_id' => $add->id()]; + // If it is a new conversation: + if (!empty($uid)) $update_data['first_post_id'] = $add->id(); + $update = DB::for_table('pms_conversations') + ->find_one($conv_id) + ->set($update_data); + // Increment replies count + if(empty($uid)) $update->set_expr('num_replies', 'num_replies+1'); + $update = $update->save(); + + DB::configure('id_column', array('conversation_id', 'user_id')); + + if (!empty($uid)) { + // New conversation + foreach ($uid as $user) { + $notifs = DB::for_table('pms_data') + ->create() + ->set(array( + 'conversation_id' => $conv_id, + 'user_id' => $user, + 'viewed' => (($user == $this->feather->user->id) ? '1' : '0'))) + ->save(); + } + } else { + // Reply + $notifs = DB::for_table('pms_data') + ->where('conversation_id', $conv_id) + ->where_not_equal('user_id', $this->feather->user->id) + ->find_result_set(); + $notifs->set('viewed', 0) + ->save(); + } + + return ($add && $update && $notifs) ? $add->id() : false; + } + + public function getMessages($conv_id = null, $limit = 50, $start = 0) + { + $select = array('m.id', 'username' => 'm.poster', 'm.poster_id', 'poster_gid' => 'u.group_id', 'u.title', 'm.message', 'm.hide_smilies', 'm.sent', 'm.conversation_id', 'g.g_id', 'g.g_user_title', 'is_online' => 'o.user_id'); + $result = DB::for_table('pms_messages') + ->table_alias('m') + ->select_many($select) + ->left_outer_join('users', array('u.id', '=', 'm.poster_id'), 'u') + ->inner_join('groups', array('g.g_id', '=', 'u.group_id'), 'g') + ->raw_join('LEFT OUTER JOIN '.$this->feather->forum_settings['db_prefix'].'online', "o.user_id!=1 AND o.idle=0 AND o.user_id=u.id", 'o') + ->where('m.conversation_id', $conv_id) + ->order_by_asc('m.sent') + ->find_array(); + return $result; + } + + public function isAllowed($username = null) + { + if (!$username) { + return false; + } + + $result = DB::for_table('users') + ->where('username', $username) + ->where_gt('id', 1) + ->find_one(); + return $result; + } + + public function isDeleted($conv_id = null, $uid = null) + { + $result = DB::for_table('pms_data') + ->where('conversation_id', $conv_id) + ->where('user_id', $uid) + ->where('deleted', 1) + ->find_one(); + return (bool) $result; + } + + public function getUserByID($id = null) + { + if (!$id) { + return false; + } + $result = DB::for_table('users') + ->where('id', $id) + ->find_one(); + return $result; + } + + public function getUserByName($username) + { + $user = DB::for_table('users') + ->select_many(['group_id', 'id']) + ->where('username', $username) + ->find_one(); + return $user; + } + + public function getMessagesFromConversation($conv_id = null, $uid = null, $limit = 50, $start = 0) + { + $result = DB::for_table('pms_messages') + ->table_alias('m') + ->where('m.conversation_id', $conv_id) + ->order_by_desc('sent') + ->limit($limit) + ->find_many(); + return $result; + } + + /** + * Get blocked users for current user + * @param (int) $user_id Current user id + * @return (object) The database results + */ + public function getBlocked($user_id) + { + $result = DB::for_table('pms_blocks') + ->table_alias('b') + ->select_many(['b.id', 'b.block_id', 'u.username', 'u.group_id']) + ->inner_join('users', array('b.block_id', '=', 'u.id'), 'u') + ->where('b.user_id', $user_id) + ->find_many(); + return $result; + } + + public function checkBlock($user_id, $block_id) + { + // var_dump($user_id, $block_id); + $result = DB::for_table('pms_blocks') + // ->select('id') + ->where('user_id', $user_id) + ->where('block_id', $block_id) + ->count(); + return $result; + } + + public function addBlock(array $data = array()) + { + $result = DB::for_table('pms_blocks') + ->create() + ->set($data); + $result->save(); + return $result->id(); + } + + public function removeBlock($user_id, $block_id) + { + $result = DB::for_table('pms_blocks') + ->where('user_id', $user_id) + ->where('block_id', $block_id) + ->find_one(); + return $result->delete(); + } + + /** + * Add a custom folder + * @param (array) $data New folder name and owner ID + * @return (bool) Creation success state + */ + public function addFolder(array $data) + { + $result = DB::for_table('pms_folders') + ->create() + ->set($data); + $result->save(); + return $result->id(); + } + + public function updateFolder($user_id, $block_id, array $data) + { + $result = DB::for_table('pms_folders') + ->find_one($block_id) + ->where('user_id', $user_id) + ->set($data); + return $result->save(); + } + + public function removeFolder($user_id, $block_id) + { + $result = DB::for_table('pms_folders') + ->where('id', $block_id) + ->where('user_id', $user_id) + ->find_one(); + return $result->delete(); + } +} diff --git a/src/PrivateMessages.php b/src/PrivateMessages.php new file mode 100644 index 0000000..4948328 --- /dev/null +++ b/src/PrivateMessages.php @@ -0,0 +1,158 @@ +feather; + + $this->hooks->bind('admin.plugin.menu', [$this, 'getName']); + $this->hooks->bind('view.header.navlinks', [$this, 'addNavlink']); + $this->hooks->bind('model.print_posts.one', function ($cur_post) use ($feather) { + $cur_post['user_contacts'][] = 'PM'; + return $cur_post; + }); + + $this->feather->group('/conversations', + function() use ($feather) { + if($feather->user->is_guest) throw new Error(__('No permission'), 403); + }, function() use ($feather){ + $feather->map('/inbox(/:inbox_id)(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:index')->conditions(array('inbox_id' => '[0-9]+'))->via('GET', 'POST')->name('Conversations.home'); + $feather->map('/inbox(/:inbox_id)/page(/:page)(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:index')->conditions(array('inbox_id' => '[0-9]+', 'page' => '[0-9]+'))->via('GET', 'POST')->name('Conversations.home.page'); + $feather->get('/thread(/:tid)(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:show')->conditions(array('tid' => '[0-9]+'))->name('Conversations.show'); + $feather->get('/thread(/:tid)/page(/:page)(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:show')->conditions(array('tid' => '[0-9]+', 'page' => '[0-9]+'))->name('Conversations.show.page'); + $feather->map('/send(/:uid)(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:send')->conditions(array('uid' => '[0-9]+'))->via('GET', 'POST')->name('Conversations.send'); + $feather->map('/reply/:tid(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:reply')->conditions(array('tid' => '[0-9]+'))->via('GET', 'POST')->name('Conversations.reply'); + $feather->map('/quote/:mid(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:reply')->conditions(array('mid' => '[0-9]+'))->via('GET', 'POST')->name('Conversations.quote'); + $feather->map('/options/blocked(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:blocked')->conditions(array('mid' => '[0-9]+'))->via('GET', 'POST')->name('Conversations.blocked'); + $feather->map('/options/folders(/)', '\FeatherBB\Plugins\Controller\PrivateMessages:folders')->conditions(array('mid' => '[0-9]+'))->via('GET', 'POST')->name('Conversations.folders'); + } + ); + + $this->feather->template->addAsset('css', 'plugins/private-messages/src/style/private-messages.css'); + } + + public function addNavlink($navlinks) + { + load_textdomain('private_messages', dirname(__FILE__).'/lang/'.$this->feather->user->language.'/private-messages.mo'); + if (!$this->feather->user->is_guest) { + $nbUnread = Model\PrivateMessages::countUnread($this->feather->user->id); + $count = ($nbUnread > 0) ? ' ('.$nbUnread.')' : ''; + $navlinks[] = '4 = PMS'.$count.''; + if ($nbUnread > 0) { + $this->hooks->bind('header.toplist', function($toplists) { + $toplists[] = ''; + return $toplists; + }); + } + } + return $navlinks; + } + + public function install() + { + load_textdomain('private_messages', dirname(__FILE__).'/lang/'.$this->feather->forum_settings['o_default_lang'].'/private-messages.mo'); + + $database_scheme = array( + 'pms_data' => "CREATE TABLE IF NOT EXISTS %t% ( + `conversation_id` int(10) unsigned NOT NULL, + `user_id` int(10) unsigned NOT NULL DEFAULT '0', + `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0', + `viewed` tinyint(1) unsigned NOT NULL DEFAULT '0', + `folder_id` int(10) unsigned NOT NULL DEFAULT '2', + PRIMARY KEY (`conversation_id`, `user_id`), + KEY `folder_idx` (`folder_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;", + 'pms_folders' => "CREATE TABLE IF NOT EXISTS %t% ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(80) NOT NULL DEFAULT 'New Folder', + `user_id` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `user_idx` (`user_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;", + 'pms_messages' => "CREATE TABLE IF NOT EXISTS %t% ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `poster` varchar(200) NOT NULL DEFAULT '', + `poster_id` int(10) unsigned NOT NULL DEFAULT '1', + `poster_ip` varchar(39) DEFAULT NULL, + `message` mediumtext, + `hide_smilies` tinyint(1) NOT NULL DEFAULT '0', + `sent` int(10) unsigned NOT NULL DEFAULT '0', + `edited` int(10) unsigned DEFAULT NULL, + `edited_by` varchar(200) DEFAULT NULL, + `conversation_id` int(10) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + KEY `conversation_idx` (`conversation_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;", + 'pms_conversations' => "CREATE TABLE IF NOT EXISTS %t% ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `poster` varchar(200) NOT NULL DEFAULT '', + `poster_id` int(10) unsigned NOT NULL DEFAULT '0', + `subject` varchar(255) NOT NULL DEFAULT '', + `first_post_id` int(10) unsigned NOT NULL DEFAULT '0', + `last_post_id` int(10) unsigned NOT NULL DEFAULT '0', + `last_post` int(10) unsigned NOT NULL DEFAULT '0', + `last_poster` varchar(200) DEFAULT NULL, + `num_replies` mediumint(8) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;", + 'pms_blocks' => "CREATE TABLE IF NOT EXISTS %t% ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `user_id` int(10) NOT NULL DEFAULT '0', + `block_id` int(10) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;" + ); + + // Create tables + $installer = new \FeatherBB\Model\Install(); + foreach ($database_scheme as $table => $sql) { + $installer->create_table($this->feather->forum_settings['db_prefix'].$table, $sql); + } + + // Create default inboxes + $folders = array( + __('New', 'private_messages'), + __('Inbox', 'private_messages'), + __('Archived', 'private_messages') + ); + + foreach ($folders as $folder) + { + $insert = array( + 'name' => $folder, + 'user_id' => 1, + ); + $installer->add_data('pms_folders', $insert); + } + } + + public function remove() + { + $db = DB::get_db(); + $tables = ['pms_data', 'pms_folders', 'pms_messages', 'pms_conversations', 'pms_blocks']; + foreach ($tables as $i) + { + $tableExists = DB::for_table($i)->raw_query('SHOW TABLES LIKE "'. $this->feather->forum_settings['db_prefix'].$i . '"')->find_one(); + if ($tableExists) + { + $db->exec('DROP TABLE '.$this->feather->forum_settings['db_prefix'].$i); + } + } + } + +} diff --git a/src/Views/blocked.php b/src/Views/blocked.php new file mode 100644 index 0000000..ba2f99e --- /dev/null +++ b/src/Views/blocked.php @@ -0,0 +1,74 @@ + +
+

+
+

+ +
+
+
+ +
+

+
+
+
+
+ +
+ + + + + +
+
+
+ + +
+
+
+
+
+
+ +

+
+
+ +
+
+
+ + + + + + + + + $block): ?> + + + + + + +
username ?>
+
+
+
+
+
+ +
+
diff --git a/src/Views/delete.php b/src/Views/delete.php new file mode 100644 index 0000000..3194682 --- /dev/null +++ b/src/Views/delete.php @@ -0,0 +1,18 @@ +
+

+
+
+ " /> + + + +
+
+

+
+
+

+
+
+
+
diff --git a/src/Views/folders.php b/src/Views/folders.php new file mode 100644 index 0000000..958b988 --- /dev/null +++ b/src/Views/folders.php @@ -0,0 +1,73 @@ + +
+

+
+

+ +
+
+
+ +
+

+
+
+ +
+
+ +
+ +
+
+
+

+
+ +
+ +

+
+
+ +
+
+
+ + + + + + + + + $folder): ?> + + + + + + +
 
+
+
+
+
+
+ +
+
diff --git a/src/Views/index.php b/src/Views/index.php new file mode 100644 index 0000000..ace14a8 --- /dev/null +++ b/src/Views/index.php @@ -0,0 +1,99 @@ + +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
1
+
+
+ [ New posts ]' : '')?> +
+
+
$name) { ?> + + + '.$feather->utils->format_time($conv['last_post']).'' : 'Never')?> by
+
+
+
+
+
+
+ + +
+
+
+
+
+ + +
+

+
+
+

+
+
+
+ +
diff --git a/src/Views/menu.php b/src/Views/menu.php new file mode 100644 index 0000000..cf18500 --- /dev/null +++ b/src/Views/menu.php @@ -0,0 +1,58 @@ + + +
+
+

+
+
+ +
+
+

+
+
+
    +
  • Inbox: 0% full
  • +
  • +
  • Quota: / ∞
  • +
+
+
+
+

+
+
+
    + > + > +
+
+
+
diff --git a/src/Views/move.php b/src/Views/move.php new file mode 100644 index 0000000..0b593d5 --- /dev/null +++ b/src/Views/move.php @@ -0,0 +1,32 @@ + +
+

+
+
+ " /> + + + +
+
+ +
+ +
+
+
+

+
+
+
+ +
diff --git a/src/Views/reply.php b/src/Views/reply.php new file mode 100644 index 0000000..11f7f56 --- /dev/null +++ b/src/Views/reply.php @@ -0,0 +1,82 @@ + + +
+

+
+
+
+
+ +
+ '."\n" ?> + +
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+

+
+
+
+ + +
+

+ + +
+
+
+
+
+
+
+
utils->format_time($msg['sent'])?>
+
+
+
+
+

+
+
+
+
+
+
+
+ + +
+ diff --git a/src/Views/send.php b/src/Views/send.php new file mode 100644 index 0000000..235ce55 --- /dev/null +++ b/src/Views/send.php @@ -0,0 +1,69 @@ + +
+

Preview

+
+
+
+
+
+ +
+
+
+
+
+
+ +
+

+
+
+
+
+ +
+ '."\n" ?> + + size="25" tabindex="1" required autofocus/>
+
+ + size="80" maxlength="70" tabindex="2" required/>
+ +
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+

+
+
+
diff --git a/src/Views/show.php b/src/Views/show.php new file mode 100644 index 0000000..97d4fda --- /dev/null +++ b/src/Views/show.php @@ -0,0 +1,49 @@ + + +
+ +
+

# utils->format_time($message['sent']) ?>

+
+
+
+
+
+
utils->escape($message['username'])?>
+
utils->get_title($message) ?>
+
+
+
+

+ utils->escape($cur_conv['subject']) ?> +

+
+

+ utils->escape($message['message'])."\n" ?> +

+
+
+
+
+
+
+
+ 1) { + echo '

'.($message['is_online'] == $message['poster_id']) ? ''.__('Online').'' : (''.__('Offline').'').'

'; + } ?> +
+
+
+
+
+ +
+
diff --git a/src/lang/English/private-messages.mo b/src/lang/English/private-messages.mo new file mode 100644 index 0000000..5389ad2 Binary files /dev/null and b/src/lang/English/private-messages.mo differ diff --git a/src/lang/English/private-messages.po b/src/lang/English/private-messages.po new file mode 100644 index 0000000..e809bb6 --- /dev/null +++ b/src/lang/English/private-messages.po @@ -0,0 +1,159 @@ +# +msgid "" +msgstr "" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Project-Id-Version: FeatherBB\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: FeatherBB \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"X-Generator: Poedit 1.8.4\n" +"X-Poedit-SourceCharset: UTF-8\n" + +msgid "Folders" +msgstr "Folders" + +msgid "PMS" +msgstr "PMS" + +msgid "New" +msgstr "Unread" + +msgid "Inbox" +msgstr "Inbox" + +msgid "Archived" +msgstr "Archived" + +msgid "Storage" +msgstr "Storage" + +msgid "Options" +msgstr "Options" + +msgid "Wrong folder owner" +msgstr "You don't own this inbox" + +msgid "Send" +msgstr "New conversation" + +msgid "Send success" +msgstr "Your PM has been sent to %s !" + +msgid "Reply" +msgstr "Reply to conversation" + +msgid "Reply success" +msgstr "You have successfully replied to the conversation %s !" + +msgid "No conv selected" +msgstr "Select more than one conversations" + +msgid "Conversations deleted" +msgstr "Conversations deleted" + +msgid "Conversations moved" +msgstr "Conversations moved" + +msgid "Error move" +msgstr "Error while moving conversations" + +msgid "Unread messages" +msgstr "You have unread private messages" + +msgid "Confirm delete" +msgstr "Are you sure you want to delete these conversations ? This operation cannot be undone." + +msgid "My conversations" +msgstr "My conversations" + +msgid "Move conversations" +msgstr "Move conversations" + +msgid "Select move destination" +msgstr "Select the folder you want to move conversations to." + +msgid "Empty inbox" +msgstr "You have no conversations in this inbox." + +msgid "Blocked Users" +msgstr "Blocked Users" + +msgid "Add block" +msgstr "Add block" + +msgid "Add block legend" +msgstr "Block a user" + +msgid "No block self" +msgstr "You cannot block yourself!" + +msgid "No user name message" +msgstr "The user %s does not exist." + +msgid "User is admin message" +msgstr "The user %s is an administrator and can't be blocked." + +msgid "User is mod message" +msgstr "The user %s is a moderator and can't be blocked." + +msgid "Already blocked" +msgstr "You already blocked the user %s." + +msgid "Block added" +msgstr "Block added." + +msgid "Block removed" +msgstr "Block removed." + +msgid "Block errors" +msgstr "Block errors." + +msgid "Block error info" +msgstr "The following errors need to be corrected before the user can be blocked:" + +msgid "My Folders" +msgstr "My Folders" + +msgid "Add folder" +msgstr "Add folder" + +msgid "Folder name" +msgstr "Folder name" + +msgid "No folder name" +msgstr "Please provide a valid name for your folder" + +msgid "Folder too short" +msgstr "The name of a folder should contain at least 2 characters" + +msgid "Folder too long" +msgstr "The name of a folder should contain at most 30 characters" + +msgid "No folder after censoring" +msgstr "The folder name contains censored words" + +msgid "Folder limit" +msgstr "You cannot create other folders" + +msgid "Folder added" +msgstr "Folder added" + +msgid "Conv review" +msgstr "Conversation review (newest first)" + +msgid "Mark read" +msgstr "Mark as read" + +msgid "Mark unread" +msgstr "Mark as unread" + +msgid "Select action" +msgstr "Select an action" + +msgid "Actions" +msgstr "Actions" diff --git a/src/style/private-messages.css b/src/style/private-messages.css new file mode 100644 index 0000000..7cc7b5d --- /dev/null +++ b/src/style/private-messages.css @@ -0,0 +1,25 @@ +.blockmenu ul .big { + text-decoration: none; + color: #555; + display: block; + padding: 10px +} + +div#pm_bar { + border: 1px solid #336699; + width: 100px; + height: 10px; + text-align: right; + display:inline-block; +} + +div#pm_bar_style { + background-color: #336699; + height: 10px; + display:block; +} + +.right { + text-align: right; + float: right; +} diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..24bc4d7 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..c8d57af --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2015 Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($baseDir . '/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..8a17845 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + return $loader; + } +} + +function composerRequire6e6e3188e82ec5b8cf53709fb56e59db($file) +{ + require $file; +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1 @@ +[]