Repository URL to install this package:
|
Version:
1.3.0 ▾
|
<?php
namespace Drupal\custom_forms;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\custom_forms\Entity\CustomFormType;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Factory services for handling various CRUD operations for CustomFormItem.
*
* @package Drupal\custom_forms
*/
class CustomFormItemFactory implements ContainerInjectionInterface {
use StringTranslationTrait;
/**
* @var \Drupal\Core\Database\Connection
* The database connection.
*/
private $connection;
/**
* @var \Drupal\Core\Language\LanguageManagerInterface
* The language manager.
*/
private $languageManager;
/**
* @var \Drupal\Core\Messenger\MessengerInterface
* The messenger service.
*/
private $messenger;
/**
* @var \Drupal\Core\Logger\LoggerChannelInterface
* The logging channel.
*/
private $logger;
/**
* @var \Drupal\custom_forms\CustomFormsFieldTypeManager
* The plugin manager for fields.
*/
private $fieldPluginManager;
/**
* @var \Drupal\custom_forms\CustomFormsGroupTypeManager
* The plugin manager for groups.
*/
private $groupPluginManager;
/**
* @var \Drupal\custom_forms\CustomFormsFieldMappingManager
* The plugin manager for field mappings.
*/
private $mappingPluginManager;
/**
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $datetime;
/**
* CustomFormItemFactory constructor.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The service to use Drupal's messenger API.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_channel_factory
* The logging channel.
* @param \Drupal\custom_forms\CustomFormsFieldTypeManager $field_type_manager
* The plugin manager for fields.
* @param \Drupal\custom_forms\CustomFormsGroupTypeManager $group_type_manager
* The plugin manager for groups.
* @param \Drupal\custom_forms\CustomFormsFieldMappingManager $field_mapping_manager
* The plugin manager for field mappings.
* @param \Drupal\Component\Datetime\TimeInterface $datetime
* The date time service.
*/
public function __construct(
Connection $connection,
LanguageManagerInterface $language_manager,
MessengerInterface $messenger,
LoggerChannelFactoryInterface $logger_channel_factory,
CustomFormsFieldTypeManager $field_type_manager,
CustomFormsGroupTypeManager $group_type_manager,
CustomFormsFieldMappingManager $field_mapping_manager,
TimeInterface $datetime
) {
$this->connection = $connection;
$this->languageManager = $language_manager;
$this->messenger = $messenger;
$this->logger = $logger_channel_factory->get('Custom Forms');
$this->fieldPluginManager = $field_type_manager;
$this->groupPluginManager = $group_type_manager;
$this->mappingPluginManager = $field_mapping_manager;
$this->datetime = $datetime;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): CustomFormItemFactory {
return new static(
$container->get('database'),
$container->get('language_manager'),
$container->get('messenger'),
$container->get('logger.factory'),
$container->get('plugin.manager.custom_forms_field_types'),
$container->get('plugin.manager.custom_forms_group_types'),
$container->get('plugin.manager.custom_forms_field_mappings'),
$container->get('datetime.time')
);
}
/**
* Gets the language manager service.
*
* @return \Drupal\Core\Language\LanguageManagerInterface
*/
public function getLanguageManager(): LanguageManagerInterface {
return $this->languageManager;
}
/**
* Gets the definition of the plugin used by the item.
*
* @param string $type
* The type of the custom form item, can either be "field", "group"
* or "mapping".
* @param string $plugin_id
* The plugin id used by the item.
*
* @return array
* Returns an array containing the plugin definition
*/
public function getItemPluginDefinition($type, $plugin_id): ?array {
// We need to load the proper plugin definition based on the specified item type.
switch ($type) {
case 'group':
return $this->groupPluginManager->getDefinition($plugin_id);
break;
case 'mapping':
return $this->mappingPluginManager->getDefinition($plugin_id);
break;
case 'field':
default:
return $this->fieldPluginManager->getDefinition($plugin_id);
break;
}
}
/**
* Gets the plugin manager used by the item.
*
* @param string $type
* The type of the custom form item, can either be "field", "group"
* or "mapping".
*
* @return \Drupal\Component\Plugin\PluginManagerInterface
*/
public function getItemPluginManager($type): PluginManagerInterface {
switch ($type) {
case 'group':
return $this->groupPluginManager;
break;
case 'mapping':
return $this->mappingPluginManager;
break;
case 'field':
default:
return $this->fieldPluginManager;
break;
}
}
/**
* Gets the plugin associated with the item.
*
* @param string $type
* The type of the custom form item, can either be "field", "group"
* or "mapping".
* @param string $plugin_id
* The plugin id used by the item.
* @param array $settings
* (Optional) The array of settings to pass to the plugin.
*
* @return \Drupal\custom_forms\Plugin\CustomForms\FieldType\CustomFormsFieldTypeInterface|\Drupal\custom_forms\Plugin\CustomForms\GroupType\CustomFormsGroupTypeInterface|\Drupal\custom_forms\Plugin\CustomForms\FieldMapping\CustomFormsFieldMappingInterface
* Returns either a field, group or mapping plugin.
*
* @throws \Drupal\Component\Plugin\Exception\PluginException
* If the instance cannot be created, such as if the ID is invalid.
*/
public function getItemPlugin($type, $plugin_id, array $settings = []) {
switch ($type) {
case 'group':
return $this->groupPluginManager->createInstance($plugin_id, $settings);
break;
case 'mapping':
return $this->mappingPluginManager->createInstance($plugin_id, $settings);
break;
case 'field':
default:
return $this->fieldPluginManager->createInstance($plugin_id, $settings);
break;
}
}
/**
* Saves the custom form item to the database.
*
* @param \Drupal\custom_forms\CustomFormItem $item
* The custom form item to be saved.
* @param bool $create_new_revision
* Whether a new revision should be created of the field when saved.
*
* @return \Drupal\custom_forms\CustomFormItem
* Returns the saved custom form item.
*
* @throws \Exception
* If the insert failed, an exception is thrown.
*/
public function saveItem(CustomFormItem $item, $create_new_revision = TRUE): CustomFormItem {
if ($create_new_revision) {
$query = $this->connection->insert('custom_forms__items');
// If ID is null, generate one as it means this is a new field.
if ($item->id() === NULL) {
// We need to get the highest id currently in use and add one, so that
// our new field gets it's own fid.
$id_query = $this->connection->query('SELECT MAX(id) AS `id` FROM custom_forms__items');
$id_result = $id_query->fetchAssoc();
// Cast as int so that NULL automatically gets converted to 0.
$id = (int) $id_result['id'];
$item->setId($id + 1);
$item->setRevision(1);
}
else {
// We need to get the highest revision currently in use and add one, so that
// our new field gets it's own revision.
$revision_query = $this->connection->query('SELECT MAX(revision) AS `revision` FROM custom_forms__items WHERE id = :id AND form = :form', [':id' => $item->id(), ':form' => $item->getFormId()]);
$revision_result = $revision_query->fetchAssoc();
// Cast as int so that NULL automatically gets converted to 0.
$revision = (int) $revision_result['revision'];
$item->setRevision($revision + 1);
}
}
else {
$query = $this->connection->update('custom_forms__items');
$query->condition('id', $item->id());
$query->condition('revision', $item->getRevision());
}
// Add values to the query.
$query->fields([
'id' => $item->id(),
'revision' => $item->getRevision(),
'form' => $item->getFormId(),
'form_revision' => $item->getFormRevisionId(),
'created' => $item->getCreated()->getTimestamp(),
'changed' => $item->getChanged()->getTimestamp(),
'langcode' => $item->getLangcode(),
'type' => $item->getType(),
'plugin' => $item->getPluginId(),
'mapping' => $item->getMappingId(),
'machine_name' => $item->getMachineName(),
'settings' => serialize($item->getSettings()),
'states' => serialize($item->getStates()),
'weight' => $item->getWeight(),
'pid' => $item->getParentId(),
]);
$query->execute();
return $item;
}
/**
* Load a custom form item based on it's machine name.
*
* Use this when you do not know it's specific id, but do know it's
* machine name.
*
* @param string $machine_name
* The machine name of the custom form item.
* @param int $form_id
* The form id that the custom form item belongs to.
* @param int $form_revision
* The revision id to load the custom form item from.
* @param string|null $langcode
* (Optional) The language code to load the custom form item as.
*
* @return bool|\Drupal\custom_forms\CustomFormItem
* Returns the matching custom form item on success, FALSE on failure.
*/
public function loadItemByMachineName($machine_name, $form_id, $form_revision, $langcode = NULL) {
$query = $this->connection->select('custom_forms__items', 'item');
$query->fields('item', ['id', 'revision', 'form_revision']);
$query->orderBy('item.revision', 'DESC');
$query->condition('item.form', $form_id);
$query->condition('item.form_revision', $form_revision);
$query->condition('item.machine_name', $machine_name);
if ($langcode !== NULL) {
$query->condition('item.langcode', $langcode);
}
$executed = $query->execute();
// Execute returns null if query is invalid, this is just extra safety check.
if ($executed instanceof StatementInterface) {
// We only fetch the latest revision.
$result = $executed->fetch();
}
if (!empty($result)) {
return new CustomFormItem(
$result->form,
$result->form_revision,
$result->created,
$result->changed,
$result->langcode,
$result->type,
$result->plugin,
$result->machine_name,
$result->settings,
$result->states,
$result->mapping,
$result->weight,
$result->pid,
$result->revision,
$result->id
);
}
return FALSE;
}
/**
* Checks if a custom form item exists.
*
* @param integer $id
* The id of the item.
* @param integer $revision
* The field id of the item.
* @param string $langcode
* The language code for the translation language.
*
* @return bool
* Returns TRUE if the item exists, otherwise FALSE.
*/
public function itemExists($id, $revision, $langcode): bool {
$query = $this->connection->select('custom_forms__items', 'item');
$query->fields('item');
$query->condition('item.id', $id);
$query->condition('item.revision', $revision);
$query->condition('item.langcode', $langcode);
$executed = $query->execute();
// Execute returns null if query is invalid, this is just extra safety check.
if ($executed instanceof StatementInterface) {
$result = $executed->fetch();
}
// If the result is empty, there is no item with the specified machine name.
if (empty($result)) {
return FALSE;
}
return TRUE;
}
/**
* Check if a translation exists for a custom form item.
*
* @param int $id
* The id of the custom form item to check for the translation.
* @param int $form
* The id of the form that the custom form item belongs to.
* @param int $form_revision
* The revision id of the form to check for translations under.
* @param string $langcode
* The language code for the translation language.
*
* @return bool
* Returns TRUE if a translation exists, otherwise FALSE.
*/
public function itemTranslationExists($id, $form, $form_revision, $langcode): bool {
$query = $this->connection->select('custom_forms__items', 'item');
$query->fields('item', ['id']);
$query->condition('item.id', $id);
$query->condition('item.form', $form);
$query->condition('item.form_revision', $form_revision);
$query->condition('item.langcode', $langcode);
$executed = $query->execute();
// Execute returns null if query is invalid, this is just extra safety check.
if ($executed instanceof StatementInterface) {
$result = $executed->fetch();
}
// If the result is empty, there are no translations for said language.
if (empty($result)) {
return FALSE;
}
return TRUE;
}
/**
* Checks if a custom form item with the specified machine name exists.
*
* @param string $machine_name
* The machine readable name of the item.
* @param array $element
* The machine_name element
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return bool
* Returns TRUE if the item exists, otherwise FALSE.
*/
public static function itemMachineNameExists($machine_name, array $element, FormStateInterface $form_state): bool {
// Return false if we do not have a form, as this means we are getting a
// malformed request.
if (empty($form_state->getValue('form'))) {
return FALSE;
}
/** @var \Drupal\custom_forms\CustomFormInterface $form */
$form = $form_state->getValue('form');
$connection = \Drupal::database();
$query = $connection->select('custom_forms__items', 'item');
$query->fields('item');
$query->condition('item.machine_name', $machine_name);
$query->condition('item.form', $form->id());
$query->condition('item.form_revision', $form->getLoadedRevisionId());
$query->condition('item.langcode', $form->language()->getId());
$executed = $query->execute();
// Execute returns null if query is invalid, this is just extra safety check.
if ($executed instanceof StatementInterface) {
$result = $executed->fetch();
}
// If the result is empty, there is no item with the specified machine name.
if (empty($result)) {
return FALSE;
}
return TRUE;
}
/**
* Clone a specific item.
*
* @param \Drupal\custom_forms\CustomFormItem $original_item
* The original item to be cloned.
* @param \Drupal\custom_forms\CustomFormInterface $cloned_form
* The form that the item should be cloned to.
* @param int $parent_id
* The parent id to set for the cloned item.
*/
public function cloneItem(CustomFormItem $original_item, CustomFormInterface $cloned_form, $parent_id = 0) : void {
$current_time = $this->datetime->getCurrentTime();
try {
$new_item = new CustomFormItem(
$cloned_form->id(),
$cloned_form->getLoadedRevisionId(),
$current_time,
$current_time,
$original_item->getLangcode(),
$original_item->getType(),
$original_item->getPluginId(),
$original_item->getMachineName(),
serialize($original_item->getSettings()),
serialize($original_item->getStates()),
$original_item->getMappingId(),
$original_item->getWeight(),
$parent_id
);
$new_item->save(TRUE);
$original_children = $original_item->getChildren();
if (!empty($original_children)) {
foreach ($original_children as $child_item) {
$this->cloneItem($child_item, $cloned_form, $new_item->id());
}
}
} catch (\Exception $e) {
$error_message = t('Failed during cloning of field %field-name. Message = %message', [
'%field-name' => $original_item->getSetting('label'),
'%message' => $e->getMessage(),
]);
$this->logger->error($error_message);
\Drupal::messenger()->addError(t('Failed during cloning of field %field-name.',
[
'%field-name' => $original_item->getSetting('label'),
]
));
}
}
/**
* Clone all children belonging to a custom form item.
*
* @param \Drupal\custom_forms\CustomFormItem $original
* The original (old) custom form item to clone children from.
* @param \Drupal\custom_forms\CustomFormItem $clone
* The new custom form item to clone children to.
*/
public function cloneItemChildren(CustomFormItem $original, CustomFormItem $clone): void {
/** @var \Drupal\custom_forms\CustomFormItem $childItem */
foreach ($original->getChildren() as $childItem) {
$newChildItem = clone $childItem;
$newChildItem->setFormRevisionId($clone->getFormRevisionId());
$newChildItem->setParentId($clone->id());
try {
$newChildItem->save();
// Remember to clone children, used for nested groups with fields, etc.
if (count($newChildItem->getChildren()) > 0) {
$this->cloneItemChildren($childItem, $newChildItem);
}
} catch (\Exception $e) {
$error_message = $this->t('Failed during cloning of child items. Message = %message',
[
'%message' => $e->getMessage(),
]
);
$this->logger->error($error_message);
$this->messenger->addError(
$this->t('Failed during cloning of child items. Please contact an administrator.')
);
}
}
}
/**
* Deletes all custom form items from the specified form.
*
* @param \Drupal\custom_forms\CustomFormInterface $form
* The form to delete the associated custom form items from.
* @param string $langcode
* The langcode to limit deletion to.
*/
public function deleteFormItems(CustomFormInterface $form, $langcode = NULL): void {
$query = $this->connection->select('custom_forms__items', 'item');
$query->condition('item.form', $form->id());
if (!empty($langcode)) {
$query->condition('item.langcode', $langcode);
}
$query->fields('item');
$executed = $query->execute();
// Execute returns null if query is invalid, this is just extra safety check.
if ($executed instanceof StatementInterface) {
// We only want the first result.
$results = $executed->fetchAll();
}
if (!empty($results)) {
foreach ($results as $result) {
$item = new CustomFormItem(
$result->form,
$result->form_revision,
$result->created,
$result->changed,
$result->langcode,
$result->type,
$result->plugin,
$result->machine_name,
$result->settings,
$result->states,
$result->mapping,
$result->weight,
$result->pid,
$result->revision,
$result->id
);
$this->deleteItem($item);
}
}
}
/**
* Deletes the custom form item from the database and updates any child items
* to move out nesting level out.
*
* @param \Drupal\custom_forms\CustomFormItem $item
* The custom form item to be deleted.
*/
public function deleteItem(CustomFormItem $item): void {
// Delete the item
$query = $this->connection->delete('custom_forms__items');
$query->condition('id', $item->id());
$query->condition('revision', $item->getRevision());
$query->condition('langcode', $item->getLangcode());
$query->execute();
// At this point new revisions have already been made so we just
// need to update their parent id.
$child_items = $this->loadMultipleItems([
'form' => $item->getFormId(),
'form_revision' => $item->getFormRevisionId(),
'pid' => $item->id(),
'langcode' => $item->getLangcode(),
]);
// Return if there are no child items.
if (!$child_items) {
return;
}
foreach ($child_items as $child_item) {
$child_item->setParentId($item->getParentId());
try {
// We save it without creating a new revision, as that has already been done at this point.
$child_item->save(FALSE);
} catch (\Exception $e) {
$error_message = $this->t('Failed during updating field parent. Message = %message', [
'%message' => $e->getMessage(),
]);
$this->logger->error($error_message);
$this->messenger->addError(
$this->t('Failed during updating field parent. If it continues please contact an administrator.')
);
}
}
}
/**
* Loads multiple existing custom form item.
*
* @param array $conditions
* An array of conditions to load by.
* @param string $sort_by
* The field name to sort by.
* @param string $sort_direction
* The direction to sort by.
*
* @return \Drupal\custom_forms\CustomFormItem[]
* Returns an array of CustomFormItem keyed by item ID if an item was
* found, otherwise an empty array.
*/
public function loadMultipleItems(array $conditions, $sort_by = 'id', $sort_direction = 'ASC'): array {
$query = $this->connection->select('custom_forms__items', 'item');
$query->fields('item', ['revision', 'id', 'form_revision', 'langcode']);
$query->groupBy('item.id, item.revision, item.form_revision, langcode');
foreach ($conditions as $field => $value) {
$query->condition('item.' . $field, $value);
}
if (!empty($sort_by)) {
$query->orderBy($sort_by, $sort_direction);
}
$executed = $query->execute();
// Execute returns null if query is invalid, this is just extra safety check.
if ($executed instanceof StatementInterface) {
// We fetch by the id so we only get the latest revision of the field.
$result = $executed->fetchAllAssoc('id');
}
if (!empty($result)) {
$items = [];
foreach ($result as $item) {
$items[$item->id] = $this->loadItem($item->id, $item->revision, $item->form_revision, $item->langcode);
}
return $items;
}
return [];
}
/**
* Loads an existing custom form item.
*
* @param integer $id
* The ID of the item to load.
* @param integer $revision
* (Optional) The ID of the item revision to load.
* @param integer $form_revision
* (Optional) The ID of the associated form revision.
* @param string $langcode
* (Optional) The language code of the item to load.
*
* @return bool|\Drupal\custom_forms\CustomFormItem
* Returns a CustomFormItem if an item was found, otherwise FALSE.
*/
public function loadItem($id, $revision = NULL, $form_revision = NULL, $langcode = NULL) {
$query = $this->connection->select('custom_forms__items', 'item');
$query->fields('item');
$query->orderBy('item.revision', 'DESC');
$query->condition('item.id', $id);
if ($revision !== NULL && is_int($revision)) {
$query->condition('item.revision', $revision);
}
if ($form_revision !== NULL) {
$query->condition('item.form_revision', $form_revision);
}
if ($langcode !== NULL) {
$query->condition('item.langcode', $langcode);
}
$executed = $query->execute();
// Execute returns null if query is invalid, this is just extra safety check.
if ($executed instanceof StatementInterface) {
// We only want the first result.
$result = $executed->fetch();
}
if (!empty($result)) {
return new CustomFormItem(
$result->form,
$result->form_revision,
$result->created,
$result->changed,
$result->langcode,
$result->type,
$result->plugin,
$result->machine_name,
$result->settings,
$result->states,
$result->mapping,
$result->weight,
$result->pid,
$result->revision,
$result->id
);
}
return FALSE;
}
/**
* Gets the items associated with a form.
*
* @param \Drupal\custom_forms\CustomFormInterface $form
* The form to get the items belonging to.
* @param bool $sort_by_weight
* (Optional) Whether to sort by weight or not, default = FALSE.
* @param bool $nested
* (Optional) Whether to nest the items according to their parent-child
* relationships.
*
* @return \Drupal\custom_forms\CustomFormItem[]
* Returns an array of CustomFormItem instances that belong to the specified
* CustomForm.
*/
public function getFormItems(CustomFormInterface $form, $sort_by_weight = FALSE, $nested = FALSE): array {
if ($sort_by_weight) {
$items = $this->loadMultipleItems(
[
'form' => $form->id(),
'form_revision' => $form->getLoadedRevisionId(),
'langcode' => $form->language()->getId(),
],
'weight',
'ASC'
);
}
else {
$items = $this->loadMultipleItems(
[
'form' => $form->id(),
'form_revision' => $form->getLoadedRevisionId(),
'langcode' => $form->language()->getId(),
]
);
}
// If no items, return an empty array.
if (empty($items)) {
return [];
}
/** @var CustomFormItem $item */
foreach ($items as $id => $item) {
if ($item->getParentId() > 0) {
$items[$item->getParentId()]->addChild($item);
}
}
// Remove the nested items from the root array.
if ($nested) {
foreach ($items as $id => $item) {
if ($item->getParentId() > 0) {
unset($items[$id]);
}
}
}
return $items;
}
/**
* Gets the items as a sorted tree based on child->parent relationship and
* optional weight.
*
* @param \Drupal\custom_forms\CustomFormInterface $form
* The form to get the items belonging to.
* @param bool $sort_by_weight
* (Optional) Whether to sort by weight or not, default = FALSE.
* @param bool $nested
* (Optional) Whether to nest the items according to their parent-child
* relationships.
*
* @return CustomFormItem[]
* An array of CustomFormItem instances formatted for use as a tree.
*/
public function getFormItemsAsTree(CustomFormInterface $form, $sort_by_weight = FALSE, $nested = FALSE): array {
if ($sort_by_weight) {
$root_items = $this->loadMultipleItems(
[
'form' => $form->id(),
'form_revision' => $form->getLoadedRevisionId(),
'pid' => '0',
'langcode' => $form->language()->getId(),
],
'weight'
);
}
else {
$root_items = $this->loadMultipleItems(
[
'form' => $form->id(),
'form_revision' => $form->getLoadedRevisionId(),
'pid' => '0',
'langcode' => $form->language()->getId(),
]
);
}
// If no items, return an empty array.
if (empty($root_items)) {
return [];
}
// Initialize a variable to store our ordered tree structure.
$tree = [];
// Depth will be incremented in our getTree()
// function for the first parent item, so we start it at -1.
$depth = -1;
// Loop through the root item, and add their trees to the array.
foreach ($root_items as $root_item) {
$this->getFormItemTree($form, $root_item, $tree, $depth, $sort_by_weight);
}
if ($nested) {
/** @var CustomFormItem $item */
foreach ($tree as $id => $item) {
if ($item->getParentId() > 0) {
$tree[$item->getParentId()]->addChild($item);
}
}
foreach ($tree as $id => $item) {
if ($item->getParentId() > 0) {
unset($tree[$id]);
}
}
}
return $tree;
}
/**
* Recursively adds custom form items to the tree, ordered by
* parent/child/weight.
*
* @param \Drupal\custom_forms\CustomFormInterface $form
* The form to get the items belonging to.
* @param CustomFormItem $item
* The item.
* @param array $tree
* (Optional) The item tree.
* @param int $depth
* (Optional) The depth of the item.
* @param bool $sorted_by_weight
* (Optional) Whether to sort by weight or not, default = FALSE.
*/
private function getFormItemTree(CustomFormInterface $form, CustomFormItem $item, array &$tree = [], $depth = 0, $sorted_by_weight = FALSE): void {
// Increase our $depth value by one.
$depth++;
// Set the current tree 'depth' for this item, used to calculate
// indentation.
$item->setDepth($depth);
// Add the item to the tree.
$tree[$item->id()] = $item;
// Retrieve each of the children belonging to this nested demo.
if ($sorted_by_weight) {
$children = $this->loadMultipleItems(
[
'form' => $form->id(),
'form_revision' => $form->getLoadedRevisionId(),
'pid' => $item->id(),
'langcode' => $form->language()->getId(),
],
'weight',
'ASC'
);
}
else {
$children = $this->loadMultipleItems(
[
'form' => $form->id(),
'form_revision' => $form->getLoadedRevisionId(),
'pid' => $item->id(),
'langcode' => $form->language()->getId(),
]
);
}
if (!empty($children)) {
foreach ($children as $child) {
// Make sure this child does not already exist in the tree, to
// avoid loops.
if (!array_key_exists($child->id(), $tree)) {
// Add this child's tree to the item tree array.
$this->getFormItemTree($form, $child, $tree, $depth);
}
}
}
// Finished processing this tree branch. Decrease our $depth value by one
// to represent moving to the next branch.
$depth--;
}
/**
* Gets all the compatible field plugins for a specific field type.
*
* @param \Drupal\custom_forms\CustomFormInterface $custom_form
* The custom form to use for checking against field limits.
*
* @return array
* Returns an array of all compatible field plugin definitions keyed by
* their id.
*/
public function getCompatibleFields(CustomFormInterface $custom_form) : array {
$field_types = [];
$custom_form_type = CustomFormType::load($custom_form->bundle());
$available_types = $this->getItemPluginManager('field')->getDefinitions();
$enabled_types = $custom_form_type->getEnabledFieldTypes();
foreach ($available_types as $id => $type) {
// Make sure the field type is enabled
if (!isset($enabled_types[$id]) || $enabled_types[$id] !== $id) {
continue;
}
// We only allow adding fields that has a UI.
if (!isset($type['no_ui']) || !$type['no_ui']) {
$enable_field = TRUE;
// If the definition limits the amount of this field that can exist on the form, check if there are any existing fields with the id.
if (isset($type['limit'])) {
/** @var CustomFormItem[] $custom_form_fields */
$custom_form_fields = $this->getFormItems($custom_form);
$count = 0;
foreach ($custom_form_fields as $field) {
if ($field->getPluginId() === $id) {
$count++;
}
}
// Disable the field if it's limit has been reached or exceeded.
if ($count >= $type['limit']) {
$enable_field = FALSE;
}
}
if ($enable_field) {
$field_types[$id] = $type['label'];
}
}
}
asort($field_types);
return $field_types;
}
/**
* Gets all the compatible group plugins for a specific field type.
*
* @param \Drupal\custom_forms\CustomFormInterface $custom_form
* The custom form to use for checking against group limits.
*
* @return array
* Returns an array of all compatible group plugin definitions keyed by
* their id.
*/
public function getCompatibleGroups(CustomFormInterface $custom_form) : array {
$group_types = [];
$custom_form_type = CustomFormType::load($custom_form->bundle());
$available_types = $this->getItemPluginManager('group')->getDefinitions();
$enabled_types = $custom_form_type->getEnabledGroupTypes();
foreach ($available_types as $id => $type) {
// Make sure the field type is enabled
if (!isset($enabled_types[$id]) || $enabled_types[$id] !== $id) {
continue;
}
// We only allow adding fields that has a UI.
if (!isset($type['no_ui']) || !$type['no_ui']) {
$enable_group = TRUE;
// If the definition limits the amount of this field that can exist on the form, check if there are any existing fields with the id.
if (isset($type['limit'])) {
/** @var CustomFormItem[] $custom_form_fields */
$custom_form_fields = $this->getFormItems($custom_form);
$count = 0;
foreach ($custom_form_fields as $field) {
if ($field->getPluginId() === $id) {
$count++;
}
}
// Disable the field if it's limit has been reached or exceeded.
if ($count >= $type['limit']) {
$enable_group = FALSE;
}
}
if ($enable_group) {
$group_types[$id] = $type['label'];
}
}
}
asort($group_types);
return $group_types;
}
/**
* Gets all the compatible mapping plugins for a specific field type.
*
* @param \Drupal\custom_forms\CustomFormInterface $custom_form
* The custom form to use for checking against mapping limits.
* @param string $field_type_id
* The field type id used for checking if a mapping plugin is compatible
* with.
* @param \Drupal\custom_forms\CustomFormItem|null $item
* The custom form item checking for compatible mappings for.
* This is only used when editing an item, to ensure we don't block
* limited mappings if the item is already mapped.
*
* @return array
* Returns an array of all compatible mapping plugin definitions keyed by
* their id.
*/
public function getCompatibleMappings(CustomFormInterface $custom_form, $field_type_id, CustomFormItem $item = NULL) : array {
$custom_form_type = CustomFormType::load($custom_form->bundle());
$available_mappings = $this->getItemPluginManager('mapping')->getDefinitions();
$enabled_mappings = $custom_form_type->getEnabledMappingTypes();
$mapping_options = [];
// Loop through all available mapping plugins
foreach ($available_mappings as $key => $mapping) {
// Make sure the field type is enabled
if (!isset($enabled_mappings[$key]) || $enabled_mappings[$key] !== $key) {
continue;
}
$enable_mapping = TRUE;
// If the definition limits the amount of this field that can exist on the form, check if there are any existing fields with the id.
if (isset($mapping['limit'])) {
/** @var \Drupal\custom_forms\CustomFormItem[] $custom_form_fields */
$custom_form_fields = $this->getFormItems($custom_form);
$count = 0;
foreach ($custom_form_fields as $field) {
if ($field->getMappingId() === $key) {
$count++;
}
}
// Disable the field if it's limit has been reached or exceeded.
if ($count >= $mapping['limit']) {
$enable_mapping = FALSE;
}
}
if ($enable_mapping) {
// If the field has no specified compatible fields, allow all fields.
if (empty($mapping['compatible_fields'])) {
// If the field is compatible, add the mapping to the select.
$mapping_options[$key] = $mapping;
} else {
// Loop through each plugins compatibility list
foreach ($mapping['compatible_fields'] as $field) {
if ($field === $field_type_id) {
// If the field is compatible, add the mapping to the select.
$mapping_options[$key] = $mapping;
}
}
}
}
}
return $mapping_options;
}
}