public function getUrl() { if (empty($this->arguments['username'])) { $userInfo = vB_Api::instanceInternal('user')->fetchProfileInfo($this->arguments['userid']); $this->arguments['username'] = $userInfo['username']; } // the regex contains the url $url = '/' . $this->prefix . '/' . $this->arguments['userid'] . '-' . vB_String::getUrlIdent($this->arguments['username']) . '/' . $this->arguments['tab']; if (isset($this->arguments['pagenum']) and is_numeric($this->arguments['pagenum']) and $this->arguments['pagenum'] > 1) { $url .= '/page' . intval($this->arguments['pagenum']); } if (strtolower(vB_String::getCharset()) != 'utf-8') { $url = vB_String::encodeUtf8Url($url); } return $url; }
public function __construct($routeInfo, $matches, $queryString = '', $anchor = '') { if (empty($matches['tab'])) { $matches['tab'] = 'profile'; } parent::__construct($routeInfo, $matches, $queryString, $anchor); if (empty($this->arguments['userid'])) { $userInfo = vB::getCurrentSession()->fetch_userinfo(); $this->arguments['userid'] = $userInfo['userid']; $this->arguments['username'] = $userInfo['username']; } else { if (empty($this->arguments['username'])) { $userInfo = vB_User::fetchUserinfo($this->arguments['userid']); $this->arguments['username'] = $userInfo['username']; } } $this->breadcrumbs = array(0 => array('title' => $this->arguments['username'], 'url' => vB5_Route::buildUrl('profile', array('userid' => $this->arguments['userid'], 'username' => vB_String::getUrlIdent($this->arguments['username'])))), 1 => array('phrase' => 'user_settings', 'url' => '')); }
/** * Moves nodes to a new parent * * @param array Node ids * @param int New parent node id * @param bool Make topic * @param bool New title * @param bool Mod log * @param array Information to leave a thread redirect. If empty, no redirect is created. * If not empty, should contain these items: * redirect (string) - perm|expires Permanent or expiring redirect * frame (string) - h|d|w|m|y Hours, days, weeks, months, years (valid only for expiring redirects) * period (int) - 1-10 How many hours, days, weeks etc, for the expiring redirect * * @return */ public function moveNodes($nodeids, $to_parent, $makeTopic = false, $newtitle = false, $modlog = true, array $leaveRedirectData = array()) { $movedNodes = array(); $oldnodeInfo = array(); $to_parent = $this->assertNodeidStr($to_parent); $userContext = vB::getUserContext(); $currentUserid = vB::getCurrentSession()->get('userid'); $channelAPI = vB_Api::instanceInternal('content_channel'); $newparent = $this->getNode($to_parent); // If all the nodes to be moved are content nodes and are currently in the same // channel, then we are only merging posts/topics and need to check different permissions. // To detemine this, we iterate through all the nodes to be moved and get all the "starter" // nodeids. We then iterate through all the starters and check if they all have the same // parentid, which is the channel they are in. $isTopicMerge = false; $checkNodes = $this->getNodes(array_merge((array) $nodeids, (array) $to_parent)); $skipCheck = false; $checkStarterIds = array(); foreach ($checkNodes as $checkNode) { if (empty($checkNode['starter'])) { // we can skip this check because one of the nodes is a channel $skipCheck = true; break; } else { $checkStarterIds[] = $checkNode['starter']; } } if (!$skipCheck) { $checkStarters = $this->getNodes($checkStarterIds); $starterParents = array(); foreach ($checkStarters as $checkStarter) { $starterParents[$checkStarter['parentid']] = $checkStarter['parentid']; } if (count($starterParents) === 1) { // the parent node of all the starters is the same, so we are // only moving topics / posts around inside the same channel, // this is likely a topic merge request and we can check a separate // permission below $isTopicMerge = true; } unset($checkStarters, $checkStarter, $starterParents); } unset($checkNode, $checkStarterIds, $skipCheck); //If the current user has can moderator canmove on the current nodes, or if the user can create in //the new channel and is the owner of the moved nodes and has forum canmove, then they can move if (!$userContext->getChannelPermission('forumpermissions', 'canmove', $to_parent) and !$userContext->getChannelPermission('moderatorpermissions', 'canmassmove', $to_parent) and (!$isTopicMerge or !$userContext->getChannelPermission('moderatorpermissions', 'canmanagethreads', $to_parent))) { throw new vB_Exception_Api('no_permission'); } $nodes = vB::getDbAssertor()->getRows('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'nodeid' => $nodeids), array('field' => array('publishdate'), 'direction' => array(vB_dB_Query::SORT_ASC)), 'nodeid'); $needRebuild = false; $firstTitle = false; $loginfo = array(); $parent = $this->getNodeFullContent($to_parent); $parent = $parent[$to_parent]; $cacheEvents = array($to_parent); $oldparents = array(); $channelTypeid = vB_Types::instance()->getContentTypeId('vBForum_Channel'); $infractionTypeid = vB_Types::instance()->getContentTypeId('vBForum_Infraction'); foreach ($nodes as $node) { if ($node['contenttypeid'] == $infractionTypeid) { throw new vB_Exception_Api('cannot_move_infraction_nodes'); } if ($node['contenttypeid'] == $channelTypeid) { // If any of the moved nodes are channels, the target must be a channel. if ($newparent['contenttypeid'] != $channelTypeid) { throw new vB_Exception_Api('invalid_request'); } // We should not allow the moving of channels from one root channel to another. if ($channelAPI->getTopLevelChannel($newparent['nodeid']) != $channelAPI->getTopLevelChannel($node['nodeid'])) { throw new vB_Exception_Api('cant_change_top_level'); } } //Only channels can be moved to categories, UI shouldn't allow this if ($parent['contenttypeid'] == $channelTypeid) { $newrouteid = vB_Api::instanceInternal('route')->getChannelConversationRoute($to_parent); if ($node['contenttypeid'] != $channelTypeid and (empty($newrouteid) or !empty($parent['category']))) { // The node we want to move is not a channel and the parent cannot have conversations // (e.g. categories, the root blog channel, the root forum channel) throw new vB_Exception_Api('invalid_request'); } } if ($node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel')) { $needRebuild = true; } if ($userContext->getChannelPermission('moderatorpermissions', 'canmassmove', $node['nodeid']) or $currentUserid == $node['userid'] and $userContext->getChannelPermission('forumpermissions', 'canmove', $node['nodeid'], false, $node['parentid']) or $isTopicMerge and $userContext->getChannelPermission('moderatorpermissions', 'canmanagethreads', $to_parent)) { if (empty($movedNodes)) { if (empty($node['title']) and !empty($node['starter'])) { $starter = vB_Library::instance('node')->getNodeBare($node['starter']); $firstTitle = $starter['title']; } else { $firstTitle = $node['title']; } } $movedNodes[] = $node['nodeid']; $oldnodeInfo[$node['nodeid']] = $node; $oldparents[$node['nodeid']] = $node['parentid']; $this->contentLibs[$node['nodeid']] = vB_Library::instance('Content_' . vB_Types::instance()->getContentTypeClass($node['contenttypeid'])); if ($modlog) { $oldparent = $this->getNode($node['parentid']); $extra = array('fromnodeid' => $oldparent['nodeid'], 'fromtitle' => $oldparent['title'], 'tonodeid' => $newparent['nodeid'], 'totitle' => $newparent['title']); $loginfo[] = array('nodeid' => $node['nodeid'], 'nodetitle' => $node['title'], 'nodeusername' => $node['authorname'], 'nodeuserid' => $node['userid'], 'action' => $extra); } if (!in_array($node['parentid'], $cacheEvents)) { $cacheEvents[] = $node['parentid']; } if (!in_array($node['starter'], $cacheEvents) and intval($node['starter'])) { $cacheEvents[] = $node['starter']; } } else { throw new vB_Exception_Api('no_permission'); } } if (empty($movedNodes)) { return false; } //can't move a node to its decendant, we like proper trees. $db = vB::getDbAssertor(); $row = $db->getRow('vBForum:closure', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'parent' => $movedNodes, 'child' => $to_parent, vB_Db_Query::PARAM_LIMIT => 1)); if (!empty($row)) { throw new vB_Exception_Api('move_node_to_child'); } //back out counts for the nodes about to be moved. //keep track of the parentids to so we can update the last content after //we've moved things around. $lastContentParents = array(); foreach ($movedNodes as $nodeid) { $node = $this->getNode($nodeid); $movedNodeParents = $this->getParents($nodeid); $parentids = array(); foreach ($movedNodeParents as $movedNodeParent) { if ($movedNodeParent['nodeid'] != $nodeid) { $parentids[] = $movedNodeParent['nodeid']; $lastContentParents[] = $movedNodeParent['nodeid']; } } $this->updateAddRemovedNodeParentCounts($node, $node, $node['showpublished'], false, $parentids); } if ($parent['contenttypeid'] == $channelTypeid and $makeTopic) { if (empty($newtitle)) { if (!empty($firstTitle)) { $newtitle = $firstTitle; } else { throw new vB_Exception_Api('notitle'); } } $newchildid = $movedNodes[0]; $this->moveNodesInternal(array($newchildid), $newparent); // We need to promote give the new node the correct title vB::getDbAssertor()->assertQuery('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_UPDATE, 'routeid' => vB_Api::instanceInternal('route')->getChannelConversationRoute($to_parent), 'title' => $newtitle, 'htmltitle' => vB_String::htmlSpecialCharsUni(vB_String::stripTags($newtitle), false), 'urlident' => vB_String::getUrlIdent($newtitle), 'description' => $newtitle, vB_dB_Query::CONDITIONS_KEY => array('nodeid' => $movedNodes[0]))); if (count($movedNodes) > 1) { $grandchildren = array_slice($movedNodes, 1); $this->moveNodesInternal($grandchildren, $checkNodes[$newchildid]); } //moving the grandchildren under the first node may have changed it's last child info $db->assertQuery('vBForum:updateLastData', array('parentid' => $newchildid, 'timenow' => vB::getRequest()->getTimeNow())); $node = $this->getNode($newchildid); $this->updateSubTreePublishStatus($node, $newparent['showpublished']); } else { $this->moveNodesInternal($movedNodes, $newparent); foreach ($movedNodes as $nodeid) { $node = $this->getNode($nodeid); $this->updateSubTreePublishStatus($node, $newparent['showpublished']); } } //dedup the array (so we don't update a node more than once) but we need to //do so in a particular way. We always want to keep the *last* occurance //of any id in the array. This ensures that a node is not updated before //any of its decendants (which could cause bad results). $seen = array(); foreach ($lastContentParents as $key => $parentid) { if (isset($seen[$parentid])) { unset($lastContentParents[$seen[$parentid]]); } $seen[$parentid] = $key; } //we can't do this before we move the nodes because the parent/child relationships haven't //changed yet. foreach ($lastContentParents as $parentid) { $this->fixNodeLast($parentid); } $userid = vB::getCurrentSession()->get('userid'); // afterMove requires some ancestors info which we just changed above, let's clear cache before updating $searchAPI = vB_Api::instanceInternal('search'); foreach ($movedNodes as $nodeid) { vB_Cache::instance()->allCacheEvent('nodeChg_' . $nodeid); //some search information may have changed, let's check $searchAPI->attributeChanged($nodeid); } // Leave a thread redirect if required // Note: UI only allows leaving a redirect when moving a thread which is one node if (!empty($leaveRedirectData) and count($movedNodes) == 1 and count($nodes) == 1) { $node = reset($nodes); $redirectData = array('title' => $node['title'], 'urlident' => $node['urlident'], 'parentid' => $node['parentid'], 'tonodeid' => $node['nodeid'], 'userid' => $node['userid'], 'publishdate' => $node['publishdate'], 'created' => $node['created']); // handle expiring redirects if (isset($leaveRedirectData['redirect']) and $leaveRedirectData['redirect'] == 'expires') { $period = (int) isset($leaveRedirectData['period']) ? $leaveRedirectData['period'] : 1; $frame = (string) isset($leaveRedirectData['frame']) ? $leaveRedirectData['frame'] : 'm'; $period = max(min($period, 10), 1); $frame = in_array($frame, array('h', 'd', 'w', 'm', 'y'), true) ? $frame : 'm'; $frames = array('h' => 3600, 'd' => 86400, 'w' => 86400 * 7, 'm' => 86400 * 30, 'y' => 86400 * 365); $redirectData['unpublishdate'] = vB::getRequest()->getTimeNow() + $period * $frames[$frame]; } // skip any text spam checks, because a redirect has no text to check. vB_Library::instance('content_redirect')->add($redirectData, array('skipSpamCheck' => true)); } vB_Api::instance('Search')->purgeCacheForCurrentUser(); vB_Library_Admin::logModeratorAction($loginfo, 'node_moved_by_x'); $cacheEvents = array_unique(array_merge($movedNodes, $cacheEvents, array($to_parent))); $this->clearChildCache($cacheEvents); $this->clearCacheEvents($cacheEvents); if ($needRebuild) { vB::getUserContext()->rebuildGroupAccess(); vB_Channel::rebuildChannelTypes(); } return $movedNodes; }
public function getUrl() { if (empty($this->arguments['title'])) { $node = vB_Library::instance('node')->getNodeBare($this->arguments['nodeid']); if (empty($node) or !empty($node['errors'])) { return FALSE; } if ($node['urlident']) { $this->arguments['title'] = $node['urlident']; } else { $this->arguments['title'] = vB_String::getUrlIdent($node['title']); } } if (empty($this->arguments['userid'])) { if (!isset($node['nodeid'])) { $node = vB_Library::instance('node')->getNodeBare($this->arguments['nodeid']); } if ($node['setfor']) { $user = vB_User::fetchUserinfo($node['setfor']); $this->arguments['userid'] = $user['userid']; $this->arguments['username'] = $user['username']; } } $url = '/member/' . $this->arguments['userid'] . '-' . vB_String::getUrlIdent($this->arguments['username']) . '/visitormessage/' . $this->arguments['nodeid'] . '-' . vB_String::vBStrToLower(vB_String::htmlSpecialCharsUni(str_replace(' ', '-', $this->arguments['title']))); if (strtolower(vB_String::getCharset()) != 'utf-8') { $url = vB_String::encodeUtf8Url($url); } return $url; }
/** * Updates from a web save * * @param int The id in the primary table * * @return int Number of updates-standard save response. */ public function updateFromWeb($nodeid, $postdata, $filedataids = array()) { //First do we have a nodeid? if (!$nodeid or !intval($nodeid) or !$this->validate($postdata, parent::ACTION_UPDATE, $nodeid)) { throw new Exception('invalid_data'); } $data = array(); //And are we authorized to make changes? if (!$this->validate($data, parent::ACTION_UPDATE, $nodeid)) { throw new Exception('no_permission'); } if (isset($postdata['title'])) { $postdata['urlident'] = vB_String::getUrlIdent($postdata['title']); } $existing = $this->getContent($nodeid); $existing = $existing[$nodeid]; $cleaner = vB::getCleaner(); //clean the gallery data. $fields = array('title' => vB_Cleaner::TYPE_STR, 'caption' => vB_Cleaner::TYPE_STR, 'htmltitle' => vB_Cleaner::TYPE_STR, 'rawtext' => vB_Cleaner::TYPE_STR, 'reason' => vB_Cleaner::TYPE_STR, 'keyfields' => vB_Cleaner::TYPE_STR, 'publishdate' => vB_Cleaner::TYPE_UINT, 'unpublishdate' => vB_Cleaner::TYPE_UINT, 'description' => vB_Cleaner::TYPE_STR, 'displayorder' => vB_Cleaner::TYPE_UINT, 'urlident' => vB_Cleaner::TYPE_STR, 'tags' => vB_Cleaner::TYPE_STR, 'enable_comments' => vB_Cleaner::TYPE_BOOL, 'parentid' => vB_Cleaner::TYPE_UINT, 'viewperms' => vB_Cleaner::TYPE_UINT, 'attachments' => vB_Cleaner::TYPE_NOCLEAN, 'removeattachments' => vB_Cleaner::TYPE_NOCLEAN); // copy data before they're dropped by the cleaner. These will be cleaned separately just a few lines down. $unclean['attachments'] = isset($postdata['attachments']) ? $postdata['attachments'] : array(); $unclean['removeattachments'] = isset($postdata['removeattachments']) ? $postdata['removeattachments'] : array(); $cleaned = $cleaner->cleanArray($postdata, $fields); // just unset the uncleaned ones. They're cleaned & set again below. I would've just unset them from // $postdata before it was tossed into cleanArray(), but there's special logic a few blocks down that // requires keys in $fields to be set in both $postdata & $cleaned for it to be sent into update() unset($cleaned['attachments']); unset($cleaned['removeattachments']); if (!empty($unclean['attachments'])) { // keep these fields in sync with controller's addAttachments() $attachfields = array('filedataid' => vB_Cleaner::TYPE_UINT, 'filename' => vB_Cleaner::TYPE_STR, 'settings' => vB_Cleaner::TYPE_STR); foreach ($unclean['attachments'] as $key => $attachdata) { $key = (int) $key; $cleaned['attachments'][$key] = $cleaner->cleanArray($attachdata, $attachfields); } unset($unclean['attachments']); } if (!empty($unclean['removeattachments'])) { // keep these fields in sync with controller's addAttachments() foreach ($unclean['removeattachments'] as $key => $attachnodeid) { $key = (int) $key; $cleaned['removeattachments'][$key] = (int) $attachnodeid; } unset($unclean['removeattachments']); } $updates = array(); //viewperms can only be 0, 1, or 2 if (empty($cleaned['viewperms']) or $cleaned['viewperms'] > 2 or $cleaned['viewperms'] < 0) { $cleaned['viewperms'] = 2; } /* * Okay, I"m pretty sure the below isn't doing what was originally intended, * judging by the comment & the fact that we grab the pre-update node values, * $existing, above. * I'm guessing what was *supposed* to happen is that each $iteam in $cleaned * is compared against $existing's data, and is set to $updates only if it's * different. * However, there's been a lot of changes to the various bits of content * update() code, so I'm afraid to unset things from $updates now. * If anyone is going to edit below, make sure that handling for * $cleaned['attachments']and $cleaned['removeattachments'] are proplery * dealt with, otherwise there will be issues when editing a gallery post * and adding/removing attachments. */ //If nothing has changed we don't need to update the parent. foreach (array_keys($fields) as $fieldname) { if (isset($postdata[$fieldname]) and isset($cleaned[$fieldname])) { $updates[$fieldname] = $cleaned[$fieldname]; } } $results = true; if (!empty($updates)) { $results = $this->update($nodeid, $updates); } if ($results and (!is_array($results) or empty($results['errors']))) { //let's get the current photo information; $existing = $this->getContent($nodeid); $existing = $existing[$nodeid]; if (empty($existing['photo'])) { $delete = array(); } else { $delete = $existing['photo']; } //Now we match the submitted data against the photos //if they match, we remove from "delete" and do nothing else. //if the title is updated we do an immediate update. //Otherwise we add. if (!empty($filedataids) and is_array($filedataids)) { $photoApi = vB_Api::instanceInternal('content_photo'); foreach ($filedataids as $filedataid => $title) { //it has to be at least a integer. if (intval($filedataid)) { //First see if we have a match. $foundMatch = false; foreach ($delete as $photoNodeid => $photo) { if ($filedataid == $photo['filedataid']) { $foundMatch = $photo; unset($delete[$photoNodeid]); break; } } if ($foundMatch) { if ($title != $foundMatch['title']) { $titles[$foundMatch['nodeid']] = $title; } //unset this record. //Skip to the next record continue; } //If we got here then this is new and must be added. //We do an add. $photoApi->add(array('parentid' => $nodeid, 'caption' => $title, 'title' => $title, 'filedataid' => intval($filedataid))); } } if (!empty($delete)) { foreach ($delete as $photo) { $photoApi->delete($photo['nodeid']); } } if (!empty($titles)) { foreach ($titles as $photonodeid => $title) { $photoApi->update($photonodeid, array('caption' => $title, 'title' => $title)); } } } } return $results; }
/** * Performs the merge of content and updates the node. * @param type $data * @return type */ public function mergeContent($data) { // modify tables records (only one record will be modified due to constraints) $sources = array_diff($data['mergePosts'], array($data['destnodeid'])); $db = vB::getDbAssertor(); $db->update('poll', array('nodeid' => $data['destnodeid']), array(array('field' => 'nodeid', 'value' => $sources))); $db->update('vBForum:node', array('contenttypeid' => $this->contenttypeid), array('nodeid' => $data['destnodeid'])); // get videoitems $polloptions = array(); foreach ($data as $key => $value) { if (preg_match('#^polloptions\\[([\\d]+)#', $key, $matches)) { $polloptions[] = array('polloptionid' => intval($matches[1]), 'title' => trim($value)); } else { if (preg_match('^polloptions\\[new', $key, $matches)) { foreach ($value as $option) { $polloptions[]['title'] = trim($option); } } } } $pollData = array('title' => $data['title'], 'rawtext' => $data['text'], 'userid' => $data['destauthorid'], 'urlident' => vB_String::getUrlIdent($data['title']), 'options' => $polloptions, 'multiple' => $data['multiple'], 'public' => $data['public'], 'parseurl' => $data['parseurl'], 'timeout' => strtotime($data['timeout'])); return vB_Api::instanceInternal('content_poll')->update($data['destnodeid'], $pollData); }
public function getUrl() { if (!empty($this->arguments['userid']) and !empty($this->arguments['username'])) { $result = '/' . $this->prefix . '/' . $this->arguments['userid'] . '-' . vB_String::getUrlIdent($this->arguments['username']); } else { return false; } // append the tab to URL only if it's a valid tab. if (isset($this->arguments['tab'])) { if (isset(self::$availableTabs[$this->arguments['tab']])) { $result .= '/' . $this->arguments['tab']; if (isset(self::$doNotIndexTabs[$this->arguments['tab']])) { $this->arguments['noindex'] = true; } // append the page number if pagenum argument is set & if a tab with pagination is set if (isset($this->arguments['pagenum']) and is_numeric($this->arguments['pagenum']) and $this->arguments['pagenum'] > 1 and isset(self::$tabsWithPagination[$this->arguments['tab']])) { $result .= '/page' . intval($this->arguments['pagenum']); } } else { // invalid tab, unset it unset($this->arguments['tab']); } } if (strtolower(vB_String::getCharset()) != 'utf-8') { $result = vB_String::encodeUtf8Url($result); } return $result; }
/** * Cleans the input in the $data array, directly updating $data. * * @param mixed Array of fieldname => data pairs, passed by reference. * @param int|false Nodeid of the node being edited, false if creating new */ public function cleanInput(&$data, $nodeid = false) { if (isset($data['userid'])) { unset($data['userid']); } if (isset($data['authorname'])) { unset($data['authorname']); } // These fields should be cleaned regardless of the user's canusehtml permission. $cleaner = vB::getCleaner(); foreach (array('title', 'htmltitle', 'description', 'prefixid', 'caption') as $fieldname) { if (isset($data[$fieldname])) { $data[$fieldname] = $cleaner->clean($data[$fieldname], vB_Cleaner::TYPE_NOHTML); } } foreach (array('open', 'showopen', 'approved', 'showapproved') as $fieldname) { if (isset($data[$fieldname])) { $data[$fieldname] = $cleaner->clean($data[$fieldname], vB_Cleaner::TYPE_INT); } } if (isset($data['urlident'])) { // Let's make sure it's a valid identifier. No spaces, UTF-8 encoded, etc. $data['urlident'] = vB_String::getUrlIdent($data['urlident']); } // These fields are cleaned for people who cannot use html foreach (array('pagetext') as $fieldname) { if (isset($data[$fieldname])) { $data[$fieldname] = $cleaner->clean($data[$fieldname], vB_Cleaner::TYPE_NOHTML); } } if (!empty($data['nodeid'])) { $checkNodeid = $data['nodeid']; } else { if (!empty($data['parentid'])) { $checkNodeid = $data['parentid']; } else { if ($nodeid) { $checkNodeid = $nodeid; } } } //Channels are handled a bit differently. if (isset($this->contenttype)) { $isChannel = $this->contenttype == 'vBForum_Channel'; } else { if (isset($data['contenttypeid'])) { $isChannel = $data['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel'); } else { if (!empty($nodeid)) { $node = $this->nodeApi->getNode($nodeid); $isChannel = $node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel'); } else { if (!empty($data['nodeid'])) { $node = $this->nodeApi->getNode($data['nodeid']); $isChannel = $node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel'); } } } } if (!empty($isChannel)) { //if this is an update we do nothing. If publishdate is already set we do nothing. if (empty($nodeid) and !isset($data['publishdate'])) { $data['publishdate'] = vB::getRequest()->getTimeNow(); } } else { if (!empty($checkNodeid)) { $checkNode = $this->nodeApi->getNodeFullContent($checkNodeid); $checkNode = array_pop($checkNode); $limits = vB::getUserContext()->getChannelLimits($checkNode['channelid']); // VBV-12342 - If this is a new node & the usergroup requires moderation on this channel, we need to // set approved & showapproved to 0. Do not mess with publishdate. If it's not an article, it'll be // published immediately in the publishdate handling below. if (!empty($limits['require_moderate']) and empty($node)) { $data['approved'] = 0; $data['showapproved'] = 0; } /* PUBLISHDATE HANDLING */ // For articles the handling is more complex if ($checkNode['channeltype'] == 'article') { $publish = true; if (!empty($nodeid)) { $node = $this->nodeApi->getNode($nodeid); $starter = $node['nodeid'] == $node['starter']; } else { if (!empty($data['nodeid'])) { $node = $this->nodeApi->getNode($data['nodeid']); $starter = $node['nodeid'] == $node['starter']; } else { $node = $this->nodeApi->getNode($data['parentid']); $starter = $node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel'); } } if ($starter) { $canpublish = vB::getUserContext()->getChannelPermission('forumpermissions2', 'canpublish', $checkNode['channelid']); //if this is a add (we don't have a nodeid) AND the user can't publish, we force publishdate to zero if (!$canpublish or isset($data['publish_now']) and $data['publish_now'] === false) { // if the user can't publish, but can create, then we save as a draft. $data['publish_now'] = false; $publish = false; if ($nodeid) { unset($data['publishdate']); } else { $data['publishdate'] = 0; } } } if ($publish) { if (empty($data['publishdate']) or !vB::getUserContext()->getChannelPermission('forumpermissions2', 'canpublish', $checkNode['channelid'])) { $data['publishdate'] = vB::getRequest()->getTimeNow(); } } } else { if (!$nodeid) { if (empty($data['publishdate']) or !vB::getUserContext()->getChannelPermission('forumpermissions2', 'canpublish', $checkNode['channelid'])) { $data['publishdate'] = vB::getRequest()->getTimeNow(); } } } } else { if (!$nodeid) { /* * I'm not certain what condition this code branch is reached. If this is a new node, * then $checkNodeid will probably be the parentid. If we don't have the parentid, we * can't even check the channel limits for require_moderate. As such I'm not making * any code changes in this area for VBV-12342. */ $data['publishdate'] = vB::getRequest()->getTimeNow(); } } } }
/** * Replaces special characters in a given string with dashes to make the string SEO friendly * * @param string The string to be converted */ protected function toSeoFriendly($str) { if (!empty($str)) { return vB_String::getUrlIdent($str); } return $str; }
/** * updates a record * * @param mixed array of nodeid's * @param mixed array of permissions that should be checked. * * @return boolean */ public function update($nodeid, $data) { $channelContentTypeId = vB_Types::instance()->getContentTypeId('vBForum_Channel'); // Verify prefixid if ($this->contenttypeid != $channelContentTypeId and isset($data['prefixid'])) { $this->verifyPrefixid($data['prefixid']); } else { // Channel can't have a prefix unset($data['prefixid']); } // Verify post iconid if ($this->contenttypeid != $channelContentTypeId and isset($data['iconid'])) { $this->verifyPostIconid($data['iconid']); } else { // Channels can't have a post icon unset($data['iconid']); } $timeNow = vB::getRequest()->getTimeNow(); $userContext = vB::getUserContext(); //If this user doesn't have the featured permission and they are trying to set it, //Let's just quietly unset it. if (isset($data['featured'])) { if (!$userContext->getChannelPermission('moderatorpermissions', 'cansetfeatured', $data['parentid'])) { unset($data['featured']); } } //We can't allow directly setting parentid. That should only happen through the node api move function //And there are number of other fields that shouldn't be changed here. We have methods for the ones that can be changed at all. foreach (array('open', 'showopen', 'approved', 'showapproved', 'protected') as $field) { if (isset($data[$field])) { unset($data[$field]); } } if (isset($data['parentid'])) { //Only allow for articles. $content = $this->nodeApi->getNodeFullContent($nodeid); $content = array_pop($content); // you can't move it to the category it's already in if ($data['parentid'] != $content['parentid']) { // only allow this for articles (currently) if ($content['channeltype'] == 'article') { if (!$userContext->getChannelPermission('forumpermissions', 'canmove', $data['parentid']) and !$userContext->getChannelPermission('moderatorpermissions', 'canmassmove', $data['parentid'])) { throw new vB_Exception_Api('no_permission'); } //If we got here, we're O.K. to move. let's do that now. vB_Library::instance('node')->moveNodes($nodeid, $data['parentid']); } } unset($data['parentid']); } //We need to see if we need to update. $prior = vB_Library::instance('node')->getNodeBare($nodeid); if ($this->contenttypeid != $channelContentTypeId) { $content = $this->getFullContent($nodeid); } if (isset($data['publish_now']) and !empty($data['publish_now'])) { $data['publishdate'] = vB::getRequest()->getTimeNow(); } if (empty($data['htmltitle']) and !empty($data['title'])) { $data['htmltitle'] = vB_String::htmlSpecialCharsUni(vB_String::stripTags($data['title']), false); } if (empty($data['urlident']) and !empty($data['title'])) { $data['urlident'] = vB_String::getUrlIdent($data['title']); } // Do not change publishdate or showpublished status unless it was explicitly set while calling update(). if ((!isset($data['publishdate']) or empty($data['publishdate']) and $data['publishdate'] !== 0) and !empty($prior['publishdate'])) { $data['publishdate'] = $prior['publishdate']; } if ($this->isPublished($data)) { $published = 1; } else { $published = 0; } $nodevals = array(); if ($published != $prior['showpublished']) { $nodevals['showpublished'] = $published; } // set default node options if ((empty($data['nodeoptions']) or !is_numeric($data['nodeoptions'])) and $prior['contenttypeid'] != $channelContentTypeId) { $parentFullContent = vB_Library::instance('node')->getNodeFullContent($prior['parentid']); if (!empty($parentFullContent[$prior['parentid']]['channeltype'])) { $data['nodeoptions'] = self::$defaultNodeOptions[$parentFullContent[$prior['parentid']]['channeltype']]; } else { $data['nodeoptions'] = self::$defaultNodeOptions['default']; } // Add or remove any nodeoptions that have been explicitly passed in. // This would have otherwise happened in updateNodeOptions/setNodeOptions // (which is where it happens when adding a node as opposed to updating // a node), but since $data['nodeoptions'] is now defined, setNodeOptions // won't take care of setting these (it will just apply the int // nodeoptions value). $baseNodeOptions = vB_Api::instanceInternal('node')->getOptions(); foreach ($baseNodeOptions as $baseOptionKey => $baseOptionVal) { if (isset($data[$baseOptionKey])) { if (intval($data[$baseOptionKey])) { $data['nodeoptions'] = $data['nodeoptions'] | intval($baseOptionVal); } else { $data['nodeoptions'] = $data['nodeoptions'] & ~intval($baseOptionVal); } } } } //node table data. $data[vB_dB_Query::TYPE_KEY] = vB_dB_Query::QUERY_UPDATE; $data['nodeid'] = $nodeid; $data['lastupdate'] = $timeNow; //If the field passed is in the $nodeFields array then we update the node table. foreach ($data as $field => $value) { if (in_array($field, $this->nodeFields)) { $nodevals[$field] = $value; } } $index = empty($data['noIndex']); unset($data['noIndex']); // Update the content-type specific data if (!is_array($this->tablename)) { $tables = array($this->tablename); } else { $tables = $this->tablename; } $success = true; foreach ($tables as $table) { $structure = $this->assertor->fetchTableStructure('vBForum:' . $table); if (empty($structure) or empty($structure['structure'])) { throw new vB_Exception_Api('invalid_query_parameters'); } $queryData = array(); $queryData[vB_dB_Query::TYPE_KEY] = vB_dB_Query::QUERY_UPDATE; $queryData['nodeid'] = $nodeid; foreach ($structure['structure'] as $fieldname) { if (isset($data[$fieldname])) { $queryData[$fieldname] = $data[$fieldname]; } } //Now we have at least a query type and a nodeid. We put those in above. So if we don't //have at least one other value there's no reason to try an update. if (count($queryData) > 2) { $success = $success and $this->assertor->assertQuery('vBForum:' . $table, $queryData); } } if ($success) { // Handle Attachments // The text library (and most derivatives) can have attachments, // for the others, this is a no-op. $this->handleAttachments('update', $nodeid, $data); //Clear cached query info that would be significantly impacted $events = array('fUserContentChg_' . $prior['userid']); if ($prior['starter']) { $starterNodeInfo = vB_Library::instance('node')->getNodeBare($prior['starter']); $events[] = 'fUserContentChg_' . $starterNodeInfo['userid']; } else { if ($prior['parentid']) { $starterNodeInfo = vB_Library::instance('node')->getNodeBare($prior['parentid']); $events[] = 'fUserContentChg_' . $starterNodeInfo['userid']; } } $this->nodeApi->clearCacheEvents($nodeid); vB_Cache::instance()->allCacheEvent($events); if (isset($nodevals['publishdate']) and $nodevals['publishdate'] > $timeNow) { if (empty($nodevals['unpublishdate']) or $nodevals['unpublishdate'] > $nodevals['publishdate']) { $nodevals['nextupdate'] = $nodevals['publishdate']; } } else { if (isset($nodevals['unpublishdate']) and $nodevals['unpublishdate'] > $timeNow) { $nodevals['nextupdate'] = $nodevals['unpublishdate']; } } // handle approved if (isset($nodevals['approved'])) { if ($nodevals['approved']) { $approved = 1; $queryName = 'approveNode'; } else { $approved = 0; $queryName = 'unapproveNode'; } // set approved to parent... $this->assertor->assertQuery('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_UPDATE, 'nodeid' => $nodeid, 'approved' => $approved)); // and handle showapproved $this->assertor->assertQuery('vBForum:' . $queryName, array('nodeid' => $nodeid)); unset($nodevals['approved']); } if (isset($nodevals)) { $nodevals[vB_dB_Query::TYPE_KEY] = vB_dB_Query::QUERY_UPDATE; $nodevals['nodeid'] = $nodeid; $success = $this->assertor->assertQuery('vBForum:node', $nodevals); } //We need to compare the current publishdate and unpublishdate values against the // parent. //But we can skip this if neither publish or unpublishdate is set $updateParents = false; if ($published != $prior['showpublished']) { $updateParents = true; //We are concerned about two possibilities. It could have gone from published to unpublished. //In either case we change by totalcount +1 (for ourselves. //Remember that published is always unpublished. //From unpublished to published. if ($published) { $nodeUpdates = $this->nodeLibrary->publishChildren($nodeid); // if $nodeUpdates is empty, that means no change was made to this node or its descendants, // and no parent count changes are necessary. If it's not empty but doesn't have totalcount set, // that means it possibly failed with a DB error. In such a case, we will just not update the // counts but continue updating the node. if (!empty($nodeUpdates) and isset($nodeUpdates['totalcount'])) { // text-counts only change by 1 (or 0 for non-text types), because it only affects the immediate parent $textChange = $this->textCountChange; $textUnPubChange = -1 * $textChange; // Note, below assumes that a DB had been diligent about // keeping track of the count fields correctly. $totalPubChange = $nodeUpdates['totalcount'] - $prior['totalcount'] + $textChange; // we add the text change because self counts for ancestors' total counts $totalUnPubChange = -1 * $totalPubChange; } else { $updateParents = false; } } else { $nodeUpdates = $this->nodeLibrary->unpublishChildren($nodeid); if (!empty($nodeUpdates) and isset($nodeUpdates['totalunpubcount'])) { $textUnPubChange = $this->textCountChange; $textChange = -1 * $textUnPubChange; $totalUnPubChange = $nodeUpdates['totalunpubcount'] - $prior['totalunpubcount'] + $textUnPubChange; $totalPubChange = -1 * $totalUnPubChange; } else { $updateParents = false; } } vB_Library::instance('node')->clearChildCache($nodeid); } //update the parent count if necessary if ($updateParents) { vB_Library::instance('node')->updateParentCounts($nodeid, $textChange, $textUnPubChange, $totalPubChange, $totalUnPubChange, $published); } //update viewperms from childs if needed, do we want this channel specific? if (isset($nodevals['viewperms']) and isset($prior['viewperms']) and $nodevals['viewperms'] != $prior['viewperms']) { vB_Api::instanceInternal('node')->setNodePerms($nodeid, array('viewperms' => $nodevals['viewperms'])); } if ($index) { vB_Api::instanceInternal('Search')->index($nodeid); } // update user tags $tags = !empty($data['tags']) ? explode(',', $data['tags']) : array(); $tagRet = vB_Api::instanceInternal('tags')->updateUserTags($nodeid, $tags); $this->updateNodeOptions($nodeid, $data); // Update childs nodeoptions $this->assertor->assertQuery('vBForum:updateChildsNodeoptions', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED, 'parentid' => $nodeid)); $this->nodeApi->clearCacheEvents(array($nodeid, $prior['parentid'])); $loginfo = array('nodeid' => $prior['nodeid'], 'nodetitle' => $prior['title'], 'nodeusername' => $prior['authorname'], 'nodeuserid' => $prior['userid']); $extra = array(); if ($nodevals !== null && isset($nodevals['title'])) { if ($prior['title'] != $nodevals['title']) { $extra = array('newtitle' => $nodevals['title']); } } vB_Library_Admin::logModeratorAction($loginfo, 'node_edited_by_x', $extra); $updateEditLog = true; if (!vB::getUserContext()->hasPermission('genericoptions', 'showeditedby') and (isset($content[$nodeid]['edit_reason']) and $data['reason'] == $content[$nodeid]['edit_reason'] or !isset($content[$nodeid]['edit_reason']) and empty($data['reason']))) { $updateEditLog = false; } // Clear autosave table of this items entry if (vB::getCurrentSession()->get('userid') and !empty($data['rawtext'])) { $this->assertor->delete('vBForum:autosavetext', array('userid' => vB::getCurrentSession()->get('userid'), 'nodeid' => $nodeid, 'parentid' => $content[$nodeid]['parentid'])); } // Log edit by info if ($updateEditLog and $this->contenttypeid != $channelContentTypeId and isset($content[$nodeid]['rawtext']) and isset($data['rawtext']) and $content[$nodeid]['rawtext'] != $data['rawtext'] and !empty($data['publishdate']) and $prior['publishdate'] and (!empty($data['reason']) or $data['publishdate'] < vB::getRequest()->getTimeNow() - $this->options['noeditedbytime'] * 60)) { $userinfo = vB::getCurrentSession()->fetch_userinfo(); // save the postedithistory if ($this->options['postedithistory']) { $record = $this->assertor->getRow('vBForum:postedithistory', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'original' => 1, 'nodeid' => $nodeid)); // insert original post on first edit if (empty($record)) { $this->assertor->assertQuery('vBForum:postedithistory', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_INSERT, 'nodeid' => $nodeid, 'userid' => $content[$nodeid]['userid'], 'username' => $content[$nodeid]['authorname'], 'dateline' => $data['publishdate'], 'pagetext' => $content[$nodeid]['rawtext'], 'original' => 1)); } // insert the new version $this->assertor->assertQuery('vBForum:postedithistory', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_INSERT, 'nodeid' => $nodeid, 'userid' => $userinfo['userid'], 'username' => $userinfo['username'], 'dateline' => vB::getRequest()->getTimeNow(), 'reason' => isset($data['reason']) ? vB5_String::htmlSpecialCharsUni($data['reason']) : '', 'pagetext' => isset($data['rawtext']) ? $data['rawtext'] : '')); } $this->assertor->assertQuery('editlog_replacerecord', array('nodeid' => $nodeid, 'userid' => $userinfo['userid'], 'username' => $userinfo['username'], 'timenow' => vB::getRequest()->getTimeNow(), 'reason' => isset($data['reason']) ? vB5_String::htmlSpecialCharsUni($data['reason']) : '', 'hashistory' => intval($this->options['postedithistory']))); } return true; } $this->nodeApi->clearCacheEvents(array($nodeid, $prior['parentid'])); return false; }
/** This creates an urlident from a title, and guarantees it will not be a duplicate * * @param string the title * @return string **/ public function getUniqueUrlIdent($title) { //first see if this is good. Note that we don't care if it's used for a node that isn't a channel. $candidate = vB_String::getUrlIdent($title); $check = $this->assertor->getRow('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'urlident' => $candidate, 'contenttypeid' => $this->contenttypeid)); if (empty($check) or !empty($check['errors'])) { return $candidate; } //So now we try adding _a, b, c, etc. If we get to z we'll throw an exception. $charVal = ord('a'); $charVal2 = ord('a'); while (true) { if ($charVal >= ord('z') and $charVal2 >= ord('z')) { throw new vB_Exception_Api('invalid_data' . $candidate . '-' . chr($charVal) . chr($charVal2)); } $check = $this->assertor->getRow('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'urlident' => $candidate . '-' . chr($charVal) . chr($charVal2), 'contenttypeid' => $this->contenttypeid)); if (empty($check) or !empty($check['errors'])) { return $candidate . '-' . chr($charVal) . chr($charVal2); } $charVal2++; if ($charVal2 >= ord('z')) { $charVal++; $charVal2 = ord('a'); } } }