Add new content type
Intro
This section talks about how to register new content types. Once a content type is registered, you can use content models of that type throughout the wf-html.
Create the entity
Create a custom entity in the project.
In v4 add @JMSS\Groups({"list"})
to all fields that will be used in the editor.
In v5+ JMS serializer was replaced by Symfony Serializer, so the annotation should be @Serializer\Groups({"list"})
. The annotation must be imported with use Symfony\Component\Serializer\Annotation as Serializer;
Register entity
Register entity parameter/repository service in DependencyInjection\CmsExtension. These are required by the CMS to link entities in your database to the editor
public function load(array $configs, ContainerBuilder $container)
{
// ...
$this->handleEntityConfig($container);
}
protected function handleEntityConfig(ContainerBuilder $container)
{
foreach (["restaurant" => Restaurant::class] as $key => $class) {
$container->setParameter(sprintf('wf_cms.entity.%s.class', $key), $class);
$repositoryService = new Definition($class);
$repositoryService->setFactoryService('doctrine.orm.default_entity_manager');
$repositoryService->setFactoryMethod('getRepository');
$repositoryService->setArguments(array($class));
$repositoryId = sprintf('wf_cms.repository.%s', $key);
$container->setDefinition($repositoryId, $repositoryService);
}
}
Index entity in ElasticSearch
For this edit the configuration file app/config/misc/fos_elastica.yml and add a new type.
indexes:
%fos_index_name%:
client: default
finder: ~
settings:
index:
analysis:
analyzer:
lc-af:
tokenizer: standard
filter: [ standard, lowercase, asciifolding ]
types:
restaurant:
_all:
analyzer: lc-af
properties:
title: { boost: 10 }
description: ~
foodType: { boost: 8 }
restaurantStatus:
type: keyword
property_path: getStatus
createdAt:
type: date
updatedAt:
type: date
persistence:
driver: orm
model: App\Bundle\CmsBundle\Entity\Restaurant
provider: ~
listener: ~
finder: ~
Register in the allowed content types parameter
Add the new type to allowed types parameter by creating a compiler pass:
$baseTypes = $container->getParameter('wf_cms_admin.content_type.allowed');
$allTypes = array_merge($baseTypes, ['restaurant']);
$container->setParameter('wf_cms_admin.content_type.allowed', $allTypes);
Create an ElasticSearch finder service for the new type
<service id="wf_cms.search.restaurant_finder" parent="wf_cms.search.general_finder" class="App\Bundle\CmsBundle\Search\RestaurantFinder"></service>
Configure the ElasticSearch finder service
Add the new type to DependencyInjection/AppCmsAdminExtension.php
so that ElasticSearch uses the new type to filter the results.
<?php
public function load(array $configs, ContainerBuilder $container)
{
// ...
$this->handleSearchConfig($container);
}
protected function handleSearchConfig(ContainerBuilder $container)
{
$indexName = $container->getParameter('fos_index_name');
$findables = ['restaurant'];
foreach($findables as $contentType) {
$finderDefinition = $container->getDefinition(sprintf('wf_cms.search.%s_finder', $contentType));
$finderDefinition->replaceArgument(0, new Reference(sprintf('fos_elastica.index.%s.%s', $indexName, $contentType)));
$finderDefinition->replaceArgument(1, new Reference(sprintf('fos_elastica.elastica_to_model_transformer.%s.%s', $indexName, $contentType)));
}
}
Create the PHP Finder class
<?php
// in src/App/Bundle/CmsBundle/Search/Finder/RestaurantFinder.php
class RestaurantFinder extends BaseFinder
{
protected function getBaseQB()
{
$qb = parent::getBaseQB();
$qb->sortBy(['updatedAt' => 'desc']);
$statusQuery = new Term([
'restaurantStatus' => Restaurant::STATUS_ENABLED
]);
$qb->addQuery($statusQuery);
return $qb;
}
}
Create the JavaScript content type
// Bundle/CmsAdminBundle/Resources/public/javascripts/content_type/restaurant_content_type.js
define([
'cnf',
'admin/model/content/restaurant_content_model',
'wfed/collection/sortable_collection',
'wfed/content_type/base_content_type'
], function (
cnf,
RestaurantContentModel,
RestaurantCollection,
Base
) {
return Base.extend({
wfId: 'content_type/restaurant_content_type',
typeId: 'restaurant',
modelClass: RestaurantContentModel,
indexName: 'restaurant',
getContentModel: function (moduleModel) {
return new this.modelClass;
},
getCollection: function () {
var collection = new RestaurantCollection();
collection.model = RestaurantContentModel;
return collection;
},
getAddViewPath: function () {
return null;
},
});
});
// Bundle/CmsAdminBundle/Resources/public/javascripts/model/content/restaurant_content_model.js
define(['backbone',
'wfed/model/content/base_content_model'
], function(Backbone,
Base
){
return Base.extend({
wfId: 'wfed/model/content/restaurant',
defaults: {
id: null, //the DB id
},
contentType: 'content_type/restaurant_content_type',
urlRoot: '/restaurant/',
});
});
Register the new JavaScript custom type
Overwrite the factory and factory loader files to add the new type:
// Bundle/CmsAdminBundle/Resources/public/javascripts/content_type_factory.js
define(['_wfed/content_type_factory'], function (baseContentFactory) {
const baseGetSearchableTypesIds = baseContentFactory.getSearchableTypesIds;
baseContentFactory.getSearchableTypesIds = function () {
return baseGetSearchableTypesIds().concat(['restaurant']);
};
const baseGetCollectionTypesOrder = baseContentFactory.getCollectionTypesOrder;
baseContentFactory.getCollectionTypesOrder = function () {
return baseGetCollectionTypesOrder().concat(['restaurant']);
};
return baseContentFactory;
});
// Bundle/CmsAdminBundle/Resources/public/javascripts/content_type_factory_loader.js
define([
'wfed/content_type_factory',
'cnf'], function (contentTypeFactory,
cnf) {
var req = require.context('wfed/content_type', false, /_content_type.js$/);
_.each(cnf.contentTypes, function (type) {
var key = contentTypeFactory.getNormalizedContentTypeId(type),
location = './' + type + '_content_type.js';
if (key === 'restaurant') {
this.types[key] = require('admin/content_type/restaurant_content_type');
} else {
this.types[key] = req(location);
}
}, contentTypeFactory);
});
// ...
adminConfig.webpack = adminConfig.webpack || {};
adminConfig.webpack.resolve = adminConfig.webpack.resolve || {};
adminConfig.webpack.resolve.alias = adminConfig.webpack.resolve.alias || {};
// console.log(Object.keys(adminConfig));
Object.assign(adminConfig.webpack.resolve.alias, {
'wfed/content_type_factory': 'admin/content_type_factory',
'wfed/content_type_factory_loader': 'admin/content_type_factory_loader',
});
Change the search result twig template
Overwrite the template of the new type displayed in the search popup window:
// app/admin/config/routing.yml
wf_cmseditor_javascripttemplate_renderitem:
path: /bundles/wfcmseditor/javascripts/template/render_item.html
defaults:
_controller: 'AppCmsAdminBundle:EditorJavascriptTemplate:renderItem'
<?php
namespace App\Bundle\CmsAdminBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
class EditorJavascriptTemplateController extends \Wf\Bundle\CmsEditorBundle\Controller\JavascriptTemplateController
{
/**
* @Template()
* @return array
*/
public function renderItemAction()
{
return parent::renderItemAction();
}
}
// Bundle/CmsAdminBundle/Resources/views/EditorJavascriptTemplate/renderItem.html.twig
{% extends "@WfCmsEditor/JavascriptTemplate/renderItem.html.twig" %}
{% block item_info %}
{{ parent() }}
<% if (item.food_type) { %>
<div class="as-field author">
<span class="label">{{ 'adv_search.fields.foodType'|trans }}</span>
<span class="value"><%= item.food_type %></span>
</div>
<% } %>
{% endblock %}
Add JavaScript translations
Add the translation for the edit popover:
// Bundle/CmsAdminBundle/Resources/views/Javascript/i18n.html.twig
{% extends "WfCmsBaseAdminBundle:Javascript:base_i18n.js.twig" %}
{% block custom_module_editor_dialog %}
{# ... #}
restaurant: '{{ 'module_edit_dialog.restaurant'|trans }}',
{# ... #}
{% endblock %}
Use the content type
To use it:
v4-6<div wf-module="wfed/composite/module"
wf-role="restaurant">
<span data-bind="text:restaurant_title" />
</div>
<div wf-module="wfed/composite/module"
wf-role="restaurant">
<span data-bind="text:restaurant.title" />
</div>
Note: The way of accessing properties has changed in v7+, in previous versions it was contentType_propertyName
(_
separator), in v7+ it's contentType.propertyName
(standard JavaScript syntax, .
separator)