You can register your own Element Type to tailor import/export behaviour for certain elements, or even extend an existing Element Type.
First, you'll need to get familiar with creating a module. In our example, we're going to create a module with the namespace set to modules\zenmodule
and the module ID to zen-module
.
Your main module class will need to register your custom class (which we're about to create). Add this to your init()
method.
namespace modules\zenmodule;
use craft\events\RegisterComponentTypesEvent;
use modules\zenmodule\CustomElement;
use verbb\zen\services\Elements;
use yii\base\Event;
use yii\base\Module;
class ZenModule extends Module
{
// ...
public function init()
{
parent::init();
// ...
Event::on(Elements::class, Elements::EVENT_REGISTER_ELEMENT_TYPES, function(RegisterComponentTypesEvent $event) {
$event->types[] = CustomElement::class;
});
// ...
}
// ...
}
Create the following class to house your Element Type logic.
<?php
namespace modules\zenmodule;
use verbb\zen\base\Element as ZenElement;
use verbb\zen\models\ImportFieldTab;
use Craft;
use craft\base\ElementInterface;
use craft\db\Table;
use craft\elements\Category as CategoryElement;
use craft\elements\db\ElementQueryInterface;
use craft\helpers\ArrayHelper;
use craft\helpers\Cp;
use craft\helpers\Db;
class CustomElement extends ZenElement
{
public static function elementType(): string
{
// Replace this with the element `ElementInterface` that you wish to support
return CategoryElement::class;
}
public static function exportKeyForElement(ElementInterface $element): array
{
// Add any element query params to be used when grouping elements for export
return ['group' => $element->group->handle];
}
public static function getExportOptions(ElementQueryInterface $query): array|bool
{
$options = [];
// Generate a collection of options for users to pick. These should relate to your own element groupings
// like Groups for Categories, Sections/Entry Types for Entries, etc.
foreach (Craft::$app->getCategories()->getAllGroups() as $group) {
$options[] = [
'label' => $group->name,
'criteria' => ['group' => $group->handle],
'count' => $query->group($group)->count(),
];
}
return $options;
}
public static function defineSerializedElement(ElementInterface $element, array $data): array
{
// When generating an element for export, we serialize it to have just what we need (and in the format we need)
// so we can import it on another install. We're pretty restrictive about what content we do save, as we don't need
// everything for an element. Importantly, any references to IDs should be swapped to UIDs or handles. This is because
// on the destination install, the ID likely won't be the same (think `authorId`).
// This is also called when comparing on the destination install, to ensure there's consistency.
$data['groupUid'] = $element->getGroup()->uid;
return $data;
}
public static function defineNormalizedElement(array $data): array
{
// And here, we are doing the reverse of `defineSerializedElement()`, where we're taking the serialized content from an export
// and converting it back to the value that's appropriate on the destination install. For example, we might have a `groupId` setting
// for an elment, but because the ID will change on each install, we store the UID. We want to convert that UID back to an ID.
$data['groupId'] = Db::idByUid(Table::CATEGORYGROUPS, ArrayHelper::remove($data, 'groupUid'));
return $data;
}
public static function defineImportTableAttributes(): array
{
// Return any columns to be shown in the configure table when importing.
return [
'group' => Craft::t('zen', 'Group'),
];
}
public static function defineImportTableValues(array $diffs, ?ElementInterface $newElement, ?ElementInterface $currentElement, ?string $state): array
{
// Return any columns to be shown in the configure table when importing.
// Use either the new or current element to get data for, at this generic stage.
$element = $newElement ?? $currentElement ?? null;
if (!$element) {
return [];
}
return [
'group' => $element->group->name,
];
}
public static function defineImportFieldTabs(ElementInterface $element, string $type): array
{
// Return any tabs to be shown in the configure table when importing. At the very least, you'll want to include a "Meta" tab which
// includes all the element attributes that are specific to this element type.
return [
new ImportFieldTab([
'name' => Craft::t('zen', 'Meta'),
'fields' => [
'slug' => Cp::textFieldHtml([
'label' => Craft::t('app', 'Slug'),
'id' => 'slug',
'value' => $element->slug,
'disabled' => true,
]),
'enabled' => Cp::lightswitchFieldHtml([
'label' => Craft::t('app', 'Enabled'),
'id' => 'enabled',
'on' => $element->enabled,
'disabled' => true,
]),
'dateCreated' => Cp::dateTimeFieldHtml([
'label' => Craft::t('app', 'Date Created'),
'id' => 'dateCreated',
'value' => $element->dateCreated,
'disabled' => true,
]),
],
]),
];
}
}