protected function process_view_filters($view) { if (empty($view)) { return; } switch ($view) { /** * only include the latest reply or comment (or the starter itself if no replies/comments yet) per starter in all the channels. * Filters out the Channel nodes from the Search API nodes results. * @include replies/comments in the second phase */ case vB_Api_Search::FILTER_VIEW_ACTIVITY: $datecheck = false; // search for publishdate through filters foreach ($this->filters as $type) { if (is_array($type) and !$datecheck) { $datecheck = array_key_exists('publishdate', $type); } } $this->what[] = "(id = starter) as id_starter2"; $this->where[] = "id_starter2 = 1"; $this->where[] = "contenttypeid <> " . vB_Api::instanceInternal('contenttype')->fetchContentTypeIdFromClass('Channel'); $channelAccess = vb::getUserContext()->getAllChannelAccess(); if (!empty($this->filters['make_equals_filter']['starter_only'])) { array_unshift($this->what, 'starter'); } else { if (!empty($channelAccess['starteronly'])) { $starterOnly = implode(',', $channelAccess['starteronly']); array_unshift($this->what, "IF(IN(starter, {$starterOnly}) OR IN(starterparent, {$starterOnly}), starter, lastcontentid) AS last"); $this->groupby = 'last'; } else { array_unshift($this->what, 'lastcontentid'); $this->groupby = 'lastcontentid'; } } unset($channelAccess); if (!$datecheck) { $age = vB::getDatastore()->getOption('max_age_channel'); if (empty($age)) { $age = 60; } $this->where[] = "created > " . (vB::getRequest()->getTimeNow() - $age * 86400); } // in activity stream we don't want deleted content even if viewed by a moderator $this->where[] = "showpublished > 0"; break; /** * The Topic view should only display the starter nodes for the specified channel. * Filters out the Channel nodes from the Search API nodes results. */ /** * The Topic view should only display the starter nodes for the specified channel. * Filters out the Channel nodes from the Search API nodes results. */ case vB_Api_Search::FILTER_VIEW_TOPIC: array_unshift($this->what, 'starter'); $this->groupby = 'starter'; $this->where[] = "contenttypeid <> " . vB_Api::instanceInternal('contenttype')->fetchContentTypeIdFromClass('Channel'); break; /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * Filters out the Comment node from the Search API nodes results. */ /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * Filters out the Comment node from the Search API nodes results. */ case vB_Api_Search::FILTER_VIEW_CONVERSATION_THREAD: $this->what[] = "(id = starter OR starter = parentid) as starter_node"; $this->where[] = "starter_node = 1"; break; case vB_Api_Search::FILTER_VIEW_CONVERSATION_THREAD_SEARCH: array_unshift($this->what, "IF(parentid = 0 OR id = starter OR starter = parentid, id, parentid) as starter_node"); $this->groupby = 'starter_node'; break; /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * the Comment nodes are not filtered out. * This should be handled by the channel filter */ /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * the Comment nodes are not filtered out. * This should be handled by the channel filter */ case vB_Api_Search::FILTER_VIEW_CONVERSATION_STREAM: break; } }
protected function process_view_filters($view) { if (empty($view)) { return; } switch ($view) { /** * only include the latest reply or comment (or the starter itself if no replies/comments yet) per starter in all the channels. * Filters out the Channel nodes from the Search API nodes results. * @include replies/comments in the second phase */ case vB_Api_Search::FILTER_VIEW_ACTIVITY: $datecheck = false; // search for publishdate through filters foreach ($this->filters as $type) { if (is_array($type) and !$datecheck) { $datecheck = array_key_exists('publishdate', $type); } } $channelAccess = vb::getUserContext()->getAllChannelAccess(); if (!empty($this->filters['make_equals_filter']['starter_only'])) { $this->what = "node.nodeid"; } else { if (!empty($channelAccess['starteronly'])) { $starterOnly = implode(',', $channelAccess['starteronly']); $this->what = "DISTINCT CASE WHEN starter.nodeid IN ({$starterOnly}) OR starter.parentid in ({$starterOnly})\n\t\t\t\t\tTHEN starter.nodeid ELSE node.lastcontentid END AS nodeid"; } else { if (!empty($this->filters['last'])) { $this->what = "node.nodeid"; } else { $this->what = "DISTINCT node.lastcontentid AS nodeid"; } } } $this->where[] = "node.nodeid = node.starter AND node.contenttypeid <> " . vB_Api::instanceInternal('contenttype')->fetchContentTypeIdFromClass('Channel'); unset($channelAccess); if (!$datecheck) { $age = vB::getDatastore()->getOption('max_age_channel'); if (empty($age)) { $age = 60; } $this->where[] = "node.created > " . (vB::getRequest()->getTimeNow() - $age * 86400); } // in activity stream we don't want deleted content even if viewed by a moderator $this->where[] = "node.showpublished > 0"; break; /** * The Topic view should only display the starter nodes for the specified channel. * Filters out the Channel nodes from the Search API nodes results. */ /** * The Topic view should only display the starter nodes for the specified channel. * Filters out the Channel nodes from the Search API nodes results. */ case vB_Api_Search::FILTER_VIEW_TOPIC: $method = false; if (isset($this->filters['make_notequals_filter']['sticky'])) { $method = 'make_notequals_filter'; } if (isset($this->filters['make_equals_filter']['sticky'])) { $method = 'make_equals_filter'; } if (!empty($method)) { if (!isset($this->join['starter'])) { $this->join['starter'] = "LEFT JOIN " . TABLE_PREFIX . "node AS starter ON starter.nodeid = IF(node.starter = 0, node.nodeid, node.starter)"; } $this->where[] = $where = $this->{$method}('starter', 'sticky', $this->filters[$method]['sticky']); unset($this->filters[$method]['sticky']); } if (!empty($this->filters['make_range_filter']['lastcontent'])) { $this->what = "node.nodeid"; } else { $this->what = "DISTINCT node.starter AS nodeid"; } $this->where[] = "node.contenttypeid <> " . vB_Api::instanceInternal('contenttype')->fetchContentTypeIdFromClass('Channel'); break; /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * Filters out the Comment node from the Search API nodes results. */ /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * Filters out the Comment node from the Search API nodes results. */ case vB_Api_Search::FILTER_VIEW_CONVERSATION_THREAD: if (empty($this->join['closure'])) { $this->join['closure'] = "JOIN " . TABLE_PREFIX . "closure AS closure ON node.nodeid = closure.child"; } $this->where[] = "(node.starter = node.nodeid OR node.starter = node.parentid)"; $this->where[] = 'closure.depth <= 1'; break; case vB_Api_Search::FILTER_VIEW_CONVERSATION_THREAD_SEARCH: // if (empty($this->join['closure'])) // { // $this->join['closure'] = "JOIN " . TABLE_PREFIX . "closure AS closure ON node.nodeid = closure.child"; // } $this->what = " DISTINCT IF (node.parentid = 0 OR node.parentid = node.starter OR node.nodeid = node.starter, node.nodeid, node.parentid) AS nodeid"; break; /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * the Comment nodes are not filtered out. * This should be handled by the channel filter */ /** * The Conversation Detail view should only display the descendant nodes of (and including) the specified starter. * the Comment nodes are not filtered out. * This should be handled by the channel filter */ case vB_Api_Search::FILTER_VIEW_CONVERSATION_STREAM: break; } }
/** * Adds content info to $result so that merged content can be edited. * * @param array $result * @param array $content */ public function mergeContentInfo(&$result, $content) { if (vb::getUserContext()->getChannelPermission('forumpermissions', 'canviewthreads', $result['nodeid'])) { $this->library->mergeContentInfo($result, $content); } }
/** * Fetch node's preview * */ public function actionFetchNodePreview() { $preview = ''; $nodeid = isset($_REQUEST['nodeid']) ? intval($_REQUEST['nodeid']) : array(); if (!empty($nodeid)) { if (!vb::getUserContext()->getChannelPermission('forumpermissions', 'canviewthreads', $nodeid)) { return ''; } $contenttypes = vB_Types::instance()->getContentTypes(); $typelist = array(); foreach ($contenttypes as $key => $type) { $typelist[$type['id']] = $key; } $api = Api_InterfaceAbstract::instance(); $contentTypes = array('vBForum_Text', 'vBForum_Gallery', 'vBForum_Poll', 'vBForum_Video', 'vBForum_Link'); $nodes = $api->callApi('node', 'getNodeContent', array($nodeid)); $node = $nodes[$nodeid]; $contentType = $typelist[$node['contenttypeid']]; if (in_array($contentType, $contentTypes)) { $preview = vB5_Template_NodeText::instance()->fetchOneNodePreview($nodeid, $api); } $previewLength = vB5_Template_Options::instance()->get('options.threadpreview'); if (strlen($preview) > $previewLength) { $preview = substr($preview, 0, $previewLength); } } return $preview; }
/** * Callback for preg_replace_callback in handle_bbcode_img */ protected function attachReplaceCallback($matches) { $currentUserid = vb::getUserContext()->fetchUserId(); $align = $matches[1]; $showOldImage = false; $tempid = false; // used if this attachment hasn't been saved yet (ex. going back & forth between source mode & wysiwyg on a new content) $filedataid = false; $attachmentid = false; // Same as before: are we looking at a legacy attachment? if (preg_match('#^n(\\d+)$#', $matches[2], $matches2)) { // if the id has 'n' as prefix, it's a nodeid $attachmentid = intval($matches2[1]); } else { if (preg_match('#^temp_(\\d+)_(\\d+)_(\\d+)$#', $matches[2], $matches2)) { // if the id is in the form temp_##_###_###, it's a temporary id that links to hidden inputs that contain // the stored settings that will be saved when it becomes a new attachment @ post save. $tempid = $matches2[0]; $filedataid = intval($matches2[1]); } else { // it's a legacy attachmentid, get the new id if (isset($this->oldAttachments[intval($matches[2])])) { // key should be nodeid, not filedataid. $attachmentid = $this->oldAttachments[intval($matches[2])]['nodeid']; //$showOldImage = $this->oldAttachments[intval($matches[2])]['cangetattachment']; } } } if ($attachmentid === false and $tempid === false) { // No data match was found for the attachment, so just return nothing return ''; } else { if ($attachmentid and !empty($this->attachments["{$attachmentid}"])) { // attachment specified by [attach] tag belongs to this post $attachment =& $this->attachments["{$attachmentid}"]; $filedataid = $attachment['filedataid']; // TODO: This doesn't look right to me. I feel like htmlSpecialCharsUni should be outside of the // fetchCensoredText() call, but don't have time to verify this right now... $attachment['filename'] = $this->fetchCensoredText(vB5_String::htmlSpecialCharsUni($attachment['filename'])); if (empty($attachment['extension'])) { $attachment['extension'] = strtolower(file_extension($attachment['filename'])); } $attachment['filesize_humanreadable'] = vb_number_format($attachment['filesize'], 1, true); $settings = array(); if (!empty($attachment['settings']) and strtolower($align) == 'config') { $settings = unserialize($attachment['settings']); // TODO: REPLACE USE OF unserialize() above WITH json_decode // ALSO UPDATE createcontent controller's handleAttachmentUploads() to use json_encode() instead of serialize() } elseif (!empty($matches['settings'])) { // Currently strictly used by VBV-12051, replacing [IMG]...?filedataid=...[/IMG] with corresponding attachment image // when &type=[a-z]+ or &thumb=1 is part of the image url, that size is passed into us as settings from handle_bbcode_img() // Nothing else AFAIK should be able to pass in the settings, but if we do add this as a main feature, // we should be sure to scrub this well (either via the regex pattern or actual cleaning) to prevent xss. if (isset($matches['settings']['size'])) { // This cleaning is not strictly necessary since the switch-case below that uses this restricts the string to a small set, so xss is not possible. $size = Api_InterfaceAbstract::instance()->callApi('filedata', 'sanitizeFiletype', array($matches['settings']['size'])); $settings['size'] = $size; } } $type = isset($settings['size']) ? $settings['size'] : ''; // <IMG > OR <A > CHECK $params = array($attachment['extension'], $type); $isImage = Api_InterfaceAbstract::instance()->callApi('content_attach', 'isImage', $params); /* The only reason we do permission checks here is to make the rendered result look nicer, NOT for security. If they have no permission to see an image, any image tags will just show a broken image, so we show a link with the filename instead. */ if (!isset($this->userImagePermissions[$currentUserid][$attachment['parentid']]['cangetimageattachment'])) { $canDownloadImages = vb::getUserContext()->getChannelPermission('forumpermissions2', 'cangetimgattachment', $attachment['parentid']); $this->userImagePermissions[$currentUserid][$attachment['parentid']] = $canDownloadImages; } if (!isset($this->userImagePermissions[$currentUserid][$attachment['parentid']]['canseethumbnails'])) { $canDownloadImages = vb::getUserContext()->getChannelPermission('forumpermissions', 'canseethumbnails', $attachment['parentid']); $this->userImagePermissions[$currentUserid][$attachment['parentid']] = $canDownloadImages; } $hasPermission = ($this->userImagePermissions[$currentUserid][$attachment['parentid']]['cangetimageattachment'] or $this->userImagePermissions[$currentUserid][$attachment['parentid']]['canseethumbnails']); $useImageTag = ($this->options['do_imgcode'] and $isImage and $hasPermission); // Special override for 'canseethumbnails' if ($useImageTag and !$this->userImagePermissions[$currentUserid][$attachment['parentid']]['cangetimageattachment']) { $settings['size'] = 'thumb'; } // OUTPUT LOGIC $link = vB5_Template_Options::instance()->get('options.frontendurl') . '/filedata/fetch?'; if (!empty($attachment['nodeid'])) { $link .= "id={$attachment['nodeid']}"; $id = 'attachment' . $attachment['nodeid']; } else { $link .= "filedataid={$attachment['filedataid']}"; $id = 'filedata' . $attachment['filedataid']; } if (!empty($attachment['resize_dateline'])) { $link .= "&d={$attachment['resize_dateline']}"; } else { $link .= "&d={$attachment['dateline']}"; } // image_x_y_z isn't strictly for images ATM. If that changes, we can check $isImage here and select // a different phrase for the title. $title = vB5_Template_Phrase::instance()->register(array('image_x_y_z', $attachment['filename'], intval($attachment['counter']), $attachment['filesize_humanreadable'])); $filename = $attachment['filename']; // html escaped above. if ($useImageTag) { $fullsize = false; // Set if $settings['size'] is 'full' or 'fullsize' $customLink = false; // If $settings['link'] is 1 and $settings['url'] isn't empty, it becomes an array of data-attributes to set in the img bits $imgclass = array(); $imgbits = array('border' => 0, 'src' => vB_String::htmlSpecialCharsUni($link)); $title_text = !empty($settings['title']) ? vB_String::htmlSpecialCharsUni($settings['title']) : ''; $description_text = !empty($settings['description']) ? vB_String::htmlSpecialCharsUni($settings['description']) : ''; if ($title_text) { $imgbits['title'] = $title_text; $imgbits['data-title'] = $title_text; // only set this if $setting['title'] is not empty } else { if ($description_text) { $imgbits['title'] = $description_text; } } if ($description_text) { $imgbits['description'] = $description_text; $imgbits['data-description'] = $description_text; } $alt_text = !empty($settings['description']) ? vB_String::htmlSpecialCharsUni($settings['description']) : $title_text; // vB4 used to use description for alt text. vB5 seems to expect title for it, for some reason. Here's a compromise if ($alt_text) { $imgbits['alt'] = $alt_text; } else { $imgbits['alt'] = vB5_Template_Phrase::instance()->register(array('image_larger_version_x_y_z', $attachment['filename'], $attachment['counter'], $attachment['filesize_humanreadable'], $attachment['filedataid'])); } // See VBV-14079 -- This requires the forumpermissions.canattachmentcss permission. // @TODO Do we want to escape this in some fashion? $styles = !empty($settings['styles']) ? $settings['styles'] : false; if ($styles) { $imgbits['style'] = $styles; $imgbits['data-styles'] = vB_String::htmlSpecialCharsUni($styles); } else { if (!$settings and $align and $align != '=CONFIG') { $imgbits['style'] = "float:{$align}"; } } // TODO: WHAT IS THIS CAPTION??? // if ($settings['caption']) // { // $caption_tag = "<p class=\"caption $size_class\">$settings[caption]</p>"; // } if (!empty($attachment['nodeid'])) { $imgbits['data-attachmentid'] = $attachment['nodeid']; // used by ckeditor.js's dialogShow handler for Image Dialogs } // image size if (isset($settings['size'])) { // For all the supported sizes, refer to vB_Api_Filedata::SIZE_{} class constants switch ($settings['size']) { case 'icon': // AFAIK vB4 didn't have this size, but vB5 does. $type = $settings['size']; break; case 'thumb': // I think 'thumbnail' was used mostly in vB4. We lean towards the usage of 'thumb' instead in vB5. // I think 'thumbnail' was used mostly in vB4. We lean towards the usage of 'thumb' instead in vB5. case 'thumbnail': $type = 'thumb'; break; case 'small': // AFAIK vB4 didn't have this size, but vB5 does. // AFAIK vB4 didn't have this size, but vB5 does. case 'medium': case 'large': $type = $settings['size']; break; case 'full': // I think 'fullsize' was used mostly in vB4. We lean towards the usage of 'full' instead in vB5. // I think 'fullsize' was used mostly in vB4. We lean towards the usage of 'full' instead in vB5. case 'fullsize': default: // @ VBV-5936 we're changing the default so that if settings are not specified, we're displaying the fullsize image. $type = 'full'; $fullsize = true; break; } if (!empty($type)) { $imgbits['src'] .= '&type=' . $type; $imgbits['data-size'] = $type; } } // alignment setting if (isset($settings['alignment'])) { switch ($settings['alignment']) { case 'left': case 'center': case 'right': $imgclass[] = 'align_' . $settings['alignment']; $imgbits['data-alignment'] = $settings['alignment']; default: break; } } if (!empty($imgclass)) { $imgbits['class'] = "bbcode-attachment " . implode(' ', $imgclass); } else { $imgbits['class'] = "bbcode-attachment " . 'thumbnail'; } /* We still need to check customURL stuff before we can build the <IMG > tag! */ $hrefbits = array('id' => $id, 'class' => 'bbcode-attachment js-slideshow__gallery-node'); // if user specified link settings, we need to override the above slidesshow gallery stuff. // link: 0 = default, 1 = url, 2 = none if (!empty($settings['link'])) { if ($settings['link'] == 1 and !empty($settings['linkurl'])) { $settings['linkurl'] = vB_String::htmlSpecialCharsUni($settings['linkurl']); // escape html-special-chars that might break the anchor or img tag // custom URL $linkinfo = $this->handle_bbcode_url('', $settings['linkurl'], true); if ($linkinfo['link'] and $linkinfo['link'] != 'http://') { $customLink = array('data-link' => $settings['link'], 'data-linkurl' => $settings['linkurl']); $hrefbits['href'] = $linkinfo['link']; // linktarget: 0 = self, 1 = new window if (!empty($settings['linktarget'])) { $customLink['data-linktarget'] = $settings['linktarget']; $hrefbits['target'] = '_blank'; } // below will always occur if it's an external link. if ($linkinfo['nofollow']) { $hrefbits["rel"] = "nofollow"; } } } else { if ($settings['link'] == 2) { // DO NOT SURROUND BY ANCHOR TAG IF LINK TYPE IS "None" $hrefbits = array(); } } } else { if ($fullsize) { // Do not link for a full sized image. There's no bigger image to view. $hrefbits = array(); } else { $hrefbits['href'] = vB_String::htmlSpecialCharsUni($link); // Use lightbox only if it's not fullsize and one of these extensions // Not sure why we have the extension list here. Maybe the plugin only supported // certain file types in vB4? $lightbox_extensions = array('gif', 'jpg', 'jpeg', 'jpe', 'png', 'bmp'); $lightbox = in_array(strtolower($attachment['extension']), $lightbox_extensions); if ($lightbox) { /* Lightbox doesn't work in vB5 for non-gallery attachments yet. */ $hrefbits["rel"] = 'Lightbox_' . $this->containerid; //$imgbits["class"] .= ' js-lightbox group-by-parent-' . $attachment['parentid']; } else { $hrefbits["rel"] = "nofollow"; } } } if (!empty($customLink)) { foreach ($customLink as $name => $value) { $imgbits[$name] = $value; } } /* MAKE THE IMG & ANCHOR TAGS */ $imgtag = ''; foreach ($imgbits as $tag => $value) { $imgtag .= "{$tag}=\"{$value}\" "; } $atag = ''; foreach ($hrefbits as $tag => $value) { $atag .= "{$tag}=\"{$value}\" "; } // If we're showing this in the editor, we don't want the surrounding anchor tags. quick work-around. // Also handle the image setting link= "none" if ($this->turnOffSurroundingAnchor or empty($hrefbits)) { $insertHtml = "<img {$imgtag}/>"; } else { $insertHtml = "<a {$atag}><img {$imgtag}/></a>"; } if (isset($settings['alignment']) && $settings['alignment'] == 'center') { return "<div class=\"img_align_center" . ($fullsize ? ' size_fullsize' : '') . "\">{$insertHtml}</div>"; } else { return ($fullsize ? '<div class="size_fullsize">' : '') . $insertHtml . ($fullsize ? '</div>' : ''); } } else { // Just display a link. $link = vB5_String::htmlSpecialCharsUni($link); return "<a href=\"" . $link . "\" title=\"" . $title . "\">{$filename}</a>"; } } else { // if we have a temporaryid, then we're probably editing a post with a new attachment that doesn't have // a node created for the attachment yet. Let's return a basic image tag and let JS handle fixing it. if ($tempid and $filedataid) { if (isset($this->filedatas[$filedataid]) and !$this->filedatas[$filedataid]['isImage']) { return "<a class=\"bbcode-attachment\" href=\"" . vB5_Template_Options::instance()->get('options.frontendurl') . "/filedata/fetch?filedataid={$filedataid}\" data-tempid=\"" . $tempid . "\" >" . vB5_Template_Phrase::instance()->register(array('attachment')) . "</a>"; } else { return "<img class=\"bbcode-attachment js-need-data-att-update\" src=\"" . vB5_Template_Options::instance()->get('options.frontendurl') . "/filedata/fetch?filedataid={$filedataid}\" data-tempid=\"" . $tempid . "\" />"; } } else { // We don't know how to handle this. It'll just be replaced by a link via attachReplaceCallbackFinal later. return $matches[0]; } } } }
/** * Given a list of nodes, removes those the user can't see. * * @param mixed array of integers * * @return mixed array of integers **/ protected function validateNodeList($nodes) { //@todo. Let's look into how this works. $userid = vB::getCurrentSession()->get('userid'); $pmIds = array(); $nodeMap = array(); $canViewIps = vB::getUserContext()->hasPermission('moderatorpermissions', 'canviewips'); //First check permissions foreach ($nodes as $key => $node) { //If they are the author they can see it. if ($node['userid'] == $userid) { continue; } else { if ($node['contenttypeid'] == $this->pmContenttypeid) { $pmIds[] = $node['nodeid']; $nodeMap[$node['nodeid']] = $key; } else { if (!vB::getUserContext()->getChannelPermission('forumpermissions', 'canview', $node['nodeid'], false, $node['parentid'])) { if ($node['public_preview']) { $this->getPreviewOnly($nodes[$key]); } else { unset($nodes[$key]); } } else { if (!vb::getUserContext()->getChannelPermission('forumpermissions', 'canviewthreads', $node['nodeid'])) { if ($node['public_preview']) { $this->getPreviewOnly($nodes[$key]); } else { //The user can't see content. We hide the content and the "last" data unset($nodes[$key]['content']); $nodes[$key]['content'] = $node; $nodes[$key]['lastcontent'] = $node['publishdate']; $nodes[$key]['lastcontentid'] = $node['nodeid']; $nodes[$key]['lastcontentauthor'] = $node['authorname']; $nodes[$key]['lastauthorid'] = $node['userid']; unset($nodes[$key]['photo']); unset($nodes[$key]['attach']); } } } } } if (!$canViewIps) { unset($nodes[$key]['ipaddress']); } } if (!empty($pmIds)) { //The user can see it if there's a record in sentto $sentQry = vB::getDbAssertor()->assertQuery('vBForum:sentto', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'nodeid' => $pmIds, 'userid' => $userid)); $cansee = array(); foreach ($sentQry as $sentto) { $cansee[] = $sentto['nodeid']; } $pmIds = array_diff($pmIds, $cansee); foreach ($pmIds as $nodeid) { $index = $nodeMap[$nodeid]; if ($index !== false) { unset($nodes[$index]); } } } return $nodes; }
/** * Sends emails to moderators configured in admincp */ protected function sendModeratorNotification($nodeid) { // This is the list of ALL mods who have either the newpostemail or the newthreademail option enabled // We'll go through this list and figure out which ones are moderators of the ancestor channels of $nodeid $notify = vB_Cache::instance(vB_Cache::CACHE_STD)->read('vB_Mod_PostNotify'); if ($notify === false) { $library = vB_Library::instance('usergroup'); $smodgroups = $library->getSuperModGroups(); //force to have a non empty array or the query will be unhappy. It's unlikely that a site will //not have any usergroups with the superadmin flag set, but its possible if (!$smodgroups) { $smodgroups = array(0); } $notify = array(); $bitFields = vb::getDatastore()->getValue('bf_misc_moderatorpermissions'); $modQry = $this->assertor->assertQuery('vBForum:modPostNotify', array('bfPost' => $bitFields['newpostemail'], 'bfTopic' => $bitFields['newthreademail'], 'smodgroups' => $smodgroups)); $events = array(); if ($modQry->valid()) { foreach ($modQry as $mod) { $events[$mod['userid']] = 'userChg_' . $mod['userid']; //Every time a moderator changes emails, is deleted, etc, we have to invalidate it. if (!isset($notify[$mod['nodeid']])) { $notify[$mod['nodeid']] = array("posts" => array(), "topics" => array()); } if ($mod['notifypost'] > 0) { $notify[$mod['nodeid']]['posts'][] = $mod['email']; } if ($mod['notifytopic'] > 0) { $notify[$mod['nodeid']]['topics'][] = $mod['email']; } } } // all these user change events could be a lot... $events['vB_ModPostNotifyChg'] = 'vB_ModPostNotifyChg'; vB_Cache::instance(vB_Cache::CACHE_STD)->write('vB_Mod_PostNotify', $notify, 1440, $events); } // grab parents of the added node, and see if we have any moderators on the channel $parents = vB::getDbAssertor()->getRows('vBForum:closure', array('child' => $nodeid)); $notifyList = array(); // the actual list of emails that are associated with this node. $node = vB_Library::instance('node')->getNodeFullContent($nodeid); $node = $node[$nodeid]; if ($node['starter'] == $node['nodeid']) { $notifyKey = "topics"; } else { $notifyKey = "posts"; } foreach ($parents as $closure) { $parentid = $closure['parent']; if (array_key_exists($parentid, $notify)) { // each found list is an array of emails, so we have to merge $notifyList = array_merge($notifyList, $notify[$parentid][$notifyKey]); } } // Global moderators case. At the moment, the global mods in the moderator table has nodeid = 0 so the // closure check above leaves them out. if (!empty($notify[0])) { $notifyList = array_merge($notifyList, $notify[0][$notifyKey]); } $notifyList = array_unique($notifyList); if (empty($notifyList)) { return; } // grab some data for the message $userinfo = vB::getCurrentsession()->fetch_userinfo(); $forumName = vB::getDatastore()->getOption('bbtitle'); $starter = vB_Library::instance('node')->getNodeFullContent($node['starter']); $starter = $starter[$node['starter']]; $threadTitle = $starter['title']; // Below is the call to fetch the url to the thread starter // $threadLink = vB5_Route::buildUrl($starter['routeid'] . '|fullurl', $starter); // If we want the direct link to the post, below is what's needed $routeInfo = array('nodeid' => $nodeid); // Using the normal vB5_Route::buildURL can throw an exception, because it'll likely use the // conversation route which checks permissions on the *current* user in the constructor and // throw an exception. // So we'll use vB5_Route_Node $nodeLink = vB5_Route::buildUrl('node|fullurl', $routeInfo); $message = vB_Phrase::fetchSinglePhrase('new_post_notification_a_b_c_d_e_f', array($userinfo['username'], $node['channeltitle'], $forumName, $threadTitle, $nodeLink, $node['rawtext'])); $subject = vB_Phrase::fetchSinglePhrase('new_post_in_forum_x', $node['channeltitle']); // send emails foreach ($notifyList as $email) { if ($email != $userinfo['email']) { vB_Mail::vbmail($email, $subject, $message, true, '', '', '', TRUE); } } }