Вход Регистрация
Файл: library/XenForo/Model/Node.php
Строк: 1734
<?php

class XenForo_Model_Node extends XenForo_Model
{
    
/**
     * Checks that the provided array is a node
     * by checking that it contains node_id and parent_node_id keys
     *
     * @param array $node
     *
     * @return boolean
     */
    
protected static function _isNode($node)
    {
        return (
            !empty(
$node) &&
            
is_array($node) &&
            
array_key_exists('node_id'$node) &&
            
array_key_exists('parent_node_id'$node)
        );
    }

    
/**
     * Checks that the provided array is a node array
     * by checking that the first element is a node
     *
     * @param mixed $nodes
     *
     * @return boolean
     */
    
protected static function _isNodesArray($nodes)
    {
        if (
is_array($nodes))
        {
            if (
count($nodes) == 0)
            {
                return 
true;
            }
            else
            {
                return (
self::_isNode(reset($nodes)));
            }
        }
        else
        {
            return 
false;
        }
    }

    
/**
     * Checks that the provided array is a node hierarchy
     * by checking that the first child of the first element has a node_id key
     *
     * @param mixed $nodeHierarchy
     *
     * @return boolean
     */
    
protected static function _isNodeHierarchy($nodeHierarchy)
    {
        if (
is_array($nodeHierarchy))
        {
            if (
count($nodeHierarchy) == 0)
            {
                return 
true;
            }

            
$firstChild reset($nodeHierarchy);
            if (
is_array($firstChild))
            {
                return (
self::_isNodesArray(reset($firstChild)));
            }
            else
            {
                return 
false;
            }
        }
        else
        {
            return 
false;
        }
    }

    
/**
     * Gets a single node record specified by its ID
     *
     * @param integer node_id
     *
     * @return array Node
     */
    
public function getNodeById($nodeId, array $fetchOptions = array())
    {
        
$fetchOptions array_merge(
            array(
                
'permissionCombinationId' => 0
            
), $fetchOptions
        
);

        
$permissionCombinationId intval($fetchOptions['permissionCombinationId']);

        
$data $this->_getDb()->fetchRow('
            SELECT node.*
                ' 
. ($permissionCombinationId ', permission.cache_value AS node_permission_cache' '') . '
            FROM xf_node AS node
            ' 
. ($permissionCombinationId '
                LEFT JOIN xf_permission_cache_content AS permission
                    ON (permission.permission_combination_id = ' 
$permissionCombinationId '
                        AND permission.content_type = '
node'
                        AND permission.content_id = node.node_id)
                ' 
'') . '
            WHERE node.node_id = ?
        '
$nodeId);

        return 
$data;
    }

    
/**
     * Gets a node of the specified type that has a given name.
     *
     * @param string $nodeName
     * @param string $nodeTypeId
     * @param array $fetchOptions
     *
     * @return array|false
     */
    
public function getNodeByName($nodeName$nodeTypeId, array $fetchOptions = array())
    {
        
$fetchOptions array_merge(
            array(
                
'permissionCombinationId' => 0
            
), $fetchOptions
        
);

        
$permissionCombinationId intval($fetchOptions['permissionCombinationId']);

        
$data $this->_getDb()->fetchRow('
            SELECT node.*
                ' 
. ($permissionCombinationId ', permission.cache_value AS node_permission_cache' '') . '
            FROM xf_node AS node
            ' 
. ($permissionCombinationId '
                LEFT JOIN xf_permission_cache_content AS permission
                    ON (permission.permission_combination_id = ' 
$permissionCombinationId '
                        AND permission.content_type = '
node'
                        AND permission.content_id = node.node_id)
                ' 
'') . '
            WHERE node.node_name = ?
                AND node.node_type_id = ?
        '
, array($nodeName$nodeTypeId));

        return 
$data;
    }

    
/**
     * Get an array of node_ids from the specified array of node_names
     *
     * @param array Node names
     *
     * @return array [node_name] => node_id
     */
    
public function getNodeIdsFromNames(array $nodeNames)
    {
        return 
$this->_getDb()->fetchPairs('
            SELECT node_name, node_id
            FROM xf_node
            WHERE node_name IN (' 
$this->_getDb()->quote($nodeNames) . ')
        '
);
    }

    
/**
     * Gets all nodes from the database
     *
     * @param boolean $ignoreNestedSetOrdering If true, ignore nested set infor for ordering and use display_order instead
     * @param boolean $listView If true, only includes nodes viewable in list
     *
     * @return array
     */
    
public function getAllNodes($ignoreNestedSetOrdering false$listView false)
    {
        if (
$ignoreNestedSetOrdering)
        {
            return 
$this->fetchAllKeyed('
                SELECT *
                FROM xf_node
                ' 
. ($listView 'WHERE display_in_list = 1' '') . '
                ORDER BY parent_node_id, display_order ASC
            '
'node_id');
        }
        else
        {
            return 
$this->fetchAllKeyed('
                SELECT *
                FROM xf_node
                ' 
. ($listView 'WHERE display_in_list = 1' '') . '
                ORDER BY lft ASC
            '
'node_id');
        }
    }

    
/**
     * Gets an array representing the node hierarchy that can be traversed recursively
     * Format: item[parent_id][node_id] = node
     *
     * @param array|null Node list from getAllNodes()
     *
     * @return array Node hierarchy
     */
    
public function getNodeHierarchy($nodes null)
    {
        if (!
$this->_isNodesArray($nodes))
        {
            
$nodes $this->getAllNodes(true);
        }

        
$nodeHierarchy = array();

        foreach (
$nodes AS $node)
        {
            
$nodeHierarchy[$node['parent_node_id']][$node['node_id']] = $node;
        }

        return 
$nodeHierarchy;
    }

    
/**
     * Gets an array of all nodes that are not decendents of the specified node
     *
     * @param array    Node
     *
     * @return array Nodes
     */
    
public function getPossibleParentNodes($node null)
    {
        
$rootNode = array($this->getRootNode());

        if (!
$this->_isNode($node))
        {
            
// we are going to return ALL nodes, as the specified node does not exist
            
$nodes $this->getAllNodes();
        }
        else
        {
            
// return only nodes that are not decendents of the specified node
            
$nodes $this->fetchAllKeyed('
                SELECT *
                FROM xf_node
                WHERE lft < ? OR rgt > ?
                ORDER BY lft ASC
            '
'node_id', array($node['lft'], $node['rgt']));
        }

        return 
$rootNode $nodes;
    }

    
/**
     * Get all ancestors for the given node, from the root of the tree
     * down the the node's direct parent.
     *
     * @param array $node
     *
     * @return array List of ancestor nodes, from root down; format: [node id] => info
     */
    
public function getNodeAncestors(array $node)
    {
        if (!empty(
$node['breadcrumb_data']))
        {
            return 
unserialize($node['breadcrumb_data']);
        }

        return 
$this->fetchAllKeyed('
            SELECT *
            FROM xf_node
            WHERE lft < ? AND rgt > ?
            ORDER BY lft ASC
        '
'node_id', array($node['lft'], $node['rgt']));
    }

    
/**
     * Gets the bread crumb nodes for the specified node.
     *
     * @param array $node
     * @param boolean $includeSelf If true, includes itself as the last entry
     *
     * @return array List of nodes that form bread crumbs, root down; [node id] => node info
     */
    
public function getNodeBreadCrumbs(array $node$includeSelf true)
    {
        
$nodes $this->getNodeAncestors($node);
        if (
$includeSelf)
        {
            
$nodes[$node['node_id']] = $node;
        }

        
$nodeTypes $this->getAllNodeTypes();

        
$breadCrumbs = array();

        foreach (
$nodes AS $nodeId => $node)
        {
            
$breadCrumbs[$nodeId] = array(
                
'href' => XenForo_Link::buildPublicLink('full:' $nodeTypes[$node['node_type_id']]['public_route_prefix'], $node),
                
'value' => $node['title'],
                
'node_id' => $node['node_id']
            );
        }

        return 
$breadCrumbs;
    }

    
/**
     * Gets all nodes that are siblings of this node
     *
     * @param array $node
     * @param boolean $includeSelf
     * @param boolean $listView If true, only includes nodes viewable in list
     *
     * @return array
     */
    
public function getSiblingNodes(array $node$includeSelf true$listView false)
    {
        if (!
$this->_isNode($node))
        {
            return 
false;
        }

        
$nodes $this->fetchAllKeyed('
            SELECT *
            FROM xf_node
            WHERE parent_node_id = ?
                ' 
. ($listView 'AND display_in_list = 1' '') . '
            ORDER BY lft
        '
'node_id'$node['parent_node_id']);

        if (!
$includeSelf)
        {
            unset(
$nodes[$node['node_id']]);
        }

        return 
$nodes;
    }

    
/**
     * Gets an array of all nodes that are decendents of the specified node
     *
     * @param array $node
     * @param boolean $listView If true, only nodes that are visible in the list view are included
     *
     * @return mixed
     */
    
public function getChildNodes($node$listView false)
    {
        if (!
$this->_isNode($node))
        {
            return 
false;
        }

        if (!
$this->hasChildNodes($node))
        {
            return array();
        }

        return 
$this->fetchAllKeyed('
            SELECT *
            FROM xf_node
            WHERE lft > ? AND rgt < ?
                ' 
. ($listView ' AND display_in_list = 1' '') . '
            ORDER BY lft ASC
        '
'node_id', array($node['lft'], $node['rgt']));
    }

    
/**
     * Gets an array of all nodes that are decendents of the specified node
     * up to $depth levels of nesting
     *
     * @param array $node
     * @param integer $depth
     * @param boolean $listView If true, only nodes that are visible in the list view are included
     *
     * @return mixed
     */
    
public function getChildNodesToDepth($node$depth$listView false)
    {
        if (!
$this->_isNode($node) || $depth 1)
        {
            return 
false;
        }
        else if (
$depth == 1)
        {
            
// use parent id to get the results
            
return $this->fetchAllKeyed('
                SELECT *
                FROM xf_node
                WHERE parent_node_id = ?
                    ' 
. ($listView ' AND display_in_list = 1' '') . '
                ORDER BY lft ASC
            '
'node_id'$node['node_id']);
        }
        else
        {
            
// use left/right/depth to get children
            
return $this->fetchAllKeyed('
                SELECT *
                FROM xf_node
                WHERE lft > ? AND rgt < ? AND depth <= ?
                    ' 
. ($listView ' AND display_in_list = 1' '') . '
                ORDER BY lft ASC
            '
'node_id', array($node['lft'], $node['rgt'], $node['depth'] + $depth));
        }
    }

    
/**
     * Gets all child nodes for each node ID. The child nodes are not
     * grouped.
     *
     * @param array $nodeIds
     *
     * @return array Child nodes, [node id] => node
     */
    
public function getChildNodesForNodeIds(array $nodeIds)
    {
        
$nodes $this->getAllNodes();

        
$ranges = array();
        foreach (
$nodeIds AS $nodeId)
        {
            if (isset(
$nodes[$nodeId]))
            {
                
$node $nodes[$nodeId];
                
$ranges[] = array($node['lft'], $node['rgt']);
            }
        }

        
$childNodes = array();
        foreach (
$nodes AS $node)
        {
            foreach (
$ranges AS $range)
            {
                if (
$node['lft'] > $range[0] && $node['lft'] < $range[1])
                {
                    
$childNodes[$node['node_id']] = $node;
                    break;
                }
            }
        }

        return 
$childNodes;
    }

    
/**
     * Gets all nodes up to a specified depth from
     * the root of the tree.
     *
     * @param integer $depth
     *
     * @return array Format: [node id] => node info
     */
    
public function getNodesToDepthFromRoot($depth)
    {
        return 
$this->fetchAllKeyed('
            SELECT *
            FROM xf_node
            WHERE depth < ?
            ORDER BY lft ASC
        '
'node_id'$depth);
    }

    
/**
     * Groups a list of nodes by their parent node ID. This allows
     * for easier recursive traversal.
     *
     * @param array $nodes Format: [node id] => info
     *
     * @return array Format: [parent node id][node id] => info
     */
    
public function groupNodesByParent(array $nodes)
    {
        
$output = array();

        foreach (
$nodes AS $nodeId => $node)
        {
            
$output[$node['parent_node_id']][$nodeId] = $node;
        }

        return 
$output;
    }

    
/**
     * Gets a unique list of node type IDs from a list of
     * ungrouped nodes.
     *
     * @param array $nodes
     *
     * @return array List of node type IDs
     */
    
public function getUniqueNodeTypeIdsFromNodeList(array $nodes)
    {
        
$output = array();

        foreach (
$nodes AS $node)
        {
            
$output[$node['node_type_id']] = true;
        }

        return 
array_keys($output);
    }

    
/**
     * Gets a node handler object for each node type in the list.
     *
     * @param array $nodeTypeIds List of node type IDs
     *
     * @return array Format: [node type id] => node handler object
     */
    
public function getNodeHandlersForNodeTypes(array $nodeTypeIds)
    {
        
$nodeTypes $this->getAllNodeTypes();

        
$output = array();
        foreach (
$nodeTypeIds AS $nodeTypeId)
        {
            
$class = isset($nodeTypes[$nodeTypeId]) ? $nodeTypes[$nodeTypeId]['handler_class'] : '';
            
$class XenForo_Application::resolveDynamicClass($class);
            
$output[$nodeTypeId] = new $class();
        }

        return 
$output;
    }

    
/**
     * Gets the viewable nodes from a list of nodes.
     *
     * @param array $nodes Format: [node id] => info
     * @param array $nodeHandlers List of node handlers
     * @param array $nodePermissions Node permissions, [node id] => permissions
     *
     * @return array List of nodes, [node id] => info
     */
    
public function getViewableNodesFromNodeList(array $nodes, array $nodeHandlers, array $nodePermissions)
    {
        
$viewable = array();
        foreach (
$nodes AS $nodeId => $node)
        {
            
$handler $nodeHandlers[$node['node_type_id']];
            
$permissions = (isset($nodePermissions[$node['node_id']]) ? $nodePermissions[$node['node_id']] : array());

            if (
$handler->isNodeViewable($node$permissions))
            {
                
$viewable[$nodeId] = $node;
            }
        }

        return 
$viewable;
    }

    
/**
     * Merges extra, node-specific data into a list of nodes.
     *
     * @param array $nodes List of nodes, [node id] => info
     * @param array $nodeHandlers List of node handlers
     *
     * @return array Node list with extra data merged in
     */
    
public function mergeExtraNodeDataIntoNodeList(array $nodes, array $nodeHandlers)
    {
        
$groupedTypes = array();
        foreach (
$nodes AS $nodeId => $node)
        {
            
$groupedTypes[$node['node_type_id']][] = $nodeId;
        }

        foreach (
$groupedTypes AS $nodeTypeId => $nodeIds)
        {
            
$extraData $nodeHandlers[$nodeTypeId]->getExtraDataForNodes($nodeIds);
            if (
$extraData)
            {
                foreach (
$extraData AS $nodeId => $data)
                {
                    if (isset(
$nodes[$nodeId]))
                    {
                        
$nodes[$nodeId] = array_merge($nodes[$nodeId], $data);
                    }
                }
            }
        }

        return 
$nodes;
    }

    
/**
     * Prepare nodes using the type-specific handlers.
     *
     * @param array $nodes Unprepared data
     * @param array $nodeHandlers List of node handlers
     *
     * @return array Prepared data
     */
    
public function prepareNodesWithHandlers(array $nodes, array $nodeHandlers)
    {
        foreach (
$nodes AS &$node)
        {
            
$node $nodeHandlers[$node['node_type_id']]->prepareNode($node);
        }

        return 
$nodes;
    }

    
/**
     * Pushes applicable child node data up the tree. This is used
     * for things like last post data.
     *
     * @param integer $parentNodeId ID to start traversing at; all children will be handled
     * @param array $groupedNodes Nodes grouped by parent, [parent node id][node id] => info
     * @param array $nodeHandlers List of node handlers
     * @param array $nodePermissions List of node permissions, [node id] => permissions
     *
     * @return array Grouped node list with data pushed up as necessary
     */
    
public function pushNodeDataUpTree($parentNodeId, array $groupedNodes, array $nodeHandlers, array $nodePermissions)
    {
        
$this->mergePushableNodeData($parentNodeId$groupedNodes$nodeHandlers$nodePermissions);
        return 
$groupedNodes;
    }

    
/**
     * Merges pushable node data into a grouped node list and
     * returns all the pushed data from this level.
     *
     * @param integer $parentNodeId Parent node, all children traversed
     * @param array $groupedNodes List of grouped nodes. This will be modified by ref!
     * @param array $nodeHandlers List of node handlers
     * @param array $nodePermissions List of node permissions
     *
     * @return array Pushed data for the nodes traversed at this level; [node id] => pushed data
     */
    
public function mergePushableNodeData($parentNodeId, array &$groupedNodes, array $nodeHandlers, array $nodePermissions)
    {
        if (empty(
$groupedNodes[$parentNodeId]))
        {
            return array();
        }

        
$pushToParent = array();

        foreach (
$groupedNodes[$parentNodeId] AS &$node)
        {
            
$childPushableData $this->mergePushableNodeData(
                
$node['node_id'], $groupedNodes$nodeHandlers$nodePermissions
            
);

            
$permissions = (isset($nodePermissions[$node['node_id']]) ? $nodePermissions[$node['node_id']] : array());
            
$pushableData $nodeHandlers[$node['node_type_id']]->getPushableDataForNode(
                
$node$childPushableData$permissions
            
);

            if (
$pushableData)
            {
                
$node array_merge($node$pushableData);
                
$pushToParent[$node['node_id']] = $pushableData;
            }
        }

        return 
$pushToParent;
    }

    
/**
     * Filters a set of grouped node data to only include nodes up to
     * a certain depth from the specified root node.
     *
     * @param integer $parentNodeId Root of the sub-tree or the whole tree (0)
     * @param array $groupedNodes Nodes, grouped by parent: [parent node id][node id] => info
     * @param integer $depth Depth to filter to; should be at least 1
     *
     * @return array Filtered, grouped nodes
     */
    
public function filterGroupedNodesToDepth($parentNodeId, array $groupedNodes$depth)
    {
        if (empty(
$groupedNodes[$parentNodeId]) || $depth 1)
        {
            return array();
        }

        
$okParentNodes = array($parentNodeId);

        
$currentDepth 1;
        
$checkNodes = array($parentNodeId);

        while (
$currentDepth $depth)
        {
            
$newCheckNodes = array();
            foreach (
$checkNodes AS $checkNodeId)
            {
                if (!empty(
$groupedNodes[$checkNodeId]))
                {
                    
$newCheckNodes array_merge(
                        
$newCheckNodesarray_keys($groupedNodes[$checkNodeId])
                    );
                }
            }

            
$okParentNodes array_merge($okParentNodes$newCheckNodes);
            
$checkNodes $newCheckNodes;
            
$currentDepth++;
        }

        
$newGroupedNodes = array();
        foreach (
$okParentNodes AS $parentNodeId)
        {
            if (isset(
$groupedNodes[$parentNodeId]))
            {
                
$newGroupedNodes[$parentNodeId] = $groupedNodes[$parentNodeId];
            }
        }

        return 
$newGroupedNodes;
    }

    
/**
     * Gets all the node data required for a node list display
     * (eg, a forum list) from a given point. Returns 3 pieces of data:
     *     * nodesGrouped - nodes, grouped by parent, with all data integrated
     *  * nodeHandlers - list of node handlers by node type
     *  * nodePermissions - the node permissions passed on
     *
     * @param array|boolean $parentNode Root node of the tree to display from; false for the entire tree
     * @param integer $displayDepth Number of levels of nodes to display below the root, 0 for all
     * @param array|null $nodePermissions List of node permissions, [node id] => permissions; if null, get's current visitor's permissions
     *
     * @return array Empty, or with keys: nodeParents, nodesGrouped, parentNodeId, nodeHandlers, nodePermissions
     */
    
public function getNodeDataForListDisplay($parentNode$displayDepth, array $nodePermissions null)
    {
        if (
is_array($parentNode))
        {
            
$nodes $this->getChildNodes($parentNodetrue);
            
$parentNodeId $parentNode['node_id'];
        }
        else if (
$parentNode === false)
        {
            
$nodes $this->getAllNodes(falsetrue);
            
$parentNodeId 0;
        }
        else
        {
            throw new 
XenForo_Exception('Unexpected parent node parameter passed to getNodeDataForListDisplay');
        }

        return 
$this->getNodeListDisplayData($nodes$parentNodeId$displayDepth$nodePermissions);
    }

    
/**
     * @param array $nodes
     * @param integer $parentNodeId
     * @param integer $displayDepth
     * @param array|null $nodePermissions
     *
     * @return array
     */
    
public function getNodeListDisplayData(array $nodes$parentNodeId$displayDepth, array $nodePermissions null)
    {
        if (!
$nodes)
        {
            return array();
        }

        if (!
is_array($nodePermissions))
        {
            
$nodePermissions $this->getNodePermissionsForPermissionCombination();
        }

        
$nodeHandlers $this->getNodeHandlersForNodeTypes(
            
$this->getUniqueNodeTypeIdsFromNodeList($nodes)
        );

        
$nodes $this->getViewableNodesFromNodeList($nodes$nodeHandlers$nodePermissions);
        
$nodes $this->mergeExtraNodeDataIntoNodeList($nodes$nodeHandlers);
        
$nodes $this->prepareNodesWithHandlers($nodes$nodeHandlers);

        
$groupedNodes $this->groupNodesByParent($nodes);
        
$groupedNodes $this->pushNodeDataUpTree($parentNodeId$groupedNodes$nodeHandlers$nodePermissions);

        if (
$displayDepth)
        {
            
$groupedNodes $this->filterGroupedNodesToDepth($parentNodeId$groupedNodes$displayDepth);
        }

        
$nodeParents XenForo_Application::arrayColumn($nodes'parent_node_id''node_id');

        return array(
            
'nodeParents' => $nodeParents,
            
'nodesGrouped' => $groupedNodes,
            
'parentNodeId' => $parentNodeId,
            
'nodeHandlers' => $nodeHandlers,
            
'nodePermissions' => $nodePermissions
        
);
    }

    
/**
     * Gets item counts across all nodes (or all nodes in a sub-tree). This does not
     * respect permissions.
     *
     * @param array|false $parentNode
     *
     * @return array Keys: discussions, messages
     */
    
public function getNodeTotalItemCounts(array $parentNode null)
    {
        if (
is_array($parentNode))
        {
            
$nodes $this->getChildNodes($parentNode);
            
$parentNodeId $parentNode['node_id'];
        }
        else
        {
            
$nodes $this->getAllNodes();
            
$parentNodeId 0;
        }

        
$output = array(
            
'discussions' => 0,
            
'messages' => 0
        
);

        
$nodeHandlers $this->getNodeHandlersForNodeTypes(
            
$this->getUniqueNodeTypeIdsFromNodeList($nodes)
        );

        
$nodes $this->mergeExtraNodeDataIntoNodeList($nodes$nodeHandlers);
        foreach (
$nodes AS $node)
        {
            
$nodeOutput $nodeHandlers[$node['node_type_id']]->getNodeItemCounts($node);
            if (
$nodeOutput)
            {
                
$output['discussions'] += $nodeOutput['discussions'];
                
$output['messages'] += $nodeOutput['messages'];
            }
        }

        return 
$output;
    }

    
/**
     * Get a list of all nodes that are viewable.
     *
     * @param array|null $nodePermissions List of node permissions, [node id] => permissions; if null, get's current visitor's permissions
     * @param boolean Get nodes in list mode (respect display_in_list option for each node)
     *
     * @return array List of viewable nodes: [node id] => info, ordered by lft
     */
    
public function getViewableNodeList(array $nodePermissions null$listView false)
    {
        
$nodes $this->getAllNodes(false$listView);
        if (!
$nodes)
        {
            return array();
        }

        if (!
is_array($nodePermissions))
        {
            
$nodePermissions $this->getNodePermissionsForPermissionCombination();
        }

        
$nodeHandlers $this->getNodeHandlersForNodeTypes(
            
$this->getUniqueNodeTypeIdsFromNodeList($nodes)
        );

        return 
$this->getViewableNodesFromNodeList($nodes$nodeHandlers$nodePermissions);
    }

    
/**
     * Gets all the node permissions for a given permission combination ID.
     *
     * @param integer|null $permissionCombinationId If null, uses current visitor permissions
     *
     * @return array
     */
    
public function getNodePermissionsForPermissionCombination($permissionCombinationId null)
    {
        if (
$permissionCombinationId === null)
        {
            
$visitor XenForo_Visitor::getInstance();
            
$permissionCombinationId $visitor['permission_combination_id'];
        }

        
$cacheKey 'nodePermissions' $permissionCombinationId;
        
$data $this->_getLocalCacheData($cacheKey);
        if (
$data === false)
        {
            
$data $this->_getDb()->fetchPairs("
                SELECT content_id, cache_value
                FROM xf_permission_cache_content
                WHERE permission_combination_id = ?
                    AND content_type = 'node'
            "
$permissionCombinationId);
            foreach (
$data AS &$perms)
            {
                
$perms XenForo_Permission::unserializePermissions($perms);
            }

            
$this->setLocalCacheData($cacheKey$data);
        }

        return 
$data;
    }

    
/**
     * Prepares the raw data of a node into human-readable information for use in
     * the admin area.
     *
     * @param array Raw node data
     *
     * @return array Prepared node
     */
    
public function prepareNodeForAdmin(array $node)
    {
        
// get human-readable node type names
        
$node['node_type'] = $this->getNodeTypeNameById($node['node_type_id']);

        return 
$node;
    }

    
/**
     * Calls prepareNodeForAdmin() on each member of the input array
     *
     * @param array Raw nodes
     *
     * @return array Prepared nodes
     */
    
public function prepareNodesForAdmin(array $nodes)
    {
        foreach (
$nodes AS $nodeId => $node)
        {
            
$nodes[$nodeId] = $this->prepareNodeForAdmin($node);
        }

        return 
$nodes;
    }

    
/**
     * Fetches a representation of the root node to be merged into an array of other nodes
     *
     * @return array
     */
    
public function getRootNode()
    {
        return array(
            
'node_id' => 0,
            
'title' => new XenForo_Phrase('root_node_meta'),
            
'node_type_id' => null,
            
'parent_node_id' => null,
            
'display_order' => 0,
            
'lft' => 0,
            
'rgt' => 0,
            
'depth' => 0
        
);
    }

    
/**
     * Fetch all information for the specified node type
     *
     * @param string node_type_id
     *
     * @return array
     */
    
public function getNodeTypeById($nodeTypeId)
    {
        
$nodeTypes $this->getAllNodeTypes();
        if (isset(
$nodeTypes[$nodeTypeId]))
        {
            return 
$nodeTypes[$nodeTypeId];
        }
        else
        {
            return 
false;
        }
    }

    
/**
     * Fetches all information for the node type attached to a particular node
     *
     * @param integer $nodeId
     *
     * @return arrayf
     */
    
public function getNodeTypeByNodeId($nodeId)
    {
        return 
$this->_getDb()->fetchRow('
            SELECT node_type.*
            FROM xf_node_type AS node_type
            INNER JOIN xf_node AS node ON
                (node_type.node_type_id = node.node_type_id)
            WHERE node.node_id = ?
        '
$nodeId);
    }

    
/**
     * Gets an array of all node types from the DB
     *
     * @return array Format: [node type id] => type info
     */
    
public function getAllNodeTypes()
    {
        
// note: this uses the global cache as this can come from the data registry
        
if (XenForo_Application::isRegistered('nodeTypes'))
        {
            return 
XenForo_Application::get('nodeTypes');
        }

        
$nodeTypes $this->getAllNodeTypesFromDb();
        
XenForo_Application::set('nodeTypes'$nodeTypes);

        return 
$nodeTypes;
    }

    
/**
     * Gets an array of all node types from the DB
     *
     * @return array Format: [node type id] => type info
     */
    
public function getAllNodeTypesFromDb()
    {
        return 
$this->fetchAllKeyed('
            SELECT *
            FROM xf_node_type
        '
'node_type_id');
    }

    
/**
     * Rebuilds the cache of node types.
     *
     * @return array Node type cache [node type id] => type info
     */
    
public function rebuildNodeTypeCache()
    {
        
$nodeTypes $this->getAllNodeTypesFromDb();
        
$this->_getDataRegistryModel()->set('nodeTypes'$nodeTypes);

        return 
$nodeTypes;
    }

    
/**
     * Gets all node types, grouped by their permission group.
     *
     * @return array Format: [permission group id][node type id] => node type info
     */
    
public function getNodeTypesGroupedByPermissionGroup()
    {
        
$groups = array();
        foreach (
$this->getAllNodeTypes() AS $nodeTypeId => $nodeType)
        {
            if (
$nodeType['permission_group_id'])
            {
                
$groups[$nodeType['permission_group_id']][$nodeTypeId] = $nodeType;
            }
        }

        return 
$groups;
    }

    
/**
     * Gets the human-readable name of the node type specified by its id
     *
     * @param string $nodeTypeId
     *
     * @return string
     */
    
public function getNodeTypeNameById($nodeTypeId)
    {
        return new 
XenForo_Phrase('node_type_' $nodeTypeId);
    }

    
/**
     * If the specified node has a lft-rgt difference of more than 1, it must have child nodes.
     *
     * @param array Node must include lft and rgt keys
     *
     * @return boolean
     */
    
public function hasChildNodes(array $node)
    {
        if (
array_key_exists('lft'$node) && array_key_exists('rgt'$node))
        {
            return (
$node['rgt'] > ($node['lft'] + 1));
        }
        else
        {
            throw new 
XenForo_Exception('The array provided to ' __CLASS__'::hasChildNodes() did not contain the necessary keys.');
        }
    }

    
/**
     * Moves all child nodes from one parent to another
     *
     * @param mixed Source node: Either node array or node id
     * @param mixed Destination node: Either node array or node id
     * @param boolean Rebuild caches afterwards
     *
     * @return null
     */
    
public function moveChildNodes($fromNode$toNode$rebuildCaches true)
    {
        if (!
$this->_isNode($fromNode))
        {
            
$fromNode $this->getNodeById($fromNode);
        }

        if (!
is_int($toNode) && $this->_isNode($toNode))
        {
            
$toNode $this->getNodeById($toNode);
            
$toNode $toNode['node_id'];
        }

        
$nodeTypes $this->getAllNodeTypes();

        if (
$childNodes $this->getChildNodesToDepth($fromNode1))
        {
            foreach (
$childNodes AS $childNodeId => $childNode)
            {
                
$dataWriterClass $nodeTypes[$childNode['node_type_id']]['datawriter_class'];

                
$writer XenForo_DataWriter::create($dataWriterClass);
                
$writer->setExistingData($childNodeId);
                
$writer->setOption(XenForo_DataWriter_Node::OPTION_POST_WRITE_UPDATE_CHILD_NODESfalse);
                
$writer->setOption(XenForo_DataWriter_Node::OPTION_REBUILD_CACHEfalse);
                
$writer->set('parent_node_id'$toNode);
                
$writer->save();
            }
        }

        if (
$rebuildCaches)
        {
            
$this->updateNestedSetInfo();
            
XenForo_Application::defer('Permission', array(), 'Permission'true);
        }
    }

    
/**
     * Deletes all child nodes of the specified parent node
     *
     * @param mixed Parent node: Either node array or node id
     * @param boolean Rebuild caches afterwards
     *
     * @return null
     */
    
public function deleteChildNodes($parentNode$rebuildCaches true)
    {
        if (!
$this->_isNode($parentNode))
        {
            
$parentNode $this->getNodeById($parentNode);
        }

        
$nodeTypes $this->getAllNodeTypes();

        if (
$childNodes $this->getChildNodes($parentNode))
        {
            foreach (
$childNodes AS $childNodeId => $childNode)
            {
                
$dataWriterClass $nodeTypes[$childNode['node_type_id']]['datawriter_class'];

                
$writer XenForo_DataWriter::create($dataWriterClass);
                
$writer->setExistingData($childNodeId);
                
$writer->setOption(XenForo_DataWriter_Node::OPTION_POST_WRITE_UPDATE_CHILD_NODESfalse);
                
$writer->setOption(XenForo_DataWriter_Node::OPTION_REBUILD_CACHEfalse);
                
$writer->delete();
            }
        }

        if (
$rebuildCaches)
        {
            
$this->updateNestedSetInfo();
            
XenForo_Application::defer('Permission', array(), 'Permission'true);
        }
    }

    
/**
     * Builds lft, rgt and depth values for all nodes, based on the parent_node_id and display_order information in the database.
     * Also rebuilds the effective style ID.
     *
     * @param array|null $nodeHierarchy - will be fetched automatically when NULL is provided
     * @param integer $parentNodeId
     * @param integer $depth
     * @param integer $lft The entry left value; note that this will be changed and returned as the rgt value
     * @param integer $effectiveStyleId The effective style ID from the parent
     * @param array $breadcrumbs Breadcrumb path to here
     *
     * @return array [node_id] => array(lft => int, rgt => int)...
     */
    
public function getNewNestedSetInfo($nodeHierarchy null$parentNodeId 0$depth 0, &$lft 1$effectiveStyleId 0, array $breadcrumbs = array())
    {
        
$nodes = array();

        if (
$depth == && !$this->_isNodeHierarchy($nodeHierarchy))
        {
            
$nodeHierarchy $this->getNodeHierarchy($nodeHierarchy);
        }

        if (empty(
$nodeHierarchy[$parentNodeId]))
        {
            return array();
        }

        foreach (
$nodeHierarchy[$parentNodeId] AS $i => $node)
        {
            
$nodes[$node['node_id']] = $node;
            
$nodes[$node['node_id']]['lft'] = $lft++;
            
$nodes[$node['node_id']]['depth'] = $depth;
            
$nodes[$node['node_id']]['breadcrumb_data'] = $breadcrumbs;

            
$newStyleId = ($node['style_id'] ? $node['style_id'] : $effectiveStyleId);
            
$nodes[$node['node_id']]['effective_style_id'] = $newStyleId;

            
$childBreadcrumbs $breadcrumbs;
            
$childBreadcrumbs[$node['node_id']] = array(
                
'node_id' => $node['node_id'],
                
'node_name' => $node['node_name'],
                
'node_type_id' => $node['node_type_id'],
                
'title' => $node['title'],
                
'depth' => $depth,
                
'lft' => $nodes[$node['node_id']]['lft']
            );

            
$childNodes $this->getNewNestedSetInfo($nodeHierarchy$node['node_id'], $depth 1$lft$newStyleId$childBreadcrumbs);

            
$nodes[$node['node_id']]['rgt'] = $lft++;
            
$childBreadcrumbs[$node['node_id']]['rgt'] = $nodes[$node['node_id']]['rgt'];

            foreach (
$childNodes AS &$childNode)
            {
                
$childNode['breadcrumb_data'][$node['node_id']]['rgt'] = $nodes[$node['node_id']]['rgt'];
            }

            
$nodes += $childNodes;
        }

        return 
$nodes;
    }

    
/**
     * Rebuilds and saves nested set info (lft, rgt, depth) for all nodes based on parent id and display order
     *
     * @return array All nodes
     */
    
public function updateNestedSetInfo()
    {
        
//TODO: This should probably have a much cleverer system than forcing a complete rebuild of the nested set info...
        
$nodes $this->getAllNodes(true);
        
$nestedSetInfo $this->getNewNestedSetInfo($nodes);

        
$db $this->_getDb();
        
XenForo_Db::beginTransaction($db);

        foreach (
$nestedSetInfo AS $nodeId => $node)
        {
            
/* @var $writer XenForo_DataWriter_Node */
            
$writer XenForo_DataWriter::create('XenForo_DataWriter_Node');

            
// we want to set nested set info, so don't prevent it
            
$writer->setOption(XenForo_DataWriter_Node::OPTION_ALLOW_NESTED_SET_WRITEtrue);

            
// prevent any child updates from occuring - we're handling it here
            
$writer->setOption(XenForo_DataWriter_Node::OPTION_POST_WRITE_UPDATE_CHILD_NODESfalse);

            
// we already have the data, don't go and query it again
            
$writer->setExistingData($nodes[$nodeId], true);

            
$writer->set('lft'$node['lft']);
            
$writer->set('rgt'$node['rgt']);
            
$writer->set('depth'$node['depth']);
            
$writer->set('effective_style_id'$node['effective_style_id']);
            
$writer->set('breadcrumb_data'$node['breadcrumb_data']);

            
// fingers crossed...
            
$writer->save();
        }

        
XenForo_Db::commit($db);

        return 
$nodes;
    }

    
/**
     * Fetches an array suitable as source for admin template 'options' tag from nodes array
     *
     * @param array Array of nodes, including node_id, title, parent_node_id and depth keys
     * @param integer NodeId of selected node
     * @param mixed Add root as the first option, and increment all depths by 1 to show indenting.
     *     If 'true', the root node will be entitled '(root node)', alternatively, specify a string to use
     *  as the option text.
     *
     * @return array
     */
    
public function getNodeOptionsArray(array $nodes$selectedNodeId 0$includeRoot false)
    {
        
$options = array();

        if (
$includeRoot !== false)
        {
            
$root $this->getRootNode();

            
$options[0] = array(
                
'value' => 0,
                
'label' => (is_string($includeRoot) === true $includeRoot $root['title']),
                
'selected' => (is_scalar($selectedNodeId) && strval($selectedNodeId) === '0'),
                
'depth' => 0,
            );
        }

        foreach (
$nodes AS $nodeId => $node)
        {
            
$node['depth'] += (($includeRoot && $nodeId) ? 0);

            
$options[$nodeId] = array(
                
'value' => $nodeId,
                
'label' => $node['title'],
                
'selected' => ($nodeId == $selectedNodeId),
                
'depth' => $node['depth'],
                
'node_type_id' => $node['node_type_id']
            );
        }

        return 
$options;
    }

    
/**
     * Returns a list of node type names for use in <xen:option> tags
     *
     * @param array Array of nodes including node_type_id key
     *
     * @return array
     */
    
public function getNodeTypeOptionsArray(array $nodes)
    {
        
$nodeTypes = array();

        foreach (
$nodes AS $nodeType)
        {
            
$nodeTypes[$nodeType['node_type_id']] = $this->getNodeTypeNameById($nodeType['node_type_id']);
        }

        return 
$nodeTypes;
    }

    
/**
     * Filters a tree of nodes to show only the specified node types and their children.
     * Children of an excluded node type will be removed, even if they are an included type.
     *
     * @param array Flattened, ordered node tree in order (by lft). Must include depth and node_type_id. Keyed by ID.
     * @param $nodeTypes [Category, Forum, ... ] or [Category => 1, Forum => 1,...]
     *
     * @return array
     */
    
public function filterNodeTypesInTree(array $nodes, array $nodeTypes)
    {
        foreach (
$nodes AS $index => $node)
        {
            if (!isset(
$nodeTypes[$node['node_type_id']]) && !in_array($node['node_type_id'], $nodeTypes))
            {
                unset(
$nodes[$index]);
                continue;
            }
        }

        return 
$this->filterOrphanNodes($nodes);
    }

    
/**
     * Filters orphaned nodes out of a list. Note the caveats:
     *     * The list must be in lft order
     *     * The nodes must be keyed by their ID
     *
     * @param array $nodes
     *
     * @return array
     */
    
public function filterOrphanNodes(array $nodes)
    {
        foreach (
$nodes AS $nodeId => $node)
        {
            if (
$node['parent_node_id'] > && !isset($nodes[$node['parent_node_id']]))
            {
                unset(
$nodes[$nodeId]);
            }
        }

        return 
$nodes;
    }

    
/**
     * Return results for admin quick search
     *
     * @param string Keywords for which to search
     *
     * @return array
     */
    
public function getNodesForAdminQuickSearch($searchText)
    {
        
$quotedString XenForo_Db::quoteLike($searchText'lr'$this->_getDb());

        return 
$this->fetchAllKeyed('
            SELECT * FROM xf_node
            WHERE title LIKE ' 
$quotedString '
            OR description LIKE ' 
$quotedString '
            ORDER BY lft
        '
'node_id');
    }
}
Онлайн: 1
Реклама