/** * Saves the node tree from the structure in text format * @param string $locale Locale of the structure * @param \ride\library\cms\node\Node $parent Parent node of the structure * @param \ride\library\cms\node\NodeModel $nodeModel Instance of the node * model * @param string $structure Node tree in text format * @return null */ public function setStructure($locale, Node $parent, NodeModel $nodeModel, $structure) { $site = $parent->getRootNode(); if ($site->isLocalizationMethodUnique()) { $isUniqueTree = true; } else { $isUniqueTree = false; } $previousNodeId = null; $previousSpaces = null; $level = 0; $levels = array(0 => $site->getId()); $order = array($site->getId() => 0); $spaces = array(); $structure = $this->parseStructure($structure); foreach ($structure as $index => $nodeArray) { $structure[$index]['node'] = $this->saveNode($locale, $site, $nodeModel, $nodeArray, $isUniqueTree); $structure[$index]['id'] = $structure[$index]['node']->getId(); if ($previousSpaces === null) { $spaces[$level] = $nodeArray['spaces']; } elseif ($nodeArray['spaces'] > $previousSpaces) { $level++; $levels[$level] = $previousNodeId; $spaces[$level] = $nodeArray['spaces']; } elseif ($nodeArray['spaces'] < $previousSpaces) { krsort($spaces); foreach ($spaces as $spaceLevel => $numSpaces) { if ($nodeArray['spaces'] >= $numSpaces) { $level = $spaceLevel; break; } } } $order[$levels[$level]]++; $order[$structure[$index]['id']] = 0; $previousNodeId = $structure[$index]['id']; $previousSpaces = $nodeArray['spaces']; } $siblings = $nodeModel->getNodesByPath($site->getId(), $site->getRevision(), $parent->getId()); foreach ($siblings as $siblingId => $sibling) { if (isset($order[$siblingId]) || $isUniqueTree && !$sibling->isAvailableInLocale($locale)) { continue; } $nodeModel->removeNode($sibling, false, null, false); } unset($order[$parent->getId()]); $nodeModel->orderNodes($site->getId(), $site->getRevision(), $parent->getId(), $order, $locale); }
/** * 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; }
/** * Get the widgets for a section in a region for the provided node * @param Node $node Node to query * @param string $region Name of the region * @param string $section Name of the section * @param boolean $inherited Fetch only inherited properties of the node * @return array Array with the block id as key and as value an array with the * widget instance id as key and the widget id as value */ protected function getSectionWidgets(Node $node, $region, $section, $inherited) { // resolve set widgets $sectionString = $node->get(self::PROPERTY_REGION . '.' . $region . '.' . $section . '.' . self::PROPERTY_WIDGETS, null, true, $inherited); if (!$sectionString) { return array(); } // parse widget string $sectionWidgets = $this->parseSectionString($node->getRootNode(), $sectionString); if ($inherited) { return $sectionWidgets; } // resolve inherited widgets $inheritedSectionWidgets = $this->getInheritedWidgets($region, $section); foreach ($sectionWidgets as $blockId => $blockWidgets) { if (!$blockWidgets && isset($inheritedSectionWidgets[$blockId])) { $sectionWidgets[$blockId] = $inheritedSectionWidgets[$blockId]; } } return $sectionWidgets; }
/** * Clones the node's properties to the destination * @param \ride\library\cms\node\Node $source Source node * @param \ride\library\cms\node\Node $destination Destination node * @param boolean $keepOriginalName Set to true to keep the name untouched * @param boolean $cloneRoutes Set to true to clone the routes of the nodes * @return null */ protected function cloneNodeProperties(Node $source, Node $destination, $keepOriginalName, $cloneRoutes) { $widgetPropertyPrefixLength = strlen(Node::PROPERTY_WIDGET) + 1; try { $site = $destination->getRootNode(); } catch (CmsException $exception) { $site = $destination; } $parent = $source->getParentNode(); $sourceProperties = $source->getProperties(); $destinationProperties = array(); // duplicate the regions and the set widgets foreach ($sourceProperties as $index => $sourceProperty) { $key = $sourceProperty->getKey(); if (strpos($key, Node::PROPERTY_REGION) !== 0 || !strpos($key, '.' . Node::PROPERTY_WIDGETS)) { continue; } $tokens = explode('.', $key); $region = $tokens[1]; $section = $tokens[2]; $newValue = array(); $inheritedWidgetIds = array(); if ($parent) { $inheritedValue = $parent->get($key, null, true, true); if ($inheritedValue) { $inheritedWidgetIds = $source->parseSectionString($source->getRootNode(), $inheritedValue); } } $sourceWidgetIds = $source->parseSectionString($source->getRootNode(), $sourceProperty->getValue()); foreach ($sourceWidgetIds as $blockId => $blockWidgets) { $newValue[$blockId] = array(); foreach ($blockWidgets as $widgetId => $widget) { if (isset($this->widgetTable[$widgetId])) { $newWidgetId = $this->widgetTable[$widgetId]; } elseif (isset($inheritedWidgetIds[$blockId][$widgetId])) { // use the same widget id for inherited widgets $this->widgetTable[$widgetId] = $widgetId; $newWidgetId = $widgetId; } else { // create a new widget for node widgets $widget = $source->getWidget($widgetId); $newWidgetId = $site->createWidget($widget); $this->widgetTable[$widgetId] = $newWidgetId; } $newValue[$blockId][] = $newWidgetId; } $newValue[$blockId] = Node::BLOCK_OPEN . implode(NodeProperty::LIST_SEPARATOR, $newValue[$blockId]) . Node::BLOCK_CLOSE; } $destinationProperty = new NodeProperty($key, implode(NodeProperty::LIST_SEPARATOR, $newValue), $sourceProperty->getInherit()); $destinationProperties[$key] = $destinationProperty; unset($sourceProperties[$index]); } // duplicate the remaining properties foreach ($sourceProperties as $index => $sourceProperty) { $key = $sourceProperty->getKey(); $value = $sourceProperty->getValue(); $destinationProperty = new NodeProperty($key, $value, $sourceProperty->getInherit()); if (!$keepOriginalName && strpos($key, Node::PROPERTY_NAME . '.') === 0) { // add copy suffix to the name $locale = str_replace(Node::PROPERTY_NAME . '.', '', $key); if ($source->getLevel() === 0) { $children = $this->io->getSites(); } else { $children = $this->io->getChildren($source->getRootNodeId(), $source->getRevision(), $source->getParent(), 0); } $baseName = $value; $name = $baseName . ' (clone)'; $index = 2; do { $found = false; foreach ($children as $child) { $childName = $child->getName(); if ($childName == $name) { $name = $baseName . ' (clone ' . $index . ')'; $index++; $found = true; break; } } } while ($found); $destinationProperty->setValue($name); } elseif (!$cloneRoutes && strpos($key, Node::PROPERTY_ROUTE . '.') === 0) { // skip this property as it's a route and it's flagged to not clone routes continue; } elseif (strpos($key, Node::PROPERTY_WIDGET . '.') === 0) { // remap the widget ids for widget properties $propertyKey = substr($key, $widgetPropertyPrefixLength); $positionPropertySeparator = strpos($propertyKey, '.'); if ($positionPropertySeparator === false) { continue; } $widgetId = substr($propertyKey, 0, $positionPropertySeparator); $propertyKey = substr($propertyKey, $positionPropertySeparator); if (!isset($this->widgetTable[$widgetId])) { continue; } $destinationProperty = new NodeProperty(Node::PROPERTY_WIDGET . '.' . $this->widgetTable[$widgetId] . $propertyKey, $destinationProperty->getValue(), $destinationProperty->getInherit()); } $destinationProperties[$destinationProperty->getKey()] = $destinationProperty; } $destination->setProperties($destinationProperties); }
/** * 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); } }
/** * Checks if this action is available for the node * @param \ride\library\cms\node\Node $node * @return boolean True if available */ public function isAvailableForNode(Node $node) { return !$node->getRootNode()->isAutoPublish(); }