Extending eZ Platform UI¶
Back-end interface¶
The back-end interface is produced by the ezplatform-admin-ui Bundle
together with ezplatform-admin-ui-modules,
which contains React modules that handle specific parts of the application.
This interface is accessible in your browser at http://[uri_of_platform]/admin
.
General extensibility¶
eZ Platform's back-end interface uses React-based modules that make each part of the UI easily extensible. The interface uses Bootstrap, which facilitates adapting and styling the interface to your needs.
Available extensibility points:
- Menus
- Dashboard
- Tabs
- Further extensibility using Components
- Universal Discovery module
- Sub-items list
- Online Editor
- Multi-file upload
Menus¶
Menus in eZ Platform are based on the KnpMenuBundle and are easily extensible. For a general idea on how to use MenuBuilder, refer to the official documentation.
Menus are extensible using event subscribers/listeners. You can hook into the following events:
ConfigureMenuEvent::MAIN_MENU
ConfigureMenuEvent::USER_MENU
ConfigureMenuEvent::CONTENT_SIDEBAR_RIGHT
ConfigureMenuEvent::CONTENT_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::CONTENT_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::CONTENT_SIDEBAR_LEFT
ConfigureMenuEvent::TRASH_SIDEBAR_RIGHT
ConfigureMenuEvent::SECTION_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::SECTION_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::POLICY_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::POLICY_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::ROLE_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::ROLE_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::USER_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::USER_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::ROLE_ASSIGNMENT_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::LANGUAGE_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::LANGUAGE_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::CONTENT_TYPE_GROUP_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::CONTENT_TYPE_GROUP_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::CONTENT_TYPE_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::CONTENT_TYPE_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::URL_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::USER_PASSWORD_CHANGE_SIDEBAR_RIGHT
ConfigureMenuEvent::OBJECT_STATE_GROUP_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::OBJECT_STATE_GROUP_EDIT_SIDEBAR_RIGHT
ConfigureMenuEvent::OBJECT_STATE_CREATE_SIDEBAR_RIGHT
ConfigureMenuEvent::OBJECT_STATE_EDIT_SIDEBAR_RIGHT
An event subscriber can be implemented as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php namespace EzSystems\EzPlatformAdminUi\EventListener; use EzSystems\EzPlatformAdminUi\Menu\Event\ConfigureMenuEvent; use EzSystems\EzPlatformAdminUi\Menu\MainMenuBuilder; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class MenuListener implements EventSubscriberInterface { public static function getSubscribedEvents() { return [ ConfigureMenuEvent::MAIN_MENU => ['onMenuConfigure', 0], ]; } public function onMenuConfigure(ConfigureMenuEvent $event) { $menu = $event->getMenu(); $factory = $event->getFactory(); $options = $event->getOptions(); // options passed from the context (i.e. Content item in Content View) // your customizations } } |
Extending menu examples¶
Add a new menu item under "Content" with custom attributes¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $menu[MainMenuBuilder::ITEM_CONTENT]->addChild( 'form_manager', [ 'route' => '_ezpublishLocation', 'routeParameters' => ['locationId' => 2], 'linkAttributes' => [ // attributes directly on <a> element 'class' => 'test_class another_class', 'data-property' => 'value', ], 'attributes' => [ // attributes on container <li> element 'data-property' => 'value', ], ] ); |
Remove the "Media" menu item from the Content tab¶
1 2 3 | $menu[MainMenuBuilder::ITEM_CONTENT]->removeChild( MainMenuBuilder::ITEM_CONTENT__MEDIA ); |
Add a top-level menu item with a child¶
1 2 3 4 5 6 7 8 | $menu->addChild( 'menu_item_1', ['label' => 'Menu Item 1', 'extras' => ['icon' => 'file']] ); $menu['menu_item_1']->addChild( '2nd_level_menu_item', ['label' => '2nd level menu item', 'uri' => 'http://example.com'] ); |
Add an item depending on a condition¶
1 2 3 4 5 6 7 | $condition = true; if ($condition) { $menu->addChild( 'menu_item_2', ['label' => 'Menu Item 2', 'extras' => ['icon' => 'article']] ); } |
Add a top-level menu item with URL redirection¶
1 2 3 4 5 6 7 8 | $menu->addChild( 'menu_item_3', [ 'label' => 'Menu Item 3', 'uri' => 'http://example.com', 'extras' => ['icon' => 'article'], ] ); |
Translatable labels¶
To have translatable labels, use translation.key
from the messages
domain:
1 2 3 4 5 6 7 8 9 10 11 | $menu->addChild( 'menu_item_3', [ 'label' => 'translation.key', 'uri' => 'http://example.com', 'extras' => [ 'icon' => 'article', 'translation_domain' => 'messages', ], ] ); |
Reorder menu items, i.e. reverse the order¶
1 2 3 | $menu->reorderChildren( array_reverse(array_keys($menu->getChildren())) ); |
Dashboard¶
To extend the Dashboard, make use of an event subscriber.
In the following example, the DasboardEventSubscriber.php
reverses the order of sections of the Dashboard
(in a default installation this makes the "Everyone" block appear above the "Me" block):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | namespace AppBundle\EventListener; use EzSystems\EzPlatformAdminUi\Component\Event\RenderGroupEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class DashboardEventSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { return [ RenderGroupEvent::NAME => ['onRenderGroupEvent', 20], ]; } public function onRenderGroupEvent(RenderGroupEvent $event) { if ($event->getGroupName() !== 'dashboard-blocks') { return; } $components = $event->getComponents(); $reverseOrder = array_reverse($components); $event->setComponents($reverseOrder); } } |
Tabs¶
Many elements of the back-office interface, such as System Info or Location View, are built using tabs.
You can extend existing tab groups with new tabs, or create your own tab groups.
Adding a new tab group¶
To create a custom tab group with additional logic you need to create it at the level of compiling the Symfony container, using a CompilerPass:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // src/AppBundle/DependencyInjection/Compiler/CustomTabGroupPass.php namespace AppBundle\DependencyInjection\Compiler; use EzSystems\EzPlatformAdminUi\Tab\TabGroup; use EzSystems\EzPlatformAdminUi\Tab\TabRegistry; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; class CustomTabGroupPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { if (!$container->hasDefinition(TabRegistry::class)) { return; } $tabRegistry = $container->getDefinition(TabRegistry::class); $tabGroupDefinition = new Definition( TabGroup::class, // or any class that extends TabGroup ['custom-tab-group'] ); $tabRegistry->addMethodCall('addTabGroup', [$tabGroupDefinition]); } } |
A shorter way that you can use when no custom logic is required, is to add your own tab with the new group. If a tab's group is not defined, it will be created automatically.
A new tab group can be rendered using the ez_platform_tabs
Twig helper:
1 2 3 | <div class="my-tabs"> {{ ez_platform_tabs('custom-tab-group') }} </div> |
This will render the group and all tabs assigned to it.
Adding a new tab¶
Before you add a tab to a group you must create the tab's PHP class and define it as a Symfony service with the ezplatform.tab
tag:
1 2 3 4 5 6 | # services.yml AppBundle\Custom\Tab: parent: EzSystems\EzPlatformAdminUi\Tab\AbstractTab tags: - { name: ezplatform.tab, group: 'custom-tab-group' } |
This configuration also assigns the new tab to custom-tab-group
.
Tip
If the custom-tab-group
does not yet exist, it will be created automatically at this point.
The tab class can look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // src/AppBundle/Custom/Tab.php namespace AppBundle\Custom; use EzSystems\EzPlatformAdminUi\Tab\AbstractTab; class Tab extends AbstractTab { public function getIdentifier(): string { return 'custom-tab'; } public function getName(): string { return /** @Desc("Custom Tab") */ $this->translator->trans('custom.tab.name', [], 'some_translation_domain'); } public function renderView(array $parameters): string { // Do rendering here return $this->twig->render('AppBundle:my_tabs/custom.html.twig', [ 'foo' => 'Bar!', ]); } } |
Beyond the AbstractTab
used in the example above you can use two specializes tab types:
AbstractControllerBasedTab
enables you to embed the results of a controller action in the tab.AbstractRouteBasedTab
embeds the results of the selected routing, passing applicable parameters.
Modifying tab display¶
You can order the tabs by making the tab implement OrderedTabInterface
.
The order will then depend on the numerical value returned by the getOrder
method.
1 2 3 4 5 6 7 | class Tab extends AbstractTab implements OrderedTabInterface { public function getOrder(): int { return 100; } } |
The tabs will be displayed according to this value in ascending order.
Tip
It is good practice to reserve some distance between these values, for example to stagger them by step of 10. It may come useful if you later need to place something between the existing tabs.
You can also influence tab display (e.g. order tabs, remove or modify them, etc.) by using Event Listeners:
1 2 3 4 5 6 7 8 9 10 11 12 | class TabEvents { /** * Happens just before rendering a tab group. */ const TAB_GROUP_PRE_RENDER = 'ezplatform.tab.group.pre_render'; /** * Happens just before rendering a tab. */ const TAB_PRE_RENDER = 'ezplatform.tab.pre_render'; } |
As an example, see how OrderedTabInterface
is implemented:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | namespace EzSystems\EzPlatformAdminUi\Tab\Event\Subscriber; use EzSystems\EzPlatformAdminUi\Tab\Event\TabEvents; use EzSystems\EzPlatformAdminUi\Tab\Event\TabGroupEvent; use EzSystems\EzPlatformAdminUi\Tab\OrderedTabInterface; use EzSystems\EzPlatformAdminUi\Tab\TabInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Reorders tabs according to their Order value (Tabs implementing OrderedTabInterface). * Tabs without order specified are pushed to the end of the group. * * @see OrderedTabInterface */ class OrderedTabSubscriber implements EventSubscriberInterface { /** * @return array */ public static function getSubscribedEvents(): array { return [ TabEvents::TAB_GROUP_PRE_RENDER => ['onTabGroupPreRender'], ]; } /** * @param TabGroupEvent $tabGroupEvent */ public function onTabGroupPreRender(TabGroupEvent $tabGroupEvent) { $tabGroup = $tabGroupEvent->getData(); $tabs = $tabGroup->getTabs(); $tabs = $this->reorderTabs($tabs); $tabGroup->setTabs($tabs); $tabGroupEvent->setData($tabGroup); } /** * @param TabInterface[] $tabs * * @return array */ private function reorderTabs($tabs): array { $orderedTabs = []; foreach ($tabs as $tab) { if ($tab instanceof OrderedTabInterface) { $orderedTabs[$tab->getIdentifier()] = $tab; unset($tabs[$tab->getIdentifier()]); } } uasort($orderedTabs, [$this, 'sortTabs']); return array_merge($orderedTabs, $tabs); } /** * @param OrderedTabInterface $tab1 * @param OrderedTabInterface $tab2 * * @return int */ private function sortTabs(OrderedTabInterface $tab1, OrderedTabInterface $tab2): int { return $tab1->getOrder() <=> $tab2->getOrder(); } } |
Further extensibility using Components¶
Components are any sort of custom elements that you can add to the back-office interface.
There are several extensibility points in the AdminUI templates that you can use for this purpose.
The only limitation to the application of these extensibility points is that the Component must implement the Renderable
interface.
The available extensibility points for Components are:
stylesheet-head
script-head
stylesheet-body
script-body
content-edit-form-before
content-edit-form-after
content-create-form-before
content-create-form-after
dashboard-blocks
Universal Discovery module¶
Universal Discovery module allows you to browse the content structure and search for content using an interactive interface: the browse view and the search view. The module is highly configurable. It can be extended with new tabs.
How to use it?¶
With vanilla JS:
1 2 3 4 5 6 | const container = document.querySelector('#react-udw'); ReactDOM.render(React.createElement(eZ.modules.UniversalDiscovery, { onConfirm: {Function}, onCancel: {Function}, }), container); |
With JSX:
1 2 3 4 5 6 | const props = { onConfirm: {Function}, onCancel: {Function} }; <UniversalDiscoveryModule {...props} /> |
Adding new tabs to the Universal Discovery module¶
The Universal Discovery module is highly customizable. It allows you to add new tabs to the module.
1 2 3 4 5 6 7 8 9 10 11 12 | const props = { onConfirm: {Function}, onCancel: {Function}, extraTabs: [{ id: {String}, title: {String}, panel: {Element}, // React component that represents content of a tab attrs: {Object} }] }; <UniversalDiscoveryModule {...props} /> |
Each tab definition is an object containing properties:
- id {String} - unique tab identifier (it cannot be:
browse
orsearch
) - title {String} - tab button title/label
- panel {Element} - any kind of React component. A panel component will receive the following props:
- isVisible {Boolean} - visible flag
- onItemSelect {Function} - a callback to be invoked when content is selected
- maxHeight {Number} - the maximum height of the panel container
- id {String} - panel identifier
- startingLocationId {Number} - location ID
- findLocationsByParentLocationId {Function} - finds locations related to the parent location
- findContentBySearchQuery {Function} - finds content matching a given text query
- contentTypesMap {Object} - content types map with content type ids as keys
- multiple {Boolean} - can select multiple content items flag
- labels {Object} - a hash containing text messages to be placed across many places in a component
- attrs {Object} - any optional list of props that should applied to the panel component. It can override the panel props listed above.
Property list¶
The <UniversalDiscoveryModule />
module can handle additional properties.
There are 2 types of properties: required and optional.
Required properties¶
Without all the following properties the Universal Discovery module will not work.
onConfirm {Function} - a callback to be invoked when a user clicks on the confirm button
in a Universal Discovery popup. The function takes one param: content
which is an array of content items structs.
onCancel {Function} - a callback to be invoked when a user clicks on the cancel button in a Universal Discovery popup. It takes no extra params.
restInfo {Function} - a config hash containing: token ({String}) and siteaccess ({String}).
Optional props¶
Optionally, Universal Discovery module can take a following list of props:
- loadContentInfo {Function} - loads content info. It takes 3 params:
restInfo
,contentId
andcallback
- loadContentTypes {Function} - loads content types data. It takes 2 params:
restInfo
,callback
, - canSelectContent {Function} - checks whether a content item can be selected. It takes one param: a
data
object containing anitem
property as the content struct anditemsCount
as a number of selected items in UDW, - findContentBySearchQuery {Function} - finds a content using a search query. It takes 3 params:
restInfo
,query
andcallback
, - findLocationsByParentLocationId {Function} - finds sub items of a given location. It takes 3 params:
restInfo
,parentLocationId
andcallback
, - title {String} - the title of Universal Discovery popup. Default value:
Find content
, - multiple {Boolean} - can select multiple content items flag. Default value:
true
, - activeTab {String} - active tab identifier. Default value:
browse
, - startingLocationId {Number} - location ID. Default value:
1
, - maxHeight {Number} - maximum height of panel container. Default value:
500
, - searchResultsPerPage {Number} - max amount of items visible per page in the search results. Default value:
10
, - extraTabs {Array} - optional, extra tabs. Each tab definition is an object containing the following properties (all of them are required):
- id {String} - unique tab identifier (it cannot be:
browse
orsearch
), - title {String} - tab button title/label,
- panel {Element} - any kind of React component,
- attrs {Object} - any optional list of props that should applied to the panel component. })),
- id {String} - unique tab identifier (it cannot be:
- labels {Object} - a hash containing text messages to be placed across many places in a component. It contains text labels for child components:
- udw {Object} - a hash of text labels for Universal Discovery module, see universal.discovery.module.js for details,
- selectedContentItem {Object} - a hash of text labels for Selected Content Item component,
- contentMetaPreview {Object} - a hash of text labels for Content Meta Preview component,
- search {Object} - a hash of text labels for Search component,
- searchPagination {Object} - a hash of text labels for Search Pagination component,
- searchResults {Object} - a hash of text labels for Search Results component,
- searchResultsItem {Object} - a hash of text labels for Search Results Item component.
- selectedItemsLimit {Number} - the limit of items that can be selected. Should be combined with the
multiple
attribute set totrue
. Default value is0
, which means no limit, - allowContainersOnly {Boolean} - when true, only containers can be selected. Default value:
false
, - onlyContentOnTheFly {Boolean} - when true, only Content on the Fly is shown in the UDW. Default value:
false
, - cotfForcedLanguage {String} - language code. When set, Content on the Fly is locked on this language.
Sub-items List¶
The Sub-items List module is meant to be used as a part of the editorial interface of eZ Platform. It provides an interface for listing the sub items of any location.
How to use it?¶
With vanilla JS:
1 2 3 4 5 6 7 | React.createElement(eZ.modules.SubItems, { parentLocationId: {Number}, restInfo: { token: {String}, siteaccess: {String} } }); |
With JSX:
1 2 3 4 5 6 7 8 9 | const attrs = { parentLocationId: {Number}, restInfo: { token: {String}, siteaccess: {String} } }; <SubItemsModule {...attrs}/> |
Properties list¶
The <SubItemsModule />
module can handle additional properties. There are 2 types of properties: required and optional. All of them are listed below.
Required props¶
Without all the following properties the Sub-items module will not work.
- parentLocationId {Number} - parent location ID.
- restInfo {Object} - backend config object:
- token {String} - CSRF token,
- siteaccess {String} - SiteAccess identifier.
- handleEditItem {Function} - callback to handle edit content action.
- generateLink {Function} - callback to handle view content action.
Optional properties¶
Optionally, Sub-items module can take a following list of props:
- loadContentInfo {Function} - loads content items info. Takes 2 params:
- contentIds {Array} - list of content IDs
- callback {Function} - a callback invoked when content info is loaded
- loadContentTypes {Function} - loads content types. Takes one param:
- callback {Function} - callback invoked when content types are loaded
- loadLocation {Function} - loads location. Takes 4 params:
- restInfo {Object} - REST info params:
- token {String} - the user token
- siteaccess {String} - the current SiteAccess
- queryConfig {Object} - query config:
- locationId {Number} - location ID
- limit {Number} - content item limit
- offset {Number} - items offset
- sortClauses {Object} - the sort clauses, e.g. {LocationPriority: 'ascending'}
- callback {Function} - callback invoked when location is loaded
- restInfo {Object} - REST info params:
- updateLocationPriority - updates item location priority. Takes 2 params:
- params {Object} - parameters hash containing:
- priority {Number} - priority value
- location {String} - REST location id
- token {String} - CSRF token
- siteaccess {String} - SiteAccess identifier
- callback {Function} - callback invoked when content location priority is updated
- params {Object} - parameters hash containing:
- activeView {String} - active list view identifier
- extraActions {Array} - list of extra actions. Each action is an object containing:
- component {Element} - React component class
- attrs {Object} - additional component properties
- items {Array} - list of location sub items
- limit {Number} - items limit count
- offset {Number} - items limit offset
- labels {Object} - list of module labels, see sub.items.module.js for details. Contains definitions for sub components:
- subItems {Object} - list of sub items module labels
- tableView {Object} - list of table view component labels
- tableViewItem {Object} - list of table item view component labels
- loadMore {Object} - list of load more component labels
- gridViewItem {Object} - list of grid item view component labels
Online Editor¶
The Online Editor is based on Alloy Editor. Refer to Alloy Editor documentation to learn how to extend the Online Editor with new elements.
Custom tags¶
Custom tags enable you to add more features to the Rich Text editor beyond the built-in ones.
Custom tags are configured under the ezrichtext
key (here on the example of a YouTube tag):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | ezpublish: system: default: fieldtypes: ezrichtext: custom_tags: [ezyoutube] ezrichtext: custom_tags: ezyoutube: # The template used for front-end rendering of the custom tag template: 'AppBundle:field_type/ezrichtext/custom_tag:ezyoutube.html.twig' # An icon for the custom tag as displayed in the Online Editor's toolbar. icon: '/assets/field_type/ezrichtext/custom_tag/icon/youtube-color.svg#youtube-color' attributes: title: type: 'string' required: true default_value: '' video_url: type: 'string' required: true width: type: 'number' required: true default_value: 640 height: type: 'number' required: true default_value: 360 autoplay: type: 'boolean' default_value: false align: type: 'choice' required: false default_value: 'left' choices: ['left', 'center', 'right'] |
Each custom tag can have any number of attributes. Supported attribute types are:
string
, number
, boolean
and choice
(which requires a list of choices provided by the choices
key).
The configuration requires a Twig template for the custom tag:
1 2 3 4 5 | <div{% if params.align is defined %} style="text-align: {{ params.align }};"{% endif %}> <iframe type="text/html" width="{{ params.width }}" height="{{ params.height }}" src="{{ params.video_url|replace({'https://youtu.be/' : 'https://www.youtube.com/embed/'}) }}?autoplay={{ params.autoplay == 'true' ? 1 : 0 }}" frameborder="0"></iframe> </div> |
Tip
Remember that if an attribute is not required, you need to check if it is defined in the template, for example:
1 2 3 | {% if params.your_attribute is defined %} ... {% endif %} |
To ensure the new tag has labels, you need to provide translations in a app/Resources/translations/custom_tags.en.yaml
file:
1 2 3 4 5 6 7 8 | ezrichtext.custom_tags.ezyoutube.label: Youtube ezrichtext.custom_tags.ezyoutube.description: '' ezrichtext.custom_tags.ezyoutube.attributes.autoplay.label: Autoplay ezrichtext.custom_tags.ezyoutube.attributes.height.label: Height ezrichtext.custom_tags.ezyoutube.attributes.title.label: Title ezrichtext.custom_tags.ezyoutube.attributes.video_url.label: 'Video url' ezrichtext.custom_tags.ezyoutube.attributes.width.label: Width ezrichtext.custom_tags.ezyoutube.attributes.align.label: 'Align' |
Multi-file Upload¶
The Multi-file Upload module is meant to be used as a part of editorial interface of eZ Platform. It provides an interface to publish content based on dropped files while uploading them in the interface.
How to use it?¶
With vanilla JS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | React.createElement(eZ.modules.MultiFileUpload, { onAfterUpload: {Function}, adminUiConfig: { multiFileUpload: { defaultMappings: [{ contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String}, mimeTypes: [{String}, {String}, ...] }], fallbackContentType: { contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String} }, locationMappings: [{Object}], maxFileSize: {Number} }, token: {String}, siteaccess: {String} }, parentInfo: { contentTypeIdentifier: {String}, contentTypeId: {Number}, locationPath: {String}, language: {String} } }); |
With JSX:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | const attrs = { onAfterUpload: {Function}, adminUiConfig: { multiFileUpload: { defaultMappings: [{ contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String}, mimeTypes: [{String}, {String}, ...] }], fallbackContentType: { contentTypeIdentifier: {String}, contentFieldIdentifier: {String}, contentNameIdentifier: {String} }, locationMappings: [{Object}], maxFileSize: {Number} }, token: {String}, siteaccess: {String} }, parentInfo: { contentTypeIdentifier: {String}, contentTypeId: {Number}, locationPath: {String}, language: {String} } }; <MultiFileUploadModule {...attrs}/> |
Properties list¶
The <MultiFileUpload />
module can handle additional properties.
There are 2 types of properties: required and optional. All of them are listed below.
Required properties¶
Without all the following properties the Multi-file Upload will not work.
- onAfterUpload {Function} - a callback to be invoked just after a file has been uploaded
- adminUiConfig {Object} - UI config object. It should keep the following structure:
- multiFileUpload {Object} - multi file upload module config:
- defaultMappings {Array} - a list of file type to content type mappings
Sample mapping be an object and should follow the convention:
- contentTypeIdentifier {String} - Content Type identifier
- contentFieldIdentifier {String} - Content field identifier
- nameFieldIdentifier {String} - name field identifier
- mimeTypes {Array} - a list of file typers assigned to a specific content type
- fallbackContentType {Object} - a fallback content type definition. Should contain the following info:
- contentTypeIdentifier {String} - Content Type identifier
- contentFieldIdentifier {String} - Content Field identifier
- nameFieldIdentifier {String} - name Field identifier
- locationMappings {Array} - list of file type to content type mappings based on a location identifier
- maxFileSize {Number} - maximum file size allowed for uploading. It's a number of bytes
- defaultMappings {Array} - a list of file type to content type mappings
Sample mapping be an object and should follow the convention:
- token {String} - CSRF token
- siteaccess {String} - SiteAccess identifier
- multiFileUpload {Object} - multi file upload module config:
- parentInfo {Object} - parent location meta information:
- contentTypeIdentifier {String} - Content Type identifier
- contentTypeId {Number} - Content Type id
- locationPath {String} - location path string
- language {String} - language code identifier
Optional properties¶
Optionally, the Multi-file Upload module can take a following list of props:
- checkCanUpload {Function} - checks whether am uploaded file can be uploaded. The callback takes 4 params:
- file {File} - file object,
- parentInfo {Object} - parent location meta information,
- config {Object} - Multi-file Upload module config,
- callbacks {Object} - error callbacks list: fileTypeNotAllowedCallback and fileSizeNotAllowedCallback.
- createFileStruct {Function} - a function that creates a ContentCreate struct. The function takes 2 params:
- file {File} - file object,
- params {Object} - params hash containing: parentInfo and adminUiConfig stored under the config key.
- deleteFile {Function} - a function deleting Content created from a given file. It takes 3 params:
- systemInfo {Object} - hash containing information about CSRF token and siteaccess: token and siteaccess,
- struct {Object} - Content struct,
- callback {Function} - content deleted callback.
- onPopupClose {Function} - function invoked when closing a Multi-file Upload popup. It takes one param: itemsUploaded - the list of uploaded items.
- publishFile {Function} - publishes an uploaded file-based content item. Takes 3 params:
- data {Object} - an object containing information about:
- struct {Object} - the ContentCreate struct (),
- token {String} - CSRF token,
- siteaccess {String} - SiteAccess identifier,
- requestEventHandlers {Object} - a list of upload event handlers:
- onloadstart {Function} - on load start callback,
- upload {Object} - file upload events:
- onabort {Function} - on abort callback,
- onload {Function} - on load callback,
- onprogress {Function} - on progress callback,
- ontimeout {Function} - on timeout callback.
- callback {Function} - a callback invoked when an uploaded file-based content has been published.
- data {Object} - an object containing information about:
Enterprise
Block templates¶
All Landing Page blocks, both those that come out of the box and custom ones, can have multiple templates. This allows you to create different styles for each block and let the editor choose them when adding the block from the UI. The templates are defined in your configuration files like in the following example, with simplelist
and special
being the template names:
1 2 3 4 5 6 7 8 9 10 | # app/config/block_templates.yml blocks: contentlist: views: simplelist: template: blocks/contentlist_simple.html.twig name: Simple Content List special: template: blocks/contentlist_special.html.twig name: Special Content List |
Some blocks can have slightly more complex configuration. An example is the Collection block, which requires an options
key.
This key defines which Content Types can be added to it.
See this example from the Studio Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 | blocks: collection: views: content: template: eZStudioDemoBundle:blocks:collection.content.html.twig name: Content List View options: match: [article, blog_post] gallery: template: eZStudioDemoBundle:blocks:collection.content.html.twig name: Gallery View options: match: [image] |
Extending the Form Builder¶
Form Builder Bundle comes a number of types of fields, but it was designed to be easy to extend by adding new ones.
Field definitions¶
Default field definitions are available in Resources\config\default_fields.yml
.
Field definition structure¶
Field definitions are contained under the fields
key. Each definition has its own key, e.g. single_line_text
. Each definition must contain two sections:
identifier
- name of the definition used on the front enddisplayName
- name displayed in the Page mode editor in thefields
tab
The definition can also contain the following optional sections:
validators
- defines validators that the field will use. This must contain the validator's identifier and the values that will be checked during validation, for example:
1 2 | validators: - { identifier: required, value: 1 } |
-
attributes
- contains the field's attributes. You can place here custom attributes for new fields. There are also four default attributes that are used for every field:LABEL_NAME
,LABEL_HELP_TEXT
,LABEL_ADMIN_LABEL
andLABEL_PLACEHOLDER_TEXT
. If you wish, you can override them in your configuration. -
views
- provides a list of views that will be used to display the field. At least one view must be defined, with the keysname
,thumbnail
andtemplate
, for example:
1 2 3 4 5 | views: basic: name: Form Test Line Text Basic thumbnail: /bundles/ezsystemsformbuilder/images/thumbnails/single_line_text/basic.svg template: EzSystemsFormBuilderBundle:fields:single_line_text/basic.html.twig |
Adding a new field definition¶
This procedure assumes you are creating a separate Bundle to house your new type of form field.
1. Create a YAML file with field definition¶
Create a YAML configuration file, e.g. Resources\config\extended_fields.yml
, with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 | fields: test_text: identifier: testLineText displayName: 'Test Line Text' validators: - { identifier: required, value: 1 } attributes: name: 'test line attribute' views: basic: name: Form Test Line Text Basic thumbnail: /bundles/ezsystemsformbuilder/images/thumbnails/single_line_text/basic.svg template: EzSystemsFormBuilderBundle:fields:single_line_text/basic.html.twig |
You can also provide additional options using the options:
key. For example, you can make sure that the data entered in a field will not to be stored in the database, like for example in the built-in Captcha field.
When creating a new template for the field definition, remember to add the mandatory ezform-field
class and field.id
as shown below:
1 2 3 4 | {% extends 'EzSystemsFormBuilderBundle:fields:field.html.twig' %} {% block input %} <input type="text" class="ezform-field ..." id="{{ field.id }}" placeholder="{{ field.placeholderText }}"> {% endblock %} |
2. Modify the DependencyInjection\TestExtension.php
class¶
The class must implement thePrependExtensionInterface
interface:
1 | class TestExtension implements PrependExtensionInterface
|
In the prepend
method in TestExtension.php
add the following lines at the end:
1 2 3 4 5 6 7 8 | public function prepend(ContainerBuilder $container) { ... $configFile = __DIR__ . '/../Resources/config/extended_fields.yml'; $config = Yaml::parse(file_get_contents($configFile)); $container->loadFromExtension('ez_systems_form_builder', $config); $container->addResource(new FileResource($configFile)); } |
Validators¶
Creating your own validators¶
You can create your own validators by reproducing the following configuration:
Validator configuration¶
A validator implements the EzSystems\FormBuilder\SPI\ValidatorInterface.php
interface and extends the abstract class EzSystems\FormBuilder\Core\Validator\Validator.php
.
The interface requires the implementation of the following methods:
Method | Returns | Parameters | Description |
---|---|---|---|
validate |
bool | $value | Contains the logic for the validation. It returns true when validation is successful, or false when the data does not validate |
getLabel |
string | Returns a string with the name of the validator that will be used in the editor | |
getErrorMessage |
array | Returns error message(s) to appear when the validate method returns false |
|
setLimitation |
mixed $limitation | $limitation | Allows the configuration of limitations. Its default implementation is contained in the Validator abstract class |
getValueType |
string | Returns the name of the checked value type |
Currently the abstract class Validator
has three value types (defined in Core\Validator\Validator.php
):
1 2 3 | const TYPE_INTEGER = 'integer'; const TYPE_STRING = 'string'; const TYPE_BOOL = 'bool'; |
The validator must be tagged as form_builder.field_validator
. Due to this the Resources\config\validator_services.yml
file contains two entries, one in the parameters
section:
1 | form_builder.validator.example.class: EzSystems\FormBuilder\Core\Validator\ExampleValidator |
and one in the services
section:
1 2 3 4 | form_builder.validator.example: class: '%form_builder.validator.example.class%' tags: - { name: form_builder.field_validator, alias: example } |
Signal slots¶
Whenever a form is submitted and stored in a database, lib\Core\SignalSlot\Signal\FormSubmit
emits a signal in a submitForm
service. You can create your own listeners, called Signal slots, to capture the FormSubmit signal.
Below you can find an example of a custom Signal slot. It saves submission to a text file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php namespace AppBundle\SignalSlot; use Symfony\Component\Filesystem\Filesystem; use eZ\Publish\Core\SignalSlot\Slot; use eZ\Publish\Core\SignalSlot\Signal; class HandleSubmission extends Slot { /** * Receive the given $signal and react on it. * * @param EzSystems\FormBuilder\Core\SignalSlot\Signal\FormSubmit $signal */ public function receive(Signal $signal) { $fs = new Filesystem(); $formId = $signal->formId; $submission = $signal->submission; $created = $submission->created->format("Y-m-d.H:i:s"); $dataRows = []; foreach ($submission->fields as $field) { $dataRows[] = "{$field->label}: {$field->value}"; } $fs->mkdir("forms/{$formId}"); $fs->dumpFile("forms/{$formId}/{$created}.txt", implode("\n", $dataRows)); } } |
It has to be registered as a tagged Symfony service, like this:
1 2 3 4 5 | app_bundle.handle_submission: class: "AppBundle\SignalSlot\HandleSubmission" tags: - { name: ezpublish.api.slot, signal: '\EzSystems\FormBuilder\Core\SignalSlot\Signal\FormSubmit' } |
Other Form Builder fields¶
The following form Builder fields require additional configuration.
Date field¶
To make use of a datepicker in the Date field you need to add the necessary assets. The assets should be added in page head with the following code:
1 2 3 4 5 6 | {% javascripts ... 'bundles/ezsystemsformbuilder/js/vendors/*/*.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %} |
1 2 3 4 5 6 | {% stylesheets filter='cssrewrite' ... 'bundles/ezsystemsformbuilder/css/vendors/*/*.css' %} <link rel="stylesheet" type="text/css" href="{{ asset_url }}"> {% endstylesheets %} |
Adding new date format¶
If you wish to add a new date format, the alias
in the date field config must follow this pattern:
d
or D
- day of the month (1-2 digits)
dd
or DD
- day of the month (2 digits)
m
or M
- month of the year (1-2 digits)
mm
or MM
- month (2 digits)
yy
or YY
- year (2 digits)
yyyy
or YYYY
- year (4 digits)
for example:
d-m-yyyy
- 16-1-2017
mm/dd/yy
- 01/16/17