/** * Gets the tree node of a node * @param \ride\library\cms\node\Node $node Node to process * @param boolean $onlyCurrentLocale Flag to see if the tree should only * rendered for the current locale * @return TreeNode */ protected function getTreeNode(Node $node, $onlyCurrentLocale) { $nodeId = $node->getId(); $nodeRevision = $node->getRevision(); $urlVars = array('site' => $this->rootNodeId, 'revision' => $nodeRevision, 'node' => $nodeId, 'locale' => $this->locale); $url = $this->web->getUrl('cms.node.default', $urlVars); $treeNode = new TreeNode($node, $this->locale, $url); // add icon state classes $nodeType = $this->cms->getNodeType($node); if ($nodeType->getFrontendCallback()) { if ($node->getRoute($this->locale) == '/') { $treeNode->setIsHomePage(true); } if (!$node->isPublished()) { $treeNode->setIsHidden(true); } } $actions = array(); foreach ($this->actions as $actionName => $action) { if (!$action->isAvailableForNode($node)) { continue; } $actionUrl = $this->web->getUrl($action->getRoute(), $urlVars); if (!$this->securityManager->isUrlAllowed($actionUrl)) { continue; } $actions[$actionName] = $actionUrl; } $actionUrl = $this->web->getUrl($nodeType->getRouteEdit(), $urlVars); if ($this->securityManager->isUrlAllowed($actionUrl)) { $actions['edit'] = $actionUrl; } $actionUrl = $this->web->getUrl($nodeType->getRouteClone(), $urlVars); if ($this->securityManager->isUrlAllowed($actionUrl)) { $actions['clone'] = $actionUrl; } $actionUrl = $this->web->getUrl($nodeType->getRouteDelete(), $urlVars); if ($this->securityManager->isUrlAllowed($actionUrl)) { $actions['delete'] = $actionUrl; } $treeNode->setActions($actions); $children = $node->getChildren(); if ($children) { foreach ($children as $childId => $child) { if (!$onlyCurrentLocale || $onlyCurrentLocale && $child->isAvailableInLocale($this->locale)) { $children[$childId] = $this->getTreeNode($child, $onlyCurrentLocale); } else { unset($children[$childId]); } } $treeNode->setChildren($children); } return $treeNode; }
/** * Removes a node * @param \ride\library\cms\node\Node $node Node to delete * @param boolean $recursive Flag to see if child nodes should be deleted * @return */ public function removeNode(Node $node, $recursive = true) { $siteId = $node->getRootNodeId(); $revision = $node->getRevision(); $parent = $node->getParent(); $path = $node->getPath(); $orderIndex = $node->getOrderIndex(); $baseOrderIndex = $orderIndex - 1; $changedNodes = array(); // remove children or move the children the the parent's path $numChildren = 0; $children = $this->getNodesByPath($siteId, $revision, $path); foreach ($children as $child) { if ($recursive) { $this->removeNode($child, true); continue; } $childParent = $child->getParent(); if ($childParent === $path) { $child->setOrderIndex($baseOrderIndex + $child->getOrderIndex()); $numChildren++; } $child->setParent(str_replace($path, $parent, $childParent)); $changedNodes[] = $child; } if (!$recursive) { // fix order index for nodes after the removed node $siblings = $this->getChildren($node->getRootNodeId(), $node->getRevision(), $parent, 0); foreach ($siblings as $sibling) { $siblingOrderIndex = $sibling->getOrderIndex(); if ($siblingOrderIndex <= $orderIndex) { continue; } $sibling->setOrderIndex($siblingOrderIndex + $numChildren - 1); $changedNodes[] = $sibling; } } // save and remove the necessairy nodes foreach ($changedNodes as $changedNode) { $this->setNode($changedNode); } $this->deleteNode($node); unset($this->nodes[$siteId][$revision][$node->getId()]); }
/** * Dispatches the node * @param \ride\library\mvc\Request $request * @param \ride\library\mvc\Response $response * @param \ride\library\security\SecurityManager $securityManager * @param \ride\library\cache\pool\CachePool $cache * @return array Array with the region name as key and a view array as * value. The view array has the widget id as key and the dispatched * widget view as value */ public function dispatch(Request $request, Response $response, SecurityManager $securityManager, CachePool $cache = null) { $this->locale = $this->view->getLocale(); // initialize context $context = array('title' => array('site' => $this->node->getRootNode()->getName($this->locale, 'title'), 'node' => $this->node->getName($this->locale, 'title')), 'breadcrumbs' => $this->breadcrumbs, 'styles' => array(), 'scripts' => array()); // prepare and process incoming route arguments $route = $request->getRoute(); $this->routeArguments = $route->getArguments(); unset($this->routeArguments['node']); unset($this->routeArguments['locale']); if (isset($this->routeArguments['site'])) { // preview has site and revision unset($this->routeArguments['site']); unset($this->routeArguments['revision']); } $nodeRoute = $this->node->getRoute($this->locale); $nodeRouteTokens = explode('/', ltrim($nodeRoute, '/')); foreach ($nodeRouteTokens as $tokenIndex => $tokenValue) { if (isset($this->routeArguments[$tokenIndex]) && $this->routeArguments[$tokenIndex] === $tokenValue) { unset($this->routeArguments[$tokenIndex]); } } $route->setPredefinedArguments(array()); $route->setArguments(); $routeArgumentsMatched = false; // check for cache $cacheKey = null; $cacheItem = null; $cachedViews = array(); $dispatchedViews = array(); $nodeCacheTtl = false; if ($cache) { $method = $request->getMethod(); $isCacheable = $method == 'GET' || $method == 'HEAD' ? true : false; $isNoCache = $request->isNoCache(); if ($isCacheable) { $parameters = $this->routeArguments ? '-' . implode('-', $this->routeArguments) : ''; $parameters .= $request->getQueryParametersAsString(); $containsUserContent = false; $nodeCacheTtl = 0; $cacheKey = 'node.view.' . $this->node->getId() . '.' . $this->node->getRevision() . '.' . $this->locale . '.' . substr(md5($parameters), 0, 10); if ($securityManager->getUser()) { $cacheKey .= '.authenticated'; } $cacheItem = $cache->get($cacheKey); if ($cacheItem->isValid()) { $cachedViews = $cacheItem->getValue(); } else { $cachedViews = array(); } } } else { $isCacheable = false; } foreach ($this->widgets as $this->region => $sections) { $dispatchedViews[$this->region] = array(); foreach ($sections as $this->section => $blocks) { $dispatchedViews[$this->region][$this->section] = array(); foreach ($blocks as $this->block => $widgets) { $dispatchedViews[$this->region][$this->section][$this->block] = array(); foreach ($widgets as $widgetId => $widget) { if ($this->log) { $this->log->logDebug('Rendering widget ' . $widget->getName() . '#' . $widgetId . ' for region ' . $this->region, null, ApplicationListener::LOG_SOURCE); } $widgetProperties = $this->node->getWidgetProperties($widgetId); if (!$widgetProperties->isPublished()) { if ($this->log) { $this->log->logDebug('Widget ' . $widget->getName() . '#' . $widgetId . ' is not published', null, ApplicationListener::LOG_SOURCE); } continue; } elseif (!$widgetProperties->isAllowed($securityManager)) { if ($this->log) { $this->log->logDebug('Widget ' . $widget->getName() . '#' . $widgetId . ' is not allowed', null, ApplicationListener::LOG_SOURCE); } continue; } if ($isCacheable) { $widgetCacheKey = $this->region . '.' . $this->section . '.' . $this->block . '.' . $widgetId . '.'; } $isWidgetCache = $widgetProperties->isCacheEnabled() || $widgetProperties->isAutoCache() && $widget->isAutoCache(); if ($isCacheable && !$isNoCache && $isWidgetCache) { if (isset($cachedViews[$this->region][$this->section][$this->block][$widgetId])) { if ($this->log) { $this->log->logDebug('Retrieved widget ' . $widget->getName() . '#' . $widgetId . ' from cache', null, ApplicationListener::LOG_SOURCE); } $cacheView = $cachedViews[$this->region][$this->section][$this->block][$widgetId]; if ($cacheView->areRoutesMatched()) { $widgetMatchedRouteArguments = true; } $cacheContext = $cacheView->getContext(); if ($cacheContext) { foreach ($cacheContext as $key => $value) { if ($value !== null) { $context[$key] = $value; } elseif (isset($context[$key])) { unset($context[$key]); } } } if ($cacheView->isContent()) { $dispatchedViews = null; $this->view->setContentView($view, $widgetId, $this->block, $this->section, $this->region); break 4; } elseif ($cacheView->isRegion()) { $dispatchedViews[$this->region] = array($this->section => array($this->block => array($widgetId => $cacheView->getView()))); break 3; } elseif ($cacheView->isSection()) { $dispatchedViews[$this->region][$this->section] = array($this->block => array($widgetId => $cacheView->getView())); break 2; } elseif ($cacheView->isBlock()) { $dispatchedViews[$this->region][$this->section][$this->block] = array($widgetId => $cacheView->getView()); break; } else { $dispatchedViews[$this->region][$this->section][$this->block][$widgetId] = $cacheView->getView(); continue; } } } $widget->setProperties($widgetProperties); $widget->setContext($context); $widgetMatchedRouteArguments = $this->dispatchWidget($request, $response, $widgetId, $widget); if ($widgetMatchedRouteArguments) { $routeArgumentsMatched = true; } $statusCode = $response->getStatusCode(); if ($statusCode != Response::STATUS_CODE_OK && $statusCode != Response::STATUS_CODE_BAD_REQUEST && $statusCode != Response::STATUS_CODE_UNPROCESSABLE_ENTITY) { return; } $view = $response->getView(); $response->setView(null); $isContent = $widget->isContent(); $isRegion = $widget->isRegion(); $isSection = $widget->isSection(); $isBlock = $widget->isBlock(); if ($isCacheable && !$containsUserContent && $widget->containsUserContent()) { $containsUserContent = true; } $oldContext = $context; $context = $widget->getContext(); if ($isCacheable && $isWidgetCache) { $widgetContext = $this->getContextDifference($context, $oldContext); if (!$widgetContext) { // calculate node cache time based on the least widget cache time $cacheTtl = $widgetProperties->getCacheTtl(); if ($nodeCacheTtl !== false && $cacheTtl) { if ($nodeCacheTtl == 0) { $nodeCacheTtl = $cacheTtl; } else { $nodeCacheTtl = min($nodeCacheTtl, $cacheTtl); } } $widgetCachedView = new WidgetCacheData($widgetContext, $isContent, $isRegion, $isSection, $isBlock, $widgetMatchedRouteArguments); $cachedViews[$this->region][$this->section][$this->block][$widgetId] = $widgetCachedView; } } if ($isContent) { $dispatchedViews = null; $this->view->setContentView($view, $widgetId, $this->block, $this->section, $this->region); break 4; } elseif ($isRegion) { $dispatchedViews[$this->region] = array($this->section => array($this->block => array($widgetId => $view))); break 3; } elseif ($isSection) { $dispatchedViews[$this->region][$this->section] = array($this->block => array($widgetId => $view)); break 2; } elseif ($isBlock) { $dispatchedViews[$this->region][$this->section][$this->block] = array($widgetId => $view); break; } $dispatchedViews[$this->region][$this->section][$this->block][$widgetId] = $view; } if (!$dispatchedViews[$this->region][$this->section][$this->block]) { unset($dispatchedViews[$this->region][$this->section][$this->block]); } } if (!$dispatchedViews[$this->region][$this->section]) { unset($dispatchedViews[$this->region][$this->section]); } } if (!$dispatchedViews[$this->region]) { unset($dispatchedViews[$this->region]); } } if ($this->routeArguments && !$routeArgumentsMatched) { // sub route provided but never matched $response->setStatusCode(Response::STATUS_CODE_NOT_FOUND); $response->setView(null); $dispatchedViews = null; } // if ($this->eventManager && $isCacheable && $nodeCacheTtl !== false) { // if ($user && $containsUserContent) { // $isCacheable = false; // } // if ($isCacheable) { // $this->cache = $cache; // $this->cacheTtl = $nodeCacheTtl; // $this->eventManager->addEventListener(WebApplication::EVENT_POST_RESPONSE, array($this, 'cacheResponse')); // } // } $this->view->setContext($context); if (is_array($dispatchedViews)) { $this->view->setDispatchedViews($dispatchedViews); $this->view->setRegions($this->regions); if ($nodeCacheTtl !== false && $cachedViews) { $cacheItem->setValue($cachedViews); $cacheItem->setTtl($nodeCacheTtl); $this->view->setCachedViews($cache, $cacheItem); } } $response->setView($this->view); }
/** * Publishes a site to the provided revision * @param \ride\library\cms\node\Node $node * @param string $revision * @param boolean $recursive Flag to see if the node's children should be * published as well * @return array|null Nodes have been deleted */ public function publish(Node $node, $revision, $recursive) { $deletedNodes = array(); if ($node->getRevision() == $revision) { return $deletedNodes; } $site = $node->getRootNode(); $siteId = $site->getId(); $nodeId = $node->getId(); $publishDirectory = $this->path->getChild($siteId . '/' . $revision); if (!$publishDirectory->exists()) { // first publish $sourceDirectory = $this->path->getChild($siteId . '/' . $node->getRevision()); $sourceDirectory->copy($publishDirectory); return $deletedNodes; } // publish revision exists, archive the revision before publishing $archiveDirectory = $this->path->getChild($siteId . '/' . $this->archiveName . '/' . date('YmdHis')); $publishDirectory->copy($archiveDirectory); // publish node $deletedNode = $this->publishNode($site, $node, $revision, $publishDirectory, false); if ($deletedNode) { $deletedNodes[$deletedNode->getId()] = $deletedNode; } if (!$recursive) { return $deletedNodes; } // publish recursive nodes $oldNodes = $this->getNodesByPath($siteId, $revision, $node->getPath()); $nodes = $this->getNodesByPath($siteId, $site->getRevision(), $node->getPath()); foreach ($nodes as $nodeId => $node) { $this->publishNode($site, $node, $revision, $publishDirectory, true); if (isset($oldNodes[$nodeId])) { unset($oldNodes[$nodeId]); } } // deleted the removed nodes foreach ($oldNodes as $oldNodeId => $oldNode) { $oldNodeFile = $this->getNodeFile($oldNode); $oldNodeFile->delete(); $deletedNodes[$oldNodeId] = $oldNode; } $this->expiredRouteModel->removeExpiredRoutesByNode($siteId, array_keys($oldNodes)); return $deletedNodes; }
/** * Gets the number of children levels for the provided node * @param \ride\library\cms\node\Node $node * @return integer */ public function getChildrenLevels(Node $node) { $nodeLevel = $node->getLevel(); $path = $node->getPath(); $levels = 0; $nodes = $this->getNodesByPath($node->getRootNodeId(), $node->getRevision(), $path); foreach ($nodes as $node) { $parent = $node->getParent(); $level = strlen($parent) - strlen(str_replace(Node::PATH_SEPARATOR, '', $parent)) + 1; $levels = max($levels, $level); } return $levels - $nodeLevel; }
/** * Gets the collapsed nodes for the provided node * @param \ride\library\cms\node\Node $node * @return array Array with the collapsed node id as key and true as value */ protected function getCollapsedNodes(Node $node) { $site = $node->getRootNodeId(); $revision = '[' . $node->getRevision() . ']'; $nodes = array(); $collapsedNodes = $this->cms->getCollapsedNodes(); foreach ($collapsedNodes as $node => $flag) { if (strpos($node, $site) !== 0 || strpos($node, $revision) === false) { continue; } $node = str_replace($revision, '', $node); $tokens = explode(Node::PATH_SEPARATOR, $node); $nodes[array_pop($tokens)] = true; } return $nodes; }
/** * Gets a list of the available nodes * @param \ride\library\cms\node\Node $node Root node for the list * @param string $locale Code of the locale * @param boolean $includeRootNode Flag to see if the root node should be * included in the result * @param boolean $includeEmpty Flag to see if a empty value should be * included in the result * @param boolean $onlyFrontendNodes Flag to filter on frontend nodes * @return array Array with the node id as key and a string as value */ public function getNodeList(Node $node, $locale, $includeRootNode = false, $includeEmpty = true, $onlyFrontendNodes = true) { $rootNode = $this->nodeModel->getNode($node->getRootNodeId(), $node->getRevision(), $node->getId(), null, true); $options = $this->nodeModel->getListFromNodes(array($rootNode), $locale, $onlyFrontendNodes); if ($includeRootNode) { $options = array($rootNode->getId() => '/' . $rootNode->getName($locale)) + $options; } if ($includeEmpty) { $options = array('' => '---') + $options; } return $options; }
/** * Validates the route of the node * @param \ride\library\cms\node\Node $node Node to be validated * @param \ride\library\cms\node\NodeModel $nodeModel Model of the nodes * @param \ride\library\validation\exception\ValidationException $exception * @return null */ protected function validateRoute(Node $node, NodeModel $nodeModel, ValidationException $exception) { if (!$node->getParent()) { return; } $rootNode = $node->getRootNode(); $rootNodeId = $rootNode->getId(); $nodeId = $node->getId(); $modelNodes = $nodeModel->getNodes($rootNodeId, $node->getRevision()); $propertyPrefix = Node::PROPERTY_ROUTE . '.'; $lengthPropertyPrefix = strlen($propertyPrefix); // loop all properties $properties = $node->getProperties(); foreach ($properties as $key => $property) { if (strpos($key, $propertyPrefix) !== 0) { // we're only interested in route properties continue; } $routeLocale = substr($key, $lengthPropertyPrefix); $route = $property->getValue(); // normalize route $route = trim($route, '/'); $baseUrls[$routeLocale] = $rootNode->getBaseUrl($routeLocale); $tokens = explode('/', $route); foreach ($tokens as $index => $token) { if ($token) { $token = StringHelper::safeString($token); } if (empty($token)) { unset($tokens[$index]); } else { $tokens[$index] = $token; } } $route = '/' . implode('/', $tokens); // check for duplicate routes $errors = array(); foreach ($modelNodes as $modelNode) { $modelNodeId = $modelNode->getId(); if ($modelNodeId == $nodeId || $modelNode->getRootNodeId() != $rootNodeId || !$modelNode->hasParent() || $modelNode->getType() == ReferenceNodeType::NAME) { // same node, different site or root node or a reference node continue; } $modelNodeRoutes = $modelNode->getRoutes(); foreach ($modelNodeRoutes as $locale => $modelNodeRoute) { if (!array_key_exists($locale, $baseUrls)) { $baseUrls[$locale] = $rootNode->getBaseUrl($locale); } if ($baseUrls[$routeLocale] . $route != $baseUrls[$locale] . $modelNodeRoute) { continue; } $errors[$modelNodeId] = new ValidationError('error.route.used.node', "Route '%route%' is already used by node %node% in locale %locale%", array('route' => $route, 'node' => $modelNodeId, 'locale' => $locale)); } } foreach ($errors as $error) { $exception->addErrors(Node::PROPERTY_ROUTE, array($error)); } // update property with normalized route $property->setValue($route); } }
/** * Set or toggle the collapse status of a node * @param \ride\library\cms\node\Node $node * @param boolean $isCollapsed * @return boolean Collapse state of the provided node */ public function collapseNode(Node $node, $isCollapsed = null) { $collapsedNodes = $this->getCollapsedNodes(); $path = $node->getPath() . '[' . $node->getRevision() . ']'; if ($isCollapsed !== null) { if ($isCollapsed && !isset($collapsedNodes[$path])) { $collapsedNodes[$path] = true; } elseif (!$isCollapsed && isset($collapsedNodes[$path])) { unset($collapsedNodes[$path]); } } elseif (isset($collapsedNodes[$path])) { unset($collapsedNodes[$path]); $isCollapsed = false; } else { $collapsedNodes[$path] = true; $isCollapsed = true; } $this->request->getSession()->set(self::SESSION_COLLAPSED_NODES, $collapsedNodes); $user = $this->securityManager->getUser(); if ($user) { $user->setPreference(self::SESSION_COLLAPSED_NODES, $collapsedNodes); $this->securityManager->getSecurityModel()->saveUser($user); } return $isCollapsed; }