1. NordLabs
  2. NordCms

Commits

Christoffer Niska  committed 4403320

added support for node levels, publishing and restricted node access

  • Participants
  • Parent commits d5c099f
  • Branches default

Comments (0)

Files changed (17)

File components/Cms.php

View file
 
 	/**
 	 * Returns whether a specific page is active.
-	 * @param $name the content name
-	 * @return boolean
+	 * @param string $name the content name
+	 * @return boolean the result
 	 */
 	public function isActive($name)
 	{
 		$node = $this->loadNode($name);
 		$controller = Yii::app()->getController();
-		return $controller->module !== null
+		return ($controller->module !== null
 				&& $controller->module->id === 'cms'
 				&& $controller->id === 'node'
 				&& $controller->action->id === 'page'
-				&& isset($_GET['id']) && $_GET['id'] === $node->id;
+				&& isset($_GET['id']) && $_GET['id'] === $node->id)
+				|| $this->isChildActive($node);
+	}
+
+	/**
+	 * Returns whether a child node of a specific page is active.
+	 * @param CmsNode $node the node
+	 * @return boolean the result
+	 */
+	protected function isChildActive($node)
+	{
+		foreach ($node->children as $child)
+			if ($this->isActive($child->name) || $this->isChildActive($child))
+				return true;
+
+		return false;
 	}
 
 	/**

File components/CmsBaseRenderer.php

View file
 class CmsBaseRenderer extends CComponent
 {
 	protected $_patterns = array(
+		'email'=>'/{{email:([\w\d!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[\w\d!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[\w\d](?:[\w\d-]*[\w\d])?\.)+[\w\d](?:[\w\d-]*[\w\d])?)}}/i',
 		'file'=>'/{{file:([\d]+)}}/i',
 		'image'=>'/{{image:([\d]+)}}/i',
 		'link'=>'/{{(#?[\w\d\._-]+|https?:\/\/[\w\d_-]*(\.[\w\d_-]*)+.*)\|([\w\d\s-]+)}}/i',
-		'email'=>'/{{email:([\w\d!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[\w\d!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[\w\d](?:[\w\d-]*[\w\d])?\.)+[\w\d](?:[\w\d-]*[\w\d])?)}}/i',
 		'node'=>'/{{node:([\w\d\._-]+)}}/i',
+		'url'=>'/{{url:([\w\d\._-]+)}}/i',
 	);
 
 	/**
 	{
 		$heading = str_replace('{heading}', $node->heading, Yii::app()->cms->headingTemplate);
 		$content = $this->renderHeading($heading, $node->body);
+		$content = $this->renderURLs($content);
 		$content = $this->renderLinks($content);
 		$content = $this->renderEmails($content);
 		$content = $this->renderImages($content);
 		$content = $this->renderAttachments($content);
 		$content = $this->renderNodes($content);
-
 		return $content;
 	}
 
 	{
 		$heading = str_replace('{heading}', $node->heading, Yii::app()->cms->widgetHeadingTemplate);
 		$content = $this->renderHeading($heading, $node->body);
+		$content = $this->renderURLs($content);
 		$content = $this->renderLinks($content);
 		$content = $this->renderEmails($content);
 		$content = $this->renderImages($content);
 		$content = $this->renderAttachments($content);
 		$content = $this->removeNodes($content); // widgets do not render inline nodes
-
 		return $content;
 	}
 
 		$matches = array();
 		preg_match_all($this->_patterns['node'], $content, $matches);
 
-		$nodes = array();
+		$pairs = array();
 		foreach ($matches[1] as $index => $name)
 		{
 			/** @var CmsNode $node */
 			$node = Yii::app()->cms->loadNode($name);
-			$nodes[$index] = $node instanceof CmsNode ? $node->renderWidget() : '';
+			$pairs[$matches[0][$index]] = $node instanceof CmsNode ? $node->renderWidget() : '';
 		}
 
 		if (!empty($nodes))
-			$content = strtr($content, array_combine($matches[0], $nodes));
+			$content = strtr($content, $pairs);
 
 		return $content;
 	}
 		$matches = array();
 		preg_match_all($this->_patterns['link'], $content, $matches);
 
-		$links = array();
+		$pairs = array();
 		foreach ($matches[1] as $index => $target)
 		{
 			// If the target doesn't include 'http' it's treated as an internal link.
 			}
 
 			$text = $matches[3][$index];
-			$links[$index] = CHtml::link($text, $target);
+			$pairs[$matches[0][$index]] = CHtml::link($text, $target);
 		}
 
-		if (!empty($links))
-			$content = strtr($content, array_combine($matches[0], $links));
+		if (!empty($pairs))
+			$content = strtr($content, $pairs);
+
+		return $content;
+	}
+
+	/**
+	 * Renders URLS within the given content.
+	 * @param string $content the content being rendered
+	 * @return string the content
+	 */
+	protected function renderURLs($content)
+	{
+		$matches = array();
+		preg_match_all($this->_patterns['url'], $content, $matches);
+
+		$pairs = array();
+		foreach ($matches[1] as $index => $target)
+		{
+			// If the target doesn't include 'http' it's treated as an internal link.
+			if (strpos($target, '#') !== 0 && strpos($target, 'http') === false)
+			{
+				/** @var Cms $cms */
+				$cms = Yii::app()->cms;
+
+				/** @var CmsNode $node */
+				$node = $cms->loadNode($target);
+				$target = $node instanceof CmsNode ? $node->getUrl() : '#';
+			}
+
+			$pairs[$matches[0][$index]] = $target;
+		}
+
+		if (!empty($pairs))
+			$content = strtr($content, $pairs);
 
 		return $content;
 	}
 		$matches = array();
 		preg_match_all($this->_patterns['email'], $content, $matches);
 
-		$mails = array();
+		$pairs = array();
 		foreach ($matches[1] as $index => $id)
 		{
 			$email = str_rot13($matches[1][$index]);
-			$mails[$index] = CHtml::mailto($email, $email, array('class'=>'email', 'rel'=>'nofollow'));
+			$pairs[$matches[0][$index]] = CHtml::mailto($email, $email, array('class'=>'email', 'rel'=>'nofollow'));
 		}
 
-		if (!empty($mails))
+		if (!empty($pairs))
 		{
-			$content = strtr($content, array_combine($matches[0], $mails));
+			$content = strtr($content, $pairs);
 
 			/** @var CClientScript $cs */
 			$cs = Yii::app()->getClientScript();
 		$matches = array();
 		preg_match_all($this->_patterns['image'], $content, $matches);
 
-		$images = array();
+		$pairs = array();
 		foreach ($matches[1] as $index => $id)
 		{
 			/** @var CmsAttachment $attachment */
 			{
 				$url = $attachment->getUrl();
 				$name = $attachment->resolveName();
-				$images[$index] = CHtml::image($url, $name);
+				$pairs[$matches[0][$index]] = CHtml::image($url, $name);
+
 			}
 		}
 
-		if (!empty($images))
-			$content = strtr($content, array_combine($matches[0], $images));
+		if (!empty($pairs) )
+			$content = strtr($content, $pairs);
 
 		return $content;
 	}
 		$matches = array();
 		preg_match_all($this->_patterns['file'], $content, $matches);
 
-		$attachments = array();
+		$pairs = array();
 		foreach ($matches[1] as $index => $id)
 		{
 			/** @var CmsAttachment $attachment */
 			{
 				$url = $attachment->getUrl();
 				$name = $attachment->resolveName();
-				$attachments[$index] = CHtml::link($name, $url);
+				$pairs[$matches[0][$index]] = CHtml::link($name, $url);
 			}
 		}
 
-		if (!empty($attachments))
-			$content = strtr($content, array_combine($matches[0], $attachments));
+		if (!empty($pairs))
+			$content = strtr($content, $pairs);
 
 		return $content;
 	}

File components/CmsPageFilter.php

View file
 		if (isset($_GET['id']) && method_exists($controller, 'loadModel'))
 		{
 			$model = $controller->loadModel($_GET['id']);
+
+			// Prevent accessing of unpublished and block-level nodes.
+			if (!$model->published || $model->level !== CmsNode::LEVEL_PAGE)
+				throw new CHttpException(404, Yii::t('CmsModule.core', 'The requested page does not exist.'));
+
 			$url = $model->getUrl();
 
 			if (strpos(Yii::app()->request->getRequestUri(), $url) === false)

File controllers/NodeController.php

View file
 				$this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));
 		}
 		else
-			throw new CHttpException(400, 'Invalid request. Please do not repeat this request again.');
+			throw new CHttpException(400, Yii::t('CmsModule.core', 'Invalid request. Please do not repeat this request again.'));
 	}
 
 	/**
 		$model = CmsNode::model()->findByPk($id, 'deleted=0');
 
 		if ($model === null)
-			throw new CHttpException(404, 'The requested page does not exist.');
+			throw new CHttpException(404, Yii::t('CmsModule.core', 'The requested page does not exist.'));
 
 		return $model;
 	}

File data/schema.sql

View file
   `updated` timestamp NULL DEFAULT NULL,
   `parentId` int(10) NOT NULL DEFAULT '0',
   `name` varchar(255) NOT NULL,
-  `deleted` tinyint(4) NOT NULL DEFAULT '0',
+  `level` varchar(255) NOT NULL DEFAULT 'page',
+  `published` tinyint(1) NOT NULL DEFAULT '0',
+  `deleted` tinyint(1) NOT NULL DEFAULT '0',
   PRIMARY KEY (`id`),
   KEY `name_deleted` (`name`,`deleted`)
   UNIQUE INDEX `name` (`name`)

File models/CmsContent.php

View file
 			array('locale', 'length', 'max'=>50),
 			array('heading, url, pageTitle, breadcrumb, metaTitle, metaDescription, metaKeywords', 'length', 'max'=>255),
             array('attachment', 'file', 'types'=>Yii::app()->cms->allowedFileTypes, 'maxSize'=>Yii::app()->cms->allowedFileSize, 'allowEmpty'=>true),
-			array('body, css', 'filter', 'filter'=>array($this->getPurifier(), 'purify')),
+			array('body', 'safe'),
+			//array('body, css', 'filter', 'filter'=>array($this->getPurifier(), 'purify')),
 			array('id, nodeId, locale, heading, url, pageTitle, breadcrumb, metaTitle, metaDescription, metaKeywords', 'safe', 'on'=>'search'),
 		);
 	}

File models/CmsNode.php

View file
  * @property string $updated
  * @property integer $parentId
  * @property string $name
+ * @property string $level
+ * @property integer $published
  * @property integer $deleted
  *
  * The following relations are available for this model:
  * @property CmsNode $parent the parent node
+ * @property CmsNode[] $children the children nodes
  * @property CmsContent $content the content model for the current language
  * @property CmsContent $default the content model for the default language
  * @property CmsContent[] $translations the related content models
  */
 class CmsNode extends CmsActiveRecord
 {
+	const LEVEL_BLOCK = 'block';
+	const LEVEL_PAGE = 'page';
+
 	/**
 	 * Returns the static model of the specified AR class.
 	 * @param string $className the class name
 	public function rules()
 	{
 		return array(
-			array('id, parentId, deleted', 'numerical', 'integerOnly'=>true),
-			array('name', 'length', 'max'=>255),
+			array('id, parentId, published, deleted', 'numerical', 'integerOnly'=>true),
+			array('name, level', 'length', 'max'=>255),
 			array('updated', 'safe'),
 			array('id, created, updated, parentId, name, deleted', 'safe', 'on'=>'search'),
 		);
 	{
 		return array(
 			'parent'=>array(self::BELONGS_TO, 'CmsNode', 'parentId'),
+			'children'=>array(self::HAS_MANY, 'CmsNode', 'parentId'),
 			'translations'=>array(self::HAS_MANY, 'CmsContent', 'nodeId'),
 			'content'=>array(self::HAS_ONE, 'CmsContent', 'nodeId',
 					'condition'=>'locale=:locale', 'params'=>array(':locale'=>Yii::app()->language)),
 			'updated' => Yii::t('CmsModule.core', 'Updated'),
 			'name' => Yii::t('CmsModule.core', 'Name'),
 			'parentId' => Yii::t('CmsModule.core', 'Parent'),
-			'deleted' => Yii::t('CmsModule.core', 'Deleted'),
+			'level' => '',
 		);
 	}
 
 		$criteria->compare('updated',$this->updated,true);
 		$criteria->compare('name',$this->name,true);
 		$criteria->compare('parentId',$this->updated);
-		$criteria->compare('deleted',$this->deleted);
 
 		return new CActiveDataProvider($this, array(
 			'criteria'=>$criteria,
 
 	/**
 	 * Renders a single branch in the node tree.
-	 * @param $branch the branch
+	 * @param array $branch the branch
 	 */
 	protected function renderBranch($branch)
 	{
 	}
 
 	/**
+	 * Returns the level select options.
+	 * @return array the options
+	 */
+	public function getLevelOptions()
+	{
+		return array(
+			self::LEVEL_BLOCK=>Yii::t('CmsModule.core','Block'),
+			self::LEVEL_PAGE=>Yii::t('CmsModule.core','Page'),
+		);
+	}
+
+	/**
 	 * Creates content for this node.
 	 * @param string $locale the locale id, e.g. 'en_us'
 	 * @return CmsContent the content model
 	{
 		return Yii::app()->cms->renderer->renderWidget($this);
 	}
+
+	public function getPublished()
+	{
+		return (bool) $this->published;
+	}
 }

File themes/bootstrap/views/cms/node/_form.php

View file
 
 </fieldset>
 
-<fieldset class="form-page-settings">
-
-	<legend><?php echo Yii::t('CmsModule.core','Page settings') ?></legend>
-	
-	<p class="help-block"><?php echo Yii::t('CmsModule.core','Please note that the fields below are only used with pages.') ?></p>
-
-	<?php echo $form->textFieldRow($model,'['.$model->locale.']url',array('class'=>'span8')) ?>
-
-	<?php echo $form->textFieldRow($model,'['.$model->locale.']pageTitle',array('class'=>'span8')) ?>
-
-	<?php echo $form->textFieldRow($model,'['.$model->locale.']breadcrumb',array('class'=>'span8')) ?>
-
-    <?php echo $form->textFieldRow($model,'['.$model->locale.']metaTitle',array('class'=>'span8')) ?>
-
-    <?php echo $form->textAreaRow($model,'['.$model->locale.']metaDescription',array('class'=>'span8','rows'=>3)) ?>
-
-    <?php echo $form->textFieldRow($model,'['.$model->locale.']metaKeywords',array('class'=>'span8')) ?>
-
-</fieldset>
-
 <fieldset class="form-attachments">
 
     <legend><?php echo Yii::t('CmsModule.core', 'Attachments') ?></legend>
 
     <?php echo $form->fileFieldRow($model,'['.$model->locale.']attachment') ?>
 
-</fieldset>
+</fieldset>
+
+<?php if ($node->level === CmsNode::LEVEL_PAGE): ?>
+
+	<fieldset class="form-page-settings">
+
+		<legend><?php echo Yii::t('CmsModule.core','Page settings') ?></legend>
+
+		<?php echo $form->textFieldRow($model,'['.$model->locale.']url',array('class'=>'span8')) ?>
+		<?php echo $form->textFieldRow($model,'['.$model->locale.']pageTitle',array('class'=>'span8')) ?>
+		<?php echo $form->textFieldRow($model,'['.$model->locale.']breadcrumb',array('class'=>'span8')) ?>
+		<?php echo $form->textFieldRow($model,'['.$model->locale.']metaTitle',array('class'=>'span8')) ?>
+		<?php echo $form->textAreaRow($model,'['.$model->locale.']metaDescription',array('class'=>'span8','rows'=>3)) ?>
+		<?php echo $form->textFieldRow($model,'['.$model->locale.']metaKeywords',array('class'=>'span8')) ?>
+
+		<p><?php echo CHtml::link(Yii::t('CmsModule.core','View page'), $node->getUrl(), array('class'=>'btn')); ?></p>
+
+	</fieldset>
+
+<?php endif; ?>

File themes/bootstrap/views/cms/node/create.php

View file
 
 		<fieldset class="form-node">
 
-			<?php echo $form->textFieldRow($model,'name',array('hint'=>Yii::t('CmsModule.core','Node name cannot be changed after creation.'))) ?>
-
+			<?php echo $form->textFieldRow($model,'name',array('hint'=>Yii::t('CmsModule.core','Node name cannot be changed afterwards.'))) ?>
 			<?php echo $form->dropDownListRow($model,'parentId',$model->getParentOptionTree()) ?>
+			<?php echo $form->radioButtonListInlineRow($model,'level',$model->getLevelOptions()) ?>
 
 		</fieldset>
 

File themes/bootstrap/views/cms/node/index.php

View file
+<?php $this->breadcrumbs = array(
+	Yii::t('CmsModule.core','Cms')=>array('admin/index'),
+	Yii::t('CmsModule.core','Nodes'),
+) ?>
+
+<div class="node-index">
+
+	<h1><?php echo Yii::t('CmsModule.core','Nodes'); ?></h1>
+
+	<p><?php echo CHtml::link('<i class="icon-file"></i> '.Yii::t('CmsModule.core','Create a new node'),array('node/create'),array('class'=>'btn')) ?></p>
+
+	<?php $this->widget('bootstrap.widgets.BootGridView',array(
+		'dataProvider'=>$model->search(),
+		'columns'=>array(
+			'id',
+			'name',
+			array(
+				'name'=>'parentId',
+				'value'=>'$data->parent !== null ? $data->parent->name : ""',
+			),
+			array(
+				'class'=>'BootButtonColumn',
+				'viewButtonUrl'=>'Yii::app()->cms->createUrl($data->name)',
+			),
+		),
+	)) ?>
+
+</div>

File themes/bootstrap/views/cms/node/page.php

View file
+<div class="cms boot">
+
+	<div class="node-page">
+
+		<div class="node-content"><?php echo $content ?></div>
+
+		<?php if (Yii::app()->cms->checkAccess()): ?>
+			<?php echo CHtml::link('<i class="icon-pencil"></i> '.Yii::t('CmsModule.core','Update'),
+					array('node/update', 'id'=>$model->id), array('class'=>'btn update-link', 'title'=>Yii::t('CmsModule.core', 'Update'))) ?>
+		<?php endif ?>
+
+	</div>
+
+</div>

File themes/bootstrap/views/cms/node/update.php

View file
-<?php $this->breadcrumbs = CMap::mergeArray($model->getBreadcrumbs(true), array(
-	Yii::t('CmsModule.core','Update'))
+<?php $this->breadcrumbs = array(
+	Yii::t('CmsModule.core', 'Cms')=>array('/cms'),
+	Yii::t('CmsModule.core', 'Nodes')=>array('/cms/node'),
+	ucfirst($model->name),
 ) ?>
 
 <div class="node-update">
 
 	<?php $form = $this->beginWidget('BootActiveForm',array(
 		'id'=>'cmsUpdateNodeForm',
-		//'enableAjaxValidation'=>true,
 		'htmlOptions'=>array('enctype'=>'multipart/form-data'),
 	)) ?>
 
 		<fieldset class="form-node">
 
-			<?php echo $form->uneditableRow($model,'name',array('hint'=>Yii::t('CmsModule.core','Node name cannot be changed.'))) ?>
-
+			<?php echo $form->uneditableRow($model,'name') ?>
 			<?php echo $form->dropDownListRow($model,'parentId',$model->getParentOptionTree()) ?>
+			<?php echo $form->radioButtonListInlineRow($model,'level',$model->getLevelOptions()) ?>
+			<?php echo $form->checkBoxRow($model,'published') ?>
 
 		</fieldset>
 
 		<?php $tabs = array();
 		foreach ($translations as $locale => $content) {
 			$language = Yii::app()->cms->languages[$locale];
-			$tab = $this->renderPartial('_form',array(
+			$tabs[] = array('label'=>$language, 'content'=>$this->renderPartial('_form',array(
 				'model'=>$content,
 				'form'=>$form,
 				'node'=>$model,
 				'language'=>$language,
-			), true);
-			$tabs[$language] = $tab;
+			), true));
 		} ?>
 
 		<?php $this->widget('bootstrap.widgets.BootTabbed',array(

File views/node/_form.php

View file
 
 </fieldset>
 
-<fieldset class="form-page-settings">
-
-    <legend><?php echo Yii::t('CmsModule.core', 'Page settings') ?></legend>
-
-	<p class="hint"><?php echo Yii::t('CmsModule.core','Please note that the fields below are only used with pages.') ?></p>
-
-	<div class="row">
-		<?php echo $form->labelEx($model,'['.$model->locale.']url') ?>
-		<?php echo $form->textField($model,'['.$model->locale.']url') ?>
-		<?php echo $form->error($model,'['.$model->locale.']url') ?>
-	</div>
-
-	<div class="row">
-		<?php echo $form->labelEx($model,'['.$model->locale.']pageTitle') ?>
-		<?php echo $form->textField($model,'['.$model->locale.']pageTitle') ?>
-		<?php echo $form->error($model,'['.$model->locale.']pageTitle') ?>
-	</div>
-
-	<div class="row">
-		<?php echo $form->labelEx($model,'['.$model->locale.']breadcrumb') ?>
-		<?php echo $form->textField($model,'['.$model->locale.']breadcrumb') ?>
-		<?php echo $form->error($model,'['.$model->locale.']breadcrumb') ?>
-        <p class="hint"><?php echo Yii::t('CmsModule.core','The breadcrumb text for this node.') ?></p>
-	</div>
-
-    <div class="row">
-        <?php echo $form->labelEx($model,'['.$model->locale.']metaTitle') ?>
-        <?php echo $form->textField($model,'['.$model->locale.']metaTitle') ?>
-        <?php echo $form->error($model,'['.$model->locale.']metaTitle') ?>
-    </div>
-
-    <div class="row">
-        <?php echo $form->labelEx($model,'['.$model->locale.']metaDescription') ?>
-        <?php echo $form->textArea($model,'['.$model->locale.']metaDescription',array('rows'=>3)) ?>
-        <?php echo $form->error($model,'['.$model->locale.']metaDescription') ?>
-    </div>
-
-    <div class="row">
-        <?php echo $form->labelEx($model,'['.$model->locale.']metaKeywords') ?>
-        <?php echo $form->textField($model,'['.$model->locale.']metaKeywords') ?>
-        <?php echo $form->error($model,'['.$model->locale.']metaKeywords') ?>
-    </div>
-
-</fieldset>
-
 <fieldset class="form-attachments">
 
     <legend><?php echo Yii::t('CmsModule.core', 'Attachments') ?></legend>
         <?php echo $form->error($model,'['.$model->locale.']attachment') ?>
     </div>
 
-</fieldset>
+</fieldset>
+
+<?php if ($node->level === CmsNode::LEVEL_PAGE): ?>
+
+	<fieldset class="form-page-settings">
+
+		<legend><?php echo Yii::t('CmsModule.core', 'Page settings') ?></legend>
+
+		<p class="hint"><?php echo Yii::t('CmsModule.core','Please note that the fields below are only used with pages.') ?></p>
+
+		<div class="row">
+			<?php echo $form->labelEx($model,'['.$model->locale.']url') ?>
+			<?php echo $form->textField($model,'['.$model->locale.']url') ?>
+			<?php echo $form->error($model,'['.$model->locale.']url') ?>
+		</div>
+
+		<div class="row">
+			<?php echo $form->labelEx($model,'['.$model->locale.']pageTitle') ?>
+			<?php echo $form->textField($model,'['.$model->locale.']pageTitle') ?>
+			<?php echo $form->error($model,'['.$model->locale.']pageTitle') ?>
+		</div>
+
+		<div class="row">
+			<?php echo $form->labelEx($model,'['.$model->locale.']breadcrumb') ?>
+			<?php echo $form->textField($model,'['.$model->locale.']breadcrumb') ?>
+			<?php echo $form->error($model,'['.$model->locale.']breadcrumb') ?>
+			<p class="hint"><?php echo Yii::t('CmsModule.core','The breadcrumb text for this node.') ?></p>
+		</div>
+
+		<div class="row">
+			<?php echo $form->labelEx($model,'['.$model->locale.']metaTitle') ?>
+			<?php echo $form->textField($model,'['.$model->locale.']metaTitle') ?>
+			<?php echo $form->error($model,'['.$model->locale.']metaTitle') ?>
+		</div>
+
+		<div class="row">
+			<?php echo $form->labelEx($model,'['.$model->locale.']metaDescription') ?>
+			<?php echo $form->textArea($model,'['.$model->locale.']metaDescription',array('rows'=>3)) ?>
+			<?php echo $form->error($model,'['.$model->locale.']metaDescription') ?>
+		</div>
+
+		<div class="row">
+			<?php echo $form->labelEx($model,'['.$model->locale.']metaKeywords') ?>
+			<?php echo $form->textField($model,'['.$model->locale.']metaKeywords') ?>
+			<?php echo $form->error($model,'['.$model->locale.']metaKeywords') ?>
+		</div>
+
+		<p><?php echo CHtml::link(Yii::t('CmsModule.core','View page'), $node->getUrl()); ?></p>
+
+	</fieldset>
+
+<?php endif; ?>

File views/node/_tags.php

View file
 <ul>
 	<li><strong>{{heading}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','displays the page heading'); ?></em></li>
 	<li><strong>{{node:name}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','displays an inline node'); ?></em></li>
+	<li><strong>{{url:name}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','creates an URL to a page'); ?></em></li>
 	<li><strong>{{image:id}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','displays an attached image'); ?></em></li>
 	<li><strong>{{file:id}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','creates a link to an attached file'); ?></em></li>
 	<li><strong>{{email:address}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','creates a mailto link'); ?></em></li>
-	<li><strong>{{name|text}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','creates a link to another page'); ?></em></li>
+	<li><strong>{{name|text}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','creates a link to a page'); ?></em></li>
 	<li><strong>{{address|text}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','creates an external link'); ?></em></li>
 	<li><strong>{{#anchor|text}}</strong> &mdash; <em><?php echo Yii::t('CmsModule.core','creates a link to an anchor on the page'); ?></em></li>
 </ul>

File views/node/create.php

View file
 			<?php echo $form->error($model,'parentId') ?>
 		</div>
 
+		<div class="row">
+			<?php echo $form->radioButtonList($model,'level',$model->getLevelOptions()) ?>
+			<?php echo $form->error($model,'level') ?>
+		</div>
+
 		<div class="row buttons">
 			<?php echo CHtml::submitButton(Yii::t('CmsModule.core', 'Create')) ?>
 		</div>

File views/node/update.php

View file
 				<?php echo $form->error($model,'parentId') ?>
 			</div>
 
+			<div class="row">
+				<?php echo $form->radioButtonList($model,'level',$model->getLevelOptions()) ?>
+				<?php echo $form->error($model,'level') ?>
+			</div>
+
+			<div class="row">
+				<?php echo $form->checkBox($model,'published') ?>
+				<?php echo $form->error($model,'published') ?>
+			</div>
+
 		</fieldset>
 
 		<?php $tabs = array();

File widgets/CmsBlock.php

View file
             $model = $cms->loadNode($this->name);
         }
 
-		if ($model->content !== null && !empty($model->content->css))
-			$app->clientScript->registerCss($model->name.'#'.$this->getId(), $model->content->css);
+		// Ensure that we only render block-level nodes.
+		if ($model->published && $model->level === CmsNode::LEVEL_BLOCK) {
+			if ($model->content !== null && !empty($model->content->css))
+				$app->clientScript->registerCss($model->name.'#'.$this->getId(), $model->content->css);
 
-        $this->render('block', array(
-            'model'=>$model,
-            'content'=>$model->renderWidget(),
-        ));
+			$this->render('block', array(
+				'model'=>$model,
+				'content'=>$model->renderWidget(),
+			));
+		}
 	}
 }