/** * Create an article category channel. This function works basically like the blog library's version * * @param array $input data array, should have standard channel data like title, parentid, * @param int $channelid parentid that the new channel should fall under. * @param int $channelConvTemplateid "Conversation" level pagetemplate to use. Typically vB_Page::getArticleConversPageTemplate() * @param int $channelPgTemplateId "Channel" level pagetemplate to use. Typically vB_Page::getArticleChannelPageTemplate() * @param int $ownerSystemGroupId * * @return int The nodeid of the new blog channel */ public function createChannel($input, $channelid, $channelConvTemplateid, $channelPgTemplateId, $ownerSystemGroupId) { if (!isset($input['parentid']) or intval($input['parentid']) < 1) { $input['parentid'] = $channelid; } $input['inlist'] = 1; // we don't want it to be shown in channel list, but we want to move them $input['protected'] = 0; if (empty($input['userid'])) { $input['userid'] = vB::getCurrentSession()->get('userid'); } if (!isset($input['publishdate'])) { $input['publishdate'] = vB::getRequest()->getTimeNow(); } $input['templates']['vB5_Route_Channel'] = $channelPgTemplateId; $input['templates']['vB5_Route_Article'] = $channelConvTemplateid; $input['childroute'] = 'vB5_Route_Article'; // add channel node $channelLib = vB_Library::instance('content_channel'); $input['page_parentid'] = 0; $result = $channelLib->add($input, array('skipNotifications' => true, 'skipFloodCheck' => true, 'skipDupCheck' => true)); //Make the current user the channel owner. $userApi = vB_Api::instanceInternal('user'); $usergroup = vB::getDbAssertor()->getRow('usergroup', array('systemgroupid' => $ownerSystemGroupId)); vB_Cache::allCacheEvent(array('nodeChg_' . $this->articleHomeChannel, "nodeChg_{$channelid}")); vB::getUserContext()->rebuildGroupAccess(); vB_Channel::rebuildChannelTypes(); // clear follow cache vB_Api::instanceInternal('follow')->clearFollowCache(array($input['userid'])); return $result['nodeid']; }
/** * Create a blog channel. * * @param array $input * @param int $channelid * @param int $channelConvTemplateid * @param int $channelPgTemplateId * @param int $ownerSystemGroupId * * @return int The nodeid of the new blog channel */ public function createChannel($input, $channelid, $channelConvTemplateid, $channelPgTemplateId, $ownerSystemGroupId) { $input['parentid'] = $channelid; $input['inlist'] = 1; // we don't want it to be shown in channel list, but we want to move them $input['protected'] = 0; if (empty($input['userid'])) { $input['userid'] = vB::getCurrentSession()->get('userid'); } if (!isset($input['publishdate'])) { $input['publishdate'] = vB::getRequest()->getTimeNow(); } $input['templates']['vB5_Route_Channel'] = $channelPgTemplateId; $input['templates']['vB5_Route_Conversation'] = $channelConvTemplateid; // add channel node $channelLib = vB_Library::instance('content_channel'); $input['page_parentid'] = 0; $result = $channelLib->add($input, array('skipFloodCheck' => true, 'skipDupCheck' => true)); //Make the current user the channel owner. $userApi = vB_Api::instanceInternal('user'); $usergroup = vB::getDbAssertor()->getRow('usergroup', array('systemgroupid' => $ownerSystemGroupId)); if (empty($usergroup) or !empty($usergroup['errors'])) { //This should never happen. It would mean an invalid parameter was passed throw new vB_Exception_Api('invalid_request'); } vB_User::setGroupInTopic($input['userid'], $result['nodeid'], $usergroup['usergroupid']); vB_Cache::allCacheEvent(array('nodeChg_' . $this->blogChannel, "nodeChg_{$channelid}")); vB::getUserContext()->rebuildGroupAccess(); vB_Channel::rebuildChannelTypes(); // clear follow cache vB_Api::instanceInternal('follow')->clearFollowCache(array($input['userid'])); return $result['nodeid']; }
/** * Permanently deletes a node * * @param integer The nodeid of the record to be deleted * * @return boolean */ public function delete($nodeid) { $nodecontent = $this->getContent($nodeid); if (isset($nodecontent[$nodeid])) { $nodecontent = $nodecontent[$nodeid]; } else { throw new vB_Exception_Api('invalid_node_id'); } // Note: The library delete has a check to prevent top level channels from being deleted, but // that excludes channels like the root channel, or the special, privatemessage or vm channels. $guids = vB_Channel::getProtectedChannelGuids(); if (in_array($nodecontent['guid'], array_values($guids))) { throw new vB_Exception_Api('can_not_remove_default_channel'); } // the reason there is no permission check in this function is because the parent (vB_Api_Content) performs the checks return parent::delete($nodeid); }
/** * 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; }
print_submit_row($vbphrase['rebuild_thread_information']); print_form_header('misc', 'updateforum'); print_table_header($vbphrase['rebuild_forum_information'], 2, 0); print_input_row($vbphrase['number_of_forums_to_process_per_cycle'], 'perpage', 100); print_submit_row($vbphrase['rebuild_forum_information']); print_form_header('misc', 'lostusers'); print_table_header($vbphrase['fix_broken_user_profiles']); print_description_row($vbphrase['finds_users_without_complete_entries']); print_submit_row($vbphrase['fix_broken_user_profiles'], NULL); if ($maintainAll) { print_form_header('misc', 'doindextypes'); print_table_header($vbphrase['rebuild_search_index'], 2, 0); print_description_row(construct_phrase($vbphrase['note_reindexing_empty_indexes_x'], vB::getCurrentSession()->get('sessionurl'))); //don't use array_merge, it will (incorrectly) assume that the keys are index values //instead of meaningful numeric keys and renumber them. $channelTypes = vB_Channel::getChannelTypes(); $types = array(0 => $vbphrase['all']); foreach ($channelTypes as $nodeId => $type) { $types[$nodeId] = $vbphrase[$type['label']]; } print_select_row($vbphrase['search_content_type_to_index'], 'indextypes', $types); print_input_row($vbphrase['search_items_batch'], 'perpage', 250); print_input_row($vbphrase['search_start_item_id'], 'startat', 0); print_yes_no_row($vbphrase['include_automatic_javascript_redirect'], 'autoredirect', 1); print_description_row($vbphrase['note_server_intensive']); print_submit_row($vbphrase['rebuild_search_index']); } print_form_header('misc', 'truncatesigcache'); print_table_header($vbphrase['empty_signature_cache']); print_description_row($vbphrase['change_output_signatures_empty_cache']); print_submit_row($vbphrase['empty_signature_cache'], NULL);
/** * Returns a channel record based on its node guid * * @param string GUID * * @return array Channel information */ public function fetchChannelByGUID($guid) { $cache = vB_Cache::instance(vB_Cache::CACHE_FAST); $channel = $cache->read('vbChannelGUID_' . $guid); if (!empty($channel)) { return $channel; } $parentChannelGUIDs = vB_Channel::getDefaultGUIDs(); $parentChannels = vB::getDbAssertor()->assertQuery('vBForum:channel', array('guid' => $parentChannelGUIDs)); $channel = array(); foreach ($parentChannels as $parentChannel) { $cache->write('vbChannelGUID_' . $parentChannel['guid'], $parentChannel, 1440, 'nodeChg_' . $parentChannel['nodeid']); if ($parentChannel['guid'] == $guid) { $channel = $parentChannel; } } return $channel; }