diff --git a/src/CmDocumentViewBuilder.php b/src/CmDocumentViewBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..0468c7bf552d2ef7ac4b3088ee5bf3076486f366 --- /dev/null +++ b/src/CmDocumentViewBuilder.php @@ -0,0 +1,212 @@ +<?php + +namespace Drupal\content_model_documentation; + +use Drupal\content_model_documentation\Entity\CMDocumentInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityViewBuilder; + +/** + * Defines a class for entity view builder for linky entities. + */ +class CmDocumentViewBuilder extends EntityViewBuilder { + + use CMDocumentConnectorTrait; + + /** + * {@inheritdoc} + */ + public function view(EntityInterface $cm_document, $view_mode = 'full', $langcode = NULL) { + $build = parent::view($cm_document, $view_mode, $langcode); + $add_ons = $this->getAddOns($cm_document); + $build = array_merge($build, $add_ons); + + return $build; + } + + /** + * Grabs all the add on render arrays for a given kind of documentation. + * + * @param \Drupal\content_model_documentation\Entity\CMDocumentInterface $cm_document + * The current document to process. + * + * @return array + * An array of render arrays to be added on. + */ + protected function getAddOnTypes(CMDocumentInterface $cm_document): array { + $documentation_for = $cm_document->get('documented_entity')->value; + $type = $cm_document->getDocumentedEntityParameter('type'); + $bundle = $cm_document->getDocumentedEntityParameter('bundle'); + $field = $cm_document->getDocumentedEntityParameter('field'); + // Order of the addons specified determines their order on the page. + switch (TRUE) { + case ($type === 'base' && !empty($field)): + $add_ons = ['SiblingFields']; + break; + + case (!empty($field)): + $add_ons = ['AppearsOn', 'BaseField', 'SiblingFields']; + break; + + default: + $add_ons = []; + break; + } + return $add_ons; + } + + /** + * Gets all the add ons that should appear on a CM Document. + * + * @param \Drupal\content_model_documentation\Entity\CMDocumentInterface $cm_document + * The current document to process. + * + * @return array + * An array of render arrays for each of the related add ons. + */ + protected function getAddOns(CMDocumentInterface $cm_document): array { + $add_on_types = $this->getAddOnTypes($cm_document); + $add_ons = []; + foreach ($add_on_types as $i => $add_on) { + $func = "get{$add_on}"; + $add_ons["$add_on"] = $this->$func($cm_document); + } + + return $add_ons; + } + + /** + * Gets a render array showing the content type this field appears on. + * + * @param \Drupal\content_model_documentation\Entity\CMDocumentInterface $cm_document + * The current document to process. + * + * @return array + * A renderable array for the appears on section of the page. + */ + protected function getAppearsOn(CMDocumentInterface $cm_document): array { + $add_on = []; + $type = $cm_document->getDocumentedEntityParameter('type'); + $bundle = $cm_document->getDocumentedEntityParameter('bundle'); + $field = $cm_document->getDocumentedEntityParameter('field'); + $documented_entity = $cm_document->getDocumentedEntity(); + $label = (empty($documented_entity)) ? $this->t('undefined') : $documented_entity->label(); + $cm_doc_link = $this->getCmDocumentLink($type, $bundle); + if ($cm_doc_link) { + $add_on = $cm_doc_link->toRenderable(); + $add_on['#title'] = "$type $label ($bundle)"; + } + else { + $add_on = [ + '#type' => 'html_tag', + '#tag' => 'span', + '#value' => "$type $label ($bundle)", + ]; + } + $add_on['#prefix'] = "<span class=\"field__label\">{$this->t('Appears on: ')}</span>"; + // This one should be near the top of the page. + $add_on['#weight'] = -10; + return $add_on; + } + + /** + * Gets a link to the base field CM Document if it exists. + * + * @param \Drupal\content_model_documentation\Entity\CMDocumentInterface $cm_document + * The current document to process. + * + * @return array + * A render array for a link if one exists, or an empty array. + */ + protected function getBaseField(CMDocumentInterface $cm_document): array { + $add_on = []; + $field = $cm_document->getDocumentedEntityParameter('field'); + $base_cm_doc = "base.field.$field"; + // Does documentation for the base field exist? + $cm_doc_link = $this->getCmDocumentLink('base', 'field', $field); + if ($cm_doc_link) { + $add_on = $cm_doc_link->toRenderable(); + $add_on['#title'] = $this->t('Base @field documentation', ['@field' => $field]); + $add_on['#weight'] = 10; + } + return $add_on; + } + + /** + * Gets a a table of siblings if they exist. + * + * @param \Drupal\content_model_documentation\Entity\CMDocumentInterface $cm_document + * The current document to process. + * + * @return array + * A render array for a table if siblings exists, or an empty array. + */ + protected function getSiblingFields(CMDocumentInterface $cm_document): array { + $add_on = []; + $rows = $this->buildSiblingRows($cm_document); + if (!empty($rows)) { + $type = $cm_document->getDocumentedEntityParameter('type'); + $field = $cm_document->getDocumentedEntityParameter('field'); + // Adjust the count for display where the documented field was removed. + $count = ($type === 'base') ? count($rows) : count($rows) + 1; + $add_on['table'] = [ + '#type' => 'table', + '#header' => [ + $this->t("Field Name"), + $this->t("Entity Type"), + $this->t("Bundle"), + $this->t("Field Type"), + $this->t("Documentation"), + ], + '#rows' => $rows, + '#footer' => [["Total instances: $count", '', '', '', '']], + '#empty' => $this->t('No table content found.'), + '#caption' => $this->t("Sibling instances of field @field.", ['@field' => $field]), + '#attributes' => [ + 'class' => ['sortable'], + ], + + '#attached' => ['library' => ['content_model_documentation/sortable-init']], + ]; + $add_on['#weight'] = 20; + } + + return $add_on; + } + + /** + * Builds the rows of all instances of a field. + * + * @param \Drupal\content_model_documentation\Entity\CMDocumentInterface $cm_document + * The current document to process. + * + * @return array + * An array of table rows. + */ + protected function buildSiblingRows(CMDocumentInterface $cm_document): array { + $sibling_rows = []; + $field = $cm_document->getDocumentedEntityParameter('field'); + $src_bundle = $cm_document->getDocumentedEntityParameter('bundle'); + $mapped_types = $cm_document->entityFieldManager->getFieldMap(); + foreach ($mapped_types as $entity_type => $fields) { + if (!empty($fields[$field])) { + foreach ($fields[$field]['bundles'] as $bundle) { + // Do not include CM Documents in this list, or the current instance. + if ($bundle !== 'cm_document' && $src_bundle !== $bundle) { + $definitions = $cm_document->entityFieldManager->getFieldDefinitions($entity_type, $bundle); + $sibling_rows[] = [ + 'Field Name' => $definitions[$field]->getLabel(), + 'Entity Type' => $entity_type, + 'Bundle' => $bundle, + 'Field Type' => $fields[$field]['type'], + 'Document' => $this->getCmDocumentLink($entity_type, $bundle, $field), + ]; + } + } + } + } + + return $sibling_rows; + } + +} diff --git a/src/Controller/ContentModelDocumentController.php b/src/Controller/ContentModelDocumentController.php index cb56511e56231a4bf9763b9cc067acc164641aa2..ec49650ec0ff6e76f761b979eac27c7e95ea7544 100644 --- a/src/Controller/ContentModelDocumentController.php +++ b/src/Controller/ContentModelDocumentController.php @@ -20,6 +20,13 @@ use Symfony\Component\DependencyInjection\ContainerInterface; */ class ContentModelDocumentController extends ControllerBase implements ContainerInjectionInterface { + /** + * The date formatter. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + /** * The renderer. * diff --git a/src/DocumentableEntityProvider.php b/src/DocumentableEntityProvider.php index 666218b9e0babca00f72504d990897eb93798c53..01d0ed44bbd7efef865701689aeb4285fe2579d9 100644 --- a/src/DocumentableEntityProvider.php +++ b/src/DocumentableEntityProvider.php @@ -104,7 +104,7 @@ class DocumentableEntityProvider extends ServiceProviderBase { $documentable_entities = $this->addEntityBundles($documentable_type, $documentable_entities); $documentable_entities = $this->addEntityFields($documentable_type, $documentable_entities); } - // Lets sort them so the make more sense in the list. + // Let's sort them so they make more sense in the list. natcasesort($documentable_entities); return $documentable_entities; } diff --git a/src/Entity/CMDocument.php b/src/Entity/CMDocument.php index 888e548f4647c46f3ca416464638a60afbf54e62..15c993499fbd8d156180246bf942fb242e81ecf3 100644 --- a/src/Entity/CMDocument.php +++ b/src/Entity/CMDocument.php @@ -26,7 +26,7 @@ use Drupal\user\UserInterface; * label_collection = @Translation("Content Model Document"), * handlers = { * "storage" = "\Drupal\Core\Entity\Sql\SqlContentEntityStorage", - * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", + * "view_builder" = "Drupal\content_model_documentation\CmDocumentViewBuilder", * "list_builder" = "Drupal\content_model_documentation\CMDocumentListBuilder", * "views_data" = "Drupal\content_model_documentation\Entity\CMDocumentViewsData", * "form" = { @@ -81,12 +81,19 @@ use Drupal\user\UserInterface; class CMDocument extends EditorialContentEntityBase implements CMDocumentInterface { use CMDocumentConnectorTrait; + /** + * The entity field manager. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + public $entityFieldManager; + /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityTypeManager; + public $entityTypeManager; /** * The entity type manager. @@ -102,6 +109,7 @@ class CMDocument extends EditorialContentEntityBase implements CMDocumentInterfa parent::__construct($values, $entity_type, $bundle, $translations); // Can't use Dependency Injection on a @ContentEntityType. $this->entityTypeManager = \Drupal::service('entity_type.manager'); + $this->entityFieldManager = \Drupal::service('entity_field.manager'); $this->pathAliasStorage = $this->entityTypeManager->getStorage('path_alias'); } @@ -249,6 +257,21 @@ class CMDocument extends EditorialContentEntityBase implements CMDocumentInterfa return $return; } + /** + * {@inheritdoc} + */ + public function getDocumentedEntity(): object|null { + $content_type = NULL; + $type = $this->getDocumentedEntityParameter('type'); + $bundle = $this->getDocumentedEntityParameter('bundle'); + $type_map = $this->getStorageMap(); + if (!empty($type_map[$type])) { + $storage = $this->entityTypeManager->getStorage($type_map[$type]); + $content_type = $storage->load($bundle); + } + return $content_type; + } + /** * Remove existing aliases that do not match new & return remaining count. * @@ -464,4 +487,18 @@ class CMDocument extends EditorialContentEntityBase implements CMDocumentInterfa return $fields; } + /** + * {@inheritdoc} + */ + public function getStorageMap(): array { + return [ + 'node' => 'node_type', + 'media' => 'media_type', + 'block_content' => 'block_content_type', + 'menu_link_content' => 'menu_link_content', + 'paragraph' => 'paragraphs_type', + 'taxonomy_term' => 'taxonomy_vocabulary', + ]; + } + } diff --git a/src/Entity/CMDocumentInterface.php b/src/Entity/CMDocumentInterface.php index 01aac895372f91050cc44510d9dcd742b32e25c3..138e85ef71c9afe28f9e5c134549addc0a10f296 100644 --- a/src/Entity/CMDocumentInterface.php +++ b/src/Entity/CMDocumentInterface.php @@ -64,6 +64,14 @@ interface CMDocumentInterface extends ContentEntityInterface, RevisionLogInterfa */ public function getDocumentedEntityParameter($element): string; + /** + * Loads and returns the entity that is being documented. + * + * @return object|null + * The object being documented (node, vocabulary, field...) NULL if none. + */ + public function getDocumentedEntity(): object|null; + /** * Calculates the intended alias for the CM Document. * @@ -88,4 +96,12 @@ interface CMDocumentInterface extends ContentEntityInterface, RevisionLogInterfa */ public static function getOtherDocumentableTypes(): array; + /** + * Gets an array of entity types mapped to storage types. + * + * @return array + * An array with elements in the form of 'entity type' => 'storage' pairs. + */ + public function getStorageMap(): array; + } diff --git a/src/Form/CMDocumentForm.php b/src/Form/CMDocumentForm.php index 2318c53f530f4ba4ae4e57d886bf036748445c30..7f19f88e12bc381a8a49a127ff0a3dc738a3c1ca 100644 --- a/src/Form/CMDocumentForm.php +++ b/src/Form/CMDocumentForm.php @@ -247,14 +247,7 @@ class CMDocumentForm extends ContentEntityForm { if (!empty($bundlename)) { // @todo Fix menu human names of the bundle not working. $bundlelabel = $bundlename; - $storage_map = [ - 'node' => 'node_type', - 'media' => 'media_type', - 'block_content' => 'block_content_type', - 'menu_link_content' => 'menu_link_content', - 'paragraph' => 'paragraphs_type', - 'taxonomy_term' => 'taxonomy_vocabulary', - ]; + $storage_map = $document->getStorageMap(); $storage_name = $storage_map[$entity_type] ?? $bundlename; $bundle_storage = $this->entityTypeManager->getStorage($storage_name); $bundle = $bundle_storage->load($bundlename);