Вход Регистрация
Файл: 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(
                        
$newCheckNodes, array_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($parentNode, true);
            
$parentNodeId = $parentNode['node_id'];
        }
        else if (
$parentNode === false)
        {
            
$nodes = $this->getAllNodes(false, true);
            
$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($fromNode, 1))
        {
            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_NODES, false);
                
$writer->setOption(XenForo_DataWriter_Node::OPTION_REBUILD_CACHE, false);
                
$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_NODES, false);
                
$writer->setOption(XenForo_DataWriter_Node::OPTION_REBUILD_CACHE, false);
                
$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 == 0 && !$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_WRITE, true);

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

            
// 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) ? 1 : 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'] > 0 && !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');
    }
}
Онлайн: 3
Реклама