From e7d03ed94c55e8d6805264debe1da712ec8e17ed Mon Sep 17 00:00:00 2001 From: astanley Date: Thu, 2 Apr 2026 14:39:25 -0300 Subject: [PATCH] added usage count --- css/metadata_profile.css | 7 + metadata_profile.module | 19 +- src/Controller/MetadataProfileController.php | 531 +++++++++++++------ 3 files changed, 393 insertions(+), 164 deletions(-) diff --git a/css/metadata_profile.css b/css/metadata_profile.css index ca675b4..97c41f5 100644 --- a/css/metadata_profile.css +++ b/css/metadata_profile.css @@ -18,3 +18,10 @@ .scrolling-table-container { overflow-x: auto; } + +.yes-icon { + color: #2e7d32; +} +.no-icon { + color: #c62828; +} diff --git a/metadata_profile.module b/metadata_profile.module index 505ad51..f82e255 100644 --- a/metadata_profile.module +++ b/metadata_profile.module @@ -1,5 +1,6 @@ getEntityTypeId() === 'node_type') { + + $operations['metadata_profile'] = [ + 'title' => t('Metadata profile'), + 'weight' => 50, + 'url' => Url::fromRoute('entity.node_type.metadata_profile', [ + 'node_type' => $entity->id(), + ]), + ]; + } +} diff --git a/src/Controller/MetadataProfileController.php b/src/Controller/MetadataProfileController.php index 91f0583..d04414a 100644 --- a/src/Controller/MetadataProfileController.php +++ b/src/Controller/MetadataProfileController.php @@ -5,7 +5,6 @@ namespace Drupal\metadata_profile\Controller; use Drupal\Core\Config\Config; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Entity\ContentEntityForm; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\FieldDefinitionInterface; @@ -15,13 +14,14 @@ use Drupal\Core\Link; use Drupal\Core\Messenger\MessengerTrait; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; -use Drupal\node\NodeForm; use Drupal\system\FileDownloadController; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; - +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityTypeManagerInterface; class MetadataProfileController extends ControllerBase { + use MessengerTrait; /** @@ -79,6 +79,7 @@ class MetadataProfileController extends ControllerBase { * @var \Drupal\Core\File\FileSystemInterface */ protected $fileSystem; + /** * The file download controller. * @@ -86,13 +87,30 @@ class MetadataProfileController extends ControllerBase { */ protected $fileDownloadController; + /** + * The Database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected Connection $database; - public function __construct(EntityFieldManagerInterface $entity_field_manager, - FieldTypePluginManagerInterface $field_type_plugin_manager, - ConfigFactoryInterface $config_factory, - RouteMatchInterface $route_match, - FileSystemInterface $file_system, - FileDownloadController $file_download_controller) { + /** + * The Entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected EntityTypeManagerInterface $entityTypeManagerService; + + public function __construct( + EntityFieldManagerInterface $entity_field_manager, + FieldTypePluginManagerInterface $field_type_plugin_manager, + ConfigFactoryInterface $config_factory, + RouteMatchInterface $route_match, + FileSystemInterface $file_system, + FileDownloadController $file_download_controller, + Connection $database, + EntityTypeManagerInterface $entity_type_manager // ✅ ADD THIS + ) { $this->entityFieldManager = $entity_field_manager; $this->fieldTypePluginManager = $field_type_plugin_manager; $this->configFactory = $config_factory; @@ -105,6 +123,8 @@ class MetadataProfileController extends ControllerBase { $this->entityTypeBundleOf = $entity_type->getEntityType()->getBundleOf(); $this->fileSystem = $file_system; $this->fileDownloadController = $file_download_controller; + $this->database = $database; + $this->entityTypeManagerService = $entity_type_manager; } public static function create(ContainerInterface $container) { @@ -114,7 +134,9 @@ class MetadataProfileController extends ControllerBase { $container->get('config.factory'), $container->get('current_route_match'), $container->get('file_system'), - FileDownloadController::create($container) + FileDownloadController::create($container), + $container->get('database'), + $container->get('entity_type.manager'), ); } @@ -124,11 +146,10 @@ class MetadataProfileController extends ControllerBase { * @return array */ public function profile() { - // Get core content type information. - $bundle_config = $this->config(str_replace('_', '.', $this->entityType) . '.' . $this->entityBundle); + $bundle_config = $this->config(str_replace('_', '.', $this->entityType) . '.' . $this->entityBundle); if (!$bundle_config) { - return ['#markup'=> 'Not a valid content type.']; + return ['#markup' => 'Not a valid content type.']; } $build = $this->formatBundle($bundle_config); @@ -136,7 +157,7 @@ class MetadataProfileController extends ControllerBase { $build['summary_table'] = [ '#type' => 'container', - '#attributes'=> ['class' => ['scrolling-table-container']], + '#attributes' => ['class' => ['scrolling-table-container']], 'data' => $this->buildSummaryTable($metadata_profile), ]; @@ -175,7 +196,7 @@ class MetadataProfileController extends ControllerBase { '#type' => 'container', '#attached' => ['library' => ['metadata_profile/metadata_profile']], - 'title' => [ + 'title' => [ '#plain_text' => $label, '#prefix' => '

', '#suffix' => '

', @@ -194,40 +215,73 @@ class MetadataProfileController extends ControllerBase { return $build; } + /** + * Builds a metadata profile for the current entity bundle. + * + * The returned array is keyed by field machine name and contains + * structured metadata describing each field. + * + * Each field profile contains: + * - label: string + * - machine_name: string + * - id: string + * - edit_url: \Drupal\Core\Url|null + * - details_link: \Drupal\Core\Link + * - description: string + * - type: string (human readable) + * - type_machine_name: string + * - required: render array + * - repeatable: render array|string + * - auto_create: render array|null + * - target_bundles: array + * - base_field: bool + * - usage_count: int + * - search_api: array + * + * @return array + * Metadata profile keyed by field machine name. + */ public function getMetadataProfile() { - // $metadata_profile is an array of arrays. Its keys are the "short" machine names of the fields - // such as field_abstract. The values are arrays with the following keys: - // * 'label' - text. the bundle's human-readable label. - // * 'machine_name' - text. The short machine name such as 'field_abstract'. - // * 'id' - text. The long machine name such as 'node.islandora_object.field_abstract'. (same as this array's key) - // * 'edit_url' - Url object. The link to the edit page for that field (or to the base field override page). - // * 'description' - the help text for the field. - // * 'type' - text. The human readable type of the field. (e.g. "Entity Reference") - // * 'type_machine_name' - text. The machine name of the type (e.g. 'entity_reference') - // * 'required' - 'Required'|'Not required'. Whether the field is required. - // * 'cardinality' - 'Not repeatable'|'Repeatable'|'Repeatable, limit N' - // * 'auto_create' - 'Create new'|'Create new #bundle'|'Do not create new'|NULL Whether a relationship field allows new entity creation on the fly. - // * 'target_bundles' - array. List of bundle names that are the target of this field. - // * 'search_api' - associative array. Has the following keys: - // * 'in_search_api' - True|False. True if some field matches. - // * 'has_facet' - True|False. True if some field has a facet. - // * 'fields' - array. The 'fields' array is keyed by the machine names of the search api fields, - // which are arrays with the following fields: - // * 'search_api_field_name' - the machine name of the search api field. - // * 'search_api_field_label' - the human readable label of the search api field. - // * 'property_path' - the method for getting the values. - // * 'type' = the search api field type. - // * 'index_name' - the name of the Search API index (in case there are more than one) - // * 'fields_included' - for aggregate and EDTF processor fields, which fields are in that field. - // * 'facets' - associative array. has the following keys - // * 'has_facet' - True|False. True if there is a facet, even if not showing up (can use with url alias) - // * 'facets' - array. the 'facets' array is keyed by the machine name of the facet. each facet is an - // array with keys: - // * 'facet_name' - // * 'facet_machine_name' - // * 'facet_source' - // * 'url_alias' - // * 'block_visible' -- not yet implemented. + /** + * Metadata profile structure. + * + * $metadata_profile is an associative array keyed by field machine names + * (e.g. "field_abstract"). Each value is an array with the following keys: + * + * - label: (string) Human-readable field label. + * - machine_name: (string) Short machine name (e.g. "field_abstract"). + * - id: (string) Full machine name + * (e.g. "node.islandora_object.field_abstract"). + * - edit_url: (\Drupal\Core\Url) Link to edit the field configuration. + * - description: (string) Field help text. + * - type: (string) Human-readable field type (e.g. "Entity Reference"). + * - type_machine_name: (string) Field type machine name + * (e.g. "entity_reference"). + * - required: (string) "Required" or "Not required". + * - cardinality: (string) "Not repeatable", "Repeatable", + * or "Repeatable, limit N". + * - auto_create: (string|null) Whether referenced entities can be created: + * "Create new", "Create new (bundle)", or "Do not create new". + * - target_bundles: (string[]) List of allowed target bundles. + * - search_api: (array) Search API metadata: + * - in_search_api: (bool) TRUE if indexed. + * - has_facet: (bool) TRUE if any facet exists. + * - fields: (array) Indexed by Search API field machine name: + * - search_api_field_name: (string) + * - search_api_field_label: (string) + * - property_path: (string) + * - type: (string) + * - index_name: (string) + * - fields_included: (array) Included fields (aggregated/EDTF). + * - facets: (array) Facet definitions: + * - has_facet: (bool) + * - facets: (array) Each facet contains: + * - facet_name: (string) + * - facet_machine_name: (string) + * - facet_source: (string) + * - url_alias: (string) + * - block_visible: (string) Not yet implemented. + */ $metadata_profile = []; $field_definitions = $this->entityFieldManager->getFieldDefinitions($this->entityTypeBundleOf, $this->entityBundle); @@ -246,13 +300,27 @@ class MetadataProfileController extends ControllerBase { 'auto_create' => $this->formatCreateNew($field_definition), 'target_bundles' => $this->formatTargetBundles($field_definition), 'base_field' => $field_definition->getFieldStorageDefinition() instanceof BaseFieldDefinition, + 'usage_count' => $this->getUsageCount($field_name), ]; - $metadata_profile[$field_name]['search_api'] = $this->getSearchApi($field_definition); + $metadata_profile[$field_name]['search_api'] = $this->getSearchApi($field_definition); } $metadata_profile = $this->addWeights($metadata_profile); return $metadata_profile; } + /** + * Builds Search API metadata for a field definition. + * + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The field definition. + * + * @return array|false + * An associative array containing: + * - in_search_api: bool + * - has_facet: bool + * - fields: array + * Or FALSE if Search API module is not enabled. + */ private function getSearchApi($field_definition) { if (!$this->moduleHandler()->moduleExists('search_api')) { return FALSE; @@ -274,7 +342,7 @@ class MetadataProfileController extends ControllerBase { // Loop over fields. foreach ($index_config->get('field_settings') as $search_api_field_name => $search_api_field_setting) { - $indexed_field_name = $index_config->getName() . '.' .$search_api_field_name; + $indexed_field_name = $index_config->getName() . '.' . $search_api_field_name; // Get Aggregated Fields. if ($search_api_field_setting['property_path'] == 'aggregated_field') { if (in_array('entity:node/' . $field_definition->getName(), $search_api_field_setting['configuration']['fields'])) { @@ -282,19 +350,23 @@ class MetadataProfileController extends ControllerBase { } } // Get EDTF fields. - else if ($field_definition->getType() == 'edtf') { - // Get EDTF year. - if ($search_api_field_setting['property_path'] == 'edtf_year') { - $edtf_year_fields = $index_config->get('processor_settings')['edtf_year_only']['fields'] ?: []; - if (in_array(str_replace('.', '|', $field_definition->id()), $edtf_year_fields)) { - $search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config); + else { + if ($field_definition->getType() == 'edtf') { + // Get EDTF year. + if ($search_api_field_setting['property_path'] == 'edtf_year') { + $edtf_year_fields = $index_config->get('processor_settings')['edtf_year_only']['fields'] ?: []; + if (in_array(str_replace('.', '|', $field_definition->id()), $edtf_year_fields)) { + $search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config); + } } - } - // Get EDTF Date - else if ($search_api_field_setting['property_path'] == 'edtf_dates') { - $edtf_date_fields = $index_config->get('processor_settings')['edtf_date_processor']['fields'] ?: []; - if (in_array(str_replace('.', '|', $field_definition->id()), $edtf_date_fields)) { - $search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config); + // Get EDTF Date + else { + if ($search_api_field_setting['property_path'] == 'edtf_dates') { + $edtf_date_fields = $index_config->get('processor_settings')['edtf_date_processor']['fields'] ?: []; + if (in_array(str_replace('.', '|', $field_definition->id()), $edtf_date_fields)) { + $search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config); + } + } } } } @@ -310,22 +382,29 @@ class MetadataProfileController extends ControllerBase { if (isset($search_api_field_setting['datasource_id']) and $search_api_field_setting['property_path'] == $field_definition->getName()) { $search_api['fields'][$indexed_field_name] = $this->getSearchApiField($search_api_field_name, $search_api_field_setting, $index_config); } - } if (count($search_api['fields']) > 0) { $search_api['in_search_api'] = TRUE; } - if (in_array(TRUE, array_column($search_api['fields'], 'has_facet'), true)) { + if (in_array(TRUE, array_column($search_api['fields'], 'has_facet'), TRUE)) { $search_api['has_facet'] = TRUE; } else { $search_api['has_facet'] = FALSE; } - } return $search_api; } + /** + * Builds a render array for the summary table. + * + * @param array $metadata_profile + * Metadata profile array. + * + * @return array + * Render array for a Drupal table. + */ protected function buildSummaryTable($metadata_profile) { $rows = $this->getRows($metadata_profile, TRUE); return [ @@ -333,13 +412,43 @@ class MetadataProfileController extends ControllerBase { '#header' => $this->getHeaders(), '#rows' => $rows, ]; - } - protected function getRows($metadata_profile, $display=NULL) { + /** + * Builds table rows from a metadata profile array. + * + * Each row is a numerically indexed array representing a table row: + * 0 => Field label or link + * 1 => Machine name + * 2 => Description + * 3 => Field type (human readable) + * 4 => Required indicator (render array) + * 5 => Repeatable indicator (render array) + * 6 => Auto-create indicator (render array|null) + * 7 => In Search API (render array) + * 8 => Has Facet (render array) + * 9 => Target bundles (array|string|render array) + * 10 => Weight (int|float) + * 11 => Usage count (int) + * + * Rows are sorted descending by usage count (index 11). + * + * @param array $metadata_profile + * Structured metadata profile keyed by field machine name. + * @param bool|null $display + * Whether to return renderable values (TRUE) or raw values (FALSE/NULL). + * + * @return array + * A numerically indexed array of row arrays. + */ + protected function getRows($metadata_profile, $display = NULL) { $rows = []; foreach ($metadata_profile as $field_name => $field_profile) { - if (str_starts_with( $field_name, 'field_') or in_array($field_name, ['title', 'name'])) { + if (str_starts_with($field_name, 'field_') or in_array($field_name, [ + 'title', + 'name', + ])) { + $bundles = $field_profile['target_bundles'] ?? []; $rows[] = [ $display ? $field_profile['details_link'] : $field_profile['label'], $field_profile['machine_name'], @@ -348,13 +457,20 @@ class MetadataProfileController extends ControllerBase { $field_profile['required'], $field_profile['repeatable'], $field_profile['auto_create'], - $display ? $this->formatListForTable($field_profile['target_bundles']) : $field_profile['target_bundles'], - $field_profile['search_api']['in_search_api'] ? 'Indexed' : '', - $field_profile['search_api']['has_facet'] ? 'Facet' : '', + + $this->yesNoIcon($field_profile['search_api']['in_search_api']), + $this->YesNoIcon($field_profile['search_api']['has_facet']), + $display + ? $this->formatListForTable($bundles) + : $bundles, $field_profile['weight'], - ]; + $field_profile['usage_count'], + ]; } } + usort($rows, function($a, $b) { + return $b[11] <=> $a[11]; + }); return $rows; } @@ -367,10 +483,11 @@ class MetadataProfileController extends ControllerBase { $this->t('Required'), $this->t('Repeatable'), $this->t('Create new'), - $this->t('Target bundles'), $this->t('In Search API'), $this->t('Has Facet'), + $this->t('Target bundles'), $this->t('Weight'), + $this->t("Usage count"), // TODO: add more columns ]; @@ -379,7 +496,10 @@ class MetadataProfileController extends ControllerBase { protected function buildField(array $field_profile) { $render_array = []; $field_name = $field_profile['machine_name']; - if (str_starts_with($field_name, 'field_') or in_array($field_name, ['title', 'name'])) { + if (str_starts_with($field_name, 'field_') or in_array($field_name, [ + 'title', + 'name', + ])) { $render_array = [ '#type' => 'container', '#attributes' => ['class' => ['field-info']], @@ -395,10 +515,20 @@ class MetadataProfileController extends ControllerBase { '#prefix' => '(', '#suffix' => ') ', ], + 'usage_count' => [ + '#markup' => $this->formatPlural( + $field_profile['usage_count'], + "Used 1 time.", + "Used @count times." + ), + '#prefix' => '', + + ], + 'edit_link' => $field_profile['edit_url'] ? $this->formatFieldEditLink($field_profile['edit_url']) : [], 'table' => [ '#type' => 'table', - '#header' => ['Field Configuration',''], + '#header' => ['Field Configuration', ''], '#rows' => $this->getFieldTableRows($field_profile), '#attributes' => ['class' => ['field-info-table']], '#prefix' => '

' . $this->t('Field Information') . '

', @@ -411,9 +541,6 @@ class MetadataProfileController extends ControllerBase { 'search_api' => $this->formatSearchApi($field_profile), 'facets' => $this->formatFacets($field_profile), ]; - - - } return $render_array; @@ -427,7 +554,7 @@ class MetadataProfileController extends ControllerBase { $edit_url = Url::fromRoute('entity.base_field_override.' . $this->entityTypeBundleOf . '_base_field_override_edit_form', [ $this->entityType => $field_definition->getTargetBundle(), 'base_field_override' => $field_definition->id(), - 'destination' => $redirect_url->toString() + 'destination' => $redirect_url->toString(), ]); } else { @@ -439,7 +566,7 @@ class MetadataProfileController extends ControllerBase { } } else { - $edit_url = Url::fromRoute('entity.field_config.' . $this->entityTypeBundleOf . '_field_edit_form', [ + $edit_url = Url::fromRoute('entity.field_config.' . $this->entityTypeBundleOf . '_field_edit_form', [ $this->entityType => $field_definition->getTargetBundle(), 'field_config' => $field_definition->id(), 'destination' => $redirect_url->toString(), @@ -448,8 +575,7 @@ class MetadataProfileController extends ControllerBase { return $edit_url; } - private function getFieldDetailsFragmentLink(string $field_name, FieldDefinitionInterface $field_definition) - { + private function getFieldDetailsFragmentLink(string $field_name, FieldDefinitionInterface $field_definition) { $url = Url::fromRoute('', [], ['fragment' => $field_name]); return Link::fromTextAndUrl($field_definition->getLabel(), $url); } @@ -463,20 +589,21 @@ class MetadataProfileController extends ControllerBase { ]; } - - protected function getFieldTableRows(array $field_profile) { - $rows = [ + $rows = [ [$this->t('Type:'), $field_profile['type']], [$this->t('Required:'), $field_profile['required']], [$this->t('Repeatable:'), $field_profile['repeatable']], + ]; + if ($field_profile['auto_create']) { + $rows[] = [$this->t('Create new:'), $field_profile['auto_create']]; + } + if ($field_profile['target_bundles']) { + $rows[] = [ + $this->t('Target bundles:'), + $this->formatListForTable($field_profile['target_bundles']), ]; - if ($field_profile['auto_create']) { - $rows[] = [$this->t('Create new:'), $field_profile['auto_create']]; - } - if ($field_profile['target_bundles']) { - $rows[] = [$this->t('Target bundles:'), $this->formatListForTable($field_profile['target_bundles'])]; - } + } return $rows; } @@ -489,24 +616,22 @@ class MetadataProfileController extends ControllerBase { } return $type_label; } + protected function formatRequired(FieldDefinitionInterface $field_definition) { - if ($field_definition->isRequired()) { - return $this->t('Required'); - } - else { - return $this->t('Not required'); - } + $cardinality = $field_definition->isRequired(); + return $this->yesNoIcon($cardinality); } /** - * This does not work - most fields dont have a form weight specified in getDisplayOptions. + * This does not work - most fields dont have a form weight specified in + * getDisplayOptions. */ protected function getWeight(string $field_name, array $form) { if (isset($form[$field_name])) { $local_weight = $form[$field_name]['#weight']; if ($parent = $form['#group_children'][$field_name]) { $parent_weight = $this->getWeight($parent, $form); - return $this->combine_weights($parent_weight, $local_weight); + return $this->combineWeights($parent_weight, $local_weight); } else { return $local_weight; @@ -518,32 +643,35 @@ class MetadataProfileController extends ControllerBase { } protected function formatCardinality(FieldDefinitionInterface $field_definition) { - $cardinality = $field_definition->getFieldStorageDefinition()->getCardinality(); - if ($cardinality == 1) { - return $this->t('Not repeatable'); - } elseif ($cardinality < 0) { - return $this->t('Repeatable'); - } elseif ($cardinality > 1) { - return $this->t('Repeatable, limit :limit', [':limit' => $cardinality]); + $cardinality = $field_definition + ->getFieldStorageDefinition() + ->getCardinality(); + + if ($cardinality === 1) { + return $this->yesNoIcon($cardinality); } - return NULL; + + if ($cardinality === -1) { + return $this->yesNoIcon($cardinality); + } + + return $this->t('Yes (max @limit)', [ + '@limit' => $cardinality, + ]); } protected function formatCreateNew(FieldDefinitionInterface $field_definition) { - if (in_array($field_definition->getType(), ['entity_reference', 'typed_relation'])) { - $create_new_bundle = $field_definition->getSetting('handler_settings')['auto_create_bundle'] ? $create_new_bundle = $field_definition->getSetting('handler_settings')['auto_create_bundle'] : ''; - $create_new = $field_definition->getSetting('handler_settings')['auto_create'] || ''; - if ($create_new) { - return $create_new_bundle ? 'Create new (' . $create_new_bundle . ')' : 'Create new'; - } - else { - return $this->t('Do not create new'); - } - } - else { + if (!in_array($field_definition->getType(), [ + 'entity_reference', + 'typed_relation', + ], TRUE)) { return NULL; } + $settings = $field_definition->getSetting('handler_settings') ?? []; + $create_new = !empty($settings['auto_create']); + + return $this->yesNoIcon($create_new); } protected function formatTargetBundles(FieldDefinitionInterface $field_definition) { @@ -555,10 +683,14 @@ class MetadataProfileController extends ControllerBase { $target_bundles = $setting['target_bundles']; foreach ($target_bundles as $index => $bundle) { if ($handler == 'default:taxonomy_term') { - $bundle_config = $this->entityTypeManager()->getStorage('taxonomy_vocabulary')->load($bundle); + $bundle_config = $this->entityTypeManager() + ->getStorage('taxonomy_vocabulary') + ->load($bundle); } if ($handler == 'default:node') { - $bundle_config = $this->entityTypeManager()->getStorage('node_type')->load($bundle); + $bundle_config = $this->entityTypeManager() + ->getStorage('node_type') + ->load($bundle); } // TODO: ADD MEDIA, PARAGRAPHS ETC if ($bundle_config) { @@ -571,16 +703,18 @@ class MetadataProfileController extends ControllerBase { '#theme' => 'item_list', '#title' => $this->t('Target bundles:'), '#items' => $target_bundles, - '#type' => 'ul' - ]; + '#type' => 'ul', + ]; } protected function formatListForTable(array $list) { - return ['data' => [ - '#theme' => 'item_list', - '#items' => array_values($list), - '#type' => 'ul', - ]]; + return [ + 'data' => [ + '#theme' => 'item_list', + '#items' => array_values($list), + '#type' => 'ul', + ], + ]; } protected function formatSearchApi($field_profile) { @@ -613,9 +747,8 @@ class MetadataProfileController extends ControllerBase { } } - protected function getSearchApiField($field_setting_name, $field_setting, $index_config) { - $search_api_field_array = [ + $search_api_field_array = [ 'search_api_field_name' => $field_setting_name, 'search_api_field_label' => $field_setting['label'], 'property_path' => $field_setting['property_path'], @@ -626,24 +759,30 @@ class MetadataProfileController extends ControllerBase { if ($field_setting['property_path'] == 'aggregated_field') { $search_api_field_array['fields_included'] = $field_setting['configuration']['fields']; } - else if ($field_setting['property_path'] == 'edtf_year') { - $search_api_field_array['fields_included'] = $index_config->get('processor_settings')['edtf_year_only']['fields']; - } - else if ($field_setting['property_path'] == 'edtf') { - $search_api_field_array['fields_included'] = $index_config->get('processor_settings')['edtf_date_processor']['fields']; - } else { - $search_api_field_array['fields_included'] = []; + if ($field_setting['property_path'] == 'edtf_year') { + $search_api_field_array['fields_included'] = $index_config->get('processor_settings')['edtf_year_only']['fields']; + } + else { + if ($field_setting['property_path'] == 'edtf') { + $search_api_field_array['fields_included'] = $index_config->get('processor_settings')['edtf_date_processor']['fields']; + } + else { + $search_api_field_array['fields_included'] = []; + } + } } // Get facet info. $search_api_field_array['has_facet'] = FALSE; if ($this->moduleHandler()->moduleExists('facets')) { - $all_facets = \Drupal::entityTypeManager()->getStorage('facets_facet')->loadMultiple(); + $all_facets = \Drupal::entityTypeManager() + ->getStorage('facets_facet') + ->loadMultiple(); foreach ($all_facets as $facet_id => $facet) { // If facet uses this solr field... then add it to the array. if ($facet->get('field_identifier') == $field_setting_name) { - $search_api_field_array['has_facet'] = True; + $search_api_field_array['has_facet'] = TRUE; $search_api_field_array['facets'][] = [ 'field_identifier' => $facet->get('field_identifier'), 'facet_name' => $facet->getName(), @@ -661,6 +800,7 @@ class MetadataProfileController extends ControllerBase { protected function getBlockVisible($facet) { return ''; } + protected function formatSearchApiField($search_api_field_profile) { return [ 'data' => [ @@ -671,26 +811,48 @@ class MetadataProfileController extends ControllerBase { $this->formatListForTable($search_api_field_profile['fields_included']), $search_api_field_profile['index_name'], $search_api_field_profile['has_facet'] ? 'True' : '', - ] + ], ]; } - - - + /** + * Sanitizes table rows for CSV export. + * + * Converts renderable elements and objects into scalar values: + * - Link objects are converted to their text. + * - Arrays are flattened into pipe-delimited strings. + * + * @param array $rows + * A numerically indexed array of row arrays. + * + * @return array + * Sanitized rows suitable for fputcsv(). + */ public function sanitizeRowsForCSV($rows) { foreach ($rows as $row_key => $row) { foreach ($row as $element_key => $element) { if ($element instanceof Link) { $rows[$row_key][$element_key] = $element->getText(); } - else if (is_array($element)) { - $rows[$row_key][$element_key] = implode('|', $element); + else { + if (is_array($element)) { + $rows[$row_key][$element_key] = implode('|', $element); + } } } } return $rows; } + + /** + * Generates and returns a CSV download of the metadata profile. + * + * Writes a temporary CSV file and serves it using Drupal's + * file download controller. + * + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + * The file download response. + */ public function download() { $contentType = $this->entityBundle; $entityKey = $this->entityTypeBundleOf; @@ -714,8 +876,7 @@ class MetadataProfileController extends ControllerBase { return $this->fileDownloadController->download($request, 'temporary'); } - private function formatFacets(array $field_profile) - { + private function formatFacets(array $field_profile) { if (!$field_profile['search_api']['has_facet']) { return []; } @@ -738,7 +899,6 @@ class MetadataProfileController extends ControllerBase { $build['#rows'][] = $this->formatFacet($facet); } } - } if (count($build['#rows']) == 0) { return []; @@ -748,8 +908,7 @@ class MetadataProfileController extends ControllerBase { } } - private function formatFacet($facet) - { + private function formatFacet($facet) { return [ 'data' => [ $facet['field_identifier'], @@ -758,49 +917,95 @@ class MetadataProfileController extends ControllerBase { $facet['facet_source'], $facet['url_alias'], $facet['block_visible'], - ] + ], ]; } - private function addWeights(array $metadata_profile) - { + private function addWeights(array $metadata_profile) { if ($this->entityTypeBundleOf == 'node') { $node = \Drupal\node\Entity\Node::create(['type' => $this->entityBundle]); $form = \Drupal::service('entity.form_builder')->getForm($node); - foreach($metadata_profile as $field_name => $field_profile) { + foreach ($metadata_profile as $field_name => $field_profile) { $metadata_profile[$field_name]['weight'] = $this->getWeight($field_name, $form); } $weights = array_column($metadata_profile, 'weight'); array_multisort($weights, SORT_ASC, $metadata_profile); - } return $metadata_profile; } - private function combine_weights($parent_weight, $local_weight) - { - $parent_int = (string)floor($parent_weight); - $parent_fraction = (string)($parent_weight - $parent_int); + private function combineWeights($parent_weight, $local_weight) { + $parent_int = (string) floor($parent_weight); + $parent_fraction = (string) ($parent_weight - $parent_int); $parent_fraction = str_replace('0.', '', $parent_fraction); - $local_int = (string)floor($local_weight); - $local_fraction = (string)($local_weight - $local_int); + $local_int = (string) floor($local_weight); + $local_fraction = (string) ($local_weight - $local_int); $combined_weight = $parent_int; $combined_weight .= '.'; if ($parent_fraction) { # pad so its length is a multiple of 2 $length = mb_strlen($parent_fraction); - if (fmod($length, 2) != 0){ + if (fmod($length, 2) != 0) { $parent_fraction .= '0'; } $combined_weight .= $parent_fraction; } $combined_weight .= str_pad($local_int, 2, '0', STR_PAD_LEFT); if ($local_fraction) { - $a=2; + $a = 2; } - return (float)$combined_weight; + return (float) $combined_weight; + } + + /** + * Gets usage count for a field. + * + * For configurable fields, counts rows in the field table. + * For base fields, counts nodes of the current bundle. + * + * @param string $field_name + * The field machine name. + * + * @return int + * Number of usages. + */ + private function getUsageCount($field_name) { + if (str_starts_with($field_name, 'field_')) { + $connection = $this->database; + $count = $connection->select("node__{$field_name}", 'f') + ->countQuery() + ->execute() + ->fetchField(); + return $count; + } + return $this->entityTypeManagerService + ->getStorage('node') + ->getQuery() + ->accessCheck(FALSE) + ->condition('type', $this->entityBundle) + ->count() + ->execute(); + } + + /** + * Returns a render array representing a yes/no icon. + * + * @param bool $value + * TRUE for yes, FALSE for no. + * + * @return array + * Render array containing markup for the icon. + */ + protected function yesNoIcon(bool $value): array { + return [ + 'data' => [ + '#markup' => $value + ? '' + : '', + ], + ]; } }