You are here

protected function FeaturesEditForm::getComponentList in Features 8.3

Same name and namespace in other branches
  1. 8.4 modules/features_ui/src/Form/FeaturesEditForm.php \Drupal\features_ui\Form\FeaturesEditForm::getComponentList()

Returns the full feature export array based upon user selections in form_state.

Parameters

\Drupal\Core\Form\FormStateInterface $form_state: Optional form_state information for user selections. Can be updated to reflect new selection status.

Return value

\Drupal\features\Package New export array to be exported array['components'][$component_name] = $component_info $component_info['_features_options'][$section] is list of available options $component_info['_features_selected'][$section] is option state TRUE/FALSE $section = array('sources', included', 'detected', 'added') sources - options that are available to be added to the feature included - options that have been previously exported to the feature detected - options that have been auto-detected added - newly added options to the feature

NOTE: This routine gets a bit complex to handle all of the different possible user checkbox selections and de-selections. Cases to test: 1a) uncheck Included item -> mark as Added but unchecked 1b) re-check unchecked Added item -> return it to Included check item 2a) check Sources item -> mark as Added and checked 2b) uncheck Added item -> return it to Sources as unchecked 3a) uncheck Included item that still exists as auto-detect -> mark as Detected but unchecked 3b) re-check Detected item -> return it to Included and checked 4a) check Sources item should also add any auto-detect items as Detected and checked 4b) uncheck Sources item with auto-detect and auto-detect items should return to Sources and unchecked 5a) uncheck a Detected item -> refreshing page should keep it as unchecked Detected 6) when nothing changes, refresh should not change any state 7) should never see an unchecked Included item

2 calls to FeaturesEditForm::getComponentList()
FeaturesEditForm::buildComponentList in modules/features_ui/src/Form/FeaturesEditForm.php
Returns the render array elements for the Components selection on the Edit form.
FeaturesEditForm::updatePackageConfig in modules/features_ui/src/Form/FeaturesEditForm.php
Updates the config stored in the package from the current edit form.

File

modules/features_ui/src/Form/FeaturesEditForm.php, line 657

Class

FeaturesEditForm
Defines the features settings form.

Namespace

Drupal\features_ui\Form

Code

protected function getComponentList(FormStateInterface $form_state) {
  $config = $this->featuresManager
    ->getConfigCollection();
  $package_name = $this->package
    ->getMachineName();

  // Auto-detect dependencies for included config.
  $package_config = $this->package
    ->getConfig();
  if (!empty($this->package
    ->getConfigOrig())) {
    $package_config = array_unique(array_merge($package_config, $this->package
      ->getConfigOrig()));
  }
  if (!empty($package_config)) {
    $this->featuresManager
      ->assignConfigDependents($package_config, $package_name);
  }
  $packages = $this->featuresManager
    ->getPackages();

  // Re-fetch the package in case config was updated with Dependents above.
  $this->package = $packages[$package_name];

  // Make a map of all config data.
  $components = [];
  $this->conflicts = [];
  foreach ($config as $item_name => $item) {
    if ($item
      ->getPackage() != $package_name && !empty($packages[$item
      ->getPackage()]) && $packages[$item
      ->getPackage()]
      ->getStatus() != FeaturesManagerInterface::STATUS_NO_EXPORT) {
      $this->conflicts[$item
        ->getType()][$item
        ->getShortName()] = $item
        ->getLabel();
    }
    if ($this->allowConflicts || !isset($this->conflicts[$item
      ->getType()][$item
      ->getShortName()]) || $this->package
      ->getConfigOrig() && in_array($item_name, $this->package
      ->getConfigOrig())) {
      $components[$item
        ->getType()][$item
        ->getShortName()] = $item
        ->getLabel();
    }
  }

  // Make a map of the config data already exported to the Feature.
  $this->missing = [];
  $exported_features_info = [];
  foreach ($this->package
    ->getConfigOrig() as $item_name) {

    // Make sure the extension provided item exists in the active
    // configuration storage.
    if (isset($config[$item_name])) {
      $item = $config[$item_name];

      // Remove any conflicts if those are not being allowed.
      // if ($this->allowConflicts || !isset($this->conflicts[$item['type']][$item['name_short']])) {
      $exported_features_info[$item
        ->getType()][$item
        ->getShortName()] = $item
        ->getLabel();

      // }
    }
    else {
      $this->missing[] = $item_name;
    }
  }
  $exported_features_info['dependencies'] = $this->package
    ->getDependencyInfo();

  // Make a map of any config specifically excluded and/or required.
  foreach ([
    'excluded',
    'required',
  ] as $constraint) {
    $this->{$constraint} = [];
    $info = !empty($this->package
      ->{'get' . $constraint}()) ? $this->package
      ->{'get' . $constraint}() : [];

    // $info may be boolean.
    if (is_array($info)) {
      foreach ($info as $item_name) {
        if (!isset($config[$item_name])) {
          continue;
        }
        $item = $config[$item_name];
        $this->{$constraint}[$item
          ->getType()][$item
          ->getShortName()] = $item
          ->getLabel();
      }
    }
  }

  // Make a map of the config data to be exported within the Feature.
  $new_features_info = [];
  foreach ($this->package
    ->getConfig() as $item_name) {
    $item = $config[$item_name];
    $new_features_info[$item
      ->getType()][$item
      ->getShortName()] = $item
      ->getLabel();
  }
  $new_features_info['dependencies'] = $this->package
    ->getDependencies();

  // Assemble the combined component list.
  $config_new = [];
  $sections = [
    'sources',
    'included',
    'detected',
    'added',
  ];

  // Generate list of config to be exported.
  $config_count = [];
  foreach ($components as $component => $component_info) {

    // User-selected components take precedence.
    $config_new[$component] = [];
    $config_count[$component] = 0;

    // Add selected items from Sources checkboxes.
    if (!$form_state
      ->isValueEmpty([
      $component,
      'sources',
      'selected',
    ])) {

      // Don't use the array_merge function, otherwise configs like
      // "metatag.metatag_defaults.404" will have the key "404" be reindexed.
      $config_new[$component] = $config_new[$component] + $this
        ->domDecodeOptions(array_filter($form_state
        ->getValue([
        $component,
        'sources',
        'selected',
      ])));
      $config_count[$component]++;
    }

    // Add selected items from already Included, newly Added, auto-detected
    // checkboxes.
    foreach ([
      'included',
      'added',
      'detected',
    ] as $section) {
      if (!$form_state
        ->isValueEmpty([
        $component,
        $section,
      ])) {
        $config_new[$component] = $config_new[$component] + $this
          ->domDecodeOptions(array_filter($form_state
          ->getValue([
          $component,
          $section,
        ])));
        $config_count[$component]++;
      }
    }

    // Only fallback to an existing feature's values if there are no export
    // options for the component.
    if ($component == 'dependencies') {
      if ($config_count[$component] == 0 && !empty($exported_features_info['dependencies'])) {
        $config_new[$component] = array_combine($exported_features_info['dependencies'], $exported_features_info['dependencies']);
      }
    }
    elseif ($config_count[$component] == 0 && !empty($exported_features_info[$component])) {
      $config_names = array_keys($exported_features_info[$component]);
      $config_new[$component] = array_combine($config_names, $config_names);
    }
  }

  // Generate new populated feature.
  $export['package'] = $this->package;
  $export['config_new'] = $config_new;

  // Now fill the $export with categorized sections of component options
  // based upon user selections and de-selections.
  foreach ($components as $component => $component_info) {
    $component_export = $component_info;
    foreach ($sections as $section) {
      $component_export['_features_options'][$section] = [];
      $component_export['_features_selected'][$section] = [];
    }
    if (!empty($component_info)) {
      $exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : [];
      $new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : [];
      foreach ($component_info as $key => $label) {
        $config_name = $this->featuresManager
          ->getFullName($component, $key);

        // If checkbox in Sources is checked, move it to Added section.
        if (!$form_state
          ->isValueEmpty([
          $component,
          'sources',
          'selected',
          $key,
        ])) {
          $form_state
            ->setValue([
            $component,
            'sources',
            'selected',
            $key,
          ], FALSE);
          $form_state
            ->setValue([
            $component,
            'added',
            $key,
          ], 1);
          $component_export['_features_options']['added'][$key] = $this
            ->configLabel($component, $key, $label);
          $component_export['_features_selected']['added'][$key] = $key;

          // If this was previously excluded, we don't need to set it as
          // required because it was automatically assigned.
          if (isset($this->excluded[$component][$key])) {
            unset($this->excluded[$component][$key]);
          }
          else {
            $this->required[$component][$key] = $key;
          }
        }
        elseif (isset($new_components[$key]) || isset($config_new[$component][$key])) {

          // Option is in the New exported array.
          if (isset($exported_components[$key])) {

            // Option was already previously exported so it's part of the
            // Included checkboxes.
            $section = 'included';
            $default_value = $key;

            // If Included item was un-selected (removed from export
            // $config_new) but was re-detected in the $new_components
            // means it was an auto-detect that was previously part of the
            // export and is now de-selected in UI.
            if ($form_state
              ->isSubmitted() && ($form_state
              ->hasValue([
              $component,
              'included',
              $key,
            ]) || $form_state
              ->isValueEmpty([
              $component,
              'detected',
              $key,
            ])) && empty($config_new[$component][$key])) {
              $section = 'detected';
              $default_value = FALSE;
            }
            elseif ($form_state
              ->isSubmitted() && $form_state
              ->isValueEmpty([
              $component,
              'added',
              $key,
            ]) && $form_state
              ->isValueEmpty([
              $component,
              'detected',
              $key,
            ]) && $form_state
              ->isValueEmpty([
              $component,
              'included',
              $key,
            ])) {
              $section = 'added';
              $default_value = FALSE;
            }
          }
          else {

            // Option was in New exported array, but NOT in already exported
            // so it's a user-selected or an auto-detect item.
            $section = 'detected';
            $default_value = NULL;

            // Check for item explicitly excluded.
            if (isset($this->excluded[$component][$key]) && !$form_state
              ->isSubmitted()) {
              $default_value = FALSE;
            }
            else {
              $default_value = $key;
            }

            // If it's already checked in Added or Sources, leave it in Added
            // as checked.
            if ($form_state
              ->isSubmitted() && (!$form_state
              ->isValueEmpty([
              $component,
              'added',
              $key,
            ]) || !$form_state
              ->isValueEmpty([
              $component,
              'sources',
              'selected',
              $key,
            ]))) {
              $section = 'added';
              $default_value = $key;
            }
            elseif ($form_state
              ->isSubmitted() && $form_state
              ->isValueEmpty([
              $component,
              'sources',
              'selected',
              $key,
            ]) && $form_state
              ->isValueEmpty([
              $component,
              'detected',
              $key,
            ]) && !$form_state
              ->hasValue([
              $component,
              'added',
              $key,
            ])) {
              $section = 'detected';
              $default_value = FALSE;
            }
          }
          $component_export['_features_options'][$section][$key] = $this
            ->configLabel($component, $key, $label);
          $component_export['_features_selected'][$section][$key] = $default_value;

          // Save which dependencies are specifically excluded from
          // auto-detection.
          if ($section == 'detected' && $default_value === FALSE) {

            // If this was previously required, we don't need to set it as
            // excluded because it wasn't automatically assigned.
            if (!isset($this->required[$component][$key]) || $this->package
              ->getRequired() === TRUE) {
              $this->excluded[$component][$key] = $key;
            }
            unset($this->required[$component][$key]);

            // Remove excluded item from export.
            if ($component == 'dependencies') {
              $export['package']
                ->removeDependency($key);
            }
            else {
              $export['package']
                ->removeConfig($config_name);
            }
          }
          else {
            unset($this->excluded[$component][$key]);
          }

          // Remove the 'input' and set the 'values' so Drupal stops looking
          // at 'input'.
          if ($form_state
            ->isSubmitted()) {
            if (!$default_value) {
              $form_state
                ->setValue([
                $component,
                $section,
                $key,
              ], FALSE);
            }
            else {
              $form_state
                ->setValue([
                $component,
                $section,
                $key,
              ], 1);
            }
          }
        }
        elseif (!$form_state
          ->isSubmitted() && isset($exported_components[$key])) {

          // Component is not part of new export, but was in original export.
          // Mark component as Added when creating initial form.
          $component_export['_features_options']['added'][$key] = $this
            ->configLabel($component, $key, $label);
          $component_export['_features_selected']['added'][$key] = $key;
        }
        else {

          // Option was not part of the new export.
          $added = FALSE;
          foreach ([
            'included',
            'added',
          ] as $section) {

            // Restore any user-selected checkboxes.
            if (!$form_state
              ->isValueEmpty([
              $component,
              $section,
              $key,
            ])) {
              $component_export['_features_options'][$section][$key] = $this
                ->configLabel($component, $key, $label);
              $component_export['_features_selected'][$section][$key] = $key;
              $added = TRUE;
            }
          }
          if (!$added) {

            // If not Included or Added, then put it back in the unchecked
            // Sources checkboxes.
            $component_export['_features_options']['sources'][$key] = $this
              ->configLabel($component, $key, $label);
            $component_export['_features_selected']['sources'][$key] = FALSE;
          }
        }
      }
    }
    $export['components'][$component] = $component_export;
  }
  $export['features_exclude'] = $this->excluded;
  $export['features_require'] = $this->required;
  $export['conflicts'] = $this->conflicts;
  $export['missing'] = $this->missing;
  return $export;
}