Commit 459898a7 authored by Kingdutch's avatar Kingdutch

WIP: Begin work on data storage

The data storage is getting a little complicated and requires some work
with forms and widgets to ensure that it works properly.
parent 54d083ea
This diff is collapsed.
......@@ -2,16 +2,10 @@
namespace Drupal\yoast_seo\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\ChangedFieldItemList;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\entity_reference_revisions\EntityNeedsSaveTrait;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\yoast_seo\EntityAnalysisInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Defines the Entity Analysis entity class.
......@@ -74,88 +68,7 @@ use Drupal\yoast_seo\EntityAnalysisInterface;
* }
* )
*/
class EntityAnalysis extends ContentEntityBase implements EntityAnalysisInterface {
use EntityNeedsSaveTrait;
/**
* {@inheritdoc}
*/
public function getParentEntity() {
if (!isset($this->get('parent_type')->value) || !isset($this->get('parent_id')->value)) {
return NULL;
}
$parent = \Drupal::entityTypeManager()->getStorage($this->get('parent_type')->value)->load($this->get('parent_id')->value);
// Return current translation of parent entity, if it exists.
if ($parent != NULL && ($parent instanceof TranslatableInterface) && $parent->hasTranslation($this->language()->getId())) {
return $parent->getTranslation($this->language()->getId());
}
return $parent;
}
/**
* {@inheritdoc}
*/
public function setParentEntity(ContentEntityInterface $parent, $parent_field_name) {
$this->set('parent_type', $parent->getEntityTypeId());
$this->set('parent_id', $parent->id());
$this->set('parent_field_name', $parent_field_name);
return $this;
}
/**
* {@inheritdoc}
*/
public function label() {
$label = '';
if ($parent = $this->getParentEntity()) {
$parent_field = $this->get('parent_field_name')->value;
$values = $parent->{$parent_field};
foreach ($values as $key => $value) {
if ($value->entity->id() == $this->id()) {
$label = $parent->label() . ' > ' . $value->getFieldDefinition()->getLabel();
}
else {
// A previous or draft revision or a deleted stale Paragraph.
$label = $parent->label() . ' > ' . $value->getFieldDefinition()->getLabel() . ' (previous revision)';
}
}
}
return $label;
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
$this->setNeedsSave(FALSE);
parent::postSave($storage, $update);
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function getRevisionLog() {
return '';
}
/**
* {@inheritdoc}
*/
public function setRevisionLog($revision_log) {
return $this;
}
class EntityAnalysis extends ReferencableEntityBase {
/**
* {@inheritdoc}
......@@ -163,135 +76,29 @@ class EntityAnalysis extends ContentEntityBase implements EntityAnalysisInterfac
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language code'))
->setDescription(t('The paragraphs entity language code.'))
->setRevisionable(TRUE);
$fields['keyword_results'] = BaseFieldDefinition::create("keyword_result")
->setLabel(new TranslatableMarkup("Keywords"))
->setDescription(new TranslatableMarkup("The keywords used in this analysis"))
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
->setRevisionable(TRUE)
->setTranslatable(TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Authored on'))
->setDescription(t('The time that the Paragraph was created.'))
// TODO: Fix max-length for title/description.
$fields['title'] = BaseFieldDefinition::create("string")
->setLabel(new TranslatableMarkup("Edited Title"))
->setDescription(new TranslatableMarkup("The title that has been overwritten from the default"))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDisplayOptions('form', array(
'region' => 'hidden',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
$fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Revision user ID'))
->setDescription(t('The user ID of the author of the current revision.'))
->setSetting('target_type', 'user')
->setRevisionable(TRUE);
->setSetting("max_length", 256);
$fields['parent_id'] = BaseFieldDefinition::create('string')
->setLabel(t('Parent ID'))
->setDescription(t('The ID of the parent entity of which this entity is referenced.'))
->setSetting('is_ascii', TRUE);
$fields['parent_type'] = BaseFieldDefinition::create('string')
->setLabel(t('Parent type'))
->setDescription(t('The entity parent type to which this entity is referenced.'))
->setSetting('is_ascii', TRUE)
->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
$fields['parent_field_name'] = BaseFieldDefinition::create('string')
->setLabel(t('Parent field name'))
->setDescription(t('The entity parent field name to which this entity is referenced.'))
->setSetting('is_ascii', TRUE)
->setSetting('max_length', FieldStorageConfig::NAME_MAX_LENGTH);
$fields['behavior_settings'] = BaseFieldDefinition::create('string_long')
->setLabel(t('Behavior settings'))
->setDescription(t('The behavior plugin settings'))
$fields['description'] = BaseFieldDefinition::create("string")
->setLabel(new TranslatableMarkup("Edited Description"))
->setDescription(new TranslatableMarkup("The description that has been overwritten from the default"))
->setRevisionable(TRUE)
->setDefaultValue(serialize([]));
return $fields;
}
/**
* {@inheritdoc}
*/
public function createDuplicate() {
$duplicate = parent::createDuplicate();
// Loop over entity fields and duplicate nested paragraphs.
foreach ($duplicate->getFields() as $field) {
if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions') {
if ($field->getFieldDefinition()->getTargetEntityTypeId() == "entity_analysis") {
foreach ($field as $item) {
$item->entity = $item->entity->createDuplicate();
}
}
}
}
return $duplicate;
}
/**
* Returns an array of field names to skip in ::isChanged.
*
* @return array
* An array of field names.
*/
protected function getFieldsToSkipFromChangedCheck() {
// A list of revision fields which should be skipped from the comparision.
$fields = [
$this->getEntityType()->getKey('revision'),
'revision_uid'
];
->setTranslatable(TRUE)
->setSetting("max_length", 1045);
return $fields;
}
/**
* {@inheritdoc}
*/
public function isChanged() {
if ($this->isNew()) {
return TRUE;
}
// $this->original only exists during save. If it exists we re-use it here
// for performance reasons.
/** @var \Drupal\paragraphs\ParagraphInterface $original */
$original = $this->original ?: NULL;
if (!$original) {
$original = $this->entityTypeManager()->getStorage($this->getEntityTypeId())->loadRevision($this->getLoadedRevisionId());
}
// If the current revision has just been added, we have a change.
if ($original->isNewRevision()) {
return TRUE;
}
// The list of fields to skip from the comparision.
$skip_fields = $this->getFieldsToSkipFromChangedCheck();
// Compare field item current values with the original ones to determine
// whether we have changes. We skip also computed fields as comparing them
// with their original values might not be possible or be meaningless.
foreach ($this->getFieldDefinitions() as $field_name => $definition) {
if (in_array($field_name, $skip_fields, TRUE)) {
continue;
}
$field = $this->get($field_name);
// When saving entities in the user interface, the changed timestamp is
// automatically incremented by ContentEntityForm::submitForm() even if
// nothing was actually changed. Thus, the changed time needs to be
// ignored when determining whether there are any actual changes in the
// entity.
if (!($field instanceof ChangedFieldItemList) && !$definition->isComputed()) {
$items = $field->filterEmptyItems();
$original_items = $original->get($field_name)->filterEmptyItems();
if (!$items->equals($original_items)) {
return TRUE;
}
}
}
return FALSE;
}
}
<?php
namespace Drupal\yoast_seo\Entity;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\ChangedFieldItemList;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\entity_reference_revisions\EntityNeedsSaveTrait;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\yoast_seo\EntityAnalysisInterface;
/**
* An entity that can be referenced by an Entity Reference field.
*
* Provides a base class for the Entity Analysis entity to separate the logic
* for a referencable entity and the Entity Analysis itself.
*/
class ReferencableEntityBase extends ContentEntityBase implements EntityAnalysisInterface {
use EntityNeedsSaveTrait;
/**
* {@inheritdoc}
*/
public function getParentEntity() {
if (!isset($this->get('parent_type')->value) || !isset($this->get('parent_id')->value)) {
return NULL;
}
$parent = \Drupal::entityTypeManager()->getStorage($this->get('parent_type')->value)->load($this->get('parent_id')->value);
// Return current translation of parent entity, if it exists.
if ($parent != NULL && ($parent instanceof TranslatableInterface) && $parent->hasTranslation($this->language()->getId())) {
return $parent->getTranslation($this->language()->getId());
}
return $parent;
}
/**
* {@inheritdoc}
*/
public function setParentEntity(ContentEntityInterface $parent, $parent_field_name) {
$this->set('parent_type', $parent->getEntityTypeId());
$this->set('parent_id', $parent->id());
$this->set('parent_field_name', $parent_field_name);
return $this;
}
/**
* {@inheritdoc}
*/
public function label() {
$label = '';
if ($parent = $this->getParentEntity()) {
$parent_field = $this->get('parent_field_name')->value;
$values = $parent->{$parent_field};
foreach ($values as $key => $value) {
if ($value->entity->id() == $this->id()) {
$label = $parent->label() . ' > ' . $value->getFieldDefinition()->getLabel();
}
else {
// A previous or draft revision or a deleted stale Paragraph.
$label = $parent->label() . ' > ' . $value->getFieldDefinition()->getLabel() . ' (previous revision)';
}
}
}
return $label;
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
$this->setNeedsSave(FALSE);
parent::postSave($storage, $update);
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function getRevisionLog() {
return '';
}
/**
* {@inheritdoc}
*/
public function setRevisionLog($revision_log) {
return $this;
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language code'))
->setDescription(t('The paragraphs entity language code.'))
->setRevisionable(TRUE);
$fields['updated'] = BaseFieldDefinition::create('updated')
->setLabel(t('Analysed on'))
->setDescription(t('The time that the Analysis was created.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDisplayOptions('form', array(
'region' => 'hidden',
'weight' => 0,
))
->setDisplayConfigurable('form', TRUE);
$fields['revision_uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Revision user ID'))
->setDescription(t('The user ID of the author of the current revision.'))
->setSetting('target_type', 'user')
->setRevisionable(TRUE);
$fields['parent_id'] = BaseFieldDefinition::create('string')
->setLabel(t('Parent ID'))
->setDescription(t('The ID of the parent entity of which this entity is referenced.'))
->setSetting('is_ascii', TRUE);
$fields['parent_type'] = BaseFieldDefinition::create('string')
->setLabel(t('Parent type'))
->setDescription(t('The entity parent type to which this entity is referenced.'))
->setSetting('is_ascii', TRUE)
->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
$fields['parent_field_name'] = BaseFieldDefinition::create('string')
->setLabel(t('Parent field name'))
->setDescription(t('The entity parent field name to which this entity is referenced.'))
->setSetting('is_ascii', TRUE)
->setSetting('max_length', FieldStorageConfig::NAME_MAX_LENGTH);
return $fields;
}
/**
* {@inheritdoc}
*/
public function createDuplicate() {
$duplicate = parent::createDuplicate();
// Loop over entity fields and duplicate nested paragraphs.
foreach ($duplicate->getFields() as $field) {
if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions') {
if ($field->getFieldDefinition()->getTargetEntityTypeId() == "entity_analysis") {
foreach ($field as $item) {
$item->entity = $item->entity->createDuplicate();
}
}
}
}
return $duplicate;
}
/**
* Returns an array of field names to skip in ::isChanged.
*
* @return array
* An array of field names.
*/
protected function getFieldsToSkipFromChangedCheck() {
// A list of revision fields which should be skipped from the comparision.
$fields = [
$this->getEntityType()->getKey('revision'),
'revision_uid'
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function isChanged() {
if ($this->isNew()) {
return TRUE;
}
// $this->original only exists during save. If it exists we re-use it here
// for performance reasons.
/** @var \Drupal\paragraphs\ParagraphInterface $original */
$original = $this->original ?: NULL;
if (!$original) {
$original = $this->entityTypeManager()->getStorage($this->getEntityTypeId())->loadRevision($this->getLoadedRevisionId());
}
// If the current revision has just been added, we have a change.
if ($original->isNewRevision()) {
return TRUE;
}
// The list of fields to skip from the comparision.
$skip_fields = $this->getFieldsToSkipFromChangedCheck();
// Compare field item current values with the original ones to determine
// whether we have changes. We skip also computed fields as comparing them
// with their original values might not be possible or be meaningless.
foreach ($this->getFieldDefinitions() as $field_name => $definition) {
if (in_array($field_name, $skip_fields, TRUE)) {
continue;
}
$field = $this->get($field_name);
// When saving entities in the user interface, the changed timestamp is
// automatically incremented by ContentEntityForm::submitForm() even if
// nothing was actually changed. Thus, the changed time needs to be
// ignored when determining whether there are any actual changes in the
// entity.
if (!($field instanceof ChangedFieldItemList) && !$definition->isComputed()) {
$items = $field->filterEmptyItems();
$original_items = $original->get($field_name)->filterEmptyItems();
if (!$items->equals($original_items)) {
return TRUE;
}
}
}
return FALSE;
}
}
<?php
namespace Drupal\yoast_seo\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
/**
* Plugin implementation of the 'keyword_result' field type.
*
* TODO: Fix default_widget and formatter.
*
* @FieldType(
* id = "keyword_result",
* label = @Translation("Keyword and results"),
* module = "yoast_seo",
* description = @Translation("Keywords used in analyses and their results"),
* default_widget = "",
* default_formatter = "",
* no_ui = TRUE
* )
*/
class KeywordResultItem extends FieldItemBase {
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
return [
'columns' => [
'keyword' => [
'type' => 'varchar',
'length' => 256,
'not null' => FALSE,
],
'seo_score' => [
'type' => 'integer',
'unsigned' => TRUE,
'not null' => FALSE,
],
'content_score' => [
'type' => 'integer',
'unsigned' => TRUE,
'not null' => FALSE,
],
],
];
}
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['keyword'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Keyword'))
->setDescription(new TranslatableMarkup("The keywords used in this analysis"));
$properties['seo_score'] = DataDefinition::create('integer')
->setLabel(new TranslatableMarkup('SEO Score'))
->setDescription(new TranslatableMarkup("The SEO score for this keyword"));
$properties['content_score'] = DataDefinition::create('integer')
->setLabel(new TranslatableMarkup("Content Score"))
->setDescription(new TranslatableMarkup("The content score for this keyword"));
return $properties;
}
}
......@@ -2,9 +2,8 @@
namespace Drupal\yoast_seo\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\entity_reference_revisions\Plugin\Field\FieldType\EntityReferenceRevisionsItem;
/**
* Plugin implementation of the 'yoast_seo' field type.
......@@ -14,63 +13,55 @@ use Drupal\Core\TypedData\DataDefinition;
* label = @Translation("Real-time SEO status & focused keywords"),
* module = "yoast_seo",
* description = @Translation("The Real-time SEO status in points and the focused keywords."),
* class = "\Drupal\yoast_seo\Plugin\Field\FieldType\YoastSeoItem",
* list_class = "\Drupal\entity_reference_revisions\EntityReferenceRevisionsFieldItemList",
* default_widget = "yoast_seo_widget",
* default_formatter = "yoastseo_empty_formatter"
* default_formatter = "yoastseo_empty_formatter",
* cardinality = 1
* )
*/
class YoastSeoItem extends FieldItemBase {
class YoastSeoItem extends EntityReferenceRevisionsItem {
/**
* {@inheritdoc}
*/
public static function mainPropertyName() {
return 'status';
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
return [];
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
public static function defaultStorageSettings() {
return [
'columns' => [
'status' => [
'type' => 'varchar',
'length' => 256,
'not null' => FALSE,
],
'focus_keyword' => [
'type' => 'varchar',
'length' => 256,
'not null' => FALSE,
],
'title' => [
'type' => 'varchar',
'length' => 1024,
'not null' => FALSE,
],
'description' => [
'type' => 'varchar',
'length' => 1024,
'not null' => FALSE,
],
],
];
'target_type' => 'entity_analysis',
] + parent::defaultStorageSettings();
}