Ebben a postban nem kisebb feladatot tűzök ki, mint hogy az sfAdminThemejRoller segítségével egy mini cmst fogunk létrehozni. Pontosabban csak egy menüt, amibe felvihetünk tartalmakat és a már meglévő modulokat.

Hozzuk kétre az adatbázis sémát:
config/doctrine/schema.yml bővítése:
[sourcecode language=”plain”]
Cms:
actAs:
Sluggable:
fields: [title]
name: slug
type: string
length: 255
unique: true
NestedSet:
hasManyRoots: true
rootColumnName: root_id
columns:
id:
type: integer(11)
primary: true
autoincrement: true
title:
type: string(60)
notnull: true
description:
type: text
notnull: true
is_module:
type: boolean
default: false
route:
type: string(200)
notnull: false
content:
type: blob
notnull: false
[/sourcecode]

Az alábbi pluginokat kapcsoljuk be:
config/ProjectConfiguration.class.php fájlt:
[sourcecode language=”php”]
$this->enablePlugins(‘sfDoctrinePlugin’,
‘sfAdminThemejRollerPlugin’,
‘sfDoctrineNestedSetPlugin’,
‘sfJqueryReloadedPlugin’);
[/sourcecode]

[sourcecode language=”bash”]
# generáljuk újra az adatbázist és az osztályokat (sajnos az adatokat így most elveszítjük):
./symfony doctrine:build –all
# majd generáljuk le a cms admin felületét:
./symfony doctrine:generate-admin –module=CmsAdmin frontend Cms
# és szükség lesz a cms frontend felületére is:
./symfony generate:module frontend cms
[/sourcecode]

A cms admin lesz tehát az a modul, ahol az sfAdminThemejRollert használjuk:
touch apps/frontend/modules/CmsAdmin/config/generator.yml
[sourcecode language=”plain”]
generator:
class: jRollerDoctrineGenerator
param:
model_class: Cms
theme: jroller
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: cms
with_doctrine_route: true
actions_base_class: sfActions

config:
actions: ~
fields: ~
list:
layout: nestedset
display: [getTitleWithId]
object_actions:
_show:
jq_dialogbox : show_dialog
_edit:
jq_dialogbox : edit_dialog
_delete:
remote : true
afterRemote : refreshList # or removeRow
batch_actions: []
actions:
_new:
jq_dialogbox : new_dialog
filter: ~
form: ~
edit: ~
new: ~
[/sourcecode]
Két fontos és eddig nem használt beállítás kerül bele a generator.yml fájlba.
A layout:nestedset beállítás a jstree plugint húzza rá a nestedset modelre generált modulunkra. A getTitleWithId pedig a model osztályunk függvénye lesz. Ilyen nincs neki, ezért létrehozzuk:
lib/model/doctrine/Cms.class.php
[sourcecode language=”php”]
class Cms extends BaseCms
{

public function getTitleWithId()
{
return $this->title.'(‘.$this->id.’)’;
}

}
[/sourcecode]

Szeretném ha a slugot (az az url, amin megjelenik a cikk) megadható lenne, de mégis szeretném, ha automatikusan felajánlana egy várhatólag megfelelőt. Ez egy javascript lesz, de nem érinti a teljes projektet, ezért nem teszem ki egy külön fájlba, csak elhelyezem a form alján:
apps/frontend/modules/CmsAdmin/templates/_form_footer.php
[sourcecode language=”javascript”]
<script type="text/javascript">
$(function() {

function slugize(str)
{
str = str.toLowerCase();
str = str.replace(/á/g,"a");
str = str.replace(/Á/g,"a");
str = str.replace(/é/g,"e");
str = str.replace(/É/g,"e");
str = str.replace(/í/g,"i");
str = str.replace(/Í/g,"i");
str = str.replace(/ó/g,"o");
str = str.replace(/Ó/g,"o");
str = str.replace(/ö/g,"o");
str = str.replace(/Ö/g,"o");
str = str.replace(/ő/g,"o");
str = str.replace(/Ő/g,"o");
str = str.replace(/ú/g,"u");
str = str.replace(/Ú/g,"u");
str = str.replace(/ü/g,"u");
str = str.replace(/Ü/g,"u");
str = str.replace(/ű/g,"u");
str = str.replace(/Ű/g,"u");
str = str.replace(/[^A-Za-z0-9\-_]/g,"_");

return str
}

$(‘#cms_title’).bind(‘keyup’, function(event){
title_id = $(this).attr(‘id’) ;
slug_id = title_id.replace(‘title’, ‘slug’) ;
$(‘#’+slug_id).val(slugize($(‘#’+title_id).val()));
})

$(‘#cms_slug’).bind(‘change’, function(){
slug_id = $(this).attr(‘id’) ;
title_id = slug_id.replace(‘slug’, ‘title’) ;
if ($(‘#’+slug_id).val() != slugize($(‘#’+title_id).val()))
{
$(‘#’+title_id).unbind(‘keyup’);
}
})

})
</script>
[/sourcecode]

Ezen kivül a generált Cms formban el kell rejteni a felhasználó számára felesleges mezőket:
lib/form/doctrine/CmsForm.class.php
[sourcecode language=”php”]
class CmsForm extends BaseCmsForm
{
public function configure()
{
$this->widgetSchema[‘root_id’] = new sfWidgetFormInputHidden();
$this->widgetSchema[‘rgt’] = new sfWidgetFormInputHidden();
$this->widgetSchema[‘lft’] = new sfWidgetFormInputHidden();
$this->widgetSchema[‘level’] = new sfWidgetFormInputHidden();
}
}
[/sourcecode]

Ezzel be is fejeztük az admin felületet. Már csak a frontend van hátra. 🙂

A frontenden egyetlen action lesz, ahol megjelenítünk egy tartalmat, feltéve hogy egy tartalomról van szó. Ha nem egy tartalom (is_module = true), akkor át kell irányítanunk a route mezőben megadott címre:
apps/frontend/modules/cms/actions/actions.class.php
[sourcecode language=”php”]
class cmsActions extends sfActions
{
/**
* Executes show action
*
* @param sfRequest $request A request object
*/
public function executeShow(sfWebRequest $request)
{
$this->page = $this->getRoute()->getObject();
if ($this->page->get(‘is_module’))
{
$this->redirect($this->page->get(‘route’));
}
}
}
[/sourcecode]

Tartozik az actionhöz egy template is:
apps/frontend/modules/cms/templates/showSuccess.php
[sourcecode language=”php”]
<?php include_partial(‘cms/assets’) ?>

<?php slot(‘title’) ?>
<?php echo $page->getTitle() ?>
<?php end_slot(); ?>

<h1><?php echo $page->getTitle() ?></h1>
<div class="cms-content">

<?php echo html_entity_decode($page->getContent()) ?>
</div>
[/sourcecode]

A beszúrt partial a jquery-ui és a jquery css és js fájljait tölti be, ez megtalálható a generált fájlok között bármelyik generált modul alatt a cache mappában. Azonban ez nem egy generált modul így másoljuk ki a cms modulunk template mappájába, vagy hozzuk létre:
apps/frontend/modules/cms/templates/_assets.php
[sourcecode language=”php”]
<?php use_stylesheet(‘/sfAdminThemejRollerPlugin/css/reset.css’, ‘first’) ?>
<?php use_javascript(‘/sfAdminThemejRollerPlugin/js/jquery.min.js’, ‘first’) ?>
<?php use_javascript(‘/sfAdminThemejRollerPlugin/js/jquery-ui.min.js’, ‘first’) ?>
<?php use_stylesheet(‘/sfAdminThemejRollerPlugin/css/jquery/ui-lightness/jquery-ui.css’) ?>
[/sourcecode]

Persze ezzel még nem jelenik meg az oldal, hiszen létre kell hoznunk a route osztályt.
Először is deklarálunk egy új routeing szabályt (tehetjük a legenerált collectionok után):

apps/frontend/config/routing.yml
[sourcecode language=”plain”]
cms_slug:
url: /:slug
class: cmsDoctrineRoute
options: { model: Cms, type: object, method: retrieveBySlug }
param: { module: cms, action: show }
requirements:
slug: (?:[^?]*)
sf_method: [post, get]
[/sourcecode]

Ahogy olvasható, el kell készítenünk egy saját route osztályt: cmsDoctrineRoute
A route osztályunk pedig a tábla (CmsTable) retrieveBySlug függvényét fogja használni.
Ha van egyezés, akkor illeszkedik a szabályra, így az actionben kiadott $this->getRoute()->getObject() lekérés a megfelelő cms rekordot fogja visszaadni.

lib/model/doctrine/CmsTable.class.php
[sourcecode language=”php”]
/**
* CmsTable
*
* This class has been auto-generated by the Doctrine ORM Framework
*/
class CmsTable extends Doctrine_Table
{
/**
* Returns an instance of this class.
*
* @return object CmsTable
*/
public static function getInstance()
{
return Doctrine_Core::getTable(‘Cms’);
}

public function retrieveBySlug($options = array())
{

if (!isset($options[‘slug’]))
{
throw new InvalidArgumentException(‘The slug is required in the options’);
}

return $this->createQuery(‘c’)
->where(‘slug = ?’, $options[‘slug’])->fetchOne();

}
}
[/sourcecode]

lib/route/cmsDoctrineRoute.class.php
[sourcecode language=”php”]
class cmsDoctrineRoute extends sfDoctrineRoute
{
public function matchesUrl($url, $context = array())
{
if (false === $parameters = parent::matchesUrl($url, $context))
{
return false;
}

if (!$parameters[‘slug’])
{
return false;
}

if ($res = parent::matchesUrl($url, $context))
{
$page = Doctrine_Core::getTable(‘Cms’)->retrieveBySlug($parameters);
}

if (!$page)
{
return false;
}
else
{
$this->object = $page;
}

return $res;
}
}
[/sourcecode]

Ezzel már elérhető lenne egy cms bejegyzés, amit a CmsAdmin felületen kreáltunk.
Viszont jó lenne az ott látható fát egy menüpontban megjeleníteni. Én a bal oldalra tervezem a menüt. Habár lehetne fent egy vízszintesen megjelenő menü is, de mivel problémás a jquery-ui menu pluginjának a vízszintes elrendezése, és nem szeretnék egy css menüvel összezavarni senkit sem, ezért maradunk a bal oldalnál 🙂

Először is valahogy ki kell tenni a menüt. Erre egy komponenst fogunk itt is létrehozni és betesszük a (közös!) layoutba, hogy mindig kint legyen. A komponensnek két függvénye van, az egyik a teljes menüt megjeleníti, a másik csak egy ágat jelenít meg. Ez utóbbi csavar azért került bele, mert habár mi most nem akarunk mindenre megoldást találni, a jövőben előfordulhat hogy nem a teljes menüt jelenítenénk meg, hanem annak csak egy darabját. Például ha felülre is akarunk egy menüt, és alulra is egyet és esetleg lenne olyan ága is, amit egyáltalán nem akarunk megjeleníteni, de a cms szerkesztőjéből szeretnénk szerkeszteni. Például lehet egy ág, ahol a hírleveleink levélsablonját tartjuk karban.
apps/frontend/modules/cms/actions/components.class.php
[sourcecode language=”php”]
<?php
class CmsComponents extends sfComponents
{
/**
* template : megadja hogy komponens jeleniti meg a menut.
* layout : hogyan jeleniti meg a visszakapott templatet.
*/
public function executeMenu()
{
$this->template = isset($this->template) ? $this->template : ‘menuItem’;
$this->layout = isset($this->layout) ? $this->layout : ‘<ul id="fomenu">%s</ul>’;

$treeObject = Doctrine_Core::getTable(‘Cms’)->getTree();

$this->root = $treeObject->fetchRoot();
}

public function executeMenuItem()
{
$this->children = $this->child->getNode()->getChildren();
$this->template = isset($this->template) ? $this->template : ‘list’;
}
}
[/sourcecode]

Kellenek a templatek is:
apps/frontend/modules/cms/templates/_menu.php
[sourcecode language=”php”]
<?php $content = ”; ?>
<?php foreach ($root->getNode()->getChildren() as $child) : ?>
<?php $content .= get_component(‘cms’, $template, compact(‘child’, ‘template’)) ; ?>
<?php endforeach; ?>
<?php echo html_entity_decode(sprintf($layout, $content)) ; ?>
[/sourcecode]
apps/frontend/modules/cms/templates/_menuItem.php
[sourcecode language=”php”]
<li class="level<?php echo $child->getLevel() ?>"><?php echo link_to($child->getTitle(), ‘@cms_slug?slug=’.$child->getSlug(), array(‘title’=>$child->getTitle())); ?>
<?php if ($children) : ?>
<ul>
<?php foreach ($children as $child) : ?>
<?php include_component(‘cms’, $template, compact(‘child’, ‘template’)) ; ?>
<?php endforeach; ?>
</ul>
<?php endif ?>
</li>
[/sourcecode]

És akkor a layoutot is szerkesszük át:
apps/frontend/templates/layout.php
[sourcecode language=”php”]

<body>
<div id="page_header">
<?php include_component(‘cms’, ‘menu’) ?>
</div>
<div id="page_content">
<?php echo $sf_content ?>
</div>
</body>

[/sourcecode]

És akkor még egy kicsit designolni is kell, és meg rá kell ereszteni a menüre a menu plugint
Mivel a menü mindenhol meg kell jelenjen, ezért ezt egy közös js és css fájlban fogom megtenni.

web/css/main.css
[sourcecode language=”css”]
.list-product #sf_admin_header{
float: left;
margin-top: 50px;
width: 15%;
}
.list-product #sf_admin_content {
float: left;
width: 85%;
}
.ui-menu { width: 90%; }

#page_header{
float: left;
margin-top: 50px;
width: 15%;
position:relative;
}
#page_content {
float: left;
width: 85%;
}

.ui-menu .ui-menu-icon {
float: right;
position: static;
}

.ui-menu .ui-menu {
margin-top: -3px;
z-index: 1000;
position: absolute;
}
[/sourcecode]

web/js/demo.js
[sourcecode language=”javascript”]
$(function() {
$( "#fomenu" ).menu();
});
[/sourcecode]

És természtesen ezen fájlokat használni is kell, amit a view.yml fájlban fogunk beállítani:
apps/frontend/config/view.yml
[sourcecode language=”plain”]

stylesheets: [main.css]
javascripts: [demo.js]

[/sourcecode]

Az elkészített mű letölthető a githubról:https://github.com/nova76/demo
illetve megtekinthető itt:http://demo1.dev.embertelen.hu/CmsAdmin