Commit f851039f authored by chr.fritsch's avatar chr.fritsch Committed by Kingdutch

Issue #2940583 by chr.fritsch, Kingdutch: Remove module specific config form

The config form in the module replicated a lot of functionality
that is actually owned by the Field UI module. Therefor it makes
sense to simply remove the config form and make use of that
functionality instead.
parent c5e5c5b4
......@@ -4,8 +4,6 @@ namespace Drupal\yoast_seo\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* SettingsController.
......@@ -23,18 +21,6 @@ class SettingsController extends ControllerBase {
public function index() {
$form = [];
// Add to the page the Yoast SEO form which allows the administrator
// to enable/disable Yoast SEO by bundles.
$config_form = \Drupal::formBuilder()
->getForm('Drupal\yoast_seo\Form\ConfigForm');
$form['yoast_seo'] = [
'#type' => 'details',
'#title' => 'Configure Real-time SEO by bundles',
'#description' => 'Select the bundles Real-time SEO will be enabled for',
'#markup' => render($config_form),
'#open' => TRUE,
];
$xmlsitemap_enabled = \Drupal::moduleHandler()->moduleExists('xmlsitemap');
$simple_sitemap_enabled = \Drupal::moduleHandler()->moduleExists('simple_sitemap');
......
<?php
namespace Drupal\yoast_seo;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Class FieldManager.
*
* @package Drupal\yoast_seo
*/
class FieldManager {
/**
* The field definitions that are required for this module.
*
* @return array
* The field definitions required for this module to work for an entity.
*/
public function getFieldDefinitions() {
return [
'field_yoast_seo' => [
'type' => 'yoast_seo',
'field_name' => 'field_yoast_seo',
'field_label' => 'Real-time SEO',
'field_type' => 'yoast_seo',
'translatable' => TRUE,
],
];
}
/**
* Attach the yoast seo fields to a target content type.
*
* @param string $entity_type
* Bundle.
* @param string $bundle
* Entity type.
*/
public function attachSeoFields($entity_type, $bundle) {
foreach ($this->getFieldDefinitions() as $field) {
$this->attachField($entity_type, $bundle, $field);
}
}
/**
* Delete the yoast seo fields from a target content type.
*
* @param string $entity_type
* Entity type.
* @param string $bundle
* Bundle.
*/
public function detachSeoFields($entity_type, $bundle) {
foreach ($this->getFieldDefinitions() as $field_name => $field) {
$this->detachField($entity_type, $bundle, $field_name);
}
}
/**
* Attach a field to a target entity type.
*
* @param string $entity_type_id
* Entity type. Example 'node'.
* @param string $bundle
* Bundle type.
* @param mixed $field
* Field.
*/
public function attachField($entity_type_id, $bundle, $field) {
// Retrieve the yoast seo field attached to the target entity.
$field_storage = $this->findOrCreateStorageConfig($entity_type_id, $field);
$field['field_storage'] = $field_storage;
$field['bundle'] = $bundle;
$this->createFieldIfNotExists($entity_type_id, $field);
}
/**
* Detach a field from a target content type.
*
* @param string $entity_type_id
* Entity type.
* @param string $bundle
* Bundle.
* @param string $field_name
* Field name.
*/
public function detachField($entity_type_id, $bundle, $field_name) {
$field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
if (!is_null($field)) {
$field->delete();
}
}
/**
* Finds or creates storage configuration for a given entity and field.
*
* This will load a FieldStorageConfig entity if one exists. If it doesn't
* yet exist then one will be created.
*
* @param string $entity_type_id
* The entity type for which to get the field storage configuration.
* @param array $field
* A field storage configuration to find or create.
*
* @return \Drupal\field\Entity\FieldStorageConfig
* The found or created FieldStorageConfig entity.
*/
protected function findOrCreateStorageConfig($entity_type_id, array $field) {
$storage = FieldStorageConfig::loadByName($entity_type_id, $field['field_name']);
if (is_null($storage)) {
$field['entity_type'] = $entity_type_id;
$storage = FieldStorageConfig::create($field);
$storage->save();
}
return $storage;
}
/**
* Creates the field config for an entity if it doesn't already exist.
*
* @param string $entity_type_id
* The entity type for which to create the field.
* @param array $field_config
* The field configuration with the bundle that defines the field.
*/
protected function createFieldIfNotExists($entity_type_id, array $field_config) {
$bundle = $field_config['bundle'];
$field_name = $field_config['field_name'];
$field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
if (is_null($field)) {
// Create the field for this bundle.
$field = FieldConfig::create($field_config);
$field->save();
// Set up form and view displays.
EntityFormDisplay::load($entity_type_id . '.' . $bundle . '.default')
->setComponent($field_name, [])
->save();
EntityViewDisplay::load($entity_type_id . '.' . $bundle . '.default')
->setComponent($field_name, [])
->save();
}
}
/**
* Check whether this module is enabled for a certain entity/bundle.
*
* @param string $entity_type_id
* The entity to check.
* @param string $bundle
* The bundle of the entity to check.
*
* @return bool
* Whether SEO analysis is enabled.
*/
public function isEnabledFor($entity_type_id, $bundle) {
// Simply check if one of our fields is attached.
$field_name = array_keys($this->getFieldDefinitions())[0];
return $this->isAttached($entity_type_id, $bundle, $field_name);
}
/**
* Check if a field has been already attached to a bundle.
*
* @param string $entity_type_id
* Entity type.
* @param string $bundle
* Bundle.
* @param string $field_name
* Field name.
*
* @return bool
* Whether it is attached or not.
*/
public function isAttached($entity_type_id, $bundle, $field_name) {
$field = FieldConfig::loadByName($entity_type_id, $bundle, $field_name);
return !is_null($field);
}
}
<?php
namespace Drupal\yoast_seo\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\yoast_seo\SeoManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class YoastSeoConfigForm.
*
* @package Drupal\metatag\Form
*/
class ConfigForm extends FormBase {
/**
* The Real-Time SEO Manager service.
*
* @var \Drupal\yoast_seo\SeoManager
*/
protected $seoManager;
/**
* {@inheritdoc}
*/
public function __construct(SeoManager $seoManager) {
$this->seoManager = $seoManager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('yoast_seo.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'yoast_seo_config_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$entity_types = $this->seoManager->getSupportedEntityTypes();
$supported_bundles = $this->seoManager->getEntityBundles();
$enabled_bundles = $this->seoManager->getEnabledBundles();
foreach ($entity_types as $entity_type => $entity_label) {
// Add a checkboxes collection to allow the administrator to
// enable/disable SEO Analysis for supported bundles.
$form[$entity_type] = [
'#type' => 'checkboxes',
'#title' => $this->t('@label', ['@label' => $entity_label]),
'#options' => $supported_bundles[$entity_type],
'#required' => FALSE,
'#default_value' => !empty($enabled_bundles[$entity_type]) ? array_keys($enabled_bundles[$entity_type]) : [],
];
}
// Add a save action.
$form['actions'] = [
'#type' => 'actions',
];
$form['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Save'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Get the available entity/bundles that the module supports.
$entity_bundles = $this->seoManager->getEntityBundles();
// Retrieve the form values.
$values = $form_state->getValues();
// Iterate over all supported entities.
foreach ($entity_bundles as $entity_type_id => $bundles) {
// Then over all bundles for that entity.
foreach ($bundles as $bundle_id => $bundle_label) {
// If this value was not in our form we skip it.
if (!isset($values[$entity_type_id][$bundle_id])) {
continue;
}
// If it's checked now but wasn't enabled, enable it.
if ($values[$entity_type_id][$bundle_id] !== 0
&& !$this->seoManager->isEnabledFor($entity_type_id, $bundle_id)) {
$this->seoManager->enableFor($entity_type_id, $bundle_id);
}
// If it's not checked but it was enabled, disable it.
elseif ($values[$entity_type_id][$bundle_id] === 0
&& $this->seoManager->isEnabledFor($entity_type_id, $bundle_id)) {
$this->seoManager->disableFor($entity_type_id, $bundle_id);
}
}
}
drupal_set_message($this->t('Real-time SEO configuration by bundles has been saved successfully.'));
}
}
<?php
namespace Drupal\yoast_seo\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin implementation of the 'yoastseo_empty_formatter' formatter.
*
* @FieldFormatter(
* id = "yoastseo_empty_formatter",
* label = @Translation("Empty formatter"),
* field_types = {
* "field_yoast_seo"
* }
* )
*/
class YoastSeoEmptyFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
// Does not actually output anything.
return [];
}
}
......@@ -15,7 +15,7 @@ use Drupal\Core\TypedData\DataDefinition;
* module = "yoast_seo",
* description = @Translation("The Real-time SEO status in points and the focused keywords."),
* default_widget = "yoast_seo_widget",
* default_formatter = "string"
* default_formatter = "yoastseo_empty_formatter"
* )
*/
class YoastSeoItem extends FieldItemBase {
......
......@@ -2,7 +2,7 @@
namespace Drupal\yoast_seo;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Symfony\Component\Yaml\Yaml;
......@@ -15,11 +15,11 @@ use Symfony\Component\Yaml\Yaml;
class SeoManager {
/**
* Real Time SEO Field Manager service.
* Entity Type Manager service.
*
* @var \Drupal\yoast_seo\FieldManager
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $fieldManager;
protected $entityTypeManager;
/**
* Entity Type Bundle Info service.
......@@ -29,160 +29,62 @@ class SeoManager {
protected $entityTypeBundleInfo;
/**
* Entity Type Manager service.
* Entity Field Manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityTypeManager;
protected $entityFieldManager;
/**
* Constructor for YoastSeoManager.
*
* @param \Drupal\yoast_seo\FieldManager $fieldManager
* Real Time SEO Field Manager service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo
* Entity Type Bundle Info service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* Entity Type Manager service.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entityTypeBundleInfo
* Entity Type Bundle Info service.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager
* Entity Field Manager service.
*/
public function __construct(FieldManager $fieldManager, EntityTypeBundleInfoInterface $entityTypeBundleInfo, EntityTypeManagerInterface $entityTypeManager) {
$this->fieldManager = $fieldManager;
public function __construct(EntityTypeManagerInterface $entityTypeManager, EntityTypeBundleInfoInterface $entityTypeBundleInfo, EntityFieldManagerInterface $entityFieldManager) {
$this->entityTypeBundleInfo = $entityTypeBundleInfo;
$this->entityTypeManager = $entityTypeManager;
$this->entityFieldManager = $entityFieldManager;
}
/**
* Returns an array of available entity types Yoast SEO can be enabled for.
* Returns an array of bundles that have a 'yoast_seo' field.
*
* @return array
* A list of available entity types as $id => $label.
* A nested array of entities and bundles. The outer array is keyed by
* entity id. The inner array is keyed by bundle id and contains the bundle
* label. If an entity has no bundles then the inner array is keyed by
* entity id.
*/
public function getSupportedEntityTypes() {
$supportedEntityTypes = [];
foreach ($this->entityTypeManager->getDefinitions() as $definition) {
if ($definition->entityClassImplements(ContentEntityInterface::class)) {
$supportedEntityTypes[$definition->id()] = $definition->getLabel();
}
}
// TODO: Could be removed when d.o/project/drupal/issues/2880149 lands.
if (\Drupal::service('module_handler')->moduleExists('taxonomy')) {
$supportedEntityTypes['taxonomy_term'] = 'Term';
}
return $supportedEntityTypes;
}
/**
* Returns an array of available bundles this module can be enabled for.
*
* Loads the bundles for the given entity types or loads the bundles for all
* supported entities if no entity types are specified.
*
* @param array $entity_types
* (optional) The entity types to return the bundles for.
*
* @return array
* A list of available bundles in the form of:
*
* $entity_type => [
* $bundle => $label
* ]
*/
public function getEntityBundles(array $entity_types = NULL) {
if (is_null($entity_types)) {
$entity_types = array_keys($this->getSupportedEntityTypes());
}
public function getEnabledBundles() {
$entities = [];
foreach ($entity_types as $entity_type) {
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type);
foreach ($bundles as $bundle_id => $bundle_metadata) {
$bundles[$bundle_id] = $bundle_metadata['label'];
}
if (!empty($bundles)) {
$entities[$entity_type] = $bundles;
}
}
/** @var \Drupal\Core\Entity\EntityTypeInterface $definition */
foreach ($this->entityTypeManager->getDefinitions() as $definition) {
$entity_id = $definition->id();
$bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_id);
return $entities;
}
foreach ($bundles as $bundle_id => $bundle_metadata) {
$bundle_label = $bundle_metadata['label'];
$field_definitions = $this->entityFieldManager->getFieldDefinitions($entity_id, $bundle_id);
/**
* Returns an array of bundles this module has been enabled for.
*
* Returns the bundles for the given entity types or checks the bundles for
* all supported entities if no entity types are specified.
*
* @param array $entity_types
* (optional) The entity types to check the bundles for.
*
* @return array
* A list of enabled bundles in the form of:
*
* $entity_type => [
* $bundle => $label
* ]
*/
public function getEnabledBundles(array $entity_types = NULL) {
$entities = $this->getEntityBundles($entity_types);
if (!empty($field_definitions['yoast_seo'])) {
if (!isset($entities[$entity_id])) {
$entities[$entity_id] = [];
}
foreach ($entities as $entity_type => &$bundles) {
foreach ($bundles as $bundle_id => $bundle_label) {
if (!$this->isEnabledFor($entity_type, $bundle_id)) {
unset($bundles[$bundle_id]);
$entities[$entity_id][$bundle_id] = $bundle_label;
}
}
if (empty($bundles)) {
unset($entities[$entity_type]);
}
}
return $entities;
}
/**
* Check whether this module is enabled for a certain entity/bundle.
*
* @param string $entity_type_id
* The entity to check.
* @param string $bundle
* The bundle of the entity to check.
*
* @return bool
* Whether SEO analysis is enabled.
*/
public function isEnabledFor($entity_type_id, $bundle) {
return $this->fieldManager->isEnabledFor($entity_type_id, $bundle);
}
/**
* Enable this module for a certain entity/bundle.
*
* @param string $entity_type_id
* The entity to enable.
* @param string $bundle
* The bundle of the entity to enable.
*/
public function enableFor($entity_type_id, $bundle) {
$this->fieldManager->attachSeoFields($entity_type_id, $bundle);
}
/**
* Disable this module for a certain entity/bundle.
*
* @param string $entity_type_id
* The entity to disable.
* @param string $bundle
* The bundle of the entity to disable.
*/
public function disableFor($entity_type_id, $bundle) {
$this->fieldManager->detachSeoFields($entity_type_id, $bundle);
}
/**
* Get the status for a given score.
*
......
......@@ -55,47 +55,4 @@ class ConfigurationPageTest extends BrowserTestBase {
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests that analysis is not enabled for any entities on first install.
*/
public function testInstallAnalysisState() {
$account = $this->drupalCreateUser(['administer yoast seo']);
$this->drupalLogin($account);
$this->drupalGet('/admin/config/yoast_seo');
// Check that the checkbox indicates disabled.
$checked = $this->assertSession()->fieldExists('Article')->getAttribute('checked');
$this->assertFalse($checked, "Expected Real-Time SEO module to be disabled for 'Article'");
}
/**
* Tests that analysis can be enabled for the node article bundle.
*/
public function testEnableForNodeBundle() {
$account = $this->drupalCreateUser([
'administer yoast seo',
'create article content',
// TODO: The administer url aliases shouldn't be necessary anymore.
'administer url aliases',
]);
$this->drupalLogin($account);
$this->drupalGet('/admin/config/yoast_seo');
// Select the article bundle to enable.
$this->assertSession()->fieldExists('Article')->check();
$this->getSession()->getPage()->pressButton('Save');
// Check that the enabled status is reflected in the interface.
$checked = $this->assertSession()->fieldExists('Article')->getAttribute('checked');
$this->assertTrue($checked, "Expected Real-Time SEO module to be enabled for 'Article'");
// Check that the SEO analyzer shows up on the article add page.