From 7e8a995f44aa405c3f139f5509beb714cc8f81ab Mon Sep 17 00:00:00 2001 From: loze <loze@ilgstudio.com> Date: Thu, 20 Mar 2025 16:10:34 -0700 Subject: [PATCH 1/9] Don't save aggregated vote results for fields that do not exist on the entity type --- votingapi_widgets.module | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/votingapi_widgets.module b/votingapi_widgets.module index 83be818..65a0f6d 100644 --- a/votingapi_widgets.module +++ b/votingapi_widgets.module @@ -91,3 +91,18 @@ function votingapi_widgets_preprocess_votingapi_widgets_summary(array &$variable $variables['results']['vote_field_count'] = 0; } } + +/** + * Implements hook_votingapi_results_alter(). + * + * Removes aggregated vote results for votingapi_widget fields that do not + * exist on the entity type, preventing unnecessary data storage. + */ +function votingapi_widgets_votingapi_results_alter(array &$vote_results, $entity_type, $entity_id) { + foreach ($vote_results as $key => $result) { + $function_id = explode(':', $result['function']); + if (count($function_id) > 1 && str_starts_with($function_id[0], 'vote_field_') && !str_starts_with($function_id[1], $entity_type . '.')) { + unset($vote_results[$key]); + } + } +} -- GitLab From 1cb591b7ebd144a6f0c6192f7b0d27c42b5b6603 Mon Sep 17 00:00:00 2001 From: Tim Rohaly <tr@202830.no-reply.drupal.org> Date: Thu, 20 Mar 2025 21:36:23 -0700 Subject: [PATCH 2/9] Move code into OO hook with BC legacy hook implementation --- src/Hook/VotingApiWidgetsVotingApiHooks.php | 28 +++++++++++++++++++++ votingapi_widgets.module | 24 +++++++----------- votingapi_widgets.services.yml | 3 +++ 3 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 src/Hook/VotingApiWidgetsVotingApiHooks.php diff --git a/src/Hook/VotingApiWidgetsVotingApiHooks.php b/src/Hook/VotingApiWidgetsVotingApiHooks.php new file mode 100644 index 0000000..b18e51a --- /dev/null +++ b/src/Hook/VotingApiWidgetsVotingApiHooks.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\votingapi_widgets\Hook; + +/** + * Implementations of Voting API module hooks. + */ +final class VotingApiVotingApiHooks { + + /** + * Implements hook_votingapi_results_alter(). + * + * Removes aggregated vote results for votingapi_widget fields that do not + * exist on the entity type, preventing unnecessary data storage. + */ + #[Hook('votingapi_results_alter')] + public function votingapiResultsAlter(array &$vote_results, string $entity_type, string|int $entity_id): void { + foreach ($vote_results as $key => $result) { + $function_id = explode(':', $result['function']); + if (count($function_id) > 1 && str_starts_with($function_id[0], 'vote_field_') && !str_starts_with($function_id[1], $entity_type . '.')) { + unset($vote_results[$key]); + } + } + } + +} diff --git a/votingapi_widgets.module b/votingapi_widgets.module index 65a0f6d..79439e3 100644 --- a/votingapi_widgets.module +++ b/votingapi_widgets.module @@ -13,6 +13,7 @@ use Drupal\field\Entity\FieldConfig; use Drupal\votingapi_widgets\Hook\VotingApiWidgetsEntityHooks; use Drupal\votingapi_widgets\Hook\VotingApiWidgetsFormHooks; use Drupal\votingapi_widgets\Hook\VotingApiWidgetsHelpHooks; +use Drupal\votingapi_widgets\Hook\VotingApiWidgetsVotingApiHooks; /** * Implements hook_help(). @@ -46,6 +47,14 @@ function votingapi_widgets_form_field_config_edit_form_alter(&$form, FormStateIn \Drupal::service(VotingApiWidgetsFormHooks::class)->fieldConfigEditFormAlter($form, $form_state); } +/** + * Implements hook_votingapi_results_alter(). + */ +#[LegacyHook] +function votingapi_widgets_votingapi_results_alter(array &$vote_results, string $entity_type, string|int $entity_id) { + \Drupal::service(VotingApiWidgetsVotingApiHooks::class)->votingApiResultsAlter($results, $entity_type, $entity_id); +} + /** * Implements hook_theme_suggestions_alter(). */ @@ -91,18 +100,3 @@ function votingapi_widgets_preprocess_votingapi_widgets_summary(array &$variable $variables['results']['vote_field_count'] = 0; } } - -/** - * Implements hook_votingapi_results_alter(). - * - * Removes aggregated vote results for votingapi_widget fields that do not - * exist on the entity type, preventing unnecessary data storage. - */ -function votingapi_widgets_votingapi_results_alter(array &$vote_results, $entity_type, $entity_id) { - foreach ($vote_results as $key => $result) { - $function_id = explode(':', $result['function']); - if (count($function_id) > 1 && str_starts_with($function_id[0], 'vote_field_') && !str_starts_with($function_id[1], $entity_type . '.')) { - unset($vote_results[$key]); - } - } -} diff --git a/votingapi_widgets.services.yml b/votingapi_widgets.services.yml index 4c0cc1a..ea63a36 100644 --- a/votingapi_widgets.services.yml +++ b/votingapi_widgets.services.yml @@ -18,3 +18,6 @@ services: Drupal\votingapi_widgets\Hook\VotingApiWidgetsHelpHooks: class: \Drupal\votingapi_widgets\Hook\VotingApiWidgetsHelpHooks autowire: true + Drupal\votingapi_widgets\Hook\VotingApiWidgetsVotingApiHooks: + class: \Drupal\votingapi_widgets\Hook\VotingApiWidgetsVotingApiHooks + autowire: true -- GitLab From a2e768d3855b12aaad5dbe497372b7f3c9f1243b Mon Sep 17 00:00:00 2001 From: Tim Rohaly <tr@202830.no-reply.drupal.org> Date: Thu, 20 Mar 2025 21:42:22 -0700 Subject: [PATCH 3/9] Forgot a use statement --- src/Hook/VotingApiWidgetsVotingApiHooks.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Hook/VotingApiWidgetsVotingApiHooks.php b/src/Hook/VotingApiWidgetsVotingApiHooks.php index b18e51a..60fb749 100644 --- a/src/Hook/VotingApiWidgetsVotingApiHooks.php +++ b/src/Hook/VotingApiWidgetsVotingApiHooks.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Drupal\votingapi_widgets\Hook; +use Drupal\Core\Hook\Attribute\Hook; + /** * Implementations of Voting API module hooks. */ -- GitLab From 61e8f01b1d4ffc5b479c4e86ad62c127cc34d316 Mon Sep 17 00:00:00 2001 From: Tim Rohaly <tr@202830.no-reply.drupal.org> Date: Thu, 20 Mar 2025 21:45:57 -0700 Subject: [PATCH 4/9] Fix naming --- src/Hook/VotingApiWidgetsVotingApiHooks.php | 2 +- votingapi_widgets.module | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Hook/VotingApiWidgetsVotingApiHooks.php b/src/Hook/VotingApiWidgetsVotingApiHooks.php index 60fb749..5aa655c 100644 --- a/src/Hook/VotingApiWidgetsVotingApiHooks.php +++ b/src/Hook/VotingApiWidgetsVotingApiHooks.php @@ -9,7 +9,7 @@ use Drupal\Core\Hook\Attribute\Hook; /** * Implementations of Voting API module hooks. */ -final class VotingApiVotingApiHooks { +final class VotingApiWidgetsVotingApiHooks { /** * Implements hook_votingapi_results_alter(). diff --git a/votingapi_widgets.module b/votingapi_widgets.module index 79439e3..6774fe4 100644 --- a/votingapi_widgets.module +++ b/votingapi_widgets.module @@ -52,7 +52,7 @@ function votingapi_widgets_form_field_config_edit_form_alter(&$form, FormStateIn */ #[LegacyHook] function votingapi_widgets_votingapi_results_alter(array &$vote_results, string $entity_type, string|int $entity_id) { - \Drupal::service(VotingApiWidgetsVotingApiHooks::class)->votingApiResultsAlter($results, $entity_type, $entity_id); + \Drupal::service(VotingApiWidgetsVotingApiHooks::class)->votingApiResultsAlter($vote_results, $entity_type, $entity_id); } /** -- GitLab From 7d10db74a467cd7c0294a6979ca0d076001cf40c Mon Sep 17 00:00:00 2001 From: loze <loze@ilgstudio.com> Date: Fri, 21 Mar 2025 22:52:47 -0700 Subject: [PATCH 5/9] Checking for other scenarios where results are sometimes created for non-existing entity/bundle/field combos. --- src/Hook/VotingApiWidgetsVotingApiHooks.php | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Hook/VotingApiWidgetsVotingApiHooks.php b/src/Hook/VotingApiWidgetsVotingApiHooks.php index 5aa655c..337cc87 100644 --- a/src/Hook/VotingApiWidgetsVotingApiHooks.php +++ b/src/Hook/VotingApiWidgetsVotingApiHooks.php @@ -19,10 +19,29 @@ final class VotingApiWidgetsVotingApiHooks { */ #[Hook('votingapi_results_alter')] public function votingapiResultsAlter(array &$vote_results, string $entity_type, string|int $entity_id): void { + $instances = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('voting_api_field'); foreach ($vote_results as $key => $result) { $function_id = explode(':', $result['function']); - if (count($function_id) > 1 && str_starts_with($function_id[0], 'vote_field_') && !str_starts_with($function_id[1], $entity_type . '.')) { - unset($vote_results[$key]); + // A votingapi_widget field function starts with 'vote_field_'. + if (str_starts_with($function_id[0], 'vote_field_') && isset($function_id[1])) { + // The second part is the derivative id 'ENTITY_TYPE.FIELD_NAME'. + $derivative_id = explode('.', $function_id[1]); + if ($entity_type != $derivative_id[0]) { + // This entity_type does not match. + unset($vote_results[$key]); + } + elseif (empty($instances[$entity_type][$derivative_id[1]])) { + // This field_name does not exist for the entity. + unset($vote_results[$key]); + } + else { + $bundles = $instances[$entity_type][$derivative_id[1]]['bundles']; + $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id); + if (!$entity || !isset($bundles[$entity->bundle()])) { + // The bundle does not match the entity. + unset($vote_results[$key]); + } + } } } } -- GitLab From 9a7210c4f819a5df66df5d0f21d45cae082c5998 Mon Sep 17 00:00:00 2001 From: loze <loze@ilgstudio.com> Date: Fri, 21 Mar 2025 22:57:02 -0700 Subject: [PATCH 6/9] Added update hook to clean up the results table. --- votingapi_widgets.install | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/votingapi_widgets.install b/votingapi_widgets.install index af35922..aec34fe 100644 --- a/votingapi_widgets.install +++ b/votingapi_widgets.install @@ -25,3 +25,51 @@ function votingapi_widgets_requirements($phase) { return $requirements; } + +/** + * Recalculate vote results for all voted entities. + */ +function votingapi_widgets_update_10001(&$sandbox) { + $votingapiResultsManager = \Drupal::service('plugin.manager.votingapi.resultfunction'); + + // Query the votes table to get the entities in need of recalculating. + $query = \Drupal::database()->select('votingapi_vote', 'v') + ->fields('v', ['entity_type', 'entity_id', 'type']) + ->groupBy('entity_type') + ->groupBy('entity_id') + ->groupBy('type'); + + // Recalculate results in a batch. + if (!isset($sandbox['total'])) { + $sandbox['total'] = $query->countQuery()->execute()->fetchField(); + $sandbox['current'] = 0; + } + + $entities_per_batch = 20; + + $rows = $query + ->range($sandbox['current'], $entities_per_batch) + ->execute() + ->fetchAll(); + + if ($rows) { + foreach ($rows as $row) { + $votingapiResultsManager->recalculateResults( + $row->entity_type, + $row->entity_id, + $row->type + ); + $sandbox['current']++; + } + if ($sandbox['total'] == 0) { + $sandbox['#finished'] = 1; + } + else { + $sandbox['#finished'] = ($sandbox['current'] / $sandbox['total']); + } + return t(':count of :total voted entities processed.', [ + ':count' => $sandbox['current'], + ':total' => $sandbox['total'], + ]); + } +} -- GitLab From 14221a00e01abed8c796dc3a6a85a58a2f7d8f2e Mon Sep 17 00:00:00 2001 From: loze <loze@ilgstudio.com> Date: Fri, 21 Mar 2025 23:17:17 -0700 Subject: [PATCH 7/9] Use dependency injection in hooks class --- src/Hook/VotingApiWidgetsVotingApiHooks.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Hook/VotingApiWidgetsVotingApiHooks.php b/src/Hook/VotingApiWidgetsVotingApiHooks.php index 337cc87..f4a9e27 100644 --- a/src/Hook/VotingApiWidgetsVotingApiHooks.php +++ b/src/Hook/VotingApiWidgetsVotingApiHooks.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Drupal\votingapi_widgets\Hook; +use Drupal\Core\Entity\EntityFieldManagerInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Hook\Attribute\Hook; /** @@ -11,6 +13,19 @@ use Drupal\Core\Hook\Attribute\Hook; */ final class VotingApiWidgetsVotingApiHooks { + /** + * Constructs a new VotingApiWidgetsVotingApiHooks service. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity_type.manager service. + * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entityFieldManager + * The entity_field.manager service. + */ + public function __construct( + protected EntityTypeManagerInterface $entityTypeManager, + protected EntityFieldManagerInterface $entityFieldManager, + ) {} + /** * Implements hook_votingapi_results_alter(). * @@ -19,7 +34,7 @@ final class VotingApiWidgetsVotingApiHooks { */ #[Hook('votingapi_results_alter')] public function votingapiResultsAlter(array &$vote_results, string $entity_type, string|int $entity_id): void { - $instances = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('voting_api_field'); + $instances = $this->entityFieldManager->getFieldMapByFieldType('voting_api_field'); foreach ($vote_results as $key => $result) { $function_id = explode(':', $result['function']); // A votingapi_widget field function starts with 'vote_field_'. @@ -36,7 +51,7 @@ final class VotingApiWidgetsVotingApiHooks { } else { $bundles = $instances[$entity_type][$derivative_id[1]]['bundles']; - $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($entity_id); + $entity = $this->entityTypeManager->getStorage($entity_type)->load($entity_id); if (!$entity || !isset($bundles[$entity->bundle()])) { // The bundle does not match the entity. unset($vote_results[$key]); -- GitLab From ac976029cd6f174e1443db8021a89351b8c69494 Mon Sep 17 00:00:00 2001 From: loze <loze@ilgstudio.com> Date: Fri, 21 Mar 2025 23:53:30 -0700 Subject: [PATCH 8/9] Clarify comment. --- src/Hook/VotingApiWidgetsVotingApiHooks.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Hook/VotingApiWidgetsVotingApiHooks.php b/src/Hook/VotingApiWidgetsVotingApiHooks.php index f4a9e27..f4b036a 100644 --- a/src/Hook/VotingApiWidgetsVotingApiHooks.php +++ b/src/Hook/VotingApiWidgetsVotingApiHooks.php @@ -22,9 +22,10 @@ final class VotingApiWidgetsVotingApiHooks { * The entity_field.manager service. */ public function __construct( - protected EntityTypeManagerInterface $entityTypeManager, + protected EntityTypeManagerInterface $entityTypeManager, protected EntityFieldManagerInterface $entityFieldManager, - ) {} + ) { + } /** * Implements hook_votingapi_results_alter(). @@ -53,7 +54,7 @@ final class VotingApiWidgetsVotingApiHooks { $bundles = $instances[$entity_type][$derivative_id[1]]['bundles']; $entity = $this->entityTypeManager->getStorage($entity_type)->load($entity_id); if (!$entity || !isset($bundles[$entity->bundle()])) { - // The bundle does not match the entity. + // The field is not present on this bundle. unset($vote_results[$key]); } } -- GitLab From 5fe2787499ce5ad3acc8002f9a1082b631d6a772 Mon Sep 17 00:00:00 2001 From: loze <loze@ilgstudio.com> Date: Wed, 2 Apr 2025 20:33:01 -0700 Subject: [PATCH 9/9] Exclude results where the vote type does not match the vote type set for the field. --- src/Hook/VotingApiWidgetsVotingApiHooks.php | 10 ++++++++++ votingapi_widgets.install | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Hook/VotingApiWidgetsVotingApiHooks.php b/src/Hook/VotingApiWidgetsVotingApiHooks.php index f4b036a..105b9bf 100644 --- a/src/Hook/VotingApiWidgetsVotingApiHooks.php +++ b/src/Hook/VotingApiWidgetsVotingApiHooks.php @@ -57,6 +57,16 @@ final class VotingApiWidgetsVotingApiHooks { // The field is not present on this bundle. unset($vote_results[$key]); } + else { + // Check for vote types not matching the one set for this field. + $field_storage = $this->entityFieldManager->getFieldStorageDefinitions($entity_type); + if (isset($field_storage[$derivative_id[1]])) { + $field_settings = $field_storage[$derivative_id[1]]->getSettings(); + if ($result['type'] != $field_settings['vote_type']) { + unset($vote_results[$key]); + } + } + } } } } diff --git a/votingapi_widgets.install b/votingapi_widgets.install index aec34fe..07fe34f 100644 --- a/votingapi_widgets.install +++ b/votingapi_widgets.install @@ -45,7 +45,7 @@ function votingapi_widgets_update_10001(&$sandbox) { $sandbox['current'] = 0; } - $entities_per_batch = 20; + $entities_per_batch = 50; $rows = $query ->range($sandbox['current'], $entities_per_batch) -- GitLab