Development Content 4.0

From Cloudrexx Development Wiki
Jump to: navigation, search


Note: This documentation refers to version 4 or older. For newer versions of Cloudrexx, please refer to Content.

Read the following descriptions to understand the difference in the architecture of the content tree between Contrex WMS Version 2 and 3:

Contrexx 2

Study the figure given here:

Contrexx WMS 2 Content Tree

For every language exists a seperate content tree. So it is possible to have a different site architecture in every language. The content tree is independent: An operation (for example deleting a page) in a language's content tree will not affect the content tree of any other language.

Contrexx 3

Study the figure given here:

Contrexx WMS 3 Content Tree

There is only one content tree for all languages. The nodes (\Cx\Core\ContentManager\Model\Entity\Node) determines the site structure. So the site structure is in every language the same. Every node has pages assigned (one page for every language). The content of the pages are stored in page objects (\Cx\Core\ContentManager\Model\Entity\Page). The root node is a special case: It has no content pages, it's just the invisible root of the node tree.

Page types

Every page has one of the following types:

Name (GUI) Name (CODE) Description Comment
Content Page CONTENT A normal content page
Redirect REDIRECTION A redirection to another node or website
Application APPLICATION A module page
Use Fallback Version FALLBACK A page using the content of another language
ALIAS An alias for a page or website This is a special case. Alias pages are not shown in Content Manager (more information at Find an alias).

Status Icons

Page Status Icons


Definition: a slug is a part of an URL:

URLs are built by traversing the node tree from the root down to the desired page. For all parent nodes we visit, the slug of the assigned page in the same language as the target page is appended to the url.

Example (see figure):

  • Assume the german Page assigned to node B has the slug 'Level1'
  • Assume the german Page assigned to node A has the slug 'Level2a'
  • Assume the german Page assigned to node C has the slug 'Level2b'

The following URLs would then be valid:

Every page stores it's slug name. The slug is automatically set when first giving the page a title.

Node-URL Notation

Areas of application

  • The Contrexx File Browser uses the Node-URL Notation to link to a internal webpage
  • Internal redirections (page type: REDIRECTION) should use the Node-URL Notation

Schema definition

A node URL can be represented by the following placeholder notation:


Part Description
<node_id> ID of the page's node
<module> name of the page's application
<cmd> additional CMD of the page's application
<lang_id> a specific language version of the page

Resolving the Node-URL Notation

Normally it's better to store the Node-URL placeholder instead of an URL and parse it when generating the output, since the placeholder is more flexible!

Contrexx 3.1+

Contrexx 3.1 includes the \Cx\Core\Routing\NodePlaceholder class. This class allows conversion from and to placeholder format. See the API documentation for further details.

Contrexx 3.0.x

A Node-URL placeholder can be resolved (replaced) to its URL-Notation by using the LinkGenerator

Use the LinkGenerator to parse the node-URL placeholders.

Resolving a Node-URL placeholder to Page Object

This is a bit tricky since Contrexx does not include a method to do this directly. The following method does the trick:

 * This resolves a node url placeholder to a page object
 * @param $placeholder Placeholder in the format [[NODE_...]] or {NODE_...}
 * @return \Cx\Core\ContentManager\Model\Entity\Page|null Page for placeholder if possible, null otherwise
public function resolvePlaceholderToPage($placeholder) {
    $placeholder = preg_replace('/\\{/', '[[', $placeholder);
    $placeholder = preg_replace('/\\}/', ']]', $placeholder);
    $pageRepository = \Env::get('em')->getRepository('Cx\Core\ContentManager\Model\Entity\Page');
    $page = new \Cx\Core\ContentManager\Model\Entity\Page();
    $nodeId = $page->getTargetNodeId();
    if ($nodeId) {
        $crit = array(
            'node' => $nodeId,
            'lang' => $page->getTargetLangId(),
    } else {
        $crit = array(
            'module' => $page->getTargetModule(),
            'cmd' => $page->getTargetCmd(),
            'lang' => $page->getTargetLangId(),
    $page = $pageRepository->findOneBy($crit);
    if (!$page) {
        // could not find the page
        return null;
    return $page;


  • [[NODE_2]]
    URL pointing to the requested language version of the page that is associated to the node with ID 2
  • [[NODE_SHOP]]
    URL pointing to the application shop
    URL pointing to the application shop that uses the cmd cart (i.e ?section=shop&cmd=cart)
  • [[NODE_SHOP_CART_2]]
    same as above, but the English version of the page (lang-ID 2 => English)

Code Examples

For Contrexx 3.1+: Please note, that the namespace for page and node has changed to \Cx\Core\ContentManager\Model\Entity.
For previous releases of Version 3 use \Cx\Model\ContentManager instead.

Find Pages

Find the actual page

Contrexx 3.0

$page = \Env::get('Resolver')->getPage();

Contrexx 3.1+

In Contrexx 3.1 the above still works, but it is recommended to use the Cx class now:

$page = \Env::get('cx')->getPage();

Find a single page

$pageRepo = \Env::get('em')->getRepository('Cx\Core\ContentManager\Model\Entity\Page');

// If you want to find a page by Page ID
$crit = array(
    'id' => $id,
$page = $pageRepo->findOneBy($crit);

// If you want to find a page by Node ID and lang
$crit = array(
    'node' => $nodeId,
    'lang' => $langId,
$page = $pageRepo->findOneBy($crit);

// If you want to find a page by Module, CMD and lang
$crit = array(
    'module' => $moduleName,
    'cmd' => $cmd,
    'lang' => $langId,
$page = $pageRepo->findOneBy($crit);

// If you want to find a module page by Module, CMD and lang which searches also in fallback pages if the page doesn't exist:
$page = $pageRepo->findOneByModuleCmdLang($module, $cmd, $lang);

Find a list of pages

$pageRepo = \Env::get('em')->getRepository('Cx\Core\ContentManager\Model\Entity\Page');

// If your search arguments match 'field' = 'value'
$crit = array(
    'lang' => $langId,
// Pages will contain an Array of Page Objects
$pages = $pageRepo->findBy($crit);

// If you need full SQL search capabilities
// You can get Page Objects by using Doctrine DQL
$qb = $this->em->createQueryBuilder();
    ->from('Cx\Core\ContentManager\Model\Entity\Page', 'p')
    ->where($qb->expr()->like('module', '?1')
    ->setParameter(1, '%'.$string.'%');

$pages = $qb->getQuery()->getResult();

Find an alias

$pageRepo = \Env::get('em')->getRepository('Cx\Core\ContentManager\Model\Entity\Page');

// find an alias by title
$criteria = array(
'title' => $title,
'type'  => \Cx\Core\ContentManager\Model\Entity\Page::TYPE_ALIAS,

$page = $pageRepo->findOneBy($criteria, true);

Aliases have the language id 0 which is not assigned to the active languages. For this reason you can't find them by passing the criteria "'lang' => 0". But you have the possibility to pass the boolean "true" as second parameter of "findOneBy()" which causes that the page repository also searches in the inactive languages.

Move an existing node

// Find the node we want to move
$nodeToMove = $nodeRepo->findOneBy(array('id'=>$moveNodeId));

// Find the new parent node (if different)
$newParentNode = $nodeRepo->findOneBy(array('id'=>$newParentId));

// Set new parent node (if different)

// Move the node to the top of the branch
$nodeRepo->moveUp($nodeToMove, true);

// Move the node to its new position within the branch
// $newPosition is the new position as an integer
$nodeRepo->moveDown($nodeToMove, $newPosition);

// Save changes to nodes

// Sanitize page paths
foreach ($nodeToMove->getPages() as $page) {

// Flush entity manager

Creating a Page

 //retrieve the doctrine entity manager
 $em = \Env::get('em');
 //retrieve the parent node
 $pageRepo = $em->getRepository('Cx\Core\ContentManager\Model\Entity\Page');
 $nodeRepo = $em->getRepository('Cx\Core\ContentManager\Model\Entity\Node');
 //parent page has id 3
 $parentPage = $pageRepo->findOneBy(array('id'=>3));
 $parentNode = $parentPage->getNode();
 //append a new child node for our page
 $node = new \Cx\Core\ContentManager\Model\Entity\Node();
 //set some data on our page
 $page = new \Cx\Core\ContentManager\Model\Entity\Page();
 //assign the page to our node

 // move it to where you want it (below same parent Node)
 $nodeRepo->moveDown($node, 3);
 //empty entity manager so the node tree is correctly re-read

Delete a single page

$page = $pageRepo->findOneBy(array('id'=>$id));

Delete a node with all its pages and subnodes

$node = $nodeRepo->findOneBy(array('id'=>$id));

Translating a Page

Every page with a language L must have all of it's parent pages available in translation to language L. If that is not the case, the page is lost and invisible. Remember how URLs are built - we can't build an URL for a page where we do not find the slugs in language L for the parent pages.

Look at the figure again. If we want to translate the german page on node A to english, we also need to insert a translated page on node B. As this is a common task, the PageRepository provides a method for translating a page in language X to language Y and automatically inserting the needed parent pages. The slugs of the translated parent pages are taken from the pages in language X.

// retrieve the doctrine entity manager
$em = Env::em();

// retrieve the page to translate
$pageRepo = $em->getRepository('Cx\Core\ContentManager\Model\Entity\Page');
$sourcePage = $pageRepo->getPagesAtPath('Level1/Level2a');

// translate it to english
$english = 2;
// see phpdoc for all available parameters of translate()
$translatedPage = $pageRepo->translate($sourcePage, $english);
// the page is now translated (copied to different language)

// set different content

// save and flush changes

Concept of owning or inverse side of a relation

In the Contrexx content manger is the page the owning side and the node the inverse side.

Database Structure

Content Model - Contrexx 3.0

Class diagram

Based on files from April 2012

Class diagram is outdated!