diff --git a/modules/access_badges/access_badges.info.yml b/modules/access_badges/access_badges.info.yml index 4590c788..288a7281 100644 --- a/modules/access_badges/access_badges.info.yml +++ b/modules/access_badges/access_badges.info.yml @@ -4,6 +4,5 @@ type: module core_version_requirement: ^10 || ^11 package: Custom dependencies: - - access - - ultimate_cron - + - drupal:access + - drupal:ultimate_cron diff --git a/modules/access_badges/access_badges.install b/modules/access_badges/access_badges.install index ca444709..300ee034 100644 --- a/modules/access_badges/access_badges.install +++ b/modules/access_badges/access_badges.install @@ -5,10 +5,12 @@ * Access Badges updates. */ +use Drupal\user\Entity\User; + /** * Add Badges to taxonomy. */ -function access_badges_install() { +function access_badges_install(): void { $terms = [ 'New to CSSN', 'New to ACCESS', @@ -79,7 +81,6 @@ function access_badges_install() { // Set badges for users. $badge_tools->setBadges($scipe, $users); - // Set Knowledge Base Resource Contributor badges. $query = \Drupal::database()->select('webform_submission', 'ws'); $query->fields('ws', ['sid', 'uid']); @@ -91,7 +92,8 @@ function access_badges_install() { foreach ($wf_submissions as $submission) { if (!isset($authors[$submission->uid])) { $authors[$submission->uid] = 1; - } else { + } + else { $authors[$submission->uid]++; } } @@ -101,18 +103,20 @@ function access_badges_install() { $kb_ten = \Drupal::service('access_badges.badgeTools')->getBadgeTid('KB Resource Contributor (10)'); foreach ($authors as $uid => $count) { - $user = \Drupal\user\Entity\User::load($uid); + $user = User::load($uid); $badgetid = $user->get('field_user_badges')->getValue(); if ($count < 4) { $badgetid[] = ['target_id' => $kb_one]; $user->set('field_user_badges', $badgetid); $user->save(); - } elseif ($count > 5 && $count < 10) { + } + elseif ($count > 5 && $count < 10) { $badgetid[] = ['target_id' => $kb_five]; $user->set('field_user_badges', $badgetid); $user->save(); - } elseif ($count >= 10) { + } + elseif ($count >= 10) { $badgetid[] = ['target_id' => $kb_ten]; $user->set('field_user_badges', $badgetid); $user->save(); @@ -129,7 +133,6 @@ function access_badges_install() { $badge_tools->fieldToBadge('field_mentor', 'CCMNet Engagement Participant', 'mentorship_engagement'); } - /** * Sort all user badges to match taxonomy term weight/name order. */ @@ -141,7 +144,7 @@ function access_badges_update_10001(): string { /** * Create access_badges_pending table for CSV badge assignment queue. */ -function access_badges_update_10002() { +function access_badges_update_10002(): void { $schema = \Drupal::database()->schema(); if (!$schema->tableExists('access_badges_pending')) { $schema->createTable('access_badges_pending', _access_badges_pending_schema()); @@ -150,8 +153,11 @@ function access_badges_update_10002() { /** * Returns the schema definition for the access_badges_pending table. + * + * @return array + * The schema definition. */ -function _access_badges_pending_schema() { +function _access_badges_pending_schema(): array { return [ 'description' => 'Pending badge assignments from CSV uploads.', 'fields' => [ @@ -234,13 +240,12 @@ function _access_badges_pending_schema() { /** * Remove Badges from taxonomy. */ -function access_badges_uninstall() { +function access_badges_uninstall(): void { // Drop the pending badges table. \Drupal::database()->schema()->dropTable('access_badges_pending'); \Drupal::database()->truncate('user__field_user_badges')->execute(); - $tids = \Drupal::entityQuery('taxonomy_term') ->condition('vid', 'badges') ->accessCheck(FALSE) diff --git a/modules/access_badges/access_badges.module b/modules/access_badges/access_badges.module index 95444c94..c7d6b296 100644 --- a/modules/access_badges/access_badges.module +++ b/modules/access_badges/access_badges.module @@ -2,18 +2,23 @@ /** * @file + * Access Badges hooks. */ +use Drupal\Core\Url; +use Drupal\user\Entity\User; use Drupal\Core\Form\FormStateInterface; use Drupal\user\UserInterface; /** * Implements hook_form_alter(). + * + * @phpstan-param array $form */ -function access_badges_form_alter(&$form, FormStateInterface $form_state, $form_id) { +function access_badges_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void { if ($form_id == 'user_form' || $form_id == 'user_register_form') { // Check if user is an administator. - $user = \Drupal\user\Entity\User::load(\Drupal::currentUser()->id()); + $user = User::load(\Drupal::currentUser()->id()); $roles = $user->getRoles(); if (!in_array('administrator', $roles)) { $form['#attached']['library'][] = 'access_badges/hide_ief_button'; @@ -26,20 +31,20 @@ function access_badges_form_alter(&$form, FormStateInterface $form_state, $form_ /** * Implements hook_user_presave(). */ -function access_badges_user_presave(UserInterface $user) { +function access_badges_user_presave(UserInterface $user): void { \Drupal::service('access_badges.badge_sorter')->sortUserBadges($user); } /** * Implements hook_cron(). */ -function access_badges_cron() { +function access_badges_cron(): void { // Run this cron job every day at 4am. $currentTime = \Drupal::time()->getCurrentTime(); $hour = date('H', $currentTime); $min = date('i', $currentTime); - // Cron runs every 10 minutes + // Cron runs every 10 minutes. if ($hour == 4 && $min < 20) { $badge_tools = \Drupal::service('access_badges.badgeTools'); // Get 'New to ACCESS' term id. @@ -79,7 +84,7 @@ function access_badges_cron() { * Auto-assigns badges from pending queue when a new user registers * with a matching email address (exact match, case-insensitive via collation). */ -function access_badges_user_insert(UserInterface $account) { +function access_badges_user_insert(UserInterface $account): void { $email = $account->getEmail(); if (empty($email)) { return; @@ -121,8 +126,11 @@ function access_badges_user_insert(UserInterface $account) { /** * Implements hook_mail(). + * + * @phpstan-param array $message + * @phpstan-param array $params */ -function access_badges_mail($key, &$message, $params) { +function access_badges_mail(string $key, array &$message, array $params): void { if ($key === 'pending_match_digest') { $message['subject'] = t('Badge Assignments: New matches need review'); @@ -140,7 +148,7 @@ function access_badges_mail($key, &$message, $params) { } $lines[] = ''; - $review_url = \Drupal\Core\Url::fromRoute('access_badges.review', [], ['absolute' => TRUE])->toString(); + $review_url = Url::fromRoute('access_badges.review', [], ['absolute' => TRUE])->toString(); $lines[] = t('Review these matches at: @url', ['@url' => $review_url]); $message['body'] = $lines; diff --git a/modules/access_badges/access_badges.services.yml b/modules/access_badges/access_badges.services.yml index 9967373e..4775975c 100644 --- a/modules/access_badges/access_badges.services.yml +++ b/modules/access_badges/access_badges.services.yml @@ -1,6 +1,10 @@ services: access_badges.badgeTools: class: Drupal\access_badges\Plugin\BadgeTools + arguments: + - '@entity_type.manager' + - '@database' + - '@datetime.time' access_badges.badge_sorter: class: Drupal\access_badges\Service\BadgeSorter @@ -17,3 +21,4 @@ services: - '@database' - '@entity_type.manager' - '@access_badges.badgeTools' + - '@datetime.time' diff --git a/modules/access_badges/src/Controller/BadgePendingController.php b/modules/access_badges/src/Controller/BadgePendingController.php index a7d995aa..7b243330 100644 --- a/modules/access_badges/src/Controller/BadgePendingController.php +++ b/modules/access_badges/src/Controller/BadgePendingController.php @@ -3,16 +3,18 @@ namespace Drupal\access_badges\Controller; use Drupal\access_badges\Plugin\BadgeTools; +use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Database\Connection; +use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; -use Drupal\Core\Url; +use Symfony\Component\HttpFoundation\RequestStack; /** * Controller for badge pending action routes. */ -class BadgePendingController extends ControllerBase { +final class BadgePendingController extends ControllerBase { /** * The database connection. @@ -28,35 +30,53 @@ class BadgePendingController extends ControllerBase { */ protected $badgeTools; + /** + * The time service. + * + * @var \Drupal\Component\Datetime\TimeInterface + */ + protected $time; + + /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + /** * Constructs a BadgePendingController. */ - public function __construct(Connection $database, BadgeTools $badge_tools) { + public function __construct(Connection $database, BadgeTools $badge_tools, TimeInterface $time, RequestStack $request_stack) { $this->database = $database; $this->badgeTools = $badge_tools; + $this->time = $time; + $this->requestStack = $request_stack; } /** * {@inheritdoc} */ - public static function create(ContainerInterface $container) { + public static function create(ContainerInterface $container): static { return new static( $container->get('database'), - $container->get('access_badges.badgeTools') + $container->get('access_badges.badgeTools'), + $container->get('datetime.time'), + $container->get('request_stack') ); } /** * Redirects the Badge Assignments landing page to the Pending sub-tab. */ - public function badgeAssignments() { + public function badgeAssignments(): RedirectResponse { return new RedirectResponse(Url::fromRoute('access_badges.pending')->toString()); } /** * Deletes a pending row. */ - public function delete($id) { + public function delete(string $id): RedirectResponse { $this->database->delete('access_badges_pending') ->condition('id', $id) ->execute(); @@ -67,8 +87,8 @@ public function delete($id) { /** * Assigns a badge from an inline possible match. */ - public function assign($uid, $badge_tid, $vocabulary, $email) { - $assigned = $this->badgeTools->assignBadgeToUser($uid, $badge_tid, $vocabulary); + public function assign(string $uid, string $badge_tid, string $vocabulary, string $email): RedirectResponse { + $assigned = $this->badgeTools->assignBadgeToUser((int) $uid, (int) $badge_tid, $vocabulary); if ($assigned) { $this->messenger()->addStatus($this->t('Badge assigned successfully.')); } @@ -88,11 +108,11 @@ public function assign($uid, $badge_tid, $vocabulary, $email) { /** * Sends an inline possible match to the pending queue. */ - public function sendToPending($email, $first_name, $last_name, $organization = '') { + public function sendToPending(string $email, string $first_name, string $last_name, string $organization = ''): RedirectResponse { // This route requires badge_tid and vocabulary from the query string. - $request = \Drupal::request(); - $badge_tid = $request->query->get('badge_tid', 0); - $vocabulary = $request->query->get('vocabulary', 'badges'); + $request = $this->requestStack->getCurrentRequest(); + $badge_tid = $request ? $request->query->get('badge_tid', 0) : 0; + $vocabulary = $request ? $request->query->get('vocabulary', 'badges') : 'badges'; if (!empty($email) && !empty($badge_tid)) { // Check for duplicate. @@ -112,7 +132,7 @@ public function sendToPending($email, $first_name, $last_name, $organization = ' 'organization' => $organization, 'badge_tid' => $badge_tid, 'vocabulary' => $vocabulary, - 'created' => \Drupal::time()->getRequestTime(), + 'created' => $this->time->getRequestTime(), 'status' => 'pending', ]) ->execute(); diff --git a/modules/access_badges/src/Form/BadgeCsvUploadForm.php b/modules/access_badges/src/Form/BadgeCsvUploadForm.php index e98f6e0c..f995d3c8 100644 --- a/modules/access_badges/src/Form/BadgeCsvUploadForm.php +++ b/modules/access_badges/src/Form/BadgeCsvUploadForm.php @@ -3,15 +3,18 @@ namespace Drupal\access_badges\Form; use Drupal\access_badges\Service\CsvProcessor; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** * CSV upload form for bulk badge assignment. */ -class BadgeCsvUploadForm extends FormBase { +final class BadgeCsvUploadForm extends FormBase { /** * The CSV processor service. @@ -20,19 +23,48 @@ class BadgeCsvUploadForm extends FormBase { */ protected $csvProcessor; + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The file system service. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + + /** + * The messenger service. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + /** * Constructs a BadgeCsvUploadForm. */ - public function __construct(CsvProcessor $csv_processor) { + public function __construct(CsvProcessor $csv_processor, EntityTypeManagerInterface $entity_type_manager, FileSystemInterface $file_system, MessengerInterface $messenger) { $this->csvProcessor = $csv_processor; + $this->entityTypeManager = $entity_type_manager; + $this->fileSystem = $file_system; + $this->messenger = $messenger; } /** * {@inheritdoc} + * + * @return static */ - public static function create(ContainerInterface $container) { + public static function create(ContainerInterface $container): static { return new static( - $container->get('access_badges.csv_processor') + $container->get('access_badges.csv_processor'), + $container->get('entity_type.manager'), + $container->get('file_system'), + $container->get('messenger') ); } @@ -45,8 +77,16 @@ public function getFormId() { /** * {@inheritdoc} + * + * @param array $form + * The form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * The form. */ - public function buildForm(array $form, FormStateInterface $form_state) { + public function buildForm(array $form, FormStateInterface $form_state): array { $form['vocabulary'] = [ '#type' => 'select', '#title' => $this->t('Vocabulary'), @@ -93,17 +133,31 @@ public function buildForm(array $form, FormStateInterface $form_state) { /** * AJAX callback to update badge options. + * + * @param array $form + * The form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * The badge_tid form element. */ - public function updateBadgeOptions(array &$form, FormStateInterface $form_state) { + public function updateBadgeOptions(array &$form, FormStateInterface $form_state): array { return $form['badge_tid']; } /** * Gets badge term options for a vocabulary. + * + * @param string $vocabulary + * The vocabulary machine name. + * + * @return array + * The badge term options, keyed by term ID. */ - protected function getBadgeOptions($vocabulary) { + protected function getBadgeOptions(string $vocabulary): array { $options = []; - $terms = \Drupal::entityTypeManager() + $terms = $this->entityTypeManager ->getStorage('taxonomy_term') ->loadByProperties(['vid' => $vocabulary]); foreach ($terms as $term) { @@ -115,8 +169,13 @@ protected function getBadgeOptions($vocabulary) { /** * {@inheritdoc} + * + * @param array $form + * The form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. */ - public function validateForm(array &$form, FormStateInterface $form_state) { + public function validateForm(array &$form, FormStateInterface $form_state): void { $files = $this->getRequest()->files->get('files'); $file = $files['csv_file'] ?? NULL; @@ -155,8 +214,13 @@ public function validateForm(array &$form, FormStateInterface $form_state) { /** * {@inheritdoc} + * + * @param array $form + * The form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. */ - public function submitForm(array &$form, FormStateInterface $form_state) { + public function submitForm(array &$form, FormStateInterface $form_state): void { $file_path = $form_state->get('csv_file_path'); $header_map = $form_state->get('csv_header_map'); $badge_tid = $form_state->getValue('badge_tid'); @@ -185,8 +249,20 @@ public function submitForm(array &$form, FormStateInterface $form_state) { /** * Processes CSV rows synchronously. + * + * @param string $file_path + * Path to the CSV file. + * @param array $header_map + * Mapping of column index to header name. + * @param int|string $badge_tid + * The badge term ID. + * @param string $vocabulary + * The vocabulary machine name. + * + * @return array>> + * The processing results, keyed by result type. */ - protected function processSync($file_path, $header_map, $badge_tid, $vocabulary) { + protected function processSync($file_path, array $header_map, $badge_tid, $vocabulary): array { $results = [ 'assigned' => [], 'already_assigned' => [], @@ -213,9 +289,20 @@ protected function processSync($file_path, $header_map, $badge_tid, $vocabulary) /** * Launches batch processing for large CSVs. + * + * @param string $file_path + * Path to the CSV file. + * @param array $header_map + * Mapping of column index to header name. + * @param int|string $badge_tid + * The badge term ID. + * @param string $vocabulary + * The vocabulary machine name. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. */ - protected function processBatch($file_path, $header_map, $badge_tid, $vocabulary, FormStateInterface $form_state) { - $temp_path = \Drupal::service('file_system')->getTempDirectory() . '/badge_csv_' . time() . '.csv'; + protected function processBatch($file_path, array $header_map, $badge_tid, $vocabulary, FormStateInterface $form_state): void { + $temp_path = $this->fileSystem->getTempDirectory() . '/badge_csv_' . time() . '.csv'; copy($file_path, $temp_path); $batch = [ @@ -234,8 +321,19 @@ protected function processBatch($file_path, $header_map, $badge_tid, $vocabulary /** * Batch operation callback. + * + * @param string $file_path + * Path to the CSV file. + * @param array $header_map + * Mapping of column index to header name. + * @param int|string $badge_tid + * The badge term ID. + * @param string $vocabulary + * The vocabulary machine name. + * @param array $context + * The batch context. */ - public static function batchProcess($file_path, $header_map, $badge_tid, $vocabulary, &$context) { + public static function batchProcess($file_path, array $header_map, $badge_tid, $vocabulary, array &$context): void { $csv_processor = \Drupal::service('access_badges.csv_processor'); if (!isset($context['sandbox']['progress'])) { @@ -290,8 +388,15 @@ public static function batchProcess($file_path, $header_map, $badge_tid, $vocabu /** * Batch finished callback. + * + * @param bool $success + * Whether the batch completed successfully. + * @param array $results + * The batch results. + * @param array $operations + * The batch operations. */ - public static function batchFinished($success, $results, $operations) { + public static function batchFinished($success, array $results, array $operations): void { if ($success) { $assigned_count = count($results['assigned'] ?? []); $pending_count = count($results['pending'] ?? []); @@ -314,8 +419,14 @@ public static function batchFinished($success, $results, $operations) { /** * Builds the results display render array. + * + * @param array $results + * The processing results. + * + * @return array + * The results render array. */ - protected function buildResultsDisplay(array $results) { + protected function buildResultsDisplay(array $results): array { $build = [ '#type' => 'container', '#attributes' => ['class' => ['badge-csv-results']], @@ -412,8 +523,18 @@ protected function buildResultsDisplay(array $results) { /** * Builds table for possible matches with action buttons. + * + * @param array> $possible_matches + * The possible match rows. + * @param int|string $badge_tid + * The badge term ID. + * @param string $vocabulary + * The vocabulary machine name. + * + * @return array + * The table render array. */ - protected function buildPossibleMatchesTable(array $possible_matches, $badge_tid = 0, $vocabulary = 'badges') { + protected function buildPossibleMatchesTable(array $possible_matches, $badge_tid = 0, $vocabulary = 'badges'): array { $header = [ $this->t('CSV Email'), $this->t('CSV Name'), diff --git a/modules/access_badges/src/Form/BadgePendingForm.php b/modules/access_badges/src/Form/BadgePendingForm.php index b1e57ac8..e8e42c3f 100644 --- a/modules/access_badges/src/Form/BadgePendingForm.php +++ b/modules/access_badges/src/Form/BadgePendingForm.php @@ -3,6 +3,7 @@ namespace Drupal\access_badges\Form; use Drupal\Core\Database\Connection; +use Drupal\Core\Datetime\DateFormatterInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; @@ -12,7 +13,7 @@ /** * Management form for pending badge assignments. */ -class BadgePendingForm extends FormBase { +final class BadgePendingForm extends FormBase { /** * The database connection. @@ -28,21 +29,30 @@ class BadgePendingForm extends FormBase { */ protected $entityTypeManager; + /** + * The date formatter. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + /** * Constructs a BadgePendingForm. */ - public function __construct(Connection $database, EntityTypeManagerInterface $entity_type_manager) { + public function __construct(Connection $database, EntityTypeManagerInterface $entity_type_manager, DateFormatterInterface $date_formatter) { $this->database = $database; $this->entityTypeManager = $entity_type_manager; + $this->dateFormatter = $date_formatter; } /** * {@inheritdoc} */ - public static function create(ContainerInterface $container) { + public static function create(ContainerInterface $container): static { return new static( $container->get('database'), - $container->get('entity_type.manager') + $container->get('entity_type.manager'), + $container->get('date.formatter') ); } @@ -55,8 +65,16 @@ public function getFormId() { /** * {@inheritdoc} + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return array + * The form structure. */ - public function buildForm(array $form, FormStateInterface $form_state) { + public function buildForm(array $form, FormStateInterface $form_state): array { // Filter by badge. $badge_options = $this->getPendingBadgeOptions('pending'); if (!empty($badge_options)) { @@ -108,8 +126,11 @@ public function buildForm(array $form, FormStateInterface $form_state) { /** * Gets pending rows from the database. + * + * @return array + * The table rows keyed by row ID. */ - protected function getPendingRows($status, $badge_filter = '') { + protected function getPendingRows(string $status, string $badge_filter = ''): array { $query = $this->database->select('access_badges_pending', 'p') ->fields('p') ->condition('p.status', $status) @@ -130,7 +151,7 @@ protected function getPendingRows($status, $badge_filter = '') { 'last_name' => $row->last_name, 'organization' => $row->organization, 'badge' => $badge_name, - 'created' => \Drupal::service('date.formatter')->format($row->created, 'short'), + 'created' => $this->dateFormatter->format($row->created, 'short'), 'operations' => [ 'data' => [ '#type' => 'link', @@ -147,8 +168,11 @@ protected function getPendingRows($status, $badge_filter = '') { /** * Gets badge term options that exist in pending rows. + * + * @return array + * The badge options keyed by term ID. */ - protected function getPendingBadgeOptions($status) { + protected function getPendingBadgeOptions(string $status): array { $tids = $this->database->select('access_badges_pending', 'p') ->fields('p', ['badge_tid']) ->condition('p.status', $status) @@ -166,6 +190,12 @@ protected function getPendingBadgeOptions($status) { /** * Gets a badge term name by ID. + * + * @param int|string $tid + * The taxonomy term ID. + * + * @return string|\Drupal\Core\StringTranslation\TranslatableMarkup + * The badge term label, or a placeholder if the term does not exist. */ protected function getBadgeName($tid) { $term = $this->entityTypeManager->getStorage('taxonomy_term')->load($tid); @@ -174,15 +204,25 @@ protected function getBadgeName($tid) { /** * Filter submit handler. + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. */ - public function filterSubmit(array &$form, FormStateInterface $form_state) { + public function filterSubmit(array &$form, FormStateInterface $form_state): void { $form_state->setRebuild(); } /** * Bulk delete submit handler. + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. */ - public function bulkDeleteSubmit(array &$form, FormStateInterface $form_state) { + public function bulkDeleteSubmit(array &$form, FormStateInterface $form_state): void { $selected = array_filter($form_state->getValue('table', [])); if (empty($selected)) { $this->messenger()->addWarning($this->t('No rows selected.')); @@ -200,8 +240,13 @@ public function bulkDeleteSubmit(array &$form, FormStateInterface $form_state) { /** * {@inheritdoc} + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. */ - public function submitForm(array &$form, FormStateInterface $form_state) { + public function submitForm(array &$form, FormStateInterface $form_state): void { // Primary submit handled by sub-handlers. } diff --git a/modules/access_badges/src/Form/BadgeReviewForm.php b/modules/access_badges/src/Form/BadgeReviewForm.php index 0d78ab52..ceb09988 100644 --- a/modules/access_badges/src/Form/BadgeReviewForm.php +++ b/modules/access_badges/src/Form/BadgeReviewForm.php @@ -7,13 +7,12 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\user\Entity\User; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Management form for badge assignments needing review. */ -class BadgeReviewForm extends FormBase { +final class BadgeReviewForm extends FormBase { /** * The database connection. @@ -48,7 +47,7 @@ public function __construct(Connection $database, EntityTypeManagerInterface $en /** * {@inheritdoc} */ - public static function create(ContainerInterface $container) { + public static function create(ContainerInterface $container): static { return new static( $container->get('database'), $container->get('entity_type.manager'), @@ -65,8 +64,16 @@ public function getFormId() { /** * {@inheritdoc} + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return array + * The form structure. */ - public function buildForm(array $form, FormStateInterface $form_state) { + public function buildForm(array $form, FormStateInterface $form_state): array { $rows = $this->getReviewRows(); if (empty($rows)) { @@ -152,8 +159,11 @@ public function buildForm(array $form, FormStateInterface $form_state) { /** * Gets review rows with matched user details. + * + * @return array> + * The review rows. */ - protected function getReviewRows() { + protected function getReviewRows(): array { $results = $this->database->select('access_badges_pending', 'p') ->fields('p') ->condition('p.status', 'review') @@ -175,20 +185,20 @@ protected function getReviewRows() { $strength = 'Possible'; if ($row->matched_uid) { - $matched_user = User::load($row->matched_uid); + $matched_user = $this->entityTypeManager->getStorage('user')->load($row->matched_uid); if ($matched_user) { $matched_name = $matched_user->getDisplayName(); $matched_email = $matched_user->getEmail(); // Get matched user's organization. if ($matched_user->hasField('field_access_organization') && !$matched_user->get('field_access_organization')->isEmpty()) { + // The referenced entity may be NULL if it has been deleted. + /** @var \Drupal\Core\Entity\EntityInterface|null $org_entity */ $org_entity = $matched_user->get('field_access_organization')->entity; - if ($org_entity) { - $matched_org = $org_entity->label(); - // Determine strength. - if (!empty($row->organization) && mb_strtolower($matched_org) === mb_strtolower($row->organization)) { - $strength = 'Recommended'; - } + $matched_org = $org_entity ? $org_entity->label() : ''; + // Determine strength. + if (!empty($row->organization) && mb_strtolower($matched_org) === mb_strtolower($row->organization)) { + $strength = 'Recommended'; } } } @@ -216,6 +226,9 @@ protected function getReviewRows() { /** * Gets the row ID from the triggering element. + * + * @return int|string|null + * The row ID, or NULL if not found. */ protected function getRowIdFromTrigger(FormStateInterface $form_state) { $trigger = $form_state->getTriggeringElement(); @@ -224,6 +237,12 @@ protected function getRowIdFromTrigger(FormStateInterface $form_state) { /** * Gets a pending row by ID. + * + * @param int|string $id + * The pending row ID. + * + * @return object|false + * The pending row, or FALSE if not found. */ protected function getPendingRow($id) { return $this->database->select('access_badges_pending', 'p') @@ -235,8 +254,13 @@ protected function getPendingRow($id) { /** * Assign submit handler. + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function assignSubmit(array &$form, FormStateInterface $form_state) { + public function assignSubmit(array &$form, FormStateInterface $form_state): void { $id = $this->getRowIdFromTrigger($form_state); $row = $this->getPendingRow($id); if (!$row) { @@ -261,8 +285,13 @@ public function assignSubmit(array &$form, FormStateInterface $form_state) { /** * Dismiss submit handler. + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function dismissSubmit(array &$form, FormStateInterface $form_state) { + public function dismissSubmit(array &$form, FormStateInterface $form_state): void { $id = $this->getRowIdFromTrigger($form_state); $this->database->update('access_badges_pending') ->fields([ @@ -276,8 +305,13 @@ public function dismissSubmit(array &$form, FormStateInterface $form_state) { /** * Delete submit handler. + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function deleteSubmit(array &$form, FormStateInterface $form_state) { + public function deleteSubmit(array &$form, FormStateInterface $form_state): void { $id = $this->getRowIdFromTrigger($form_state); $this->database->delete('access_badges_pending') ->condition('id', $id) @@ -287,8 +321,13 @@ public function deleteSubmit(array &$form, FormStateInterface $form_state) { /** * {@inheritdoc} + * + * @param array $form + * The form structure. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. */ - public function submitForm(array &$form, FormStateInterface $form_state) { + public function submitForm(array &$form, FormStateInterface $form_state): void { // Primary submit handled by sub-handlers. } diff --git a/modules/access_badges/src/Plugin/BadgeTools.php b/modules/access_badges/src/Plugin/BadgeTools.php index 8b97ba11..56759429 100644 --- a/modules/access_badges/src/Plugin/BadgeTools.php +++ b/modules/access_badges/src/Plugin/BadgeTools.php @@ -2,6 +2,9 @@ namespace Drupal\access_badges\Plugin; +use Drupal\Component\Datetime\TimeInterface; +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\user\Entity\User; /** @@ -19,7 +22,7 @@ class BadgeTools { /** * User badges. * - * @var array + * @var array> */ protected $userBadges; @@ -30,6 +33,22 @@ class BadgeTools { */ protected $currentVocabulary = 'badges'; + /** + * Constructs a BadgeTools object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager. + * @param \Drupal\Core\Database\Connection $database + * The database connection. + * @param \Drupal\Component\Datetime\TimeInterface $time + * The time service. + */ + public function __construct( + protected EntityTypeManagerInterface $entityTypeManager, + protected Connection $database, + protected TimeInterface $time, + ) {} + /** * Returns the user field name for a given vocabulary. * @@ -75,9 +94,17 @@ private function getBadgeTargetColumn($vocabulary) { /** * Return badge term ID by name. + * + * @param string $badge_name + * The badge term name. + * @param string $vocabulary + * The vocabulary machine name. + * + * @return string + * The matching term ID, or an empty string. */ public function getBadgeTid($badge_name, $vocabulary = 'badges') { - $query = \Drupal::entityQuery('taxonomy_term'); + $query = $this->entityTypeManager->getStorage('taxonomy_term')->getQuery(); $query->condition('vid', $vocabulary); $query->condition('name', $badge_name); $query->accessCheck(FALSE); @@ -89,11 +116,14 @@ public function getBadgeTid($badge_name, $vocabulary = 'badges') { /** * Return Users that have access-ci in name. + * + * @return array + * User IDs keyed by revision ID. */ public function getAccessUsers() { // User entity lookup that were created 90 days or less ago and has // access-ci.org in their name. - $query = \Drupal::entityQuery('user'); + $query = $this->entityTypeManager->getStorage('user')->getQuery(); $query->condition('created', strtotime('-90 days'), '>'); $query->condition('name', '%access-ci.org%', 'LIKE'); $query->accessCheck(FALSE); @@ -103,10 +133,16 @@ public function getAccessUsers() { /** * Return Users that have a certain region/program. + * + * @param int|string $program + * The region/program value. + * + * @return array + * User IDs keyed by revision ID. */ public function getProgramUsers($program) { // User entity lookup that have a certain region/program. - $query = \Drupal::entityQuery('user'); + $query = $this->entityTypeManager->getStorage('user')->getQuery(); $query->condition('field_region', $program); $query->accessCheck(FALSE); $users = $query->execute(); @@ -115,8 +151,13 @@ public function getProgramUsers($program) { /** * Load the users badges. + * + * @param int|string $user_id + * The user ID. + * @param string $vocabulary + * The vocabulary machine name. */ - public function loadUserBadges($user_id, $vocabulary = 'badges') { + public function loadUserBadges($user_id, $vocabulary = 'badges'): void { $this->currentUser = User::load($user_id); $this->currentVocabulary = $vocabulary; $field_name = $this->getBadgeFieldName($vocabulary); @@ -125,8 +166,11 @@ public function loadUserBadges($user_id, $vocabulary = 'badges') { /** * Add badges to user. + * + * @param int|string $badge + * The badge term ID. */ - public function addUserBadges($badge) { + public function addUserBadges($badge): void { foreach ($this->userBadges as $existing) { if ($existing['target_id'] == $badge) { return; @@ -137,8 +181,11 @@ public function addUserBadges($badge) { /** * Save user. + * + * @param string|null $vocabulary + * The vocabulary machine name, or NULL to use the current context. */ - public function saveUserBadges($vocabulary = NULL) { + public function saveUserBadges($vocabulary = NULL): void { $vocab = $vocabulary ?? $this->currentVocabulary; $field_name = $this->getBadgeFieldName($vocab); $this->currentUser->set($field_name, $this->userBadges); @@ -147,6 +194,9 @@ public function saveUserBadges($vocabulary = NULL) { /** * Return the users badges. + * + * @return array> + * The user badge field values. */ public function getUserBadges() { return $this->userBadges; @@ -154,9 +204,12 @@ public function getUserBadges() { /** * Return Users that have the affinity group leader role. + * + * @return array + * User IDs keyed by revision ID. */ public function getAgRoleUsers() { - $query = \Drupal::entityQuery('user'); + $query = $this->entityTypeManager->getStorage('user')->getQuery(); $query->condition('roles', 'affinity_group_leader'); $query->accessCheck(FALSE); $users = $query->execute(); @@ -165,16 +218,19 @@ public function getAgRoleUsers() { /** * Return Users that have submitted the CSSN webform in the last 90 days. + * + * @return array + * The owner user IDs of recent submissions. */ public function getNewCssnUsers() { // Lookup webform submissions for 'join_the_cssn_network'. - $webform = \Drupal::entityTypeManager()->getStorage('webform')->load('join_the_cssn_network'); - $webform_submissions = \Drupal::entityTypeManager()->getStorage('webform_submission')->loadByProperties(['webform_id' => $webform->id()]); + $webform = $this->entityTypeManager->getStorage('webform')->load('join_the_cssn_network'); + $webform_submissions = $this->entityTypeManager->getStorage('webform_submission')->loadByProperties(['webform_id' => $webform->id()]); // Grab all submissions submited in the last 90 days. $submission_users = []; foreach ($webform_submissions as $submission) { $created = $submission->getCreatedTime(); - $now = \Drupal::time()->getCurrentTime(); + $now = $this->time->getCurrentTime(); $diff = $now - $created; if ($diff < 7776000) { $submission_users[] = $submission->getOwnerId(); @@ -185,9 +241,19 @@ public function getNewCssnUsers() { /** * Check if user has badge, return boolean. + * + * @param int|string $badge + * The badge term ID. + * @param int|string|array $user + * The user ID, or an array of user IDs. + * @param string $vocabulary + * The vocabulary machine name. + * + * @return bool + * TRUE if the user has the badge. */ public function checkBadges($badge, $user, $vocabulary = 'badges') { - $connection = \Drupal::database(); + $connection = $this->database; $table = $this->getBadgeTableName($vocabulary); $target_column = $this->getBadgeTargetColumn($vocabulary); $query = $connection->select($table, 't'); @@ -201,9 +267,16 @@ public function checkBadges($badge, $user, $vocabulary = 'badges') { /** * Set multiple users badge via the database. + * + * @param int|string $badge + * The badge term ID. + * @param array $users + * The user IDs to assign the badge to. + * @param string $vocabulary + * The vocabulary machine name. */ - public function setBadges($badge, $users, $vocabulary = 'badges') { - $connection = \Drupal::database(); + public function setBadges($badge, $users, $vocabulary = 'badges'): void { + $connection = $this->database; $table = $this->getBadgeTableName($vocabulary); $target_column = $this->getBadgeTargetColumn($vocabulary); @@ -242,15 +315,19 @@ public function setBadges($badge, $users, $vocabulary = 'badges') { /** * Set user badge via saving user. + * + * @param int|string $badge + * The badge term ID. + * @param array> $users + * The user reference field values, each containing a 'target_id'. */ - public function setUserBadge($badge, $users) { + public function setUserBadge($badge, $users): void { foreach ($users as $user) { $uid = $user['target_id']; - $badge_load = $this->loadUserBadges($uid); + $this->loadUserBadges($uid); // Check if user has badge. $badge_check = $this->checkBadges($badge, [$uid]); if (!$badge_check) { - $badges = $this->getUserBadges(); // Set badges for user. $this->addUserBadges($badge); $this->saveUserBadges(); @@ -260,9 +337,16 @@ public function setUserBadge($badge, $users) { /** * Fields with user id's to badge. + * + * @param string $field + * The node field machine name. + * @param string $badge + * The badge term name. + * @param string $bundle + * The node bundle machine name. */ - public function fieldToBadge($field, $badge, $bundle) { - $query = \Drupal::database()->select('node__' . $field, 'fd'); + public function fieldToBadge($field, $badge, $bundle): void { + $query = $this->database->select('node__' . $field, 'fd'); $query->fields('fd', [$field . '_target_id']); $query->condition('fd.bundle', $bundle); $field_users = $query->execute()->fetchAll(); diff --git a/modules/access_badges/src/Plugin/CronManager.php b/modules/access_badges/src/Plugin/CronManager.php index 23c22d42..d363a6b6 100644 --- a/modules/access_badges/src/Plugin/CronManager.php +++ b/modules/access_badges/src/Plugin/CronManager.php @@ -17,11 +17,12 @@ class CronManager { * flags rows as 'review' with matched_uid. Sends digest email to * administrators if new matches are found. */ - public static function checkPendingMatches() { + public static function checkPendingMatches(): void { $database = \Drupal::database(); $entity_type_manager = \Drupal::entityTypeManager(); // Load all pending rows. + /** @var \stdClass[] $pending_rows */ $pending_rows = $database->select('access_badges_pending', 'p') ->fields('p') ->condition('p.status', 'pending') @@ -65,7 +66,7 @@ public static function checkPendingMatches() { // Check organization match. if (!empty($row->organization) && $user->hasField('field_access_organization') && !$user->get('field_access_organization')->isEmpty()) { $org_entity = $user->get('field_access_organization')->entity; - if ($org_entity && mb_strtolower($org_entity->label()) === mb_strtolower($row->organization)) { + if (mb_strtolower($org_entity->label()) === mb_strtolower($row->organization)) { $strength = 'Recommended'; } } @@ -114,10 +115,10 @@ public static function checkPendingMatches() { /** * Sends a digest email to all administrators about new matches. * - * @param array $new_matches + * @param array> $new_matches * Array of match data. */ - protected static function sendDigestEmail(array $new_matches) { + protected static function sendDigestEmail(array $new_matches): void { // Get all active administrator users. $query = \Drupal::entityTypeManager()->getStorage('user')->getQuery(); $query->condition('roles', 'administrator'); diff --git a/modules/access_badges/src/Service/BadgeSorter.php b/modules/access_badges/src/Service/BadgeSorter.php index f3fd77b0..9dcbf948 100644 --- a/modules/access_badges/src/Service/BadgeSorter.php +++ b/modules/access_badges/src/Service/BadgeSorter.php @@ -17,7 +17,7 @@ public function __construct( ) {} /** - * Sort field_user_badges on a user entity to match taxonomy weight/name order. + * Sort field_user_badges on a user to match taxonomy weight/name order. */ public function sortUserBadges(UserInterface $user): void { $badges = $user->get('field_user_badges')->getValue(); diff --git a/modules/access_badges/src/Service/CsvProcessor.php b/modules/access_badges/src/Service/CsvProcessor.php index d836c8d8..b78d9726 100644 --- a/modules/access_badges/src/Service/CsvProcessor.php +++ b/modules/access_badges/src/Service/CsvProcessor.php @@ -3,6 +3,7 @@ namespace Drupal\access_badges\Service; use Drupal\access_badges\Plugin\BadgeTools; +use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -32,13 +33,21 @@ class CsvProcessor { */ protected $badgeTools; + /** + * The time service. + * + * @var \Drupal\Component\Datetime\TimeInterface + */ + protected $time; + /** * Constructs a CsvProcessor object. */ - public function __construct(Connection $database, EntityTypeManagerInterface $entity_type_manager, BadgeTools $badge_tools) { + public function __construct(Connection $database, EntityTypeManagerInterface $entity_type_manager, BadgeTools $badge_tools, TimeInterface $time) { $this->database = $database; $this->entityTypeManager = $entity_type_manager; $this->badgeTools = $badge_tools; + $this->time = $time; } /** @@ -47,10 +56,10 @@ public function __construct(Connection $database, EntityTypeManagerInterface $en * @param resource $handle * An open file handle. * - * @return array|false + * @return array|false * An associative map of recognized column => index, or FALSE on failure. */ - public function parseHeaders($handle) { + public function parseHeaders($handle): array|false { $row = fgetcsv($handle); if (!$row) { return FALSE; @@ -92,8 +101,14 @@ public function parseHeaders($handle) { * * "Mary Jane Watson" -> first: "Mary Jane", last: "Watson". * Single token "Prince" -> first: "", last: "Prince". + * + * @param string $full_name + * The full name to split. + * + * @return array{first_name: string, last_name: string} + * The split first and last name. */ - public function splitName($full_name) { + public function splitName($full_name): array { $name = $this->normalizeName($full_name); if ($name === '') { return ['first_name' => '', 'last_name' => '']; @@ -110,23 +125,29 @@ public function splitName($full_name) { /** * Normalizes a name string (trim + collapse whitespace). + * + * @param string $name + * The name to normalize. + * + * @return string + * The normalized name. */ - public function normalizeName($name) { - return preg_replace('/\s+/', ' ', trim($name)); + public function normalizeName($name): string { + return preg_replace('/\s+/', ' ', trim($name)) ?? ''; } /** * Extracts a row's data from a CSV line using the header map. * - * @param array $row + * @param array $row * A CSV row array. - * @param array $header_map + * @param array $header_map * The header map from parseHeaders(). * - * @return array + * @return array * Associative array with email, first_name, last_name, organization. */ - public function extractRowData(array $row, array $header_map) { + public function extractRowData(array $row, array $header_map): array { $email = isset($header_map['email']) ? trim($row[$header_map['email']] ?? '') : ''; $first_name = ''; $last_name = ''; @@ -158,18 +179,18 @@ public function extractRowData(array $row, array $header_map) { /** * Processes a single CSV row: match, possible-match, or pending. * - * @param array $data + * @param array $data * Row data from extractRowData(). * @param int $badge_tid * The badge term ID. * @param string $vocabulary * The vocabulary machine name. * - * @return array - * Result with 'type' key: 'assigned', 'already_assigned', 'duplicate_pending', - * 'possible_matches', or 'pending'. + * @return array + * Result with 'type' key: 'assigned', 'already_assigned', + * 'duplicate_pending', 'possible_matches', or 'pending'. */ - public function processRow(array $data, $badge_tid, $vocabulary) { + public function processRow(array $data, $badge_tid, $vocabulary): array { $email = $data['email']; if (empty($email)) { return ['type' => 'pending', 'email' => '']; @@ -330,8 +351,10 @@ public function getUserOrganization($user) { if (!$user->hasField('field_access_organization') || $user->get('field_access_organization')->isEmpty()) { return ''; } + // The referenced entity may be NULL if it has been deleted. + /** @var \Drupal\Core\Entity\EntityInterface|null $org_entity */ $org_entity = $user->get('field_access_organization')->entity; - return $org_entity ? $org_entity->label() : ''; + return $org_entity ? (string) $org_entity->label() : ''; } /** @@ -358,7 +381,7 @@ public function pendingRowExists($email, $badge_tid) { /** * Inserts a 'review' status row with a matched UID into the pending table. * - * @param array $data + * @param array $data * Row data with email, first_name, last_name, organization. * @param int $badge_tid * The badge term ID. @@ -367,7 +390,7 @@ public function pendingRowExists($email, $badge_tid) { * @param int $matched_uid * The UID of the best-matched user candidate. */ - public function insertReviewRow(array $data, $badge_tid, $vocabulary, $matched_uid) { + public function insertReviewRow(array $data, $badge_tid, $vocabulary, $matched_uid): void { $this->database->insert('access_badges_pending') ->fields([ 'email' => $data['email'], @@ -376,7 +399,7 @@ public function insertReviewRow(array $data, $badge_tid, $vocabulary, $matched_u 'organization' => $data['organization'], 'badge_tid' => $badge_tid, 'vocabulary' => $vocabulary, - 'created' => \Drupal::time()->getRequestTime(), + 'created' => $this->time->getRequestTime(), 'status' => 'review', 'matched_uid' => $matched_uid, ]) @@ -386,14 +409,14 @@ public function insertReviewRow(array $data, $badge_tid, $vocabulary, $matched_u /** * Inserts a row into the pending table. * - * @param array $data + * @param array $data * Row data with email, first_name, last_name, organization. * @param int $badge_tid * The badge term ID. * @param string $vocabulary * The vocabulary machine name. */ - public function insertPendingRow(array $data, $badge_tid, $vocabulary) { + public function insertPendingRow(array $data, $badge_tid, $vocabulary): void { $this->database->insert('access_badges_pending') ->fields([ 'email' => $data['email'], @@ -402,7 +425,7 @@ public function insertPendingRow(array $data, $badge_tid, $vocabulary) { 'organization' => $data['organization'], 'badge_tid' => $badge_tid, 'vocabulary' => $vocabulary, - 'created' => \Drupal::time()->getRequestTime(), + 'created' => $this->time->getRequestTime(), 'status' => 'pending', ]) ->execute(); diff --git a/modules/access_entity_copy/access_entity_copy.info.yml b/modules/access_entity_copy/access_entity_copy.info.yml index def34a40..24f9126c 100644 --- a/modules/access_entity_copy/access_entity_copy.info.yml +++ b/modules/access_entity_copy/access_entity_copy.info.yml @@ -4,5 +4,4 @@ type: module core_version_requirement: ^10 || ^11 package: Custom dependencies: - - access - + - access:access diff --git a/modules/access_entity_copy/access_entity_copy.module b/modules/access_entity_copy/access_entity_copy.module index 80d60024..7d255f00 100644 --- a/modules/access_entity_copy/access_entity_copy.module +++ b/modules/access_entity_copy/access_entity_copy.module @@ -1,16 +1,18 @@ get('title')->value; $form['field_me_looking_for']['widget']['#default_value'] = $entity->get('field_me_looking_for')->value; $form['field_me_preferred_attributes']['widget'][0]['#default_value'] = $entity->get('field_me_preferred_attributes')->value; - $form['body']['widget'][0]['summary']['#default_value'] = $entity->get('body')->summary; + $form['body']['widget'][0]['summary']['#default_value'] = $entity->get('body')->getValue()[0]['summary'] ?? NULL; $form['body']['widget'][0]['#default_value'] = $entity->get('body')->value; $form['field_mentorship_program']['widget']['#default_value'] = $entity->get('field_mentorship_program')->target_id; } - diff --git a/modules/access_match_engagement/access_match_engagement.info.yml b/modules/access_match_engagement/access_match_engagement.info.yml index 6197e8f7..d5fd9c35 100644 --- a/modules/access_match_engagement/access_match_engagement.info.yml +++ b/modules/access_match_engagement/access_match_engagement.info.yml @@ -4,4 +4,4 @@ type: module core_version_requirement: ^10 || ^11 package: Custom dependencies: - - access + - access:access diff --git a/modules/access_match_engagement/access_match_engagement.install b/modules/access_match_engagement/access_match_engagement.install index 9b56b51b..8dcfce11 100644 --- a/modules/access_match_engagement/access_match_engagement.install +++ b/modules/access_match_engagement/access_match_engagement.install @@ -9,18 +9,18 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; /** - * Hook_update. + * Sets the default 'interested' setting. */ -function access_match_engagement_update_8001() { +function access_match_engagement_update_8001(): void { $config = \Drupal::configFactory()->getEditable('access_match_engagement.settings'); $config->set('interested', 0); $config->save(); } /** - * + * Migrates field_status values to match moderation_state values. */ -function access_match_engagement_update_9001() { +function access_match_engagement_update_9001(): void { // Change status field values to match moderation_state values. $new_status_values = [ 'Draft' => 'draft', @@ -71,14 +71,14 @@ function access_match_engagement_update_9001() { } // Restore existing data in fields & revision tables. - if (!is_null($rows)) { + if (!empty($rows)) { foreach ($rows as $row) { $row = (array) $row; $row['field_status_value'] = $new_status_values[$row['field_status_value']]; $database->insert($table)->fields($row)->execute(); } } - if (!is_null($revision_rows)) { + if (!empty($revision_rows)) { foreach ($revision_rows as $row) { $row = (array) $row; $row['field_status_value'] = $new_status_values[$row['field_status_value']]; @@ -91,9 +91,9 @@ function access_match_engagement_update_9001() { /** * Change the match_engagement field_status values of 'accepted' to 'prelaunch'. */ -function access_match_engagement_update_9002() { +function access_match_engagement_update_9002(): void { $database = \Drupal::database(); - $query = $database->update('node__field_status') + $database->update('node__field_status') ->fields(['field_status_value' => 'prelaunch']) ->condition('field_status_value', 'accepted') ->execute(); diff --git a/modules/access_match_engagement/access_match_engagement.module b/modules/access_match_engagement/access_match_engagement.module index 01c62de8..45dd3005 100644 --- a/modules/access_match_engagement/access_match_engagement.module +++ b/modules/access_match_engagement/access_match_engagement.module @@ -10,12 +10,14 @@ use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; use Drupal\Core\Render\Markup; use Drupal\Core\Url; +use Drupal\node\NodeInterface; use Drupal\user\Entity\User; +use Drupal\user\UserInterface; use Drupal\views\ViewExecutable; use Symfony\Component\HttpFoundation\RedirectResponse; use Drupal\access_misc\Plugin\Util\SiteTools; @@ -23,23 +25,25 @@ use Drupal\access_misc\Plugin\Util\SiteTools; /** * Implements hook_views_pre_render(). */ -function access_match_engagement_views_pre_render(ViewExecutable $view) { - if (isset($view) && ($view->storage->id() == 'match_engagement_view')) { +function access_match_engagement_views_pre_render(ViewExecutable $view): void { + if ($view->storage->id() == 'match_engagement_view') { $view->element['#attached']['library'][] = 'access_match_engagement/match_engagement_view'; } } /** - * Implements hook_preprocess_views_view_field for views-view-field.html.twig. + * Implements hook_preprocess_views_view_field(). + * + * @phpstan-param array $variables */ -function access_match_engagement_preprocess_views_view_field(&$variables) { +function access_match_engagement_preprocess_views_view_field(array &$variables): void { $view = $variables['view']; $field = $variables['field']; // Overwrite MATCH Engagements submissions on /match-engagements-submissions. - // /admin/structure/views/view/match_engagements_submissions/edit/page_1 + // /admin/structure/views/view/match_engagements_submissions/edit/page_1. if ($view->id() == 'match_engagements_submissions' && $view->current_display == 'page_1' && $field->options['id'] == 'nothing') { - $output =$variables['output']; + $output = $variables['output']; $matches = []; preg_match('/~\s*(\d+)\s*~/', $output, $matches); @@ -59,8 +63,10 @@ function access_match_engagement_preprocess_views_view_field(&$variables) { /** * Implements hook_block_build_BASE_BLOCK_ID_alter(). + * + * @phpstan-param array $build */ -function access_match_engagement_block_build_views_block_alter(array &$build, BlockPluginInterface $block) { +function access_match_engagement_block_build_views_block_alter(array &$build, BlockPluginInterface $block): void { // Using label because block numbers seem to change. if ($block->label() == 'match engagement view: Block - My Engagement') { $build['#create_placeholder'] = FALSE; @@ -69,8 +75,10 @@ function access_match_engagement_block_build_views_block_alter(array &$build, Bl /** * Implements hook_form_alter(). + * + * @phpstan-param array $form */ -function access_match_engagement_form_alter(&$form, FormStateInterface $form_state, $form_id) { +function access_match_engagement_form_alter(array &$form, FormStateInterface $form_state, string $form_id): void { if ($form_id == 'node_match_engagement_edit_form' || $form_id == 'node_match_engagement_form') { // Only allow MATCH Engagement creation on the ACCESS domain. $siteTools = \Drupal::service('access_misc.sitetools'); @@ -109,8 +117,8 @@ function access_match_engagement_form_alter(&$form, FormStateInterface $form_sta 'class' => [ 'bg-light-teal', 'my-5', - 'p-5' - ] + 'p-5', + ], ], ]; @@ -121,10 +129,10 @@ function access_match_engagement_form_alter(&$form, FormStateInterface $form_sta $form['field_tags_replace']['field_suggest']['replace_button'] = [ '#type' => 'button', '#value' => t('Suggest Tags'), - '#limit_validation_errors' => array(), + '#limit_validation_errors' => [], '#attributes' => [ 'class' => [ - 'ml-0' + 'ml-0', ], ], '#ajax' => [ @@ -136,18 +144,19 @@ function access_match_engagement_form_alter(&$form, FormStateInterface $form_sta '#markup' => "", ]; - $add_tags = \Drupal::service('access_misc.addtags'); $output = $add_tags->getView(); $tag_label = t('Tags'); $tag_description = t('Select up to 6 tags that relate to your engagement. Tags will help us find people with related expertise.'); $tag_summary = t('Select Tags'); + $node_add_tags_markup = "
$tag_label
" + . "
$tag_description
" + . "
" + . "
$tag_summary$output
"; + $form['node_add_tags'] = [ - '#markup' => "
$tag_label
-
$tag_description
-
-
$tag_summary$output
", + '#markup' => $node_add_tags_markup, '#weight' => 15, '#allowed_tags' => [ 'ul', @@ -183,7 +192,7 @@ function access_match_engagement_form_alter(&$form, FormStateInterface $form_sta // Form has been accepted, moderation-wise. // Fixup certain date-time fields to act as date-only. - $date_only_fields = ame_get_dateonly_fields(); + $date_only_fields = access_match_engagement_ame_get_dateonly_fields(); foreach ($date_only_fields as $fieldname) { $form[$fieldname]['widget'][0]['value']['#date_time_element'] = 'none'; $form[$fieldname]['widget'][0]['value']['#date_time_format'] = ''; @@ -191,10 +200,6 @@ function access_match_engagement_form_alter(&$form, FormStateInterface $form_sta $current_user = \Drupal::currentUser(); $roles = $current_user->getRoles(); - $unaccepted_states = ['draft', 'in_review']; - $moderation_state = $form['moderation_state']['widget'][0]['state']['#default_value']; - $accepted = !in_array($moderation_state, $unaccepted_states); - if (!in_array('administrator', $roles) && !in_array('match_sc', $roles)) { $form['field_email_user']['#access'] = FALSE; $form['field_notes_to_author']['#access'] = FALSE; @@ -213,13 +218,13 @@ function access_match_engagement_form_alter(&$form, FormStateInterface $form_sta $form['#validate'][] = 'access_match_engagement_form_validate'; $form['save_description'] = [ '#markup' => '
' . - t("Save as Submitted when your request is ready, or Draft if you need more time") . - '
', + t('Save as Submitted when your request is ready, or Draft if you need more time') . + '', '#weight' => 99, '#allowed_tags' => [ 'a', 'div', - 'span' + 'span', ], ]; $form['moderation_state']['widget'][0]['state']['#title'] = t('Save as'); @@ -239,8 +244,14 @@ function access_match_engagement_form_alter(&$form, FormStateInterface $form_sta /** * Ajax callback function to replace the section with '#markup'. + * + * @param array $form + * The form array. + * + * @return array + * The updated form element. */ -function access_match_engagement_replace_section_callback(array &$form, FormStateInterface $form_state) { +function access_match_engagement_replace_section_callback(array &$form, FormStateInterface $form_state): array { $raw_data = $form_state->getUserInput(); $body = $raw_data['body'][0]['value']; $body_filter = $body ? Xss::filter($body) : ''; @@ -282,17 +293,25 @@ function access_match_engagement_replace_section_callback(array &$form, FormStat ]; } - $form['field_tags_replace']['#attributes']['data-suggest'] = $suggested_tag_ids ; + $form['field_tags_replace']['#attributes']['data-suggest'] = $suggested_tag_ids; // Return the updated section. return $form['field_tags_replace']; } /** + * Marks a required date field as not required. + * * This callback is used on a date field, the first milestone goal, that is * required only after the state of the form has been changed to accepted. + * + * @param array $element + * The form element. + * + * @return array + * The modified form element. */ -function access_match_engagement_customize_required_date($element, $form_state) { +function access_match_engagement_customize_required_date(array $element, FormStateInterface $form_state): array { $element['#required'] = FALSE; $element['value']['#required'] = FALSE; @@ -300,13 +319,16 @@ function access_match_engagement_customize_required_date($element, $form_state) } /** - * Implements hook_ENTITY_TYPE_view_alter(). + * Implements hook_ENTITY_TYPE_view(). + * * For the display of a single match engagement (it's not a view) * replace the link to user profile with link to community-persona. + * + * @phpstan-param array $build */ -function access_match_engagement_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) { +function access_match_engagement_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, string $view_mode): void { - if ($display->getTargetBundle() == 'match_engagement') { + if ($display->getTargetBundle() == 'match_engagement' && $entity instanceof FieldableEntityInterface) { if (!empty($build['field_researcher'])) { foreach ($entity->get('field_researcher')->getValue() as $i => $researcher) { $uid = $researcher['target_id']; @@ -319,35 +341,43 @@ function access_match_engagement_node_view(array &$build, EntityInterface $entit } /** + * Submit handler that displays a message based on the moderation state. * + * @param array $form + * The form array. */ -function access_match_engagement_submit_function(&$form, FormStateInterface $form_state) { +function access_match_engagement_submit_function(array &$form, FormStateInterface $form_state): void { $moderation_state = $form_state->getValues()['moderation_state'][0]['value']; - update_drupal_message($moderation_state); + access_match_engagement_update_drupal_message($moderation_state); } /** - * Validate form - * Need at least one tag. - * Check that names in the Match Team student(s), consultant(s) or mentors(s) fields have the correct role s + * Validate form, need at least one tag. + * + * Check that names in the Match Team student(s), consultant(s) or + * mentors(s) fields have the correct role s * (student-facilitator/consultant/mentor). - * For mentor(s) or consultant(s) field, we will automatically add the role to the user iff they already - * are in the cssn system (defined by have an item in their program list = "ACCESS CSSN"). + * For mentor(s) or consultant(s) field, we will automatically add the + * role to the user iff they already are in the cssn system (defined by + * have an item in their program list = "ACCESS CSSN"). + * + * @param array $form + * The form array. */ -function access_match_engagement_form_validate(&$form, FormStateInterface $form_state) { +function access_match_engagement_form_validate(array &$form, FormStateInterface $form_state): void { $error = ''; $values = $form_state->getValues(); $tags = $values['field_tags']; if (!empty($values)) { $person_array = $values['field_students']; - $error .= match_check_role($person_array, 'student', FALSE); + $error .= access_match_engagement_match_check_role($person_array, 'student', FALSE); $person_array = $values['field_mentor']; - $error .= match_check_role($person_array, 'mentor', TRUE); + $error .= access_match_engagement_match_check_role($person_array, 'mentor', TRUE); $person_array = $values['field_consultant']; - $error .= match_check_role($person_array, 'consultant', TRUE); + $error .= access_match_engagement_match_check_role($person_array, 'consultant', TRUE); } if (count($tags) > 6) { @@ -355,58 +385,69 @@ function access_match_engagement_form_validate(&$form, FormStateInterface $form_ } if (!empty($error)) { - $form_state->setError($form, t($error)); + // $error is already-rendered, safe markup assembled from t() calls (it + // contains an link). Wrap it in Markup::create() so the @error + // placeholder does not re-escape the HTML. + $form_state->setError($form, t('@error', ['@error' => Markup::create($error)])); } } /** - * Assemble a list of messages for the user that tell if any - * of the users in person_array do not have the proper roles as - * described in the _validate function above. If do_add_Role is + * Assemble a list of messages for the user. + * + * Tells if any of the users in person_array do not have the proper + * roles as described in the _validate function above. If do_add_role is * true, add the role to the user if the are already in the - * ACCESS_CSSN program + * ACCESS_CSSN program. + * + * @param array $person_array + * The list of person field values to check. */ -function match_check_role($person_array, $role_name, $do_add_role) { +function access_match_engagement_match_check_role(array $person_array, string $role_name, bool $do_add_role): string { $error_msg = ''; - // While checking each of these fields, the is_array skips the 'add another item' translatable markup item. + // While checking each of these fields, the is_array skips the 'add + // another item' translatable markup item. foreach ($person_array as $person) { if (is_array($person) && !empty($person['target_id'])) { $user = User::load($person['target_id']); - $has_role = validate_user_has_role($user, $role_name); + $has_role = access_match_engagement_validate_user_has_role($user, $role_name); if (!$has_role) { - if ($do_add_role && user_in_program($user, 'ACCESS CSSN')) { + if ($do_add_role && access_match_engagement_user_in_program($user, 'ACCESS CSSN')) { $user->addRole($role_name); $user->save(); } else { - $error_msg .= t($user->getDisplayName() . ' is not a CSSN ' . $role_name . '. Please ask them to complete the form to join the CSSN.
', [':url' => 'https://support.access-ci.org/form/join-the-cssn-network']); + $error_msg .= t('@name is not a CSSN @role. Please ask them to complete the form to join the CSSN.
', [ + '@name' => $user->getDisplayName(), + '@role' => $role_name, + ':url' => 'https://support.access-ci.org/form/join-the-cssn-network', + ]); } } } } - return ($error_msg); + return $error_msg; } /** * Return boolean - whether a program is included in list of user's programs. */ -function user_in_program($user, $program_name) { +function access_match_engagement_user_in_program(UserInterface $user, string $program_name): bool { $regions = $user->get('field_region')->getValue(); $terms = []; foreach ($regions as $region) { $region_tid = $region['target_id']; $terms[$region_tid] = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->load($region_tid)->getName(); } - $program = implode(', ', $terms); - return (in_array($program_name, $terms)); + return in_array($program_name, $terms); } /** - * + * Checks whether the given user account has the given role. */ -function validate_user_has_role($user, $role_name) { +function access_match_engagement_validate_user_has_role(UserInterface $user, string $role_name): bool { $roles_array = $user->getRoles(); foreach ($roles_array as $role) { @@ -418,10 +459,12 @@ function validate_user_has_role($user, $role_name) { } /** - * Return a list of datetime field names where we - * want to hide the time portion. + * Return a list of datetime field names where we want to hide the time portion. + * + * @return array + * The list of field names. */ -function ame_get_dateonly_fields() { +function access_match_engagement_ame_get_dateonly_fields(): array { return [ "field_milestone_actual_date", "field_actual_completion_date_1", @@ -440,8 +483,11 @@ function ame_get_dateonly_fields() { /** * Implements hook_mail(). + * + * @phpstan-param array $message + * @phpstan-param array $params */ -function access_match_engagement_mail($key, &$message, $params) { +function access_match_engagement_mail(string $key, array &$message, array $params): void { switch ($key) { case 'ame-email-notes': case 'interested-update': @@ -453,10 +499,9 @@ function access_match_engagement_mail($key, &$message, $params) { } /** - * + * Sets a status message for the user based on the moderation state. */ -function update_drupal_message($moderation_state) { - $message = ''; +function access_match_engagement_update_drupal_message(string $moderation_state): void { if ($moderation_state == 'draft') { $message = t('Your project has been saved. Please send for review when you’re ready and we’ll get back to you as soon as possible.'); } @@ -467,18 +512,16 @@ function update_drupal_message($moderation_state) { $moderation_state = str_replace('_', ' ', $moderation_state); $message = t('Your project has been saved. The current status is: @status.', ['@status' => $moderation_state]); } - // If $message is not empty, add it to the drupal message queue. - if ($message) { - \Drupal::messenger()->addMessage($message); - } + // Add the message to the drupal message queue. + \Drupal::messenger()->addMessage($message); } /** * Implements hook_entity_presave(). */ -function access_match_engagement_entity_presave(EntityInterface $entity) { +function access_match_engagement_entity_presave(EntityInterface $entity): void { $type = $entity->bundle(); - if ($type == 'match_engagement') { + if ($type == 'match_engagement' && $entity instanceof NodeInterface) { $get_fields = $entity->getFields(); // Set field_status to moderation_state. $moderation_state = $get_fields['moderation_state']->getValue(); @@ -486,7 +529,7 @@ function access_match_engagement_entity_presave(EntityInterface $entity) { $moderation_state = $moderation_state[0]['value']; $entity->set('field_status', $moderation_state); Cache::invalidateTags($entity->getCacheTagsToInvalidate()); - update_drupal_message($moderation_state); + access_match_engagement_update_drupal_message($moderation_state); } // Set field_researcher to current user if field_researcher is empty. $researcher = $get_fields['field_researcher']->getValue(); @@ -536,17 +579,21 @@ function access_match_engagement_entity_presave(EntityInterface $entity) { /** * Build access_match_engagement interested Email. + * + * @param array $to_uids + * The user IDs to notify. */ -function access_match_engagement_interested_email($to_uids) { +function access_match_engagement_interested_email(array $to_uids): void { $body['string'] = [ '#type' => 'inline_template', '#template' => '

{{ intro }} {{ link | raw }}.

', '#context' => [ - 'intro' => t('New interested user alert! Please check website for '), - 'link' => "details", + 'intro' => t('New interested user alert! Please check website for'), + 'link' => " details", ], ]; - // Lookup each user by to_uids and add their email to the $to_email string comma separated. + // Lookup each user by to_uids and add their email to the $to_email + // string comma separated. $to_email = ''; foreach ($to_uids as $uid) { $user = User::load($uid); @@ -560,14 +607,21 @@ function access_match_engagement_interested_email($to_uids) { $params['body'] = $render_service->renderRoot($body); $params['title'] = "New Interested User Alert"; // Add logger message of sending interested email. - \Drupal::logger('access_match_engagement')->notice('Sending interested email to: ' . $to_email); - ame_send('interested-update', $params); + \Drupal::logger('access_match_engagement')->notice('Sending interested email to: @email', ['@email' => $to_email]); + access_match_engagement_ame_send('interested-update', $params); } /** * Build access_match_engagement notes Email. + * + * @param int|string $ame_nid + * The node ID of the engagement. + * @param string $ame_notes + * The notes to include in the email. + * @param int|string $author + * The user ID of the engagement author. */ -function access_match_engagement_email($ame_nid, $ame_title, $ame_notes, $author, $ame_type) { +function access_match_engagement_email($ame_nid, string $ame_title, string $ame_notes, $author, string $ame_type): void { $options = ['absolute' => TRUE]; $here = Url::fromRoute('entity.node.canonical', ['node' => $ame_nid], $options); $body['string'] = [ @@ -593,13 +647,19 @@ function access_match_engagement_email($ame_nid, $ame_title, $ame_notes, $author $params['to'] = $to_email; $params['body'] = $render_service->render($body); $params['title'] = "MATCH$ame_type Engagement: $ame_title"; - ame_send('ame-email-notes', $params); + access_match_engagement_ame_send('ame-email-notes', $params); } /** * Send email. + * + * @param array $params + * Parameters used to build the message. + * + * @return array + * The result of the mail manager's mail() call. */ -function ame_send($key, $params) { +function access_match_engagement_ame_send(string $key, array $params): array { $to = $params['to']; $langcode = \Drupal::currentUser()->getPreferredLangcode(); $send = TRUE; @@ -612,7 +672,7 @@ function ame_send($key, $params) { /** * Hook_cron. */ -function access_match_engagement_cron() { +function access_match_engagement_cron(): void { $env = getenv('PANTHEON_ENVIRONMENT'); if ($env == 'live') { $config = \Drupal::configFactory()->getEditable('access_match_engagement.settings'); diff --git a/modules/access_match_engagement/src/Controller/MatchController.php b/modules/access_match_engagement/src/Controller/MatchController.php index 8dd9130b..cafc7bbe 100644 --- a/modules/access_match_engagement/src/Controller/MatchController.php +++ b/modules/access_match_engagement/src/Controller/MatchController.php @@ -2,24 +2,148 @@ namespace Drupal\access_match_engagement\Controller; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\PageCache\ResponsePolicy\KillSwitch; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountProxyInterface; +use Drupal\Core\State\StateInterface; use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; /** * Controller for Match. */ -class MatchController extends ControllerBase { +final class MatchController extends ControllerBase { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The current route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountProxyInterface + */ + protected $currentUser; + + /** + * The config factory. + * + * @var \Drupal\Core\Config\ConfigFactoryInterface + */ + protected $configFactory; + + /** + * The state service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * The logger channel factory. + * + * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface + */ + protected $loggerFactory; + + /** + * The page cache kill switch. + * + * @var \Drupal\Core\PageCache\ResponsePolicy\KillSwitch + */ + protected $killSwitch; + + /** + * Constructs a MatchController object. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager + * The entity type manager. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The current route match. + * @param \Drupal\Core\Session\AccountProxyInterface $current_user + * The current user. + * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory + * The config factory. + * @param \Drupal\Core\State\StateInterface $state + * The state service. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory + * The logger channel factory. + * @param \Drupal\Core\PageCache\ResponsePolicy\KillSwitch $kill_switch + * The page cache kill switch. + */ + public function __construct( + EntityTypeManagerInterface $entity_type_manager, + RouteMatchInterface $route_match, + AccountProxyInterface $current_user, + ConfigFactoryInterface $config_factory, + StateInterface $state, + MessengerInterface $messenger, + LoggerChannelFactoryInterface $logger_factory, + KillSwitch $kill_switch, + ) { + $this->entityTypeManager = $entity_type_manager; + $this->routeMatch = $route_match; + $this->currentUser = $current_user; + $this->configFactory = $config_factory; + $this->state = $state; + $this->messenger = $messenger; + $this->loggerFactory = $logger_factory; + $this->killSwitch = $kill_switch; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): self { + return new self( + $container->get('entity_type.manager'), + $container->get('current_route_match'), + $container->get('current_user'), + $container->get('config.factory'), + $container->get('state'), + $container->get('messenger'), + $container->get('logger.factory'), + $container->get('page_cache_kill_switch'), + ); + } /** * Build content to display on page. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + * A redirect response back to the node. */ public function interestedContent() { - $nid = \Drupal::routeMatch()->getRawParameter('node'); + $nid = $this->routeMatch->getRawParameter('node'); // Load entity node using node id. - $node = \Drupal::entityTypeManager()->getStorage('node')->load($nid); + $node = $this->entityTypeManager->getStorage('node')->load($nid); if ($node->getType() == 'match_engagement' || $node->getType() == 'mentorship_engagement') { - $current_user = \Drupal::currentUser()->id(); + $current_user = $this->currentUser->id(); $interested_users = $node->get('field_match_interested_users')->getValue(); if (array_search($current_user, array_column($interested_users, 'target_id')) !== FALSE) { foreach ($interested_users as $key => $interested_user) { @@ -29,24 +153,24 @@ public function interestedContent() { } $node->set('field_match_interested_users', $interested_users); $node->save(); - \Drupal::messenger()->addStatus(t("You have been removed from the interested list")); + $this->messenger->addStatus($this->t("You have been removed from the interested list")); } else { $interested_users[] = ['target_id' => $current_user]; // Get current user. - $current_user = \Drupal::currentUser(); - \Drupal::logger('access_match_engagement')->notice('User @current_user added to interested list', ['@current_user' => $current_user->getAccountName()]); + $current_user = $this->currentUser; + $this->loggerFactory->get('access_match_engagement')->notice('User @current_user added to interested list', ['@current_user' => $current_user->getAccountName()]); if ($node->getType() == 'match_engagement') { - $config = \Drupal::configFactory()->getEditable('access_match_engagement.settings'); + $config = $this->configFactory->getEditable('access_match_engagement.settings'); $config->set('interested', 1); $config->save(); } if ($node->getType() == 'mentorship_engagement') { - $interested_list = \Drupal::state()->get('access_mentorship_interested'); + $interested_list = $this->state->get('access_mentorship_interested'); $create_list = []; if (!empty($interested_list) && $interested_list !== '0') { $decoded = json_decode($interested_list, TRUE); - if ($decoded !== null && is_array($decoded)) { + if ($decoded !== NULL && is_array($decoded)) { $create_list = $decoded; } } @@ -54,15 +178,15 @@ public function interestedContent() { $create_list[] = $nid; } $interested_list = json_encode($create_list); - \Drupal::state()->set('access_mentorship_interested', $interested_list); + $this->state->set('access_mentorship_interested', $interested_list); } // Update node field. $node->set('field_match_interested_users', $interested_users); $node->save(); - \Drupal::messenger()->addStatus($this->t("You have been added to the interested list")); + $this->messenger->addStatus($this->t("You have been added to the interested list")); } } - \Drupal::service('page_cache_kill_switch')->trigger(); + $this->killSwitch->trigger(); // Redirect to node. $url = Url::fromRoute('entity.node.canonical', ['node' => $nid]); return new RedirectResponse($url->toString()); diff --git a/modules/access_match_engagement/src/Plugin/Block/MatchNodeBlock.php b/modules/access_match_engagement/src/Plugin/Block/MatchNodeBlock.php index b5bb1d51..ed00bdd1 100644 --- a/modules/access_match_engagement/src/Plugin/Block/MatchNodeBlock.php +++ b/modules/access_match_engagement/src/Plugin/Block/MatchNodeBlock.php @@ -7,11 +7,9 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\file\Entity\File; use Drupal\Core\File\FileUrlGeneratorInterface; -use Drupal\Core\Url; -use Drupal\image\Entity\ImageStyle; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Session\AccountProxyInterface; use Drupal\Core\Cache\Cache; /** @@ -22,7 +20,7 @@ * admin_label = @Translation("Access Match Node Block") * ) */ -class MatchNodeBlock extends BlockBase implements +final class MatchNodeBlock extends BlockBase implements ContainerFactoryPluginInterface { /** @@ -46,12 +44,19 @@ class MatchNodeBlock extends BlockBase implements */ protected $routMatchInterface; + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountProxyInterface + */ + protected $currentUser; + /** * {@inheritdoc} * * @param \Symfony\Component\DependencyInjection\ContainerInterface $container * Container pulled in. - * @param array $configuration + * @param array $configuration * Configuration added. * @param string $plugin_id * Plugin_id added. @@ -68,13 +73,14 @@ public static function create(ContainerInterface $container, array $configuratio $container->get('entity_type.manager'), $container->get('current_route_match'), $container->get('file_url_generator'), + $container->get('current_user'), ); } /** * {@inheritdoc} * - * @param array $configuration + * @param array $configuration * Configuration array. * @param string $plugin_id * Plugin id string. @@ -82,26 +88,34 @@ public static function create(ContainerInterface $container, array $configuratio * Plugin Definition mixed. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_interface * Invokes renderer. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match_interface + * The current route match. * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator * File url generator. + * @param \Drupal\Core\Session\AccountProxyInterface $current_user + * The current user. */ public function __construct( array $configuration, $plugin_id, $plugin_definition, - EntityTypeManagerInterface - $entity_interface, + EntityTypeManagerInterface $entity_interface, RouteMatchInterface $route_match_interface, - FileUrlGeneratorInterface $file_url_generator + FileUrlGeneratorInterface $file_url_generator, + AccountProxyInterface $current_user, ) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityInterface = $entity_interface; $this->routMatchInterface = $route_match_interface; $this->fileUrlGenerator = $file_url_generator; + $this->currentUser = $current_user; } /** * {@inheritdoc} + * + * @return array + * A render array. */ public function build() { $thisNode = $this->routMatchInterface->getParameter('node'); @@ -109,6 +123,7 @@ public function build() { $nid = $thisNode->id(); $node = $this->entityInterface->getStorage('node')->load($nid); $status = $node->get('field_status')->getValue(); + $is_recruiting = FALSE; if ($status) { $status = $status[0]['value']; $is_recruiting = strcasecmp($status, 'recruiting') == 0 ? TRUE : FALSE; @@ -119,18 +134,17 @@ public function build() { $interested_users = $node->get('field_match_interested_users')->getValue(); // Lookup user names from uid. $interested_users = $this->getInterestedUsers($interested_users); - $status = $node->get('field_status')->getValue() ?? $node->get('field_status')->getValue()[0]['value']; $interested_button = ''; if ($is_recruiting) { $interested_list = $node->get('field_match_interested_users')->getValue(); - $user = \Drupal::currentUser()->id(); + $user = $this->currentUser->id(); if (array_search($user, array_column($interested_list, 'target_id')) !== FALSE) { $uninterested_text = $this->t("I'm no longer Interested"); $interested_button = "$uninterested_text"; } else { $interested_text = $this->t("I'm Interested"); - $interested_button = $is_recruiting ? "$interested_text" : ''; + $interested_button = "$interested_text"; } } $match_node_block['string'] = [ @@ -158,7 +172,7 @@ public function build() { } else { return [ - '#markup' => $this->t('Match Node Block - not a match node') + '#markup' => $this->t('Match Node Block - not a match node'), ]; } @@ -166,15 +180,21 @@ public function build() { /** * Get interested users. + * + * @param array> $interested_users + * The interested users field values. + * + * @return array + * The interested users' full names. */ - public function getInterestedUsers($interested_users) { + public function getInterestedUsers(array $interested_users): array { // Only show interested users to match_sc, match_pm, and admin. $accepted_roles = ['administrator', 'match_sc', 'match_pm']; - $current_user = \Drupal::currentUser(); + $current_user = $this->currentUser; $roles = $current_user->getRoles(); if (empty(in_array($accepted_roles, $roles))) { return []; - }; + } $interested_users = array_column($interested_users, 'target_id'); $users = $this->entityInterface->getStorage('user')->loadMultiple($interested_users); @@ -193,7 +213,8 @@ public function getCacheTags() { if ($node = $this->routMatchInterface->getParameter('node')) { // If there is node add its cachetag. return Cache::mergeTags(parent::getCacheTags(), ['node:' . $node->id()]); - } else { + } + else { // Return default tags instead. return parent::getCacheTags(); } @@ -208,4 +229,5 @@ public function getCacheContexts() { // Every new route this block will rebuild. return Cache::mergeContexts(parent::getCacheContexts(), ['route']); } + } diff --git a/modules/access_outages/access_outages.info.yml b/modules/access_outages/access_outages.info.yml index 17451e90..593600c6 100644 --- a/modules/access_outages/access_outages.info.yml +++ b/modules/access_outages/access_outages.info.yml @@ -3,9 +3,8 @@ description: ACCESS Outages module package: Custom type: module core_version_requirement: ^10 || ^11 -version: 1.0 dependencies: - - access + - access:access libraries: - access_outages/outages_library diff --git a/modules/access_outages/access_outages.module b/modules/access_outages/access_outages.module index 65a9fd6a..8c3f9681 100644 --- a/modules/access_outages/access_outages.module +++ b/modules/access_outages/access_outages.module @@ -2,12 +2,15 @@ /** * @file + * Hooks and preprocess functions for the ACCESS Outages module. */ /** * Implements hook_theme(). + * + * @phpstan-return array */ -function access_outages_theme() { +function access_outages_theme(): array { return [ 'outages_block' => [ 'variables' => [ @@ -18,10 +21,14 @@ function access_outages_theme() { } /** - * Help display affinity group outages by passing cider resource ids to - * to the access_outages library + * Implements hook_preprocess_HOOK() for layout. + * + * Helps display affinity group outages by passing cider resource ids to the + * access_outages library. + * + * @phpstan-param array $vars */ -function access_outages_preprocess_layout(&$vars) { +function access_outages_preprocess_layout(array &$vars): void { $entity = $vars['content']['#entity']; if ($entity->bundle() == 'affinity_group') { $node_storage = \Drupal::entityTypeManager()->getStorage('node'); diff --git a/modules/access_outages/src/Plugin/Block/OutagesBlock.php b/modules/access_outages/src/Plugin/Block/OutagesBlock.php index 2dea09ae..7d2af777 100644 --- a/modules/access_outages/src/Plugin/Block/OutagesBlock.php +++ b/modules/access_outages/src/Plugin/Block/OutagesBlock.php @@ -17,8 +17,11 @@ class OutagesBlock extends BlockBase { /** * {@inheritdoc} + * + * @return array + * The block render array. */ - public function build() { + public function build(): array { return [ '#theme' => 'outages_block', '#data' => [], diff --git a/modules/access_shortcodes/access_shortcodes.info.yml b/modules/access_shortcodes/access_shortcodes.info.yml index 3dc06341..b244fec9 100644 --- a/modules/access_shortcodes/access_shortcodes.info.yml +++ b/modules/access_shortcodes/access_shortcodes.info.yml @@ -4,4 +4,4 @@ type: module core_version_requirement: ^10 || ^11 package: Custom dependencies: - - access + - access:access diff --git a/modules/access_shortcodes/access_shortcodes.module b/modules/access_shortcodes/access_shortcodes.module index 247ebaa7..53860a39 100644 --- a/modules/access_shortcodes/access_shortcodes.module +++ b/modules/access_shortcodes/access_shortcodes.module @@ -2,12 +2,16 @@ /** * @file + * Hooks and theme definitions for the ACCESS Shortcodes module. */ /** * Define our variables (parameters) for each shortcode. + * + * @return array + * The theme definitions. */ -function access_shortcodes_theme() { +function access_shortcodes_theme(): array { return [ 'shortcode_icon_block' => [ 'variables' => [ diff --git a/modules/access_shortcodes/src/Plugin/Shortcode/AccordionShortcode.php b/modules/access_shortcodes/src/Plugin/Shortcode/AccordionShortcode.php index e4dda8ed..e4ae4f51 100644 --- a/modules/access_shortcodes/src/Plugin/Shortcode/AccordionShortcode.php +++ b/modules/access_shortcodes/src/Plugin/Shortcode/AccordionShortcode.php @@ -18,6 +18,13 @@ class AccordionShortcode extends ShortcodeBase { /** * {@inheritdoc} + * + * @param array $attributes + * The shortcode attributes. + * @param string $text + * The text between the shortcode tags. + * @param string $langcode + * The language code. */ public function process(array $attributes, $text, $langcode = Language::LANGCODE_NOT_SPECIFIED) { $attributes = $this->getAttributes([ diff --git a/modules/access_shortcodes/src/Plugin/Shortcode/CTABlockShortcode.php b/modules/access_shortcodes/src/Plugin/Shortcode/CTABlockShortcode.php index 361984e7..b341a064 100644 --- a/modules/access_shortcodes/src/Plugin/Shortcode/CTABlockShortcode.php +++ b/modules/access_shortcodes/src/Plugin/Shortcode/CTABlockShortcode.php @@ -18,6 +18,13 @@ class CTABlockShortcode extends ShortcodeBase { /** * {@inheritdoc} + * + * @param array $attributes + * The shortcode attributes. + * @param string $text + * The text between the shortcode tags. + * @param string $langcode + * The language code. */ public function process(array $attributes, $text, $langcode = Language::LANGCODE_NOT_SPECIFIED) { $attributes = $this->getAttributes([ diff --git a/modules/access_shortcodes/src/Plugin/Shortcode/IconBlockShortcode.php b/modules/access_shortcodes/src/Plugin/Shortcode/IconBlockShortcode.php index e7330118..48a4baeb 100644 --- a/modules/access_shortcodes/src/Plugin/Shortcode/IconBlockShortcode.php +++ b/modules/access_shortcodes/src/Plugin/Shortcode/IconBlockShortcode.php @@ -18,6 +18,13 @@ class IconBlockShortcode extends ShortcodeBase { /** * {@inheritdoc} + * + * @param array $attributes + * The shortcode attributes. + * @param string $text + * The text between the shortcode tags. + * @param string $langcode + * The language code. */ public function process(array $attributes, $text, $langcode = Language::LANGCODE_NOT_SPECIFIED) { $attributes = $this->getAttributes([ @@ -62,7 +69,7 @@ public function process(array $attributes, $text, $langcode = Language::LANGCODE */ public function tips($long = FALSE) { $output = []; - $output[] = '

' . $this->t('[icon_box img="image url" alt="alt text for image" title="Your title here" text="Your text here" link="Link for icon box" boxed="boxed" btnLink="https://example.com" btnText="Optional button Link"][/icon_box] ') . ' '; + $output[] = '

' . $this->t('[icon_box img="image url" alt="alt text for image" title="Your title here" text="Your text here" link="Link for icon box" boxed="boxed" btnLink="https://example.com" btnText="Optional button Link"][/icon_box]') . ' '; if ($long) { $output[] = $this->t('Builds an icon box with the image that you specify and the title text.') . '

'; } diff --git a/modules/access_shortcodes/src/Plugin/Shortcode/SquareShortcode.php b/modules/access_shortcodes/src/Plugin/Shortcode/SquareShortcode.php index a2f0f235..c677704b 100644 --- a/modules/access_shortcodes/src/Plugin/Shortcode/SquareShortcode.php +++ b/modules/access_shortcodes/src/Plugin/Shortcode/SquareShortcode.php @@ -18,6 +18,13 @@ class SquareShortcode extends ShortcodeBase { /** * {@inheritdoc} + * + * @param array $attributes + * The shortcode attributes. + * @param string $text + * The text between the shortcode tags. + * @param string $langcode + * The language code. */ public function process(array $attributes, $text, $langcode = Language::LANGCODE_NOT_SPECIFIED) { $attributes = $this->getAttributes([ diff --git a/modules/ondemand/ondemand.info.yml b/modules/ondemand/ondemand.info.yml index 39045c69..41ed1ca0 100644 --- a/modules/ondemand/ondemand.info.yml +++ b/modules/ondemand/ondemand.info.yml @@ -4,4 +4,4 @@ type: module core_version_requirement: ^10 || ^11 package: Custom dependencies: - - access + - access:access diff --git a/modules/ondemand/ondemand.module b/modules/ondemand/ondemand.module index 8657f9aa..ab4cd3dd 100644 --- a/modules/ondemand/ondemand.module +++ b/modules/ondemand/ondemand.module @@ -6,14 +6,16 @@ */ use Drupal\Component\Utility\Html; +use Drupal\search_api\Plugin\views\query\SearchApiQuery; use Drupal\user\Entity\User; +use Drupal\user\UserInterface; use Drupal\views\ViewExecutable; use Drupal\views\Plugin\views\query\QueryPluginBase; /** - * + * Implements hook_user_login(). */ -function ondemand_user_login($account) { +function ondemand_user_login(UserInterface $account): void { $current_domain_name = \Drupal::service('access_misc.sitetools')->getDomain(); if ($current_domain_name == 'open-ondemand' || $current_domain_name == 'pa-science') { @@ -56,16 +58,17 @@ function ondemand_user_login($account) { } /** - * Filters for People SearchAPI view. - * - * @param \Drupal\views\ViewExecutable $view - * @param \Drupal\views\Plugin\views\query\QueryPluginBase $query + * Implements hook_views_query_alter(). * - * @return void + * Filters for People SearchAPI view. */ -function ondemand_views_query_alter(ViewExecutable $view, QueryPluginBase $query) { +function ondemand_views_query_alter(ViewExecutable $view, QueryPluginBase $query): void { if ($view->id() === 'cyberteam_people_facets' && $view->current_display === 'page_1') { + // The People view is powered by Search API. + if (!$query instanceof SearchApiQuery) { + return; + } $token = \Drupal::token(); $domain_name = Html::getClass($token->replace(t('[domain:name]')));