A simple CMS plugin for Symfony 1.4 that uses dynamic routing to create a (potentially multisite) sitetree - into which any module can be plugged.
Default configuration is specified in the plugin's config/app.yml
.
- PHP 5.2.4 or later
- symfony 1.3/1.4
- sfDoctrineGuardUserPlugin (for the CMS)
- sfImagePoolPlugin
- sfMooToolsFormExtraPlugin
- Blamable (external in lib/doctrine_extensions)
- Orderable (external in lib/doctrine_extensions)
- sfFeed2Plugin (for the RSS feeds)
- ysfDimensionsPlugin (for multiple sites)
NOTE: This assumes you have already got a lib/vendor folder with symfony it in.
If it's a fresh setup of a project, you can use the installer to create a skeleton project using the sfCmsPlugin
. If you are using SVN this will install all of the appropriate plugins and dependancies.
php lib/vendor/symfony/data/bin/symfony generate:project --installer=plugins/sfCmsPlugin/data/installer.php PROJECT_IDENTIFIER
Otherwise you will need to add the following configuration to your existing project in config/app.yml
for the main site. For
multiple sites, you will add a new config file per dimension (as specified in the ysfDimensionsPlugin
README).
all:
# Basic site configuration
site:
identifier: ##SITENAME##
definition:
name: ##FRIENDLYNAME##
cultures: [en]
default_culture: en
url_prefix: http://www.example.com # optional url_prefix if cms has a different domain to the main site
root_module: index # You will need to ensure you have a module.yml for whichever module this is - see below
# the default site
default_site: ##SITENAME##
available_modules:
- sitemap
- pageDisplay
- listingDisplay
- iframeDisplay
- redirectDisplay
Finally, enable the admin modules in your backend app's settings.yml
.
enabled_modules:
- sfGuardAuth
- sfGuardUser
- sitetree
- pageAdmin
- listingAdmin
- contentAdmin
- redirectAdmin
- iframeAdmin
The first visit to the admin sitetree module will set up the sitetree (including the root node - based on the config above). You will
need to publish the root node and delete the default @homepage
route in the frontend app to use the dynamic routing.
Enable the frontend display modules in the frontend settings.yml
.
enabled_modules:
- pageDisplay
- listingDisplay
- iframeDisplay
- redirectDisplay
- sitemap
NOTE: If you want a sitemap - including an XML page, you'll have to set up a hidden node for it.
If the site includes javascript at the bottom of the template in the CMS then you need to enable the slots setting in site config; and include the js slot at the bottom of your layout template.
In the config:
site:
....
use_slots: true
In the backend layout:
<?php include_slot('cms_js') ?>
Follow the instructions in ysfDimensionsPlugin
if you have multiple sites to set up your configuration files.
In config/dimensions.yml
define the allowed sites:
allowed:
site: [ gb, fr ]
default: gb
With this plugin dimensions are used to control the site (and cultures are handled within these). If your setup is one domain per site, you can set
the dimension on the frontend app based on the URL by using a filter. You will need to set up the URL to dimensions relationship in your config/app.yml
# Site configuration
all:
site:
identifier: gb
definition:
name: UK site
cultures: [en]
default_culture: en
url_prefix: 'http://www.example.co.uk'
# the default site
default_site: gb
# if you have more than one site (through dimensions) - then list them here for the CMS site select
active_sites:
gb: 'UK site'
fr: 'French site'
......
# when the config has loaded this will determine which dimension is set based on the URL
# it will always default to the default_site above if the domain doesn't match
dimensions:
'www.example.co.uk': gb
'www.example.fr': fr
The dimension is set when the plugin's configuration is initialised so all you need to do is make sure the dimensions are defined in the main app.yml.
Also set the default dimension in ProjectConfiguration::setup()
- this is so the command line doesn't error out as the configuration is loaded after.
// setup dimensions before calling parent::setup(); for command line operations
// Frontend handled by a filter
if ('cli' == php_sapi_name()) $this->setDimension(array('site' => 'gb'));
When you add a new site, at the very least you'll need to add it to the active_sites
config array; and create a folder in the main config folder for
the site specific app.yml
, e.g: config/fr/app/yml
.
This should contain the site configuration, so that the routing knows which routes to load, etc.
# Site configuration
all:
site:
identifier: fr
definition:
name: French site
cultures: [fr]
default_culture: fr
url_prefix: 'http://www.example.fr'
For the backend - you'll want to set your homepage
route to point to sitetree/changeSite
(or point to this action where you want to implement the
change site functionality).
To add the sites to the database (specifically to allow sfGuardGroup to have site specific permissions) run the following task (and each time a new site is added):
./symfony cms:populate-sites
In your CMS application (usually backend) set your sfUser to extend cmsSecurityUser
(this extends sfGuardSecurityUser
) to allow part permissions.
Import the plugin's fixtures to include the CMS's user permissions:
./symfony doctrine:data-load --append plugins/sfCmsPlugin/data/fixtures/permissions.yml
Permissions are as follows:
Allows editing, publishing, importing and soft deleting of sitetree nodes and all content
Allows editing, importing and publishing of sitetree nodes and all content
Allows editing of versionnable content
Only users with the superadmin permission can restore or permanently delete sitetree nodes.
Sites and permissions are assigned to groups. Permissions act as normal (though you only need one of the above permissions to gain sitetree permissions); but with sites, it assumes you can access all sites unless sites are assigned to your group (this is for viewing only - can import content from any site).
To make use of a dynamic sitetree with your own modules you need to add a module.yml
config file to the module config folder, and enable the module
in the app.yml
under available_modules
(this will enable you to select it in the module dropdown on the sitetree edit/add form).
# Site configuration all:
site:
.....
available_modules:
- sitemap
- newModule
The module can handle itĘĽs own events - including what happens on sitetree deletion and with sitetree routing. If the new module only requires one route based on the URL entered, then leave the routing as default.
all:
site:
module_definition:
name: "New module"
# This is a required field if you want the event handler to be called
# If you have no admin module, but need to handle events - use sitetree/index
admin_url: moduleAdmin/editByRoute
# If you need to set extra routes for the module
use_custom_routing: true
event_handler: [newModuleClass, siteEventHandler]
The event handler manages the sitetree events (as required). This can either be a global class with a method per module (so the events are managed in one place), or per module (maybe in a model file).
<?php
class newModuleClass
{
/**
* Handle the site events - e.g: routing
*
* @param siteEvent $event
*/
public static function siteEventHandler($event)
{
if ($event->getName() == siteEvent::SITETREE_DELETE)
{
// node has been deleted
$sitetree = $event->getSubject();
$site = $sitetree->site;
// handle delete
}
else if ($event->getName() == siteEvent::SITETREE_COPY)
{
$copyFromSitetree = $event->getSubject();
$params = $event->getParameters();
if (!$copyToSitetree = @$params['copyTo'])
{
throw new sfException('No sitetree to copy to');
}
// get the object from the old Sitetree
$fromObj = siteManager::getInstance()->loadItemFromSitetree(%%MODEL_NAME%%, $copyFromSitetree);
if (!$fromObj)
{
// there was no object at the old sitetree, maybe it hadn't been created yet.
return;
}
// handle copy
}
else if ($event->getName() == siteEvent::SITETREE_ROUTING)
{
// handle the routing... i.e: register our routes.
$sitetree = $event->getSubject();
$params = $event->getParameters();
$routingProxy = $params['routingProxy'];
$urlStack = $params['urlStack'];
$nodeUrl = sitetree::makeUrl($sitetree, $urlStack);
// add in index route
$routingProxy->addRoute(
$sitetree,
'',
$nodeUrl,
array('module' => 'newModule', 'action' => 'index')
);
// further route examples
$routingProxy->addRoute(
$sitetree,
'page',
$nodeUrl . '/page/:page',
array('module' => 'newModule', 'action' => 'index', 'page' => 1),
array('page' => '\d+')
);
$routingProxy->addRoute(
$sitetree,
'item',
$nodeUrl . '/:slug,
array('module' => 'newModule', 'action' => 'item')
);
}
}
}
In the frontend module, you should initialise the sitetree as this will both sort out the meta information, and provide you with the current tree node.
$siteManager = siteManager::getInstance();
$sitetreeNode = $siteManager->initCurrentSitetreeNode();
Then continue with the module's functionality.
All classes retrieved via the siteManager, listingManager and pageManager (including the managers themselves) can be extended by creating a local copy, setting the class in the configuration and extending the functionality as required.
Content pages are covered below, but there are a couple of simple modules that add useful functionality to the CMS.
Redirect allows site nodes to be set up that purely redirect to a different URL, useful for external sites so the core navigation can still be controlled.
iFrame allows site nodes to be set up that are iFrames to content created externally. Useful if third parties (e.g: clients) want to create customised pages un-influenced by the main site. These can be created from an external link or an internal HTML file (FTP'd to data/static). Obviously the iframe containing the internal HTML file can be resized via JavaScript as the internal content height and width can be determined (this is not included).
The configuration for the templates can be added in separate configuration files, depending on how many templates there are.
If they are separate files, then ensure they are included in app.yml
under the site:
configuration
site:
.. # other config here
<?php
include(sfConfig::get('sf_root_dir') . "/config/templates.yml");
?>
Otherwise, if there are only a few templates - then just add them in config/app.yml
Various classes can be overwritten locally in a project by defining them in the configuration - check out the appropriate manager classes for more information.
Templates are made up of content blocks.
A content block is a logical separate item of content on a page - for example, an image or a piece of text. It could be something as small as one piece of text, or as large as the entire HTML content of a page.
Each page on the site, whether it's a content page, a listing page or a listing item, is made up of a collection of content blocks.
A content block has an identifier (for example: image_1
OR main_content
) and a type (see below for the content block types) at the minimum. Certain content blocks may require additional configuration parameters.
An identifier must be:
- Unique (within the template)
- Maximum length of 50 characters
- Only contain letters, numbers or _ - advisably start with a letter to avoid confusion
The minimum declaration of a content block requires an identifier, a name and a type.
The name is the friendly name that is displayed to the user in the CMS, the identifier is used by the code to call the content block, and the type tells the code what the content block should do.
content_block_1:
name: Content Block 1
type: ContentBlockType
Content blocks are versioned by default - the CMS saves each edit to a content block so they can be viewed or changed back (reverted) easily.
They can also have a different copy for each language. This is implemented by default, but you can specify it in the config as well, or turn it off. This advisable for images - especially if they don't have text.
# Different for each language
use_lang: true
You can also add help text, which is displayed on the right hand side of the edit page in the CMS. This is useful for editing tips, requirements, dynamic variables, etc.
help: This field is required, 500 character limit
# Multiline - don't forget the |
help: |
Lots of help text<br />
<b>This can contain HTML</b>
<h3> - escape to show the HTML markup
A piece of text that requires formatting or user controlled content. Uses a rich text editor called MooEditable (via the sfMooToolsFormExtraPlugin) in the CMS for editing.
The HTML content block only requires the minimum declaration to work, with a content block type of HTML
.
content_block_1:
name: Content Block 1
type: HTML
The HTML content block can be restricted in length (useful for styling restrictions), and also can be a required field. These are optional and validation is ignored if they are not set. It is advisable to add a * to the name of required fields.
character_limit: 500 # excluding HTML
required: true
And then it is rendered thusly (NOTE: This will more than likely contains <p>
tags, so don't wrap it in them):
<?php echo $page->renderContent('content_block_1'); ?>
A single piece of format-less text.
The Text content block only requires the minimum declaration to work, with a content block type: Text.
content_block_1:
name: Content Block 1
type: Text
The Text content block can be restricted in length (useful for styling restrictions), and also can be a required field. These are optional and validation is ignored if they are not set. It is advisable to add a * to the name of required fields.
character_limit: 50 # excluding HTML
required: true
And rendered thusly:
<?php echo $page->renderContent('content_block_1'); ?>
Requires: sfImagePoolPlugin
An image asset - the images can be managed in the Image Pool tab. The image can either be selected from existing images, or uploaded on the page.
content_block_1:
name: Content Block 1
type: Image
tag: [icon] # set if tag restriction required, as per sfImagePoolable (none set by default)
multiple: false # set if multiple images allowed, as per sfImagePoolable (false by default)
The image pool rendering options are set in the template itself, as the content block returns an sfImagePoolCollection (if multiple), or an sfImagePoolImage (if not multiple) - so you can treat it like any other image poolable object.
<?php echo pool_image_tag($page->renderContent('content_block_1'), '200'); ?>
A content block which allows integration of a simple widget and validator - such as a dropdown or a checkbox. Currently only supports single value returned.
The widget, its options and attributes; and the validator, its options and messages can all be defined in the config.
traffic:
name: Traffic lights *
type: Widget
widget: sfWidgetFormChoice
widget_options:
choices: { '':'', 'green' : 'green', 'amber' : 'amber', 'red' : 'red' }
widget_attributes: []
validator: sfValidatorChoice
validator_options:
required: true
choices: { 'green' : 'green', 'amber' : 'amber', 'red' : 'red' }
validator_messages:
required: "You need to select a colour"
enable:
name: Enable something
type: Widget
widget: sfWidgetFormInputCheckbox
widget_attributes:
value: 1
validator: sfValidatorBoolean
validator_options:
required: false
empty_value: 0
validator_messages: []
date:
name: Date
type: Widget
widget: sfWidgetFormDate
widget_options:
years: { 2011: 2011, 2012: 2012 }
format: '%day% %month% %year%'
widget_attributes:
style: 'width: auto;'
validator: sfValidatorDate
validator_options:
required: false
Render will return the saved value, and it is up to the template to decide how to display it
Further content block types can be added by extending the ContentBlockType
class and form.
Custom configuration can be set on each content block and retrieved in the template file. This is useful on shared templates. Example here if for a page, can also be used for listings.
shared_1:
template: shared
blocks:
content_block_1:
custom_class: shared_1
shared_2:
template: shared
blocks:
content_block_1:
custom_class: shared_2
In the template
<?php
$identifier = 'content';
$defn = $contentGroup->getBlockDefinition($identifier);
$custom_class = $defn['custom_class'];
?>
<div class="<?php echo $custom_class’ ?>">
<?php echo $page->renderContent($identifier);
</div>
A content page should be used for any page where a single page of content should be displayed. Example usage: Home page, Terms and conditions, About us, etc.
To create templates, add configuration as above to config/app.yml
site:
... # other config
page_templates:
Then you need to add configuration for each template type - depending on what content you need on each page
Restrictions on template identifiers:
- Unique
- 50 character or less
- Only contain letters, numbers or _ - advisably start with a letter to avoid confusion
The basic template configuration contains no content blocks - which is useful when pulling in content from other areas.
blank:
name: Blank template
blocks:
# none
Generally, though, templates contain dynamic content
homepage:
name: Homepage
blocks:
hero_image:
name: Hero image
type: AssetImage
width: 1500
height: Any
introduction:
name: Introduction
type: HTML
You will then need to create a file called homepage.php
in the templates/page folder for the frontend.
This would contain markup, plus the following to render the content.
<?php echo $page->renderContent('hero_image'); ?>
<?php echo $page->renderContent('introduction'); ?>
Additional parameters can be set for the templates
layout: layout
Template files can be shared between template - this can be used on conjunction with the custom configuration mentioned above. This overrides the default IDENTIFIER.php
template file
template: shared
Turn caching on and off - this is important if a page has a form on it - forms should not be cached
cacheable: false
If a different header/footer is required, a specific layout can be set for a template
stylesheets: [page]
javascripts: [page]
As with view.yml
(same format), stylesheets and javascripts can be set for specific templates
Templates can be restricted to a particular page (or pages), for example: for registration / homepage. This is done by specifying a route name (the identifier for a sitetree node).
route_name: homepage
restricted: true # if restricted, only this template is returned
Or only available for particular pages:
route_name: [ offers, personal ]
Or only available for certain sites (if multi-site setup):
site: [ gb, fr ]
NOTE: Restrictions can be an array or single string, the manager handles converting it.
Should be used on any page which requires a list or repetitive items of content, such as Articles, Offers, FAQs, etc.
To create templates, add configuration as above to config/app.yml
site:
... # other config
listing_templates:
Then you need to add configuration for each template type - depending on what content you need on each page. See the content page information.
It makes no sense to have a listing without content, so the basic listing uses the default listingItem.
article:
name: Article (with Category)
use_categories: true # this is the default
item_status:
- featured
- editors_pick
- thought_about
listing_blocks:
# no content
item_blocks:
image:
name: Image *
type: AssetImage
width: 290
height: Any
use_lang: false
required: true
content:
name: Content
type: HTML
use_lang: true
This definition will require article_listing.php
and article_item.php
created in the templates/listing
folder.
This particular listing contains a category and status for each item. If listings have categories, these are managed in the same place as the items. Statuses are set per listing, in the configuration. These also certain actions to be performed on items, or specific items to be included on other pages (e.g: featured on the homepage).
Statuses and categories are not required, but are a core part of the listing items.
When you render a listing, you have to render the items within the listing. The included pager manages ordering (either automatic, set in the configuration; or manual, set in the CMS) and pagination.
<?php echo $listing->renderContent('content'); ?>
<?php if (0 < $pager->getNbResults()) : ?>
<ul>
<?php foreach ($pager->getResults() as $item) :
$item->ContentGroup->initialiseForRender($sf_user->getCulture());
// If there is a category
if ($item->ListingCategory->id)
{
$itemUrl = internal_url_for_sitetree($sitetree, 'category_item',
array('slug'=>$item->slug, 'category'=>$item->ListingCategory->slug));
}
else $itemUrl = internal_url_for_sitetree($sitetree, 'item', array('slug'=>$item->slug)); ?>
<li>
<a href="<?php echo url_for($itemUrl); ?>"><?php echo $item->renderContent('image'); ?></a>
<?php if ($item->status) : ?>
<h3><?php echo $item->status; ?></h3>
<?php endif; ?>
<h2><a href="<?php echo url_for($itemUrl); ?>"><?php echo $item->getTitle(); ?></a></h2>
<?php echo $item->renderContent('content'); ?>
<a class="read_more" href="<?php echo url_for($itemUrl); ?>">Read More</a>
<?php if ($item->item_date) : ?>
<?php echo format_date($item->item_date); ?>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php if ($pager->haveToPaginate()) :
$routing_type = ($category ? 'category_page' : 'page');
$routing_params = array();
if ($category) $routing_params['category'] = $category->getSlug();
$currentPage = $pager->getPage(); ?>
<div class="pagination">
<ul class="pagination">
<?php if ($currentPage != 1) : ?>
<?php if (1 == $pager->getPreviousPage()) : ?>
<?php if ($category) : ?>
<li class="previous"><a href="<?php echo url_for(internal_url_for_sitetree($sitetree, 'category', $routing_params)); ?>">< Previous</a></li>
<?php else : ?>
<li class="previous"><a href="<?php echo url_for(internal_url_for_sitetree($sitetree)); ?>">< Previous</a></li>
<?php endif; ?>
<?php else : ?>
<li class="previous"><a href="<?php echo url_for(internal_url_for_sitetree($sitetree, $routing_type,
(array('page'=>$pager->getPreviousPage()) + $routing_params))); ?>">< Previous</a></li>
<?php endif; ?>
<?php endif; ?>
<?php foreach ($pager->getLinks() as $link) : ?>
<?php if ($link == $pager->getPage()) : ?>
<li><?php echo $link; ?></li>
<?php elseif (1 == $link) : ?>
<?php if ($category) : ?>
<li><a href="<?php echo url_for(internal_url_for_sitetree($sitetree, 'category', $routing_params)); ?>">1</a></li>
<?php else : ?>
<li><a href="<?php echo url_for(internal_url_for_sitetree($sitetree)); ?>">1</a></li>
<?php endif; ?>
<?php else : ?>
<li><a href="<?php echo url_for(internal_url_for_sitetree($sitetree, $routing_type,
(array('page'=>$link) + $routing_params))); ?>"><?php echo $link; ?></a></li>
<?php endif; ?>
<?php endforeach; ?>
<?php if ($currentPage != $pager->getLastPage()) : ?>
<li class="next"><a href="<?php echo url_for(internal_url_for_sitetree($sitetree, $routing_type,
(array('page'=>$pager->getNextPage()) + $routing_params))); ?>">Next ></a></li>
<?php endif; ?>
</ul>
</div>
<?php endif; ?>
<?php endif; ?>
And the listing item, with appropriate markup (it can also render content from the listing).
<?php echo $item->renderContent('image'); ?>
<?php echo $item->renderContent('content'); ?>
As per pages, the layout and template can be changed
listing_layout: blank
item_layout: layout
listing_template: shared_listing
item_template: shared_item
And items or listings can be excluded from the cache
listing_cacheable: false
item_cacheable: false
As per pages, specific stylesheets and javascripts can be added to the listing and/or item
stylesheets: [custom]
javascripts: [custom]
item_stylesheets: [custom_item]
item_javascripts: [custom_item]
Again, as per pages, listings can be restricted to certain route name(s).
route_name: homepage
restricted: true # if restricted, only this template is returned
Or only available for particular pages:
route_name: [ offers, personal ]
Or only available for certain sites (if multi-site setup):
site: [ gb, fr ]
NOTE: Restrictions can be an array or single string, the manager handles converting it.
Set site-wide site-specific RSS settings in config/app.yml
Config that is not set, will not be used (no config is required)
site:
definition:
name: ##PROJECTNAME##
cultures: [en]
default_culture: en
rss_config:
author: { name: SITE_NAME, email: SITE_EMAIL, link: SITE_URL }
logo: { url: /images/logo.png, width: 131, height: 123 }
favicon: /favicon.ico
Enable and set listing RSS settings in listing_templates
(only required item is rss_enabled
)
Description and Content use renderContent()
on $item
article:
name: Articles (with RSS)
# RSS config
rss_enabled: true
rss_item_ordering: i.created_at # default
rss_config:
feed:
# set here for feed specific, or in app_site_rss_config for sitewide
author: { name: SITE_NAME, email: SITE_EMAIL, link: SITE_URL }
logo: { url: /images/logo.png, width: 131, height: 123 }
favicon: /favicon.ico
item:
description: summary # identifier of a content block in blocks:
content: page_content # identifier of a content block in blocks:
Add the following to your apps/frontend/template/layout.php
file, to include an auto-discoverable feed (make sure you have $sitetree = siteManager::getInstance()->getCurrentSitetreeNode();
in the layout)
This also works for any feeds referenced in the External RSS Url field of the listing ($listing->getRssUrl();
) - which can use Feedburner on the /rss
url for the listing
Alternatively, substitute atom
for rss
to get an atom link
<?php if (rss_for_sitetree($sitetree)) : ?>
<link rel="alternate" type="application/rss+xml" href="<?php echo rss_for_sitetree($sitetree); ?>" />
<?php endif; ?>
Creating new types of content blocks is a matter of creating a model and a form which extend the base content block types.
You will require a form, which is displayed on the page to enter and edit the content block.
You have to set up the widget and validator (along with implementing the getValidatorOptions()
method - though if there is no validation for your content block, just leave it empty). You can extend the rest as required.
<?php
class contentBlockTypeNEWTYPEForm extends contentBlockTypeForm
{
/**
* Set up the field
*
* @see contentBlockType/contentBlockTypeForm::configure()
*/
public function configure()
{
parent::configure();
$this->widgetSchema['value'] = new sfWidgetTYPE();
$this->validatorSchema['value'] = new sfValidatorTYPE($this->getValidatorOptions());
$this->widgetSchema->setLabel('value',' ');
}
/**
* Get validation from the config
*/
protected function getValidatorOptions()
{
// Use validator if set
$definition = $this->getObject()->getContentBlockVersion()->getDefinition();
$validatorOptions = array();
... calculate validation
return $validatorOptions;
}
/**
* Link to the content block
*/
public function getModelName()
{
return 'contentBlockTypeNEWTYPE';
}
}
You will also require a content block class, which is linked to a content block and used when rendering the content block in the CMS and on the frontend.
There are several method that can be overwritten from the basic implementation in the class. There are three required methods that need to be implemented from the interface for the content block to be usable.
Below is the standard implementation, which can obviously be tweaked, depending on the functionality of the content block. The crux of the content block is the value - which should never be an id (or something easily changeable). This value can either be rendered raw, or manipulated (i.e: used to retrieve an object from the database) before rendering - see the existing content blocks for examples.
<?php
/**
* Basic NEWTYPE content block
*
* @see ContentBlockType for details
*/
class ContentBlockTypeNEWTYPE extends ContentBlockType
{
/**
* @see ContentBlockType/ContentBlockTypeInterface::editRender()
*
* @param sfWebRequest $request
* @return sfForm
*/
public function editRender(sfWebRequest $request)
{
$field = $this->getFormName();
$form = new ContentBlockTypeNEWTYPEForm($this);
if ($request->hasParameter($field)
&& ($request->hasParameter('save') || $request->hasParameter('save_and_publish') || $request->hasParameter('preview')))
{
$form->bind($request->getParameter($field));
}
return $form;
}
/**
* Validation options:
*
* set out validation options that can be used in the definition
*
* @see ContentBlockType/ContentBlockTypeInterface::editIsValid()
*
* @param sfWebRequest $request
* @return boolean
*/
public function editIsValid(sfWebRequest $request)
{
$field = $this->getFormName();
$form = new contentBlockTypeTextForm($this);
if ($request->hasParameter($field))
{
$form->bind($request->getParameter($field));
}
return $form->isValid();
}
/**
* Render from value
* This is called from render() and renderFromRequest() with the appropriate value
*
* @see ContentBlockType/ContentBlockType::renderFromValue()
*
* @param string $value
* @return string
*/
public function renderFromValue($value)
{
return $value;
}
}
In order to integrate existing data from a previous site, or to extend the functionality of the listing item it may become necessary to extend the listing item.
The default listing item provides the following functionality:
- Translatable title (and automatic URL generation)
- Item date
- Category
- Configurable status
- Any other fields in content blocks via template configuration
If further functionality is required, you should extend ListingItem
in Doctrine, so that the ListingItem
functionality (such as rendering, and links to listings) is carried over.
You will need to create a new model in the local site schema.yml
(this inheritance creates new tables in the database)
Please note: Due to a limitation in Doctrine 1.x new translatable fields cannot be added without "hacking" the model (which will interfere with automatic class generation and updating - so not advisable).
# model definitions ----------------------
newListingItem:
inheritance:
extends: listingItem
type: concrete
columns:
# additional fields
...
To set up a listing with the new class you will need to add new lines into the configuration for the listing template. Any items left undefined will use the default classes set in the listingManager (see the manager for more information).
listing_templates:
new_template:
name: New item listing
list_item_class: newListingItem
list_item_form_class: newListingItemForm
...