field_completeness-1.0.3/src/FieldCompletenessManager.php
src/FieldCompletenessManager.php
<?php
namespace Drupal\field_completeness;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\field_completeness\FieldCompletenessStorage;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Component\Serialization\PhpSerialize;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\paragraphs\Entity\Paragraph;
/**
* Defines field completeness manager.
*/
class FieldCompletenessManager {
/**
* Entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Field completeness storage.
*
* @var \Drupal\field_completeness\FieldCompletenessStorage
*/
public $fieldCompletenessStorage;
/**
* Constructs a FieldCompletenessManager object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\field_completeness\FieldCompletenessStorage $field_completeness_storage
* The Field completeness storage.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager, FieldCompletenessStorage $field_completeness_storage) {
$this->entityTypeManager = $entity_type_manager;
$this->languageManager = $language_manager;
$this->fieldCompletenessStorage = $field_completeness_storage;
}
/**
* Determines if a given node type is in the list of types allowed for field completeness.
*
* @param string $type
* A node type.
*
* @return bool
* A Boolean TRUE if the node type can be included in field completeness; otherwise, FALSE.
*/
public function isAllowedContentType($type) {
if (NULL === $this->getAllowedContentTypes()) {
return FALSE;
}
return in_array($type, $this->getAllowedContentTypes());
}
/**
* Determines new or existing record and update or add in field completeness database table
*
* @param \Drupal\Core\Entity\EntityInterface $node
*
*/
public function updateOutline(EntityInterface $node) {
$is_allowed_type = $this->isAllowedContentType($node->bundle());
if ($is_allowed_type) {
$lang_code = "";
$is_existing = FALSE;
if ($this->languageManager->isMultilingual()) {
$lang_code = $node->get('langcode')->value;
}
$is_existing = $this->fieldCompletenessStorage->select($node->id());
$fc_data = $this->getNodeData($node, $lang_code);
$nid = $node->id();
if (empty($is_existing)) {
$this->fieldCompletenessStorage->insert($fc_data);
}
else {
if ($this->isModified($node)) {
$this->fieldCompletenessStorage->update($nid, $fc_data);
}
}
}
}
/**
* insert data into Field completeness table
* @param \Drupal\Core\Entity\EntityInterface $node
*/
public function getNodeData(EntityInterface $node, $lang_code) {
$values = [];
$values['entity_type'] = $node->bundle();
$values['entity_id'] = $node->id();
$values['langcode'] = $lang_code;
$values['revision_id'] = $this->entityTypeManager->getStorage('node')->getLatestRevisionId($node->id());
$values['completeness'] = $this->getPhpSerialized($this->getCompleteness($node, 'all'));
$values['percentage'] = $this->getPercentage($node);
$values['complete'] = ($values['percentage'] == 100) ? 1 : 0;
return $values;
}
/**
* Determines included fields and their values
* @param \Drupal\Core\Entity\EntityInterface $node
* @param $wrap
* 'all' - gets all the fields whether its completed or not
* 'completed' - Gets only completed fields
* 'incomplete' - Gets only incompleted fields
*/
public function getCompleteness(EntityInterface $node, $wrap = 'all') {
$completeness = [];
$paragraph_fields = [];
$included_fields = $this->getIncludedFields($node->bundle());
$nid = $node->id();
foreach ($included_fields as $included_field) {
if ($node->hasField($included_field)) {
if ($wrap == 'all') {
if ($this->isEntityReferenceRevisions($included_field)) {
$paragraph_fields = $this->getReferenceRevisionFields($node, $included_field, FALSE);
if (!empty($paragraph_fields)) {
foreach ($paragraph_fields as $paragraph_field => $isFilled) {
$completeness["p_" . $paragraph_field] = $isFilled;
}
}
}
else {
//Returns all the fields status
if (!$node->get($included_field)->isEmpty() && !empty($node->get($included_field)->getString())) {
$completeness[$included_field] = TRUE;
}
else {
$completeness[$included_field] = FALSE;
}
}
}
elseif ($wrap == 'completed') {
if ($this->isEntityReferenceRevisions($included_field)) {
$paragraph_fields = $this->getReferenceRevisionFields($node, $included_field, FALSE);
foreach ($paragraph_fields as $paragraph_field => $isFilled) {
if ($isFilled) {
$completeness[] = "p_" . $paragraph_field;
}
}
}
else {
//Returns completed fields, it will consider 0/FALSE as empty
if (!$node->get($included_field)->isEmpty() && !empty($node->get($included_field)->getString())) {
$completeness[] = $included_field;
}
}
}
else {
if ($this->isEntityReferenceRevisions($included_field) && !$this->isParagraphHasValue($node, $included_field)) {
$completeness[] = $included_field;
}
else {
//Returns incompleted fields, it will consider 0/FALSE as empty
if ($node->get($included_field)->isEmpty() || empty($node->get($included_field)->getString())) {
$completeness[] = $included_field;
}
}
}
}
else {
//Unset from included fields
$this->unSetIncludedFields($node->getEntityTypeId(), $node->bundle(), $included_field);
}
}
return $completeness;
}
public function getPhpSerialized($data) {
return PhpSerialize::encode($data);
}
public function getPhpUnSerialized($data) {
return PhpSerialize::decode($data);
}
public function getIncludedFields($type) {
$included_field_config = \Drupal::config('field_completeness.node.' . $type . '.settings');
$allowed_fields = [];
if (NULL !== $included_field_config->get('node')) {
foreach ($included_field_config->get('node') as $field => $isIncluded) {
if ($isIncluded) {
$allowed_fields[] = $field;
}
}
}
return $allowed_fields;
}
public function isEntityReferenceRevisions($field_name) {
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
if ($field_storage->getType() == 'entity_reference_revisions') {
if (!is_null($field_storage) && $field_storage->getSetting('target_type') == 'paragraph') {
return TRUE;
}
}
return FALSE;
}
/**
* Get included fields of Paragraph type. In future other types are going to include.
* is_empty
* TRUE - It will return field names as array if any field is empty or return empty array.
* FALSE - It will return all the included paragraph fields
*/
public function getReferenceRevisionFields(EntityInterface $node, $field_name, $is_empty = FALSE) {
$fields = [];
if (!empty($node->get($field_name)->first())) {
$reference_revision = $node->get($field_name)->first()->get('entity')->getTarget()->getValue();
}
$paragraph_types = $node->get($field_name)->getSetting('handler_settings')['target_bundles'];
if (!empty($reference_revision)) {
if ($reference_revision instanceof Paragraph) {
$paragraph_type = $reference_revision->getType();
$paragraph_config = \Drupal::config('field_completeness.paragraph.' . $paragraph_type . '.settings');
foreach ($paragraph_config->get('paragraph') as $field => $isIncluded) {
if ($isIncluded) {
if ($is_empty) {
if ($reference_revision->get($field)->isEmpty() || empty($reference_revision->get($field)->getString())) {
$fields[] = $field;
}
}
else {
if (!$reference_revision->get($field)->isEmpty() && !empty($reference_revision->get($field)->getString())) {
$fields[$field] = TRUE;
}
else {
$fields[$field] = FALSE;
}
}
}
}
}
}
else {
if (!empty($paragraph_types)) {
foreach ($paragraph_types as $paragraph_type) {
$paragraph_config = \Drupal::config('field_completeness.paragraph.' . $paragraph_type . '.settings');
foreach ($paragraph_config->get('paragraph') as $field => $isIncluded) {
if ($isIncluded) {
if ($is_empty) {
$fields[] = $field;
}
else {
$fields[$field] = FALSE;
}
}
}
}
}
}
return $fields;
}
public function isParagraphHasValue($node, $included_field) {
$reference_revision_fields = $this->getReferenceRevisionFields($node, $included_field, TRUE);
if (empty($reference_revision_fields)) {
return TRUE;
}
return FALSE;
}
/**
* Remove included fields from configuration which is not existing in Entity
*/
public function unSetIncludedFields($entity_type_id, $bundle, $field_name) {
$config = \Drupal::service('config.factory')->getEditable('field_completeness.node.' . $bundle . '.settings');
$fc_config = $entity_type_id . "." . $field_name;
$config->set($fc_config, 0)->save();
}
/**
* Gets allowed content types for field completeness
*
* @return array
* An array of content types list for field completeness
*/
public function getAllowedContentTypes() {
return \Drupal::config('field_completeness.settings')->get('allowed_types');
}
/**
* Determines percentage of field completion
*/
public function getPercentage(EntityInterface $node) {
if (empty($this->getCompleteness($node, 'incomplete'))) {
return 100;
}
$total = count($this->getCompleteness($node, 'all'));
$completed = count($this->getCompleteness($node, 'completed'));
$percentage = floor($completed * (100 / $total));
return $percentage;
}
/**
* Determines percentage of field completion
*/
public function getStaticPercentage(EntityInterface $node) {
$percentage = 0;
$select = $this->fieldCompletenessStorage->select($node->id());
$percentage = $select[$node->id()]['percentage'];
return $percentage;
}
/**
* Gets current node object.
*/
public function getNode() {
//need to verify for add page
$node = \Drupal::routeMatch()->getParameter('node');
if (!$node instanceof EntityInterface) {
return FALSE;
}
return $node;
}
/**
* Determines if a given paragraph type is allowed for field completeness paragraph.
*
* @param string $type
* A node type.
*
* @return bool
* A Boolean TRUE if the node type can be included in field completeness; otherwise, FALSE.
*/
public function isAllowedParagraphType($type) {
return in_array($type, $this->getAllowedParagraphTypes());
}
/**
* Gets allowed paragraph types for field completeness
*
* @return array
* An array of paragraph types list for field completeness
*/
public function getAllowedParagraphTypes() {
return \Drupal::config('field_completeness.paragraphs.settings')->get('allowed_types');
}
/**
* Gets entity type from route parameter
*/
public function getEntityBundleFromRoute() {
$route_parameters = \Drupal::routeMatch()->getParameters();
foreach ($route_parameters as $entity) {
if ($entity instanceof EntityInterface) {
return $entity->id();
}
}
return NULL;
}
/**
* While Adding or removing a field, it will update required items to reflect immediately to all the nodes of field bundle
* Update below items to field_completeness DB table
* - completeness
* - percentage
* - complete
*/
public function setLatestPercentageToBundle($field, $bundle) {
$fc_data = $this->fieldCompletenessStorage->selectByBundle($bundle);
if (!empty($fc_data)) {
foreach ($fc_data as $nid => $fc_values) {
$node = $this->entityTypeManager->getStorage('node')->load($nid);
if (is_null($node)) {
$this->removeDeletedNodeFromCompleteness($nid);
continue;
}
$fc_values['completeness'] = $this->getCompleteness($node, 'all');
$this->fieldCompletenessStorage->update($nid, $this->calculatePercentageForExistingItem($fc_values));
}
}
}
/**
* While Adding or removing a field, it will update required items to reflect immediately to a node
* Update below items to field_completeness DB table
* - completeness
* - percentage
* - complete
*/
public function setPercentageWhileUpdateNode($field, $is_empty, $bundle, $nid) {
if (in_array($field, $this->getIncludedFields($bundle))) {
$fc_data = $this->fieldCompletenessStorage->select($nid);
$fc_values['completeness'] = $this->getPhpUnSerialized($fc_data[$nid]['completeness']);
$fc_values['completeness'][$field] = (bool) $is_empty;
$this->fieldCompletenessStorage->update($nid, $this->calculatePercentageForExistingItem($fc_values));
}
}
/**
* Calculate percentage for existing record while updating Field completeness.
*/
public function calculatePercentageForExistingItem($fc_values) {
$fc_total = count($fc_values['completeness']);
$fc_completed = count(array_filter($fc_values['completeness']));
$fc_values['completeness'] = $this->getPhpSerialized($fc_values['completeness']);
if ($fc_total > 0) {
$fc_values['percentage'] = floor($fc_completed * (100 / $fc_total));
}
else {
$fc_values['percentage'] = 100;
}
$fc_values['complete'] = ($fc_values['percentage'] == 100) ? 1 : 0;
return $fc_values;
}
/**
* Observe the changes of Node revision and Field changes
*/
public function isModified(EntityInterface $node) {
$nid = $node->id();
$select = $this->fieldCompletenessStorage->select($nid);
$is_modified = FALSE;
//verify existing revision id
$revision_id = $this->entityTypeManager->getStorage('node')->getLatestRevisionId($nid);
$exisiting_revision_id = $select[$nid]['revision_id'];
if ($revision_id <> $exisiting_revision_id) {
$is_modified = TRUE;
}
$old_fields = $this->getPhpUnSerialized($select[$nid]['completeness']);
$new_fields = $this->getCompleteness($node, 'all');
if ($old_fields <> $new_fields) {
$is_modified = TRUE;
}
return $is_modified;
}
/**
* Remove a record from field completeness which is not existing in current site
*/
public function removeDeletedNodeFromCompleteness($nid) {
$this->fieldCompletenessStorage->delete($nid);
}
}
