module_builder-8.x-3.x-dev/src/Form/ModuleNameForm.php
src/Form/ModuleNameForm.php
<?php
namespace Drupal\module_builder\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\module_builder\DrupalCodeBuilder;
use MutableTypedData\Data\DataItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form for editing basic information, and also for adding new module entities.
*/
class ModuleNameForm extends ComponentSectionForm {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('module_builder.drupal_code_builder'),
$container->get('extension.list.module'),
);
}
/**
* Creates a ModuleNameForm instance.
*
* @param \Drupal\module_builder\DrupalCodeBuilder $code_builder
* The drupal code builder service.
* @param \Drupal\Core\Extension\ModuleExtensionList $moduleExtensionList
* The module extension list service.
*/
public function __construct(
DrupalCodeBuilder $code_builder,
protected ModuleExtensionList $moduleExtensionList,
) {
parent::__construct($code_builder);
}
/**
* {@inheritdoc}
*/
protected function getFormComponentProperties(DataItem $data) {
// Get the list of component properties this section form uses from the
// handler, which gets them from the entity type annotation.
$component_entity_type_id = $this->entity->getEntityTypeId();
$component_sections_handler = $this->entityTypeManager->getHandler($component_entity_type_id, 'component_sections');
// Need to override this method to hardcode the form operation name, because
// there is a mismatch between our system which wants this to be the 'name'
// op, but uses the 'edit' op's route and form class.
// TODO: clean up!
$operation = 'name';
$component_properties_to_use = $component_sections_handler->getSectionFormComponentProperties($operation);
return $component_properties_to_use;
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$module = $this->entity;
// The name.
$form['name'] = array(
'#type' => 'textfield',
'#title' => $this->t('Readable name'),
'#maxlength' => 255,
'#default_value' => isset($module->name) ? $module->name : '',
'#description' => $this->t("The form of the module name that appears in the UI."),
'#required' => TRUE,
);
// The machine name.
$form['id'] = array(
'#type' => 'machine_name',
'#title' => $this->t('Name'),
'#description' => $this->t("The module's machine name, used in function and file names. May only contain lowercase letters, numbers, and underscores."),
'#maxlength' => 32,
'#default_value' => $module->id,
'#machine_name' => array(
'exists' => '\Drupal\module_builder\Entity\ModuleBuilderModule::load',
'source' => ['name'],
'standalone' => TRUE,
),
);
$form['location'] = [
'#type' => 'details',
'#title' => $this->t('Write location'),
'#tree' => TRUE,
'#weight' => 20,
];
$form['location']['#open'] = ($module->location['location_type'] ?? 'standard') != 'standard';
$form['location']['location_type'] = [
'#type' => 'radios',
'#title' => $this->t('Write location'),
'#options' => [
'standard' => $this->t("Standard. Writes to the existing module's location if it exists, 'modules/custom' if that exists, and finally 'modules'."),
'test' => $this->t("Test module. Writes inside a /tests/modules/ folder inside another module."),
'sub' => $this->t("Submodule. Writes inside a /modules/ folder inside another module."),
'custom' => $this->t("Custom location. Use an absolute path to write outside the current project."),
],
'#default_value' => $module->location['location_type'] ?? 'standard',
];
$module_list = $this->moduleExtensionList->getList();
$map_module_to_name = fn ($module) => $module->info['name'];
// Submodules can go inside a top-level module.
$sub_parent_modules = array_filter($module_list, function ($module) {
$pathname = $module->getPathname();
// Get the info file pathname within the main modules folder.
// Figure out how much of the pathname to trim.
$trim_offset = match (TRUE) {
str_starts_with($pathname, 'core/modules/') => 13,
str_starts_with($pathname, 'modules/contrib/') => 16,
str_starts_with($pathname, 'modules/custom/') => 15,
// This goes last so it only gets used for sites that don't have contrib
// and custom folders.
str_starts_with($pathname, 'modules/') => 8,
default => 0,
};
// Project modules are directly within a modules discovery folder, and so
// their info file once trimmed only contains 1 '/', because it's of the
// format module_name/module_name.info.yml.
$pathname_within_modules_folder = substr($pathname, $trim_offset);
return substr_count($pathname_within_modules_folder, '/') == 1;
});
$form['location']['parent_module'] = [
'#type' => 'select',
'#title' => $this->t('Parent module of submodule'),
'#description' => $this->t("."),
'#options' => array_map($map_module_to_name, $sub_parent_modules),
'#empty_value' => '',
'#sort_options' => TRUE,
'#default_value' => $module->location['parent_module'] ?? '',
'#states' => [
'visible' => [
':input[name="location[location_type]"]' => ['value' => 'sub'],
],
'required' => [
':input[name="location[location_type]"]' => ['value' => 'sub'],
],
],
];
// Test modules can go inside any module, except for other testing modules.
$test_parent_modules = array_filter($module_list, fn ($module) => $module->info['package'] != 'Testing');
$form['location']['test_parent_module'] = [
'#type' => 'select',
'#title' => $this->t('Parent module of test module'),
'#description' => $this->t("."),
'#options' => array_map($map_module_to_name, $test_parent_modules),
'#empty_value' => '',
'#sort_options' => TRUE,
'#default_value' => $module->location['test_parent_module'] ?? '',
'#states' => [
'visible' => [
':input[name="location[location_type]"]' => ['value' => 'test'],
],
'required' => [
':input[name="location[location_type]"]' => ['value' => 'test'],
],
],
];
$form['location']['custom'] = [
'#type' => 'textfield',
'#title' => $this->t('Custom write location'),
'#maxlength' => 128,
'#default_value' => $module->location['custom'] ?? '',
'#description' => $this->t("A sub-path inside the Drupal root into which to place this module. Do not include the module name."),
'#states' => [
'visible' => [
':input[name="location[location_type]"]' => ['value' => 'custom'],
],
'required' => [
':input[name="location[location_type]"]' => ['value' => 'custom'],
],
],
];
$form = parent::form($form, $form_state);
return $form;
}
/**
* Returns an array of supported actions for the current entity form.
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
if ($this->entity->isNew()) {
$button = $actions['submit'];
$button['#value'] = $this->t('Save basic information');
// Babysit core bug: dropbutton with only one item looks wrong.
unset($button['#dropbutton']);
$actions = [
'submit' => $button,
];
}
return $actions;
}
/**
* Copies top-level form values to entity properties
*
* This should not change existing entity properties that are not being edited
* by this form.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon.
* @param array $form
* A nested array of form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
$values = $form_state->getValues();
if ($this->entity instanceof EntityWithPluginCollectionInterface) {
// Do not manually update values represented by plugin collections.
$values = array_diff_key($values, $this->entity->getPluginCollections());
}
// Zap unrelated values in the location values.
$additional_key = match ($values['location']['location_type']) {
'standard' => NULL,
'test' => 'test_parent_module',
'sub' => 'parent_module',
'custom' => 'custom',
};
$location_keys = [
'location_type' => TRUE,
$additional_key => TRUE,
];
$values['location'] = array_intersect_key($values['location'], $location_keys);
// Set the base properties.
foreach (['name', 'id', 'location'] as $key) {
$entity->set($key, $values[$key]);
// Remove so it doesn't end up in the $entity->data array.
unset($values[$key]);
}
// Call the parent to set the data array properties.
parent::copyFormValuesToEntity($entity, $form, $form_state);
}
}
