features-8.x-3.11/modules/features_ui/src/Form/AssignmentConfigureForm.php
modules/features_ui/src/Form/AssignmentConfigureForm.php
<?php
namespace Drupal\features_ui\Form;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\features\FeaturesManagerInterface;
use Drupal\features\FeaturesAssignerInterface;
use Drupal\features\FeaturesBundleInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configures the configuration assignment methods for this site.
*/
class AssignmentConfigureForm extends FormBase {
/**
* Bundle select value that should trigger a new bundle to be created.
*/
const NEW_BUNDLE_SELECT_VALUE = 'new';
/**
* The features manager.
*
* @var \Drupal\features\FeaturesManagerInterface
*/
protected $featuresManager;
/**
* The package assigner.
*
* @var \Drupal\features\FeaturesAssignerInterface
*/
protected $assigner;
/**
* Constructs a AssignmentConfigureForm object.
*
* @param \Drupal\features\FeaturesManagerInterface $features_manager
* The features manager.
* @param \Drupal\features\FeaturesAssignerInterface $assigner
* The configuration assignment methods manager.
*/
public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner) {
$this->featuresManager = $features_manager;
$this->assigner = $assigner;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('features.manager'),
$container->get('features_assigner')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'features_assignment_configure_form';
}
/**
* Load the values from the bundle into the user input.
* Used during Ajax callback since updating #default_values is ignored.
*
* @param mixed $bundle_name
* The bundle name.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form values.
*/
protected function loadBundleValues($bundle_name, FormStateInterface &$form_state, $current_bundle, $enabled_methods, $methods_weight) {
$input = $form_state->getUserInput();
if ($bundle_name == self::NEW_BUNDLE_SELECT_VALUE) {
$input['bundle']['name'] = '';
$input['bundle']['machine_name'] = '';
$input['bundle']['description'] = '';
$input['bundle']['is_profile'] = NULL;
$input['bundle']['profile_name'] = '';
}
else {
$input['bundle']['name'] = $current_bundle->getName();
$input['bundle']['machine_name'] = $current_bundle->getMachineName();
$input['bundle']['description'] = $current_bundle->getDescription();
$input['bundle']['is_profile'] = $current_bundle->isProfile() ? 1 : NULL;
$input['bundle']['profile_name'] = $current_bundle->isProfile() ? $current_bundle->getProfileName() : '';
}
foreach ($methods_weight as $method_id => $weight) {
$enabled = isset($enabled_methods[$method_id]);
$input['weight'][$method_id] = $weight;
$input['enabled'][$method_id] = $enabled ? 1 : NULL;
}
$form_state->setUserInput($input);
}
/**
* Detects if an element triggered the form submission via Ajax.
* TODO: SHOULDN'T NEED THIS! BUT DRUPAL IS CALLING buildForm AFTER THE
* BUNDLE AJAX IS SELECTED AND DOESN'T HAVE getTriggeringElement() SET YET.
*/
protected function elementTriggeredScriptedSubmission(FormStateInterface &$form_state) {
$input = $form_state->getUserInput();
if (!empty($input['_triggering_element_name'])) {
return $input['_triggering_element_name'];
}
return '';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $bundle_name = NULL) {
$load_values = FALSE;
$trigger = $form_state->getTriggeringElement();
// TODO: See if there is a Drupal Core issue for this.
// Sometimes the first ajax call on the page causes buildForm to be called
// twice! First time form_state->getTriggeringElement is NOT SET, but
// the form_state['input'] shows the _triggering_element_name. Then the
// SECOND time it is called the getTriggeringElement is fine.
$real_trigger = $this->elementTriggeredScriptedSubmission($form_state);
if (!isset($trigger) && ($real_trigger == 'bundle[bundle_select]')) {
$input = $form_state->getUserInput();
$bundle_name = $input['bundle']['bundle_select'];
if ($bundle_name != self::NEW_BUNDLE_SELECT_VALUE) {
$this->assigner->setCurrent($this->assigner->getBundle($bundle_name));
}
$load_values = TRUE;
}
elseif (isset($trigger['#name']) && $trigger['#name'] == 'bundle[bundle_select]') {
$bundle_name = $form_state->getValue(['bundle', 'bundle_select']);
if ($bundle_name != self::NEW_BUNDLE_SELECT_VALUE) {
$this->assigner->setCurrent($this->assigner->getBundle($bundle_name));
}
$load_values = TRUE;
}
elseif (isset($trigger['#name']) && $trigger['#name'] == 'removebundle') {
$current_bundle = $this->assigner->loadBundle($bundle_name);
$bundle_name = $current_bundle->getMachineName();
$this->assigner->removeBundle($bundle_name);
return $this->redirect('features.assignment', ['']);
}
if (!isset($current_bundle)) {
switch ($bundle_name) {
// If no bundle is selected, use the current one.
case NULL:
$current_bundle = $this->assigner->loadBundle();
$bundle_name = $current_bundle->getMachineName();
break;
case self::NEW_BUNDLE_SELECT_VALUE:
$current_bundle = $this->assigner->loadBundle(FeaturesBundleInterface::DEFAULT_BUNDLE);
break;
default:
$current_bundle = $this->assigner->loadBundle($bundle_name);
break;
}
}
$enabled_methods = $current_bundle->getEnabledAssignments();
$methods_weight = $current_bundle->getAssignmentWeights();
// Add missing data to the methods lists.
$assignment_info = $this->assigner->getAssignmentMethods();
foreach ($assignment_info as $method_id => $method) {
if (!isset($methods_weight[$method_id])) {
$methods_weight[$method_id] = isset($method['weight']) ? $method['weight'] : 0;
}
}
// Order methods list by weight.
asort($methods_weight);
if ($load_values) {
$this->loadBundleValues($bundle_name, $form_state, $current_bundle, $enabled_methods, $methods_weight);
}
$form = [
'#attached' => [
'library' => [
// Provides the copyFieldValue behavior invoked below.
'system/drupal.system',
'features_ui/drupal.features_ui.admin',
],
],
// '#attributes' => array('class' => 'edit-bundles-wrapper'),
'#tree' => TRUE,
'#show_operations' => FALSE,
'weight' => ['#tree' => TRUE],
'#prefix' => '<div id="edit-bundles-wrapper">',
'#suffix' => '</div>',
];
$form['bundle'] = [
'#type' => 'fieldset',
'#title' => $this->t('Bundle'),
'#tree' => TRUE,
'#weight' => -9,
];
if ($bundle_name == self::NEW_BUNDLE_SELECT_VALUE) {
$default_values = [
'bundle_select' => self::NEW_BUNDLE_SELECT_VALUE,
'name' => '',
'machine_name' => '',
'description' => '',
'is_profile' => FALSE,
'profile_name' => '',
];
}
else {
$default_values = [
'bundle_select' => $current_bundle->getMachineName(),
'name' => $current_bundle->getName(),
'machine_name' => $current_bundle->getMachineName(),
'description' => $current_bundle->getDescription(),
'is_profile' => $current_bundle->isProfile(),
'profile_name' => $current_bundle->getProfileName(),
];
}
$form['bundle']['bundle_select'] = [
'#title' => $this->t('Bundle'),
'#title_display' => 'invisible',
'#type' => 'select',
'#options' => [self::NEW_BUNDLE_SELECT_VALUE => $this->t('--New--')] + $this->assigner->getBundleOptions(),
'#default_value' => $default_values['bundle_select'],
'#ajax' => [
'callback' => '::updateForm',
'wrapper' => 'edit-bundles-wrapper',
],
];
// Don't show the remove button for the default bundle or when adding a new
// bundle.
if ($bundle_name != self::NEW_BUNDLE_SELECT_VALUE && !$current_bundle->isDefault()) {
$form['bundle']['remove'] = [
'#type' => 'button',
'#name' => 'removebundle',
'#value' => $this->t('Remove bundle'),
];
}
$form['bundle']['name'] = [
'#title' => $this->t('Bundle name'),
'#type' => 'textfield',
'#description' => $this->t('A unique human-readable name of this bundle.'),
'#default_value' => $default_values['name'],
'#required' => TRUE,
'#disabled' => $bundle_name == FeaturesBundleInterface::DEFAULT_BUNDLE,
];
// Don't allow changing the default bundle machine name.
if ($bundle_name == FeaturesBundleInterface::DEFAULT_BUNDLE) {
$form['bundle']['machine_name'] = [
'#type' => 'value',
'#value' => $default_values['machine_name'],
];
}
else {
$form['bundle']['machine_name'] = [
'#title' => $this->t('Machine name'),
'#type' => 'machine_name',
'#required' => TRUE,
'#default_value' => $default_values['machine_name'],
'#description' => $this->t('A unique machine-readable name of this bundle. Used to prefix exported packages. It must only contain lowercase letters, numbers, and underscores.'),
'#machine_name' => [
'source' => ['bundle', 'name'],
'exists' => [$this, 'bundleExists'],
],
];
}
$form['bundle']['description'] = [
'#title' => $this->t('Distribution description'),
'#type' => 'textfield',
'#default_value' => $default_values['description'],
'#description' => $this->t('A description of the bundle.'),
'#size' => 80,
];
$form['bundle']['is_profile'] = [
'#type' => 'checkbox',
'#title' => $this->t('Include install profile'),
'#default_value' => $default_values['is_profile'],
'#description' => $this->t('Select this option to have your features packaged into an install profile.'),
'#attributes' => [
'data-add-profile' => 'status',
],
];
$show_and_require_if_profile_checked = [
'visible' => [
':input[data-add-profile="status"]' => ['checked' => TRUE],
],
'required' => [
':input[data-add-profile="status"]' => ['checked' => TRUE],
],
];
$form['bundle']['profile_name'] = [
'#title' => $this->t('Profile name'),
'#type' => 'textfield',
'#default_value' => $default_values['profile_name'],
'#description' => $this->t('The machine name (directory name) of your profile.'),
'#size' => 30,
// Show and require only if the profile.add option is selected.
'#states' => $show_and_require_if_profile_checked,
];
// Attach the copyFieldValue behavior to the profile_name field. In
// practice this only works if a user tabs through the bundle machine name
// field or manually edits it.
$form['#attached']['drupalSettings']['copyFieldValue']['edit-bundle-machine-name'] = ['edit-bundle-profile-name'];
foreach ($methods_weight as $method_id => $weight) {
// A packaging method might no longer be available if the defining module
// has been uninstalled after the last configuration saving.
if (!isset($assignment_info[$method_id])) {
continue;
}
$enabled = isset($enabled_methods[$method_id]);
$method = $assignment_info[$method_id];
$method_name = Html::escape($method['name']);
$form['weight'][$method_id] = [
'#type' => 'weight',
'#title' => $this->t('Weight for @title package assignment method', ['@title' => mb_strtolower($method_name)]),
'#title_display' => 'invisible',
'#default_value' => $weight,
'#attributes' => ['class' => ['assignment-method-weight']],
'#delta' => 20,
];
$form['title'][$method_id] = ['#markup' => $method_name];
$form['enabled'][$method_id] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable @title package assignment method', ['@title' => mb_strtolower($method_name)]),
'#title_display' => 'invisible',
'#default_value' => $enabled,
];
$form['description'][$method_id] = ['#markup' => $method['description']];
$config_op = [];
if (isset($method['config_route_name'])) {
$config_op['configure'] = [
'title' => $this->t('Configure'),
'url' => Url::fromRoute($method['config_route_name'], ['bundle_name' => $current_bundle->getMachineName()]),
];
// If there is at least one operation enabled, show the operation
// column.
$form['#show_operations'] = TRUE;
}
$form['operation'][$method_id] = [
'#type' => 'operations',
'#links' => $config_op,
];
}
$form['actions'] = ['#type' => 'actions', '#weight' => 9];
$form['actions']['submit'] = [
'#type' => 'submit',
'#button_type' => 'primary',
'#value' => $this->t('Save settings'),
];
return $form;
}
/**
* Ajax callback for handling switching the bundle selector.
*/
public function updateForm($form, FormStateInterface $form_state) {
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue(['bundle', 'is_profile']) && empty($form_state->getValue(['bundle', 'profile_name']))) {
$form_state->setErrorByName('bundle][profile_name', $this->t('To create a profile, please enter a profile name.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$enabled_methods = array_filter($form_state->getValue('enabled'));
ksort($enabled_methods);
$method_weights = $form_state->getValue('weight');
ksort($method_weights);
$machine_name = $form_state->getValue(['bundle', 'machine_name']);
// If this is a new bundle, create it.
if ($form_state->getValue(['bundle', 'bundle_select']) == self::NEW_BUNDLE_SELECT_VALUE) {
$bundle = $this->assigner->createBundleFromDefault($machine_name);
}
// Otherwise, load the current bundle and rename if needed.
else {
$bundle = $this->assigner->loadBundle();
$old_name = $bundle->getMachineName();
$new_name = $form_state->getValue(['bundle', 'machine_name']);
if ($old_name != $new_name) {
$bundle = $this->assigner->renameBundle($old_name, $new_name);
}
}
$bundle->setName($form_state->getValue(['bundle', 'name']));
$bundle->setDescription($form_state->getValue(['bundle', 'description']));
$bundle->setEnabledAssignments(array_keys($enabled_methods));
$bundle->setAssignmentWeights($method_weights);
$bundle->setIsProfile($form_state->getValue(['bundle', 'is_profile']));
$bundle->setProfileName($form_state->getValue(['bundle', 'profile_name']));
$bundle->save();
$this->assigner->setBundle($bundle);
$this->assigner->setCurrent($bundle);
$form_state->setRedirect('features.assignment');
$this->messenger()->addStatus($this->t('Package assignment configuration saved.'));
}
/**
* Callback for machine_name exists()
*
* @param $value
* @param $element
* @param $form_state
*
* @return bool
*/
public function bundleExists($value, $element, $form_state) {
$bundle = $this->assigner->getBundle($value);
return isset($bundle);
}
}
