/** * Undelete a set of nodes * @param array $nodeids * @param boolean is rebuild needed * @throws vB_Exception_Api * @return array - the nodeids that have been deleted */ public function undeleteNodes($nodeids, $needRebuild = false) { if (empty($nodeids)) { return false; } $errors = array(); $events = array(); $loginfo = array(); $counts = $updates = array(); $nodeids = array_unique($nodeids); $assertor = vB::getDbAssertor(); $result = $assertor->assertQuery('vBForum:node', array('nodeid' => $nodeids, 'deleteuserid' => 0, 'deletereason' => '', 'unpublishdate' => 0, 'showpublished' => 1, vB_db_Query::TYPE_KEY => vB_db_Query::QUERY_UPDATE)); if (!empty($result['errors'])) { $errors[] = $result['errors']; } $searchAPI = vB_Api::instanceInternal('Search'); foreach ($nodeids as $nodeid) { $events[] = $nodeid; $result = $this->publishChildren($nodeid); if (!empty($result['errors'])) { $errors[] = $result['errors']; } // Clear cache for this node or user post count won't update vB_Cache::allCacheEvent('nodeChg_' . $nodeid); $node = $this->getNode($nodeid); // Update user post count (un(soft)delete) vB_Library_Content::getContentLib($node['contenttypeid'])->incrementUserPostCount($node); $loginfo[] = array('nodeid' => $node['nodeid'], 'nodetitle' => $node['title'], 'nodeusername' => $node['authorname'], 'nodeuserid' => $node['userid']); $parents = $this->fetchClosureParent($nodeid); foreach ($parents as $parent) { $nodeInfo = $this->getNodeBare($parent['parent']); if ($nodeInfo['contenttypeid'] == $this->channelTypeId) { $result = $this->fixNodeLast($parent['parent']); } else { $result = $assertor->assertQuery('vBForum:updateLastData', array('parentid' => $parent['parent'], 'timenow' => vB::getRequest()->getTimeNow())); } if (!empty($result['errors'])) { $errors[] = $result['errors']; } switch ($parent['depth']) { case 0: // Actual node. vB_Node::fixNodeCount($parent['parent']); break; case 1: // Immediate parent. $parentinfo = $this->getNodeBare($parent['parent']); $counts = array('totalcount' => $parentinfo['totalcount'], 'totalunpubcount' => $parentinfo['totalunpubcount']); vB_Node::fixNodeCount($parent['parent']); $parentinfo = $this->getNodeBare($parent['parent']); $counts = array('totalcount' => $parentinfo['totalcount'] - $counts['totalcount'], 'totalunpubcount' => $parentinfo['totalunpubcount'] - $counts['totalunpubcount']); break; default: // Higher parents. $updates['totalcount'][$parent['parent']] = $counts['totalcount']; $updates['totalunpubcount'][$parent['parent']] = $counts['totalunpubcount']; break; } } $assertor->assertQuery('vBForum:updateNodeTotals', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_METHOD, 'updates' => $updates)); $searchAPI->attributeChanged($nodeid); } $searchAPI->purgeCacheForCurrentUser(); if ($needRebuild) { vB::getUserContext()->rebuildGroupAccess(); vB_Channel::rebuildChannelTypes(); } $this->clearCacheEvents($nodeids); $this->clearChildCache($nodeids); if (!empty($errors)) { return array('errors' => $errors); } vB_Library_Admin::logModeratorAction($loginfo, 'node_restored_by_x'); return $nodeids; }
/** * Determines if the logged-in user can infract the (author of) the given node * * @param int Node ID * @param array Node record, if you have it * * @return bool The node (user) can be infracted by current user (or not) */ public function canInfractNode($nodeid, array $node = null) { $nodeid = (int) $nodeid; $infractionContentTypeId = null; if ($infractionContentTypeId === null) { $infractionContentTypeId = vB_Types::instance()->getContentTypeID('vBForum_Infraction'); } if ($node === null or !is_array($node)) { // needs getNodeFullContent to pull the node[infraction] field $nodeBare = vB_Library::instance('node')->getNodeBare($nodeid); $node = vB_Library_Content::getContentLib($nodeBare['contenttypeid'])->getBareContent($nodeid); $node = array_pop($node); } return empty($node['infraction']) and $node['contenttypeid'] != $infractionContentTypeId and $this->canInfractUser($node['userid']); }
protected function sendLegacyEmailNotification($data) { $options = vB::getDatastore()->getValue('options'); if (isset($options['enableemail']) and !$options['enableemail']) { return; // email notifications are globally disabled } /* * Save some data for this page load. Since we can potentially fetch a bunch of the same * data over and over if multiple users are getting notifications about the same content * that was just created, let's get rid of the redundant calls when this function is called * in a loop. * Todo: we should write a bulk-send-email-notification function * * I can't actually think of a case where a single content creation that triggers a number * of email notifications will have different contentnodeid in its group of notifications, * but let's just be safe and allow for that possibility. */ static $recipientIndependentDataArray; $contentnodeid = isset($data['contentnodeid']) ? $data['contentnodeid'] : 0; if (!empty($contentnodeid) and empty($recipientIndependentDataArray[$contentnodeid])) { //we need to load this using the correct library class or things get weird. //note that if we load it from cache here it will be in the local memory cache //when we load the full content and we won't do it twice. $cached = vB_Library_Content::fetchFromCache(array($contentnodeid), vB_Library_Content::CACHELEVEL_FULLCONTENT); if (isset($cached['found'][$contentnodeid])) { $contenttypeid = $cached['found'][$contentnodeid]['contenttypeid']; } else { $row = $this->assertor->getRow('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, vB_dB_Query::COLUMNS_KEY => array('contenttypeid'), 'nodeid' => $contentnodeid)); $contenttypeid = $row['contenttypeid']; } $contentLib = vB_Library_Content::getContentLib($contenttypeid); $currentNode = $contentLib->getContent($contentnodeid); $currentNode = $currentNode[$contentnodeid]; /* * These data are static & independent of the recipient of this message, assuming that * we're not trying to hide any data. If we are going to check permissions for each * recipient, we should probably check view perms & remove from the recipients list * BEFORE we ever get to this function, and just not send them a notification instead of * hiding data. */ $recipientIndependentDataArray[$contentnodeid] = array('authorname' => $currentNode['userinfo']['username'], 'nodeurl' => vB5_Route::buildUrl('node|fullurl', array('nodeid' => $contentnodeid)), 'previewtext' => vB_String::getPreviewText($currentNode['rawtext']), 'nodeid' => $currentNode['nodeid'], 'starter' => $currentNode['starter'], 'startertitle' => isset($currentNode['startertitle']) ? $currentNode['startertitle'] : '', 'parentid' => $currentNode['parentid'], 'channeltitle' => $currentNode['channeltitle'], 'channeltype' => $currentNode['channeltype']); } // additional data used for subscription notifications if (isset($data['subscriptionnodeid'])) { $subId = $data['subscriptionnodeid']; if (!isset($recipientIndependentDataArray[$contentnodeid]['add_sub_data'][$subId])) { //we need to load this using the correct library class or things get weird. //note that if we load it from cache here it will be in the local memory cache //when we load the full content and we won't do it twice. $cached = vB_Library_Content::fetchFromCache(array($subId), vB_Library_Content::CACHELEVEL_FULLCONTENT); if (isset($cached['found'][$subId])) { $contenttypeid = $cached['found'][$subId]['contenttypeid']; } else { $row = $this->assertor->getRow('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, vB_dB_Query::COLUMNS_KEY => array('contenttypeid'), 'nodeid' => $subId)); $contenttypeid = $row['contenttypeid']; } if ($contenttypeid) { $contentLib = vB_Library_Content::getContentLib($contenttypeid); $subbedNode = $contentLib->getContent($subId); $subbedNode = $subbedNode[$subId]; $channelTypeId = vB_Types::instance()->getContentTypeID('vBForum_Channel'); if ($subbedNode['contenttypeid'] == $channelTypeId) { $nodetype = 'channel'; } else { $nodetype = 'post'; } $recipientIndependentDataArray[$contentnodeid]['add_sub_data'][$subId] = array('title' => $subbedNode['title'], 'nodetype' => $nodetype); } } // We only expect channeltype_forum, channeltype_article, channeltype_blog, channeltype_group, but // a channeltype_<> phrase for each of vB_Channel::$channelTypes should exist, so we can do this $channeltype = "channeltype_" . $recipientIndependentDataArray[$contentnodeid]['channeltype']; $nodetype = $recipientIndependentDataArray[$contentnodeid]['add_sub_data'][$subId]['nodetype']; // While phrases are dependent on languageid, it's not really recipient or userid // dependent and is shared/static among different recipients, so let's keep track // of them. if (!isset($phrases[$data['languageid']])) { $phrases[$data['languageid']] = vB_Api::instanceInternal('phrase')->fetch(array($channeltype, $nodetype), $data['languageid']); } } // keep track of the about strings that are for "content" notifications $temporary = array(vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_REPLY, vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_COMMENT, vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_THREADCOMMENT, vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_SUBSCRIPTION, vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_USERMENTION); $contentNotificationAboutStrings = array(); foreach ($temporary as $aboutString) { $contentNotificationAboutStrings[$aboutString] = $aboutString; } if ($data['about'] == vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_VM) { // A VM should have only 1 recipient, so no good reason to cache this like other URLs. Also // VMs don't seem to work with the /node/ route, so we can't rely on that. $vmURL = vB5_Route::buildUrl('visitormessage|fullurl', array('nodeid' => $contentnodeid)); $maildata = vB_Api::instanceInternal('phrase')->fetchEmailPhrases('visitormessage', array($data['username'], $recipientIndependentDataArray[$contentnodeid]['authorname'], $vmURL, $recipientIndependentDataArray[$contentnodeid]['previewtext'], $options['bbtitle']), array(), $data['languageid']); } elseif ($data['about'] == vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_VOTE) { // Vote notifications have their own section because their phrases aren't in the same format as the others // Since a vote doesn't have a node associated with it, we don't have a "currentnode" data. // Note that the poll library sets aboutid & contentid both to the nodeid of the poll-post $maildata = vB_Api::instanceInternal('phrase')->fetchEmailPhrases('vote', array($data['username'], vB_Api::instanceInternal('user')->fetchUserName(vB::getCurrentSession()->get('userid')), $recipientIndependentDataArray[$contentnodeid]['nodeurl'], $options['bbtitle']), array($recipientIndependentDataArray[$contentnodeid]['startertitle']), $data['languageid']); } elseif (isset($contentNotificationAboutStrings[$data['about']])) { // Normally the subject would contain the topic title, but if it's a subscription let's pass in the // title of the actual subscription node whether it's a channel, thread, blog etc. $emailSubjectVars = array($recipientIndependentDataArray[$contentnodeid]['startertitle']); // Since subscription email subjects don't have a title, we'll include the title in // the email body as a 7th variable. $emailBodyVars = array($data['username'], $recipientIndependentDataArray[$contentnodeid]['authorname'], $recipientIndependentDataArray[$contentnodeid]['nodeurl'], $recipientIndependentDataArray[$contentnodeid]['previewtext'], $options['bbtitle'], $options['frontendurl'] . '/member/' . $data['userid'] . '/subscriptions'); // blog & social groups have special phrases, unless it's a subscription notification or a user mention. // If the latter, just send the generic subscription or user mention email. // As of 5.1.4, we do not expect to hit this block for blogs, because the people who *would* // receive this (the blog poster) are automatically subscribed to the blog channel, and // subscription notifications always trump other types of notifications. See VBV-13466 if ($recipientIndependentDataArray[$contentnodeid]['channeltype'] == 'blog' and $data['about'] != vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_SUBSCRIPTION and $data['about'] != vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_USERMENTION) { $mailPhrase = 'comment_blogentry'; } else { if ($recipientIndependentDataArray[$contentnodeid]['channeltype'] == 'group' and $data['about'] != vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_SUBSCRIPTION and $data['about'] != vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_USERMENTION) { $mailPhrase = 'comment_grouptopic'; } else { switch ($data['about']) { case vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_REPLY: $mailPhrase = 'reply_thread'; break; case vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_COMMENT: $mailPhrase = 'comment_post'; break; case vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_THREADCOMMENT: $mailPhrase = 'comment_thread'; break; case vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_SUBSCRIPTION: $mailPhrase = 'subscribed_thread'; // $subId, $nodetype, $channeltype aer set above as long as 'subscriptionnodeid' was passed in. if (!empty($subId)) { // A new post in your {2} {3} subscription: {1} $emailSubjectVars = array($recipientIndependentDataArray[$contentnodeid]['add_sub_data'][$subId]['title'], $phrases[$data['languageid']][$channeltype], $phrases[$data['languageid']][$nodetype]); // Since we removed the starter title from the subject, add it to the message. $emailBodyVars[] = $recipientIndependentDataArray[$contentnodeid]['startertitle']; } break; case vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_USERMENTION: $mailPhrase = 'usermention_post'; // subject: <username> mentioned you in <content item title> $emailSubjectVars = array($recipientIndependentDataArray[$contentnodeid]['authorname'], $recipientIndependentDataArray[$contentnodeid]['startertitle']); break; default: if ($recipientIndependentDataArray[$contentnodeid]['starter'] == $recipientIndependentDataArray[$contentnodeid]['parentid']) { $mailPhrase = 'reply_thread'; } else { $mailPhrase = 'reply_post'; } break; } } } $maildata = vB_Api::instanceInternal('phrase')->fetchEmailPhrases($mailPhrase, $emailBodyVars, $emailSubjectVars, $data['languageid']); } elseif ($data['about'] == vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_RATE) { $node = $recipientIndependentDataArray[$contentnodeid]; // It doesn't make sense to call blog & article starters as "threads", so just go with "post" if ($node['nodeid'] == $node['starter'] and ($node['channeltype'] == 'forum' or $node['channeltype'] == 'group')) { $mailPhrase = 'like_thread'; } else { $mailPhrase = 'like_post'; } $maildata = vB_Api::instanceInternal('phrase')->fetchEmailPhrases($mailPhrase, array($data['username'], vB_Api::instanceInternal('user')->fetchUserName($data['senderid']), $node['nodeurl'], $options['bbtitle']), array($options['bbtitle'])); } elseif ($data['about'] == vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_FOLLOW) { // the use of vB5_Route::buildUrl() below should be ok performant wise, because we're not expecting to send a bulk email for this // notification type. We should avoid hacky hacks (see above in the content notification section for avoiding vB5_Route::buildUrl() // to each recipient's subscription page) when forgivable. $maildata = vB_Api::instanceInternal('phrase')->fetchEmailPhrases('follow_approve', array($data['username'], vB_Api::instanceInternal('user')->fetchUserName($data['senderid']), vB5_Route::buildUrl('profile|fullurl', array('userid' => $data['senderid'], 'tab' => 'subscribed')), $options['bbtitle']), array($options['bbtitle'])); } elseif ($data['about'] == vB_Library_Content_Privatemessage::NOTIFICATION_TYPE_FOLLOWING) { /* Per dev chat discussion, this never existed. Since there doesn't seem to be a strong customer request for this, I'm gonna leave it out. I'm leaving this section in so that it's trivial to add in the future. */ $maildata = array(); } else { // We don't know how to handle this. $maildata = array(); } if (!empty($data['email']) and !empty($maildata)) { // Send the email vB_Mail::vbmail($data['email'], $maildata['subject'], $maildata['message'], false); } }
/** * Gets the damaged nodeids */ public function getDamagedNodes($params, $db, $check_only = false) { if ($check_only) { return isset($params['start']) and isset($params['end']) and isset($params['contenttypeid']); } else { $cleaner = vB::getCleaner(); $params = $cleaner->cleanArray($params, array('start' => vB_Cleaner::TYPE_UINT, 'end' => vB_Cleaner::TYPE_UINT, 'contenttypeid' => vB_Cleaner::TYPE_UINT)); $contentLib = vB_Library_Content::getContentLib($params['contenttypeid']); $tables = $contentLib->fetchTableName(); $sql = "SELECT DISTINCT node.nodeid FROM " . TABLE_PREFIX . "node AS node \n"; $where = array(); foreach ($tables as $table) { $sql .= "LEFT JOIN " . TABLE_PREFIX . "{$table} AS {$table} ON {$table}.nodeid = node.nodeid\n"; $where[] = "{$table}.nodeid IS NULL\n"; } $sql .= "WHERE (" . implode(' OR ', $where) . ")\n AND node.contenttypeid = " . $params['contenttypeid'] . " AND node.nodeid >= " . $params['start'] . " AND node.nodeid <=" . $params['end'] . "\n/**" . __FUNCTION__ . (defined('THIS_SCRIPT') ? '- ' . THIS_SCRIPT : '') . "**/"; $resultclass = 'vB_dB_' . $this->db_type . '_result'; $config = vB::getConfig(); if (isset($config['Misc']['debug_sql']) and $config['Misc']['debug_sql']) { echo "sql: {$sql}<br />\n"; } $result = new $resultclass($db, $sql); return $result; } }
public function canViewPostHistory($nodeid) { $postedithistory = vB::getDatastore()->getOption('postedithistory'); $node = vB_Library::instance('node')->getNodeBare($nodeid); return (bool) $postedithistory and vB_Library_Content::getContentLib($node['contenttypeid'])->getCanEdit($node); }
/** * Return whether the user can delete the node or not. Used by createcontent controller * * @param array $node Existing node data array including nodeid, starter, channelid, contenttypeid * @param boolean $specific (OPTIONAL) Whether to specifically check for hard delete or soft delete. By default * it is false, meaning it will check whether user can soft OR hard delete the node * @param boolean $hard (OPTIONAL) Only used if $specific is true. Whether it's checking if user can hard * delete (true) or soft delete (false) * * @return array key 'candelete': * boolean true if they can delete the node */ public function getCanDeleteForEdit($node, $specific = false, $hard = false) { // This function is only meant to be called from the createcontent controller's actionLoadEditor() // It's not meant to be very versatile. if (!is_array($node) or empty($node['nodeid']) or empty($node['starter']) or empty($node['channelid']) or empty($node['userid']) or empty($node['contenttypeid'])) { return array('candelete' => false); } // Let's grab some params that getCanDelete() requires if we want to use the $hard param. $userContext = vB::getUserContext(); $starter = vB_Library::instance('node')->getNodeBare($node['starter']); $channelid = $starter['parentid']; $channelPerms = vB::getUserContext()->fetchPermsForChannels(array($channelid)); $thisChannelPerms = $channelPerms[$channelid]; $thisChannelPerms['global'] = $channelPerms['global']; $contentLib = vB_Library_Content::getContentLib($node['contenttypeid']); if ($specific) { $canDelete = $contentLib->getCanDelete($node, $userContext, $thisChannelPerms, $hard); } else { $canDelete = ($contentLib->getCanDelete($node, $userContext, $thisChannelPerms, false) or $contentLib->getCanDelete($node, $userContext, $thisChannelPerms, true)); } return array("candelete" => $canDelete); }
function delete($nodeid) { if (empty($nodeid)) { return false; } // prevent deleting of top level channels if (in_array($nodeid, vB_Api::instanceInternal('content_channel')->fetchTopLevelChannelIds())) { throw new vB_Exception_Api('cant_delete_top_level'); } // get the direct children. $children_nodes = vB::getDbAssertor()->assertQuery('vBForum:getChildrenOnly', array('nodeid' => $nodeid)); $nodeids = array(); $children_by_type = array(); foreach ($children_nodes as $node) { $children_by_type[$node['contenttypeid']][$node['nodeid']] = $node['nodeid']; $nodeids[] = $node['nodeid']; } foreach ($children_by_type as $contenttypeid => $nodes) { $contentLib = vB_Library_Content::getContentLib($contenttypeid); $contentLib->deleteChildren($nodes); } if (!empty($nodeids)) { vB_Search_Core::instance()->deleteBulk($nodeids); } // deleting the node $success = parent::delete($nodeid); // delete pages and routes $this->deleteChannelPages($nodeid); vB_Cache::instance()->event('vB_ChannelStructure_chg'); vB::getUserContext()->rebuildGroupAccess(); vB_Channel::rebuildChannelTypes(); return $success; }
protected function getTitleAndText($node, $propagate = true) { try { $indexableContent = vB_Library_Content::getContentLib($node['contenttypeid'])->getIndexableContent($node, $propagate); } catch (Exception $e) { //whatever the reason, just ignore and move on $indexableContent = array(); } $title = ""; if (!empty($indexableContent['title'])) { $title = strtolower($indexableContent['title']); unset($indexableContent['title']); } //need to put a space between segments or we could concatenate words and distort our index $text = implode(' ', $indexableContent); $text = strtolower($text); return array($title, $text); }