Working with modules in PHP
Intro
These page contains functions for working with modules in PHP.
For JavaScript ( v7+ only) check this page
Querying modules
A Page
entity offers access to a PageEditorModulesCollection
instance that wraps its own modules and helps querying modules. In the subsequent section, module
refers to an array containing the data related to that module.
$collection = $page->getModulesCollection();
// the 'title' module
$collection->getRoledModule('title');
// an array of all 'paragraph' modules
$collection->getRoledModules('paragraph'); // returns the data array corresponding to the 'title' module
// the string contents of the 'title' module
$collection->getTextModuleContent('title');
// an array with the string contents of all paragraphs
$collection->getTextModulesContents('paragraph');
The methods above accept either a string, for top level modules, or an array containing the full role path to the module, in case of submodules:
$collection->getTextModuleContent(['main_image', 'description']);
Getting embedded resources (content models)
// The main image used in the current page.
// Xalok uses PageEditorModuleCollection::getMainImagesRoles
// method to see if the page has a module with one of these roles.
// If no such module is found, it takes the first image in the page and uses that
// as the main image
$collection->getMainImage();
// an array with `Image` entities used in the current page
$collection->getImages();
// an array with `Video` entities used in the current page
$collection->getVideos();
// an array with `Audio` entities used in the current page
$collection->getAudios();
// an array with `File` entities used in the current page
$collection->getFiles();
// an array with `Page` (most likely articles) entities used in the current page
// if there are manual articles linked in the board, they will be returned
// if there's a listing embedded in the board, the articles of that listing will be returned
$collection->getRelated();
// an array with `PageListing` entities used in the current page
// use it to extract listings inside boards, for example
$collection->getListings();
// an array with all entities related to the current page,
// optionally filtered by type ('image', 'video', 'audio', etc.)
$collection->getContentModels($type = null);
Note: On versions prior to v8.0 There's also $page->getRelated()
method that is a relic from previous versions and returns nothing. On v8.0+ this method is a proxy for $collection->getRelated()
(they return the same thing). Note: There's also $page->getRelatedPages()
. This returns the same as $collection->getRelated()
if there are any pages included, but, if the given $page
has an automated listing, it returns the articles shown by that listing.
Looping modules
Although the modules' array seems simple to loop through (foreach ($modules['__roles'] as $role)
...), there are countless issues that arose from the fact that there are edge cases that developers seemed to have missed.
Because of this, you are encouraged to use eachModule
helper that irons out some of these edge cases and offer some convenience:
$page->getModulesCollection()->eachModule(
function ($moduleData, $role, $primaryRole, $rolePath, $primaryRolePath) {
// do something with the data here
}
}
As you see, the callback where you need to do work receives, besides the module's data ($module
) and role ($role
) some additional arguments that one might need:
eachModule
receives the following arguments:
$cb
(required): the callback to call for each module that has data. This receives the following arguments:$moduleData
: module's data$role
: module's role (e.g.paragraph
, orparagraph--1
)$primaryRole
: the primary role of the given module (e.g.paragraph
for bothparagraph
andparangraph--1
modules) - useful if one needs to know whether this module is of a certain type (e.g.$primaryRole === 'paragraph'
)$rolePath
: the full role path of the current module - useful if the module that we're interested in is a submodule of another one$primaryRolePath
: a combination of the$primaryRole
and$rolePath
above - the path of primary roles.
$elements
(optional, defaultnull
): pass this as non-null if you want to loop through a different array instead of the page's modules. Example: Loop through all the submodules of themain_image
modulephp$collection = $page->getModulesCollection; $collection->eachModule( function() { // do something here }, $collection->getRoledModule('main_image'), $recursive = true, $rolePath = ['main_image'] )
$recursive
(optional, defaulttrue
): whether one wants to dig through submodules (true
) or only through top level modules (false
)$rolePath
(optional, default[]
): see the example for the$elements
argument - it also passes this argument, the role paths that get to the$cb
are prefixed with this value - useful if one passes the$elements
argument and needs to know the full role path, not the one relative to the$elements
argument.
Building modules
Building modules intro
The body of the articles must be composed as a modules array. XalokNext offers the wf_cms.page_editor_module_builder
(aliased as Wf\Bundle\CmsBaseBundle\Templating\ModulesBuilder
) service.
$modules = [];
$modulesBuilder->add($modules, 'title', ['content' => 'Article title']);
// for text modules there's a shorter version:
$modulesBuilder->addTextModule($modules, 'title', 'Article title');
// add composite modules
$modulesBuilder->addTextModule($modules, ['main_image', 'description'], 'Image description');
// link an image to the module
$modulesBuilder->add($modules, 'main_image', $content = null, $imageEntity);
The add*
methods return the role path of the added module.
Link resources (content models)
$modulesBuilder->addContentModel($modules, ['gallery', 'slide'], $image1);
$modulesBuilder->addContentModel($modules, ['gallery', 'slide--1'], $image2);
Specify increment
When called repeatedly with the same role path, ModulesBuilder
adds the module in the last part of the role path:
$modulesBuilder->addTextModule($modules, ['gallery', 'slide', 'image_description'], 'Description1');
$modulesBuilder->addTextModule($modules, ['gallery', 'slide', 'image_description'], 'Description2');
will result in one gallery.slide
module with two image_description
(image_description
and image_description--1
) inside it.
One can specify in which part of the role path should the second module be added by appending --
to its role:
// add slides in a gallery by adding the image description submodule
$modulesBuilder->addTextModule($modules, ['gallery', 'slide--', 'image_description'], 'Description1');
$modulesBuilder->addTextModule($modules, ['gallery', 'slide--', 'image_description'], 'Description2');
This will result in two gallery.slide
modules, each with their own image_description
.
Note: While this shortcut might prove to be useful in some cases, creating the slide
modules separately is supported in the same way and it makes the code more readable:
$slide1 = [];
$modulesBuilder->add($slide1, 'image_description', 'Description1');
$slide2 = [];
$modulesBuilder->add($slide2, 'image_description', 'Description2');
$modulesBuilder->add($modules, ['gallery', 'slide'], $slide1);
$modulesBuilder->add($modules, ['gallery', 'slide'], $slide2);
v6+Positioning modules
The add*
methods recieve a Wf\Bundle\CmsBaseBundle\Templating\ModulePosition
argument:
$modulesBuilder->addTextModule(
$modules,
'paragraph',
'Automatic first paragraph',
$contentModel = null,
new ModulePosition(ModulePosition::POSITION_FIRST)
);
$modulesBuilder->addTextModule(
$modules,
'paragraph',
'Automatic last paragraph',
$contentModel = null,
new ModulePosition(ModulePosition::POSITION_LAST)
);
$modulesBuilder->addTextModule(
$modules,
'paragraph',
'Automatic after some paragraph',
$contentModel = null,
new ModulePosition(ModulePosition::POSITION_AFTER, 'paragraph--2')
);
$modulesBuilder->addTextModule(
$modules,
'paragraph',
'Automatic before some paragraph',
$contentModel = null,
new ModulePosition(ModulePosition::POSITION_BEFORE, 'paragraph--2')
);
Removing modules
$modulesBuilder->remove('paragraph'); // removes module with wfRolePath ['paragraph'];
$modulesBuilder->remove('paragraph--2'); // removes module with wfRolePath ['paragraph--2'];
$modulesBuilder->removeAll('paragraph'); // removes *all* modules with primary role 'paragraph' (wfRolePath ['paragraph'], ['paragraph--1'], ['paragraph--2'], etc.)
In PageSerializer
XalokNext:6.0 comes with AbstractModulesPageSerializer that helps querying and modifying the modules before they're sent to NodeJS for Server Side Rendering.