/** * Returns the full feature export array based upon user selections in * form_state. * * @param \Drupal\Core\Form\FormStateInterface $form_state * Optional form_state information for user selections. Can be updated to * reflect new selection status. * * @return \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 */ 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 = array(); $this->conflicts = array(); 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. $exported_features_info = array(); 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(); // } } } $exported_features_info['dependencies'] = $this->package->getDependencyInfo(); // Make a map of any config specifically excluded and/or required. foreach (array('excluded', 'required') as $constraint) { $this->{$constraint} = array(); $info = !empty($this->package->getFeaturesInfo()[$constraint]) ? $this->package->getFeaturesInfo()[$constraint] : array(); foreach ($info as $item_name) { $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 = array(); 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 = array(); $sections = array('sources', 'included', 'detected', 'added'); // Generate list of config to be exported. $config_count = array(); foreach ($components as $component => $component_info) { // User-selected components take precedence. $config_new[$component] = array(); $config_count[$component] = 0; // Add selected items from Sources checkboxes. if (!$form_state->isValueEmpty(array($component, 'sources', 'selected'))) { $config_new[$component] = array_merge($config_new[$component], $this->domDecodeOptions(array_filter($form_state->getValue(array($component, 'sources', 'selected'))))); $config_count[$component]++; } // Add selected items from already Included, newly Added, auto-detected // checkboxes. foreach (array('included', 'added', 'detected') as $section) { if (!$form_state->isValueEmpty(array($component, $section))) { $config_new[$component] = array_merge($config_new[$component], $this->domDecodeOptions(array_filter($form_state->getValue(array($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] = array(); $component_export['_features_selected'][$section] = array(); } if (!empty($component_info)) { $exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : array(); $new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : array(); 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(array($component, 'sources', 'selected', $key))) { $form_state->setValue(array($component, 'sources', 'selected', $key), FALSE); $form_state->setValue(array($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])) { // 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(array($component, 'included', $key)) || $form_state->isValueEmpty(array($component, 'detected', $key))) && empty($config_new[$component][$key])) { $section = 'detected'; $default_value = FALSE; } elseif ($form_state->isSubmitted() && $form_state->isValueEmpty(array($component, 'added', $key)) && $form_state->isValueEmpty(array($component, 'detected', $key)) && $form_state->isValueEmpty(array($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->hasValue(array($component, 'detected', $key))) { $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(array($component, 'added', $key)) || !$form_state->isValueEmpty(array($component, 'sources', 'selected', $key)))) { $section = 'added'; $default_value = $key; } elseif ($form_state->isSubmitted() && $form_state->isValueEmpty(array($component, 'sources', 'selected', $key)) && $form_state->isValueEmpty(array($component, 'detected', $key)) && !$form_state->hasValue(array($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])) { unset($this->required[$component][$key]); } else { $this->excluded[$component][$key] = $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(array($component, $section, $key), FALSE); } else { $form_state->setValue(array($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 (array('included', 'added') as $section) { // Restore any user-selected checkboxes. if (!$form_state->isValueEmpty(array($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; return $export; }
/** * @depends testAppendConfig * @covers ::removeConfig */ public function testRemoveConfig(Package $package) { $package->removeConfig('test_config_a'); $this->assertEquals(['test_config_b', 'test_config_c'], array_values($package->getConfig())); }
/** * {@inheritdoc} */ public function detectNew(Package $feature) { $result = array(); foreach ($feature->getConfig() as $name) { $extension = $this->extensionStorages->read($name); if (empty($extension)) { $result[] = $name; } } return $result; }