Skip to content

added cron jobs and attendee feedback form dynamic form link#540

Closed
nishant22029 wants to merge 16 commits intodevelopfrom
feat/goonj-activities-crons
Closed

added cron jobs and attendee feedback form dynamic form link#540
nishant22029 wants to merge 16 commits intodevelopfrom
feat/goonj-activities-crons

Conversation

@nishant22029
Copy link
Copy Markdown
Collaborator

@nishant22029 nishant22029 commented Dec 5, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced email notifications for Goonj activities, enhancing communication regarding logistics.
    • Added API endpoints for handling cron jobs related to Goonj activities and volunteer feedback.
    • Implemented token evaluation for institution dropping centers, improving data handling.
  • Improvements

    • Enhanced data retrieval capabilities for action targets, improving the efficiency of information access.
    • Updated URL formatting for attendee feedback links, ensuring better integration with feedback forms.
    • Expanded permissions for various tabs, allowing broader access control.
  • Bug Fixes

    • Implemented error handling to log exceptions during email sending processes, improving reliability.
    • Refined logic for handling contact details in email notifications, ensuring valid data usage.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 5, 2024

Walkthrough

The changes in this pull request enhance the GoonjActivitiesService class by adding email notification capabilities for Goonj activities. A new method for sending logistics emails is introduced, along with private methods for constructing email content and retrieving the sender's address. Additionally, new API endpoints are created for handling cron jobs related to Goonj activities and volunteer feedback. Modifications to URL formatting for feedback links are also made across several files, ensuring the correct parameters are passed. Overall, the changes improve functionality and maintainability of the Goonj custom extension.

Changes

File Path Change Summary
wp-content/civi-extensions/goonjcustom/Civi/GoonjActivitiesService.php - Added methods: sendActivityLogisticsEmail, getFromAddress, getLogisticsEmailHtml.
- Added variable: $fromAddress.
- Updated method: goonjActivitiesTabset.
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/GoonjActivitiesCron.php - Added functions: _civicrm_api3_goonjcustom_goonj_activities_cron_spec, civicrm_api3_goonjcustom_goonj_activities_cron.
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/VolunteerFeedbackGoonjActivityCron.php - Added functions: civicrm_api3_goonjcustom_volunteer_feedback_goonj_activity_cron, goonjcustom_volunteer_feedback_collection_activity_email_html.
- Updated function: _civicrm_api3_goonjcustom_volunteer_feedback_goonj_activity_cron_spec.
wp-content/plugins/goonj-blocks/build/render.php - Modified URL construction for attendee_activity_feedback_link, changing format without altering logic.
wp-content/plugins/goonj-blocks/goonj-blocks.php - Added field: 'Goonj_Activities.Select_Attendee_feedback_form' to $entity_fields array.
wp-content/plugins/goonj-blocks/src/render.php - Modified URL construction for attendee_activity_feedback_link, changing format without altering logic.
wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Token/InstitutionDroppingCenter.php - Added class: CRM_Goonjcustom_Token_InstitutionDroppingCenter.
- Added methods for token evaluation and formatting.
wp-content/civi-extensions/goonjcustom/Civi/CollectionBaseService.php - Updated methods: handleAuthorizationEmails, handleAuthorizationEmailsPost to include additional field in selection.
wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php - Updated permissions for tabs and customized email content in alterReceiptMail.
wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php - Updated permissions for tabs in droppingCenterTabset method.
wp-content/civi-extensions/goonjcustom/Civi/InductionService.php - Enhanced logic for email notifications and contact retrieval in sendInductionRescheduleEmail and sendRemainderEmails.
wp-content/civi-extensions/goonjcustom/Civi/InstitutionCollectionCampService.php - Updated methods to include institution details in email notifications.
wp-content/civi-extensions/goonjcustom/Civi/Traits/CollectionSource.php - Added private method: getInitiatorId for handling collection camp data.
wp-content/civi-extensions/goonjcustom/goonjcustom.php - Enhanced token registration and evaluation processes for induction-related data.

Possibly related PRs

Suggested labels

status : ready for review

Suggested reviewers

  • belwalshubham

🎉 In the land of code, where functions reside,
New emails are sent, with logistics as guide.
From camps to feedback, the messages flow,
With careful design, watch the notifications grow!
So here's to the changes, both clever and bright,
In the world of Goonj, everything feels right! 🎉


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (3)
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/GoonjActivitiesCron.php (1)

83-86: Use Appropriate Logging Level for Exception Handling

Exceptions are currently logged using \Civi::log()->info. To ensure errors are properly flagged and monitored, consider using \Civi::log()->error when logging exceptions.

wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/VolunteerFeedbackGoonjActivityCron.php (1)

113-129: Consider Utilizing a Templating System for Email Content

The function goonjcustom_volunteer_feedback_collection_activity_email_html constructs HTML email content within the code. For better readability and maintainability, consider using a templating system or moving the HTML content to a separate template file.

wp-content/plugins/goonj-blocks/goonj-blocks.php (1)

95-95: Ensure Consistent Naming Conventions

The field name 'Goonj_Activities.Select_Attendee_feedback_form' uses inconsistent casing (feedback_form vs. Feedback_Form). For consistency and clarity, consider renaming it to 'Goonj_Activities.Select_Attendee_Feedback_Form'.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between af65baa and f189c3d.

📒 Files selected for processing (6)
  • wp-content/civi-extensions/goonjcustom/Civi/GoonjActivitiesService.php (3 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/GoonjActivitiesCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/VolunteerFeedbackGoonjActivityCron.php (1 hunks)
  • wp-content/plugins/goonj-blocks/build/render.php (1 hunks)
  • wp-content/plugins/goonj-blocks/goonj-blocks.php (1 hunks)
  • wp-content/plugins/goonj-blocks/src/render.php (1 hunks)
🔇 Additional comments (5)
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/VolunteerFeedbackGoonjActivityCron.php (2)

38-43: Refactor Duplicated Code for Retrieving Option Values

The code from lines 38-43 duplicates the option value retrieval logic found in GoonjActivitiesCron.php. Refactoring this into a shared method will promote code reuse and comply with the DRY principle.


102-104: Use Appropriate Logging Level for Exception Handling

Similar to the previous file, exceptions are logged using \Civi::log()->info. Utilize \Civi::log()->error to log exceptions appropriately and improve error monitoring.

wp-content/plugins/goonj-blocks/src/render.php (1)

109-111: Add Null Checks to Prevent Undefined Index Errors

As mentioned in build/render.php, ensure that $action_target['Goonj_Activities.Select_Attendee_feedback_form'] is set before using it to avoid undefined index errors.

wp-content/civi-extensions/goonjcustom/Civi/GoonjActivitiesService.php (2)

375-376: LGTM: Tab configuration update.

The module and directive names have been updated correctly for the 'campOutcome' tab.


647-653: LGTM: Email sender configuration.

The implementation correctly retrieves and caches the default sender's email address.

Comment on lines +38 to +44

$optionValues = OptionValue::get(FALSE)
->addWhere('option_group_id:name', '=', 'eck_sub_types')
->addWhere('name', '=', 'Goonj_activities')
->addWhere('grouping', '=', 'Collection_Camp')
->setLimit(1)
->execute()->single();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor Duplicated Code for Retrieving Option Values

The code segment from lines 38-44 duplicates logic that's also present in VolunteerFeedbackGoonjActivityCron.php. Extract this common functionality into a shared method to adhere to the DRY (Don't Repeat Yourself) principle and enhance maintainability.

Comment on lines +109 to +111
'%s#?Eck_Collection_Camp1=%s',
$action_target['Goonj_Activities.Select_Attendee_feedback_form'],
$action_target['id']
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add Null Checks to Prevent Undefined Index Errors

The code assumes that $action_target['Goonj_Activities.Select_Attendee_feedback_form'] exists. To prevent potential undefined index errors, add a check to ensure this key is set before using it.

Apply the following diff to address the issue:

+if (isset($action_target['Goonj_Activities.Select_Attendee_feedback_form'])) {
  $attendee_activity_feedback_link = sprintf(
    '%s#?Eck_Collection_Camp1=%s',
    $action_target['Goonj_Activities.Select_Attendee_feedback_form'],
    $action_target['id']
  );
+} else {
+  $attendee_activity_feedback_link = '#';
+}

Committable suggestion skipped: line range outside the PR's diff.

const GOONJ_ACTIVITIES_INTENT_FB_NAME = 'afformGoonjActivitiesIndividualIntentForm';
const RELATIONSHIP_TYPE_NAME = 'Goonj Activities Coordinator of';
private static $goonjActivitiesAddress = NULL;
private static $fromAddress = NULL;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider moving email-related functionality to a separate service class.

The class is handling multiple responsibilities (QR codes, activities, tabs, and now emails), which violates the Single Responsibility Principle. Consider extracting email-related functionality into a dedicated GoonjEmailService class.

Comment on lines +658 to +675
private static function getLogisticsEmailHtml($contactName, $collectionCampId, $campAttendedById, $collectionCampGoonjOffice, $campCode, $campAddress, $outcomeFormLink) {
$homeUrl = \CRM_Utils_System::baseCMSURL();
// Construct the full URLs for the forms.
$campOutcomeFormUrl = $homeUrl . $outcomeFormLink . '#?Eck_Collection_Camp1=' . $collectionCampId . '&Camp_Outcome.Filled_By=' . $campAttendedById;
\Civi::log()->info('campOutcomeFormUrl', ['campOutcomeFormUrl'=>$campOutcomeFormUrl]);
$html = "
<p>Dear $contactName,</p>
<p>Thank you for attending the goonj activity <strong>$campCode</strong> at <strong>$campAddress</strong>. Their is one forms that require your attention during and after the goonj activity:</p>
<ol>
Please complete this form from the goonj activity location once the goonj activity ends.</li>
<li><a href=\"$campOutcomeFormUrl\">Goonj Activity Outcome Form</a><br>
This feedback form should be filled out after the goonj activity/session ends, once you have an overview of the event's outcomes.</li>
</ol>
<p>We appreciate your cooperation.</p>
<p>Warm Regards,<br>Urban Relations Team</p>";

return $html;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix HTML content and improve email template.

Issues in the email template:

  1. Typo: "Their" should be "There"
  2. Malformed HTML list with a closing </li> without an opening tag
  3. Hardcoded strings should be moved to language files for i18n

Apply this diff to fix the issues:

   private static function getLogisticsEmailHtml($contactName, $collectionCampId, $campAttendedById, $collectionCampGoonjOffice, $campCode, $campAddress, $outcomeFormLink) {
     $homeUrl = \CRM_Utils_System::baseCMSURL();
     // Construct the full URLs for the forms.
     $campOutcomeFormUrl = $homeUrl . $outcomeFormLink . '#?Eck_Collection_Camp1=' . $collectionCampId . '&Camp_Outcome.Filled_By=' . $campAttendedById;
-    \Civi::log()->info('campOutcomeFormUrl', ['campOutcomeFormUrl'=>$campOutcomeFormUrl]);
     $html = "
     <p>Dear $contactName,</p>
-    <p>Thank you for attending the goonj activity <strong>$campCode</strong> at <strong>$campAddress</strong>. Their is one forms that require your attention during and after the goonj activity:</p>
+    <p>Thank you for attending the goonj activity <strong>$campCode</strong> at <strong>$campAddress</strong>. There is one form that requires your attention during and after the goonj activity:</p>
     <ol>
-        Please complete this form from the goonj activity location once the goonj activity ends.</li>
         <li><a href=\"$campOutcomeFormUrl\">Goonj Activity Outcome Form</a><br>
         This feedback form should be filled out after the goonj activity/session ends, once you have an overview of the event's outcomes.</li>
     </ol>
     <p>We appreciate your cooperation.</p>
     <p>Warm Regards,<br>Urban Relations Team</p>";

     return $html;
   }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +588 to +642
public static function sendActivityLogisticsEmail($collectionCamp) {
try {
$campId = $collectionCamp['id'];
$activityCode = $collectionCamp['title'];
$activityOffice = $collectionCamp['Goonj_Activities.Goonj_Office'];
$activityAddress = $collectionCamp['Goonj_Activities.Where_do_you_wish_to_organise_the_activity_'];
$activityAttendedById = $collectionCamp['Logistics_Coordination.Camp_to_be_attended_by'];
$logisticEmailSent = $collectionCamp['Logistics_Coordination.Email_Sent'];
$outcomeFormLink = $collectionCamp['Goonj_Activities.Select_Goonj_POC_Attendee_Outcome_Form'];

$startDate = new \DateTime($collectionCamp['Goonj_Activities.Start_Date']);


$today = new \DateTimeImmutable();
$endOfToday = $today->setTime(23, 59, 59);


if (true) {
$campAttendedBy = Contact::get(FALSE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $activityAttendedById)
->execute()->single();

$attendeeEmail = $campAttendedBy['email.email'];
$attendeeName = $campAttendedBy['display_name'];

if (!$attendeeEmail) {
throw new \Exception('Attendee email missing');
}

$mailParams = [
'subject' => 'Goonj Activity Notification: ' . $activityCode . ' at ' . $activityAddress,
'from' => self::getFromAddress(),
'toEmail' => $attendeeEmail,
'replyTo' => self::getFromAddress(),
'html' => self::getLogisticsEmailHtml($attendeeName, $campId, $activityAttendedById, $activityOffice, $activityCode, $activityAddress, $outcomeFormLink),
];

$emailSendResult = \CRM_Utils_Mail::send($mailParams);

if ($emailSendResult) {
\Civi::log()->info("Logistics email sent for collection camp: $campId");
EckEntity::update('Collection_Camp', FALSE)
->addValue('Logistics_Coordination.Email_Sent', 1)
->addWhere('id', '=', $campId)
->execute();
}
}
}
catch (\Exception $e) {
\Civi::log()->error("Error in sendLogisticsEmail for $campId " . $e->getMessage());
}

}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Improve error handling and add input validation.

Several issues need attention:

  1. The condition if (true) at line 605 seems incorrect
  2. Missing validation for required fields before processing
  3. Exception handling could be more specific

Apply this diff to improve the implementation:

 public static function sendActivityLogisticsEmail($collectionCamp) {
+    if (empty($collectionCamp['id'])) {
+      throw new \InvalidArgumentException('Collection camp ID is required');
+    }
+
     try {
       $campId = $collectionCamp['id'];
+      // Validate required fields
+      $requiredFields = [
+        'title' => 'Activity code',
+        'Goonj_Activities.Goonj_Office' => 'Activity office',
+        'Goonj_Activities.Where_do_you_wish_to_organise_the_activity_' => 'Activity address',
+        'Logistics_Coordination.Camp_to_be_attended_by' => 'Camp attendee',
+      ];
+      
+      foreach ($requiredFields as $field => $label) {
+        if (empty($collectionCamp[$field])) {
+          throw new \InvalidArgumentException("$label is required");
+        }
+      }
+
       $activityCode = $collectionCamp['title'];
       $activityOffice = $collectionCamp['Goonj_Activities.Goonj_Office'];
       $activityAddress = $collectionCamp['Goonj_Activities.Where_do_you_wish_to_organise_the_activity_'];
       $activityAttendedById = $collectionCamp['Logistics_Coordination.Camp_to_be_attended_by'];
       $logisticEmailSent = $collectionCamp['Logistics_Coordination.Email_Sent'];
       $outcomeFormLink = $collectionCamp['Goonj_Activities.Select_Goonj_POC_Attendee_Outcome_Form'];

       $startDate = new \DateTime($collectionCamp['Goonj_Activities.Start_Date']);
       $today = new \DateTimeImmutable();
       $endOfToday = $today->setTime(23, 59, 59);

-      if (true) {
+      if (!$logisticEmailSent) {
         $campAttendedBy = Contact::get(FALSE)
           ->addSelect('email.email', 'display_name')
           ->addJoin('Email AS email', 'LEFT')
           ->addWhere('id', '=', $activityAttendedById)
           ->execute()->single();

         $attendeeEmail = $campAttendedBy['email.email'];
         $attendeeName = $campAttendedBy['display_name'];

         if (!$attendeeEmail) {
           throw new \Exception('Attendee email missing');
         }

         $mailParams = [
           'subject' => 'Goonj Activity Notification: ' . $activityCode . ' at ' . $activityAddress,
           'from' => self::getFromAddress(),
           'toEmail' => $attendeeEmail,
           'replyTo' => self::getFromAddress(),
           'html' => self::getLogisticsEmailHtml($attendeeName, $campId, $activityAttendedById, $activityOffice, $activityCode, $activityAddress, $outcomeFormLink),
         ];

         $emailSendResult = \CRM_Utils_Mail::send($mailParams);

         if ($emailSendResult) {
           \Civi::log()->info("Logistics email sent for collection camp: $campId");
           EckEntity::update('Collection_Camp', FALSE)
             ->addValue('Logistics_Coordination.Email_Sent', 1)
             ->addWhere('id', '=', $campId)
             ->execute();
         }
       }
     }
-    catch (\Exception $e) {
+    catch (\InvalidArgumentException $e) {
+      \Civi::log()->error("Invalid input in sendLogisticsEmail for $campId: " . $e->getMessage());
+      throw $e;
+    }
+    catch (\Exception $e) {
       \Civi::log()->error("Error in sendLogisticsEmail for $campId " . $e->getMessage());
+      throw $e;
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static function sendActivityLogisticsEmail($collectionCamp) {
try {
$campId = $collectionCamp['id'];
$activityCode = $collectionCamp['title'];
$activityOffice = $collectionCamp['Goonj_Activities.Goonj_Office'];
$activityAddress = $collectionCamp['Goonj_Activities.Where_do_you_wish_to_organise_the_activity_'];
$activityAttendedById = $collectionCamp['Logistics_Coordination.Camp_to_be_attended_by'];
$logisticEmailSent = $collectionCamp['Logistics_Coordination.Email_Sent'];
$outcomeFormLink = $collectionCamp['Goonj_Activities.Select_Goonj_POC_Attendee_Outcome_Form'];
$startDate = new \DateTime($collectionCamp['Goonj_Activities.Start_Date']);
$today = new \DateTimeImmutable();
$endOfToday = $today->setTime(23, 59, 59);
if (true) {
$campAttendedBy = Contact::get(FALSE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $activityAttendedById)
->execute()->single();
$attendeeEmail = $campAttendedBy['email.email'];
$attendeeName = $campAttendedBy['display_name'];
if (!$attendeeEmail) {
throw new \Exception('Attendee email missing');
}
$mailParams = [
'subject' => 'Goonj Activity Notification: ' . $activityCode . ' at ' . $activityAddress,
'from' => self::getFromAddress(),
'toEmail' => $attendeeEmail,
'replyTo' => self::getFromAddress(),
'html' => self::getLogisticsEmailHtml($attendeeName, $campId, $activityAttendedById, $activityOffice, $activityCode, $activityAddress, $outcomeFormLink),
];
$emailSendResult = \CRM_Utils_Mail::send($mailParams);
if ($emailSendResult) {
\Civi::log()->info("Logistics email sent for collection camp: $campId");
EckEntity::update('Collection_Camp', FALSE)
->addValue('Logistics_Coordination.Email_Sent', 1)
->addWhere('id', '=', $campId)
->execute();
}
}
}
catch (\Exception $e) {
\Civi::log()->error("Error in sendLogisticsEmail for $campId " . $e->getMessage());
}
}
public static function sendActivityLogisticsEmail($collectionCamp) {
if (empty($collectionCamp['id'])) {
throw new \InvalidArgumentException('Collection camp ID is required');
}
try {
$campId = $collectionCamp['id'];
// Validate required fields
$requiredFields = [
'title' => 'Activity code',
'Goonj_Activities.Goonj_Office' => 'Activity office',
'Goonj_Activities.Where_do_you_wish_to_organise_the_activity_' => 'Activity address',
'Logistics_Coordination.Camp_to_be_attended_by' => 'Camp attendee',
];
foreach ($requiredFields as $field => $label) {
if (empty($collectionCamp[$field])) {
throw new \InvalidArgumentException("$label is required");
}
}
$activityCode = $collectionCamp['title'];
$activityOffice = $collectionCamp['Goonj_Activities.Goonj_Office'];
$activityAddress = $collectionCamp['Goonj_Activities.Where_do_you_wish_to_organise_the_activity_'];
$activityAttendedById = $collectionCamp['Logistics_Coordination.Camp_to_be_attended_by'];
$logisticEmailSent = $collectionCamp['Logistics_Coordination.Email_Sent'];
$outcomeFormLink = $collectionCamp['Goonj_Activities.Select_Goonj_POC_Attendee_Outcome_Form'];
$startDate = new \DateTime($collectionCamp['Goonj_Activities.Start_Date']);
$today = new \DateTimeImmutable();
$endOfToday = $today->setTime(23, 59, 59);
if (!$logisticEmailSent) {
$campAttendedBy = Contact::get(FALSE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $activityAttendedById)
->execute()->single();
$attendeeEmail = $campAttendedBy['email.email'];
$attendeeName = $campAttendedBy['display_name'];
if (!$attendeeEmail) {
throw new \Exception('Attendee email missing');
}
$mailParams = [
'subject' => 'Goonj Activity Notification: ' . $activityCode . ' at ' . $activityAddress,
'from' => self::getFromAddress(),
'toEmail' => $attendeeEmail,
'replyTo' => self::getFromAddress(),
'html' => self::getLogisticsEmailHtml($attendeeName, $campId, $activityAttendedById, $activityOffice, $activityCode, $activityAddress, $outcomeFormLink),
];
$emailSendResult = \CRM_Utils_Mail::send($mailParams);
if ($emailSendResult) {
\Civi::log()->info("Logistics email sent for collection camp: $campId");
EckEntity::update('Collection_Camp', FALSE)
->addValue('Logistics_Coordination.Email_Sent', 1)
->addWhere('id', '=', $campId)
->execute();
}
}
}
catch (\InvalidArgumentException $e) {
\Civi::log()->error("Invalid input in sendLogisticsEmail for $campId: " . $e->getMessage());
throw $e;
}
catch (\Exception $e) {
\Civi::log()->error("Error in sendLogisticsEmail for $campId " . $e->getMessage());
throw $e;
}
}

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Outside diff range and nitpick comments (21)
wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php (3)

Line range hint 103-159: Consider refactoring repeated permissions array.

The permissions array ['goonj_chapter_admin', 'urbanops'] is duplicated across multiple tab configurations. This violates the DRY principle and makes maintenance harder.

Consider extracting the common permissions into a class constant:

class CollectionCampService extends AutoSubscriber {
+  const DEFAULT_TAB_PERMISSIONS = ['goonj_chapter_admin', 'urbanops'];
   
   // ... other code ...
   
   public static function collectionCampTabset($tabsetName, &$tabs, $context) {
     // ... other code ...
     $tabConfigs = [
       'logistics' => [
         'title' => ts('Logistics'),
         'module' => 'afsearchCollectionCampLogistics',
         'directive' => 'afsearch-collection-camp-logistics',
         'template' => 'CRM/Goonjcustom/Tabs/CollectionCamp.tpl',
-        'permissions' => ['goonj_chapter_admin', 'urbanops'],
+        'permissions' => self::DEFAULT_TAB_PERMISSIONS,
       ],
       // Apply similar changes to other tabs
     ];

Line range hint 1277-1309: Refactor email template handling to reduce duplication.

The email content is duplicated between online and offline receipt workflows with minimal differences. Additionally, HTML templates are hardcoded in the method.

Consider these improvements:

  1. Extract email templates to separate template files
  2. Create a unified template generation method:
class CollectionCampService extends AutoSubscriber {
+  private static function getEmailTemplate($donorName, $isOffline = false) {
+    $type = $isOffline ? 'offline' : 'online';
+    return [
+      'text' => "Dear {$donorName},\n\nThank you for your {$type} contribution...",
+      'html' => self::renderEmailTemplate('contribution_receipt', [
+        'donorName' => $donorName,
+        'isOffline' => $isOffline,
+      ]),
+    ];
+  }

  public static function alterReceiptMail(&$params, $context) {
    if (empty($params['workflow'])) {
      return;
    }

    $isOffline = $params['workflow'] === 'contribution_offline_receipt';
    $donorName = $isOffline 
      ? (!empty($params['toName']) ? $params['toName'] : 'Valued Supporter')
      : (!empty($params['tplParams']['displayName']) ? $params['tplParams']['displayName'] : 'Valued Supporter');

    $template = self::getEmailTemplate($donorName, $isOffline);
    $params['text'] = $template['text'];
    $params['html'] = $template['html'];
  }

Line range hint 1277-1293: Add parameter validation for the email workflow.

The method assumes the presence of required parameters without validation.

Add validation for required parameters:

public static function alterReceiptMail(&$params, $context) {
+  if (empty($params['workflow']) || !in_array($params['workflow'], [
+    'contribution_online_receipt',
+    'contribution_offline_receipt'
+  ])) {
+    return;
+  }
wp-content/civi-extensions/goonjcustom/Civi/Traits/CollectionSource.php (3)

261-270: Extract duplicate relationship query logic

The relationship query logic is duplicated. Consider extracting it into a private method.

Create a helper method:

+ private static function findRelationship(int $organizationId, string $relationshipType): ?array {
+     return Relationship::get(FALSE)
+         ->addWhere('contact_id_a', '=', $organizationId)
+         ->addWhere('relationship_type_id:name', '=', $relationshipType)
+         ->execute();
+ }

Then simplify the code:

-    $relationships = Relationship::get(FALSE)
-      ->addWhere('contact_id_a', '=', $organizationId)
-      ->addWhere('relationship_type_id:name', '=', $relationshipType)
-      ->execute();
-    if (empty($relationships)) {
-      $relationships = Relationship::get(FALSE)
-        ->addWhere('contact_id_a', '=', $organizationId)
-        ->addWhere('relationship_type_id:name', '=', $alternateType)
-        ->execute();
-    }
+    $relationships = self::findRelationship($organizationId, $relationshipType)
+        ?: self::findRelationship($organizationId, $alternateType);

247-259: Simplify control flow using early returns

The nested if-else structure can be simplified using early returns and guard clauses.

Consider this refactor:

-    if ($subtypeName === 'Institution_Collection_Camp') {
-      $organizationId = $collectionCamp['Institution_Collection_Camp_Intent.Organization_Name.id'];
-      $relationshipType = 'Institution POC of';
-      $alternateType = 'Primary Institution POC of';
-    }
-    elseif ($subtypeName === 'Institution_Dropping_Center') {
-      $organizationId = $collectionCamp['Institution_Dropping_Center_Intent.Organization_Name.id'];
-      $relationshipType = 'Institution POC of';
-      $alternateType = 'Secondary Institution POC of';
-    }
-    else {
-      return $collectionCamp['Collection_Camp_Core_Details.Contact_Id'];
-    }
+    if ($subtypeName !== 'Institution_Collection_Camp' && 
+        $subtypeName !== 'Institution_Dropping_Center') {
+        return $collectionCamp['Collection_Camp_Core_Details.Contact_Id'];
+    }
+
+    $isCollectionCamp = $subtypeName === 'Institution_Collection_Camp';
+    $organizationId = $collectionCamp[
+        $isCollectionCamp 
+            ? 'Institution_Collection_Camp_Intent.Organization_Name.id'
+            : 'Institution_Dropping_Center_Intent.Organization_Name.id'
+    ];
+    $alternateType = $isCollectionCamp 
+        ? self::RELATIONSHIP_TYPE_PRIMARY_POC 
+        : self::RELATIONSHIP_TYPE_SECONDARY_POC;

248-248: Define field names as class constants

Field names are hardcoded throughout the method. These should be defined as class constants to improve maintainability and prevent typos.

Add these constants at the class level:

+ private const FIELD_ORGANIZATION_NAME_COLLECTION = 'Institution_Collection_Camp_Intent.Organization_Name.id';
+ private const FIELD_ORGANIZATION_NAME_DROPPING = 'Institution_Dropping_Center_Intent.Organization_Name.id';
+ private const FIELD_CONTACT_ID = 'Collection_Camp_Core_Details.Contact_Id';

Then use them in the code:

-      $organizationId = $collectionCamp['Institution_Collection_Camp_Intent.Organization_Name.id'];
+      $organizationId = $collectionCamp[self::FIELD_ORGANIZATION_NAME_COLLECTION];

Also applies to: 253-253, 258-258

wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php (4)

Line range hint 435-512: Consider extracting common permissions to a constant.

The permission array ['goonj_chapter_admin', 'urbanops'] is duplicated across multiple tab configurations. This violates the DRY principle and makes permission updates error-prone.

Consider creating a class constant for the common permissions:

class DroppingCenterService extends AutoSubscriber {
+  const DEFAULT_TAB_PERMISSIONS = ['goonj_chapter_admin', 'urbanops'];
   // ...
   
   public static function droppingCenterTabset($tabsetName, &$tabs, $context) {
     $tabConfigs = [
       'logistics' => [
         // ...
-        'permissions' => ['goonj_chapter_admin', 'urbanops'],
+        'permissions' => self::DEFAULT_TAB_PERMISSIONS,
       ],
       // Apply similar changes to other tabs
     ];
   }
}

Line range hint 271-284: Improve email handling with a dedicated email service.

The email handling logic is scattered across multiple methods with similar patterns. This violates the Single Responsibility Principle and leads to code duplication.

Consider creating a dedicated EmailService class to handle all email-related functionality:

class EmailService {
  public function sendTemplatedEmail($template, $params, $recipient) {
    $mailParams = [
      'subject' => $params['subject'],
      'from' => HelperService::getDefaultFromEmail(),
      'toEmail' => $recipient,
      'html' => $this->renderTemplate($template, $params),
    ];
    
    try {
      return \CRM_Utils_Mail::send($mailParams);
    } catch (\Exception $e) {
      \CRM_Core_Error::debug_log_message('Failed to send email: ' . $e->getMessage());
      return false;
    }
  }
}

Also applies to: 386-401


Line range hint 289-379: Refactor setOfficeDetails for better separation of concerns.

The setOfficeDetails method is handling too many responsibilities: state field validation, office lookup, coordinator assignment, and entity updates. This makes the code harder to maintain and test.

Consider breaking down the method into smaller, focused methods:

private static function validateStateField($params) {
  $stateField = self::findStateField($params);
  if (!$stateField || !$stateField['value']) {
    throw new \CRM_Core_Exception('Invalid state field');
  }
  return $stateField;
}

private static function findOfficeForState($stateId) {
  $office = Contact::get(FALSE)
    ->addSelect('id')
    ->addWhere('contact_type', '=', 'Organization')
    ->addWhere('contact_sub_type', 'CONTAINS', 'Goonj_Office')
    ->addWhere('Goonj_Office_Details.Collection_Camp_Catchment', 'CONTAINS', $stateId)
    ->execute()
    ->first();
    
  return $office ?: self::getFallbackOffice();
}

Line range hint 1-24: Improve class documentation and error handling strategy.

The class lacks comprehensive documentation about its purpose and responsibilities. Additionally, error handling is inconsistent across methods, with some using debug logs and others silently failing.

Consider adding proper class documentation and standardizing error handling:

/**
+ * DroppingCenterService handles the management of dropping centers in the Goonj system.
+ * 
+ * This service is responsible for:
+ * - Managing dropping center tabs and permissions
+ * - Handling email notifications for material dispatch
+ * - Managing office assignments and coordinator relationships
+ * 
+ * @package Civi\Goonjcustom
+ */
class DroppingCenterService extends AutoSubscriber {
+  /**
+   * Custom exception for dropping center related errors.
+   */
+  class DroppingCenterException extends \CRM_Core_Exception {}

   use QrCodeable;
   use CollectionSource;
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/VolunteerFeedbackGoonjActivityCron.php (2)

70-70: Remove unnecessary logging or adjust logging level

The logging statement for $volunteerFeedbackForm may not be necessary in production code and could clutter the logs. If it's used for debugging, consider removing it or adjusting the logging level accordingly.

Apply this diff to remove the logging statement:

-      \Civi::log()->info('volunteerFeedbackForm', ['volunteerFeedbackForm'=>$volunteerFeedbackForm]);

103-103: Adjust exception logging level for better error tracking

Currently, exceptions are logged with an info level, which may not adequately highlight errors. Use the error level to ensure that issues are appropriately flagged and can be monitored effectively.

Apply this diff to adjust the logging level:

-      \Civi::log()->info("Error processing camp ID $collectionCampId: " . $e->getMessage());
+      \Civi::log()->error("Error processing camp ID $collectionCampId: " . $e->getMessage());
wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Token/InstitutionDroppingCenter.php (1)

43-47: Improve handling when 'collectionSourceId' is absent

If 'collectionSourceId' is missing in the context (line 43~), the method logs a debug message and returns an empty token. Consider providing a more informative error message or throwing an exception to aid in debugging and ensure robustness.

wp-content/civi-extensions/goonjcustom/goonjcustom.php (1)

148-165: Simplify the logic for setting 'inductionDate' and 'inductionTime'

The code sets 'inductionDate' and 'inductionTime' to 'Not Scheduled' in two places (lines 148~ and 162~). This redundancy can be avoided for cleaner code.

Consider initializing $inductionDate and $inductionTime to 'Not Scheduled' before the if-condition, and updating them only if valid data is available.

Apply this diff to refactor the code:

$inductionDate = 'Not Scheduled';
$inductionTime = 'Not Scheduled';

if ($inductionDateTimeString !== 'Not Scheduled') {
    $dateTime = new DateTime($inductionDateTimeString);
    $inductionDate = $dateTime->format('Y-m-d');
    $inductionTime = $dateTime->format('g:i A');
}
wp-content/civi-extensions/goonjcustom/Civi/InductionService.php (4)

524-533: Refactor to eliminate duplicate code when checking contact existence

The pattern of fetching contact details and checking if the contact exists (lines 529~ to 533~) is repeated multiple times. Consider extracting this logic into a helper method to improve code reuse and readability.


659-668: Refactor duplicate contact existence checks

Similar to earlier code, the check for empty contacts (lines 666~ to 668~) duplicates code from other methods. Refactoring this into a reusable method can enhance maintainability and reduce code duplication.


713-724: Handle missing contacts consistently and log appropriately

In handleRescheduleEmailActivity (lines 721~ to 724~), returning FALSE when a contact is not found may not be sufficient. Ensure consistent error handling across methods and consider logging this event to aid in debugging.


775-784: Improve error handling when contacts are not found

In the sendRemainderEmails method (lines 782~ to 784~), the function returns FALSE if contacts are empty. Consistent error handling and logging of such events will improve debuggability and maintain consistency across methods.

wp-content/civi-extensions/goonjcustom/Civi/InstitutionCollectionCampService.php (3)

Line range hint 775-824: Refactor duplicated permissions array

The permissions array ['goonj_chapter_admin', 'urbanops'] is duplicated across multiple tab configurations. Consider extracting it into a constant or variable to improve maintainability.

+ private const DEFAULT_PERMISSIONS = ['goonj_chapter_admin', 'urbanops'];

 $tabConfigs = [
   'logistics' => [
     'title' => ts('Logistics'),
     'module' => 'afsearchInstitutionCollectionCampLogistics',
     'directive' => 'afsearch-institution-collection-camp-logistics',
     'template' => 'CRM/Goonjcustom/Tabs/CollectionCamp.tpl',
-    'permissions' => ['goonj_chapter_admin', 'urbanops'],
+    'permissions' => self::DEFAULT_PERMISSIONS,
   ],
   // Apply similar changes to other tab configurations

834-836: Remove or improve the unclear comment

The comment "does not permission; just continue" is grammatically incorrect and doesn't add value. Consider removing it as the code is self-explanatory.

 if (!\CRM_Core_Permission::checkAnyPerm($config['permissions'])) {
-  // does not permission; just continue
   continue;
 }

Line range hint 1-850: Consider splitting the class to improve maintainability

The InstitutionCollectionCampService class has grown to handle multiple responsibilities:

  • Email notifications
  • Permission management
  • Tab configurations
  • Coordinator assignments
  • Camp outcome tracking

Consider splitting these responsibilities into separate service classes to improve maintainability and adhere to the Single Responsibility Principle:

  • InstitutionCollectionCampEmailService
  • InstitutionCollectionCampPermissionService
  • InstitutionCollectionCampTabService
  • InstitutionCollectionCampCoordinatorService
  • InstitutionCollectionCampOutcomeService
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f189c3d and 31b56e1.

📒 Files selected for processing (13)
  • wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Token/InstitutionDroppingCenter.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/Civi/CollectionBaseService.php (2 hunks)
  • wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php (5 hunks)
  • wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php (3 hunks)
  • wp-content/civi-extensions/goonjcustom/Civi/GoonjActivitiesService.php (5 hunks)
  • wp-content/civi-extensions/goonjcustom/Civi/InductionService.php (4 hunks)
  • wp-content/civi-extensions/goonjcustom/Civi/InstitutionCollectionCampService.php (6 hunks)
  • wp-content/civi-extensions/goonjcustom/Civi/Traits/CollectionSource.php (2 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/GoonjActivitiesCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/VolunteerFeedbackGoonjActivityCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/goonjcustom.php (4 hunks)
  • wp-content/plugins/goonj-blocks/build/render.php (1 hunks)
  • wp-content/plugins/goonj-blocks/src/render.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • wp-content/plugins/goonj-blocks/src/render.php
  • wp-content/plugins/goonj-blocks/build/render.php
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/GoonjActivitiesCron.php
🔇 Additional comments (6)
wp-content/civi-extensions/goonjcustom/Civi/Traits/CollectionSource.php (1)

8-8: LGTM: Import statement is correctly placed

The Relationship API import is properly grouped with other Civi\Api4 imports and is necessary for the new functionality.

wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php (1)

522-523: Verify permission check impact.

The change to use checkAnyPerm instead of individual permission checks could potentially grant access to users who previously didn't have it.

✅ Verification successful

Change to checkAnyPerm is appropriate and secure.

The switch from individual permission checks to checkAnyPerm for the array ['goonj_chapter_admin', 'urbanops'] is a valid and secure change because:

  1. The original code was checking for the same set of permissions across multiple tabs
  2. The permissions are consistently defined as an array requiring either 'goonj_chapter_admin' OR 'urbanops' access
  3. checkAnyPerm is the correct method to use when any one of multiple permissions should grant access
  4. The change maintains the same access control logic while making it more efficient
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for other permission checks in the codebase that might be affected
rg -A 2 "CRM_Core_Permission::check.*(?:goonj_chapter_admin|urbanops)"

# Check for existing roles and their permissions
ast-grep --pattern 'function install() {
  $$$
  CRM_Core_Permission::add($$$);
  $$$
}'

Length of output: 9435


Script:

#!/bin/bash
# Let's try a different approach to find permission checks
rg -A 2 "CRM_Core_Permission::check" 

# Also search for permission definitions
rg -A 2 "CRM_Core_Permission::add"

# Look for the specific file to understand the context better
fd -t f "DroppingCenterService.php" -x cat {}

Length of output: 71082


Script:

#!/bin/bash
# Let's look for the specific file to understand the permission configuration
fd -t f "DroppingCenterService.php" -x grep -A 5 -B 5 "permissions"

# Also search for any related permission configuration files
fd -t f "goonjcustom.php" -x cat {}

Length of output: 12373

wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/VolunteerFeedbackGoonjActivityCron.php (1)

55-55: Review the need for date filtering in the query

The date filtering condition in the query is commented out. If filtering collection camps based on end date is required, consider uncommenting or updating the condition to ensure the cron job processes the correct set of camps.

Apply this diff to re-enable date filtering:

-    // ->addWhere('Collection_Camp_Intent_Details.End_Date', '<=', $endOfDay)
+    ->addWhere('Goonj_Activities.End_Date', '<=', $endOfDay)

Note: Ensure the field name used in the condition matches the correct data field.

wp-content/civi-extensions/goonjcustom/Civi/GoonjActivitiesService.php (3)

29-29: Consider adhering to the Single Responsibility Principle (SRP)

The GoonjActivitiesService class is handling multiple responsibilities (QR codes, activities, tabs, and emails), which violates the Single Responsibility Principle. Refactoring the email-related functionality into a dedicated GoonjEmailService class would improve maintainability and separation of concerns.


588-642: ⚠️ Potential issue

Improve error handling and add input validation

Several issues in the sendActivityLogisticsEmail method need attention:

  • The condition if (true) at line 605~ seems incorrect and may be a placeholder.
  • Missing validation for required fields before processing.
  • Exception handling could be more specific.

Apply this diff to enhance the implementation:

 public static function sendActivityLogisticsEmail($collectionCamp) {
+    if (empty($collectionCamp['id'])) {
+      throw new \InvalidArgumentException('Collection camp ID is required');
+    }
+
     try {
       $campId = $collectionCamp['id'];
+      // Validate required fields
+      $requiredFields = [
+        'title' => 'Activity code',
+        'Goonj_Activities.Goonj_Office' => 'Activity office',
+        'Goonj_Activities.Where_do_you_wish_to_organise_the_activity_' => 'Activity address',
+        'Logistics_Coordination.Camp_to_be_attended_by' => 'Camp attendee',
+      ];
+
+      foreach ($requiredFields as $field => $label) {
+        if (empty($collectionCamp[$field])) {
+          throw new \InvalidArgumentException("$label is required");
+        }
+      }
+
       $activityCode = $collectionCamp['title'];
       $activityOffice = $collectionCamp['Goonj_Activities.Goonj_Office'];
       $activityAddress = $collectionCamp['Goonj_Activities.Where_do_you_wish_to_organise_the_activity_'];
       $activityAttendedById = $collectionCamp['Logistics_Coordination.Camp_to_be_attended_by'];
       $logisticEmailSent = $collectionCamp['Logistics_Coordination.Email_Sent'];
       $outcomeFormLink = $collectionCamp['Goonj_Activities.Select_Goonj_POC_Attendee_Outcome_Form'];

       $startDate = new \DateTime($collectionCamp['Goonj_Activities.Start_Date']);
       $today = new \DateTimeImmutable();
       $endOfToday = $today->setTime(23, 59, 59);

-      if (true) {
+      if (!$logisticEmailSent) {
         // Existing code...
       }
     }
-    catch (\Exception $e) {
+    catch (\InvalidArgumentException $e) {
+      \Civi::log()->error("Invalid input in sendLogisticsEmail for $campId: " . $e->getMessage());
+      throw $e;
+    }
+    catch (\Exception $e) {
       \Civi::log()->error("Error in sendLogisticsEmail for $campId " . $e->getMessage());
+      throw $e;
     }
   }

658-674: 🛠️ Refactor suggestion

Fix HTML content and improve the email template

Issues in the email template:

  1. Typo: "Their" should be "There" in line 666~.
  2. Malformed HTML list with a closing </li> without an opening <li> at line 668~.
  3. Hardcoded strings should be moved to language files for internationalization (i18n).

Apply this diff to correct the issues:

 private static function getLogisticsEmailHtml($contactName, $collectionCampId, $campAttendedById, $collectionCampGoonjOffice, $campCode, $campAddress, $outcomeFormLink) {
   // ... existing code ...

   $html = "
   <p>Dear $contactName,</p>
-  <p>Thank you for attending the goonj activity <strong>$campCode</strong> at <strong>$campAddress</strong>. Their is one forms that require your attention during and after the goonj activity:</p>
+  <p>Thank you for attending the Goonj activity <strong>$campCode</strong> at <strong>$campAddress</strong>. There is one form that requires your attention during and after the Goonj activity:</p>
   <ol>
-      Please complete this form from the goonj activity location once the goonj activity ends.</li>
+      <li>Please complete this form from the Goonj activity location once the activity ends.</li>
     <li><a href=\"$campOutcomeFormUrl\">Goonj Activity Outcome Form</a><br>
     This feedback form should be filled out after the Goonj activity/session ends, once you have an overview of the event's outcomes.</li>
   </ol>
   <p>We appreciate your cooperation.</p>
   <p>Warm Regards,<br>Urban Relations Team</p>";

   return $html;
 }

Comment on lines +249 to +251
$relationshipType = 'Institution POC of';
$alternateType = 'Primary Institution POC of';
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Define relationship types as class constants

Relationship type strings are hardcoded in multiple places. These should be defined as class constants for better maintainability and to avoid typos.

Add these constants at the class level:

+ private const RELATIONSHIP_TYPE_INSTITUTION_POC = 'Institution POC of';
+ private const RELATIONSHIP_TYPE_PRIMARY_POC = 'Primary Institution POC of';
+ private const RELATIONSHIP_TYPE_SECONDARY_POC = 'Secondary Institution POC of';

Then use them in the code:

-      $relationshipType = 'Institution POC of';
-      $alternateType = 'Primary Institution POC of';
+      $relationshipType = self::RELATIONSHIP_TYPE_INSTITUTION_POC;
+      $alternateType = self::RELATIONSHIP_TYPE_PRIMARY_POC;

Also applies to: 254-256

Comment on lines +244 to +245
private static function getInitiatorId(array $collectionCamp) {
$subtypeName = $collectionCamp['subtype:name'];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation to prevent undefined index errors

The method accepts an array parameter but doesn't validate the required keys before accessing them.

Add validation at the start of the method:

 private static function getInitiatorId(array $collectionCamp) {
+    if (!isset($collectionCamp['subtype:name'])) {
+        throw new \InvalidArgumentException('Collection camp must have a subtype');
+    }
     $subtypeName = $collectionCamp['subtype:name'];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static function getInitiatorId(array $collectionCamp) {
$subtypeName = $collectionCamp['subtype:name'];
private static function getInitiatorId(array $collectionCamp) {
if (!isset($collectionCamp['subtype:name'])) {
throw new \InvalidArgumentException('Collection camp must have a subtype');
}
$subtypeName = $collectionCamp['subtype:name'];

$endDate = new DateTime($camp['Goonj_Activities.End_Date']);
$collectionCampId = $camp['id'];
$endDateFormatted = $endDate->format('Y-m-d');
$feedbackEmailSent = $camp['Logistics_Coordination.Feedback_Email_Sent'];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Utilize $feedbackEmailSent variable to prevent duplicate emails

The variable $feedbackEmailSent is retrieved but not used. Incorporate it into your condition to avoid sending feedback emails multiple times to the same recipient.

$organizingContactName = $campAttendedBy['display_name'];

// Send email if the end date is today or earlier.
if (true) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace placeholder condition with actual logic to control email sending

The if (true) condition ensures that the email sending block always executes, which may lead to sending duplicate emails or emails sent at inappropriate times. Replace it with a meaningful condition, such as checking if the feedback email has not been sent and if the activity end date has passed.

Apply this diff to fix the condition:

-      if (true) {
+      if ($endDateFormatted <= $todayFormatted && !$feedbackEmailSent) {

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +113 to +130
function goonjcustom_volunteer_feedback_collection_activity_email_html($organizingContactName, $collectionCampId, $campAddress, $volunteerFeedbackForm ) {
$homeUrl = \CRM_Utils_System::baseCMSURL();

// URL for the volunteer feedback form.
// $campVolunteerFeedback = $homeUrl . $volunteerFeedbackForm . '#?Eck_Collection_Camp1=' . $collectionCampId;
$campVolunteerFeedback = $homeUrl . 'volunteer-activity-feedback/#?Eck_Collection_Camp1=' . $collectionCampId;

$html = "
<p>Dear $organizingContactName,</p>
<p>Thank you for stepping up and organising the recent goonj activity at <strong>$campAddress</strong>! Your time, effort, and enthusiasm made all the difference, and we hope that it was a meaningful effort for you as well.</p>
<p>To help us improve, we’d love to hear your thoughts and experiences. Kindly take a few minutes to fill out our feedback form. Your input will be valuable to us:</p>
<p><a href=\"$campVolunteerFeedback\">Feedback Form Link</a></p>
<p>Feel free to share any highlights, suggestions, or challenges you faced. We're eager to learn how we can make it better together!</p>
<p>We look forward to continuing this journey together!</p>
<p>Warm Regards,<br>Team Goonj</p>";

return $html;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unused parameter or restore dynamic URL generation

The parameter $volunteerFeedbackForm is passed to the function but is not used because the code that utilizes it is commented out. This results in an unnecessary parameter and potential confusion. Consider either removing the parameter or uncommenting and using it for dynamic URL generation to avoid hardcoding the feedback form URL.

If you choose to remove the unused parameter, apply this diff:

-function goonjcustom_volunteer_feedback_collection_activity_email_html($organizingContactName, $collectionCampId, $campAddress, $volunteerFeedbackForm ) {
+function goonjcustom_volunteer_feedback_collection_activity_email_html($organizingContactName, $collectionCampId, $campAddress) {

...

-      'html' => goonjcustom_volunteer_feedback_collection_activity_email_html($organizingContactName, $collectionCampId, $campAddress, $volunteerFeedbackForm),
+      'html' => goonjcustom_volunteer_feedback_collection_activity_email_html($organizingContactName, $collectionCampId, $campAddress),

Alternatively, if the dynamic URL is required, uncomment and use the variable:

-  // $campVolunteerFeedback = $homeUrl . $volunteerFeedbackForm . '#?Eck_Collection_Camp1=' . $collectionCampId;
-  $campVolunteerFeedback = $homeUrl . 'volunteer-activity-feedback/#?Eck_Collection_Camp1=' . $collectionCampId;
+  $campVolunteerFeedback = $homeUrl . $volunteerFeedbackForm . '#?Eck_Collection_Camp1=' . $collectionCampId;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +58 to +89
switch ($field) {
case 'venue':
$value = $this->formatVenue($collectionSource);
break;

case 'date':

case 'time':
$value = $collectionSource['Institution_Dropping_Center_Intent.Timing'];
break;

case 'contact':
$value = $this->formatVolunteers($collectionSource);
break;

case 'remarks':
$value = $collectionSource['Collection_Camp_Core_Details.Remarks'];
break;

case 'coordinator':
$value = $this->formatCoordinator($collectionSource);
break;

case 'address_city':
$value = $collectionSource['Institution_Dropping_Center_Intent.District_City'];
break;

default:
$value = '';
break;

}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure proper formatting of 'date' and 'time' tokens

In the evaluateToken method, the cases for 'date' (line 63~) and 'time' (line 65~) retrieve raw timing data without formatting, which may lead to unformatted outputs.

Consider using the formatDate and formatTime methods to format these tokens, ensuring that date and time are presented correctly.

Apply this diff to adjust the cases:

        case 'date':
-           
-        case 'time':
-            $value = $collectionSource['Institution_Dropping_Center_Intent.Timing'];
+            $timing = $collectionSource['Institution_Dropping_Center_Intent.Timing'];
+            $start = new \DateTime($timing['start']);
+            $end = new \DateTime($timing['end']);
+            $value = $this->formatDate($start, $end);
+            break;
+
+        case 'time':
+            $timing = $collectionSource['Institution_Dropping_Center_Intent.Timing'];
+            $start = new \DateTime($timing['start']);
+            $end = new \DateTime($timing['end']);
+            $value = $this->formatTime($start, $end);
+            break;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +286 to 289
->addSelect('Collection_Camp_Core_Details.Status', 'Collection_Camp_Core_Details.Contact_Id', 'Institution_Collection_Camp_Intent.Organization_Name.id', 'Institution_Dropping_Center_Intent.Organization_Name.id', 'subtype:name')
->addWhere('id', '=', $objectId)
->execute()->single();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: 'getInitiatorId' method removed but still referenced

The method getInitiatorId has been removed, but it is still being called on line 286~. This will lead to a fatal error when the method is not found.

Reintroduce the getInitiatorId method or adjust the code to obtain the initiator ID correctly.

Comment on lines +314 to 317
->addSelect('Collection_Camp_Core_Details.Status', 'Collection_Camp_Core_Details.Contact_Id', 'Institution_Collection_Camp_Intent.Organization_Name.id', 'Institution_Dropping_Center_Intent.Organization_Name.id', 'subtype', 'subtype:name')
->addWhere('id', '=', $objectRef->id)
->execute()->single();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: 'getInitiatorId' method called but not defined

In the handleAuthorizationEmailsPost method, the call to self::getInitiatorId($collectionCamp); (line 314~) will fail because the getInitiatorId method has been removed.

Implement the getInitiatorId method or modify the code to retrieve the initiator ID using an alternative approach.

$nameOfInstitution = $collectionSourceVehicleDispatch['Camp_Institution_Data.Name_of_the_institution'];
$addressOfInstitution = $collectionSourceVehicleDispatch['Camp_Institution_Data.Address'];
$pocEmail = $collectionSourceVehicleDispatch['Camp_Institution_Data.Email'];
$pocContactNumber = $collectionSourceVehicleDispatch['Camp_Institution_Data.Contact_Numbe'];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in field name

There's a typo in the field name 'Contact_Numbe' which should be 'Contact_Number'.

-    $pocContactNumber = $collectionSourceVehicleDispatch['Camp_Institution_Data.Contact_Numbe'];
+    $pocContactNumber = $collectionSourceVehicleDispatch['Camp_Institution_Data.Contact_Number'];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$pocContactNumber = $collectionSourceVehicleDispatch['Camp_Institution_Data.Contact_Numbe'];
$pocContactNumber = $collectionSourceVehicleDispatch['Camp_Institution_Data.Contact_Number'];

Comment on lines +267 to +274
$materialdispatchUrl = $homeUrl . 'institution-camp-acknowledgement-dispatch/#?Eck_Collection_Source_Vehicle_Dispatch1=' . $vehicleDispatchId
. '&Camp_Vehicle_Dispatch.Collection_Camp=' . $collectionCampId
. '&id=' . $vehicleDispatchId
. '&Eck_Collection_Source_Vehicle_Dispatch_Eck_Collection_Camp_Collection_Camp_01.id=' . $collectionCampId
. '&Camp_Institution_Data.Name_of_the_institution=' . $nameOfInstitution
. '&Camp_Institution_Data.Address=' . $addressOfInstitution
. '&Camp_Institution_Data.Email=' . $pocEmail
. '&Camp_Institution_Data.Contact_Number=' . $pocContactNumber;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

URL-encode parameters to handle special characters

The URL parameters should be URL-encoded to handle special characters in institution names, addresses, and other fields.

-    $materialdispatchUrl = $homeUrl . 'institution-camp-acknowledgement-dispatch/#?Eck_Collection_Source_Vehicle_Dispatch1=' . $vehicleDispatchId
-    . '&Camp_Vehicle_Dispatch.Collection_Camp=' . $collectionCampId
-    . '&id=' . $vehicleDispatchId
-    . '&Eck_Collection_Source_Vehicle_Dispatch_Eck_Collection_Camp_Collection_Camp_01.id=' . $collectionCampId
-    . '&Camp_Institution_Data.Name_of_the_institution=' . $nameOfInstitution
-    . '&Camp_Institution_Data.Address=' . $addressOfInstitution
-    . '&Camp_Institution_Data.Email=' . $pocEmail
-    . '&Camp_Institution_Data.Contact_Number=' . $pocContactNumber;
+    $materialdispatchUrl = $homeUrl . 'institution-camp-acknowledgement-dispatch/#?Eck_Collection_Source_Vehicle_Dispatch1=' . urlencode($vehicleDispatchId)
+    . '&Camp_Vehicle_Dispatch.Collection_Camp=' . urlencode($collectionCampId)
+    . '&id=' . urlencode($vehicleDispatchId)
+    . '&Eck_Collection_Source_Vehicle_Dispatch_Eck_Collection_Camp_Collection_Camp_01.id=' . urlencode($collectionCampId)
+    . '&Camp_Institution_Data.Name_of_the_institution=' . urlencode($nameOfInstitution)
+    . '&Camp_Institution_Data.Address=' . urlencode($addressOfInstitution)
+    . '&Camp_Institution_Data.Email=' . urlencode($pocEmail)
+    . '&Camp_Institution_Data.Contact_Number=' . urlencode($pocContactNumber);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$materialdispatchUrl = $homeUrl . 'institution-camp-acknowledgement-dispatch/#?Eck_Collection_Source_Vehicle_Dispatch1=' . $vehicleDispatchId
. '&Camp_Vehicle_Dispatch.Collection_Camp=' . $collectionCampId
. '&id=' . $vehicleDispatchId
. '&Eck_Collection_Source_Vehicle_Dispatch_Eck_Collection_Camp_Collection_Camp_01.id=' . $collectionCampId
. '&Camp_Institution_Data.Name_of_the_institution=' . $nameOfInstitution
. '&Camp_Institution_Data.Address=' . $addressOfInstitution
. '&Camp_Institution_Data.Email=' . $pocEmail
. '&Camp_Institution_Data.Contact_Number=' . $pocContactNumber;
$materialdispatchUrl = $homeUrl . 'institution-camp-acknowledgement-dispatch/#?Eck_Collection_Source_Vehicle_Dispatch1=' . urlencode($vehicleDispatchId)
. '&Camp_Vehicle_Dispatch.Collection_Camp=' . urlencode($collectionCampId)
. '&id=' . urlencode($vehicleDispatchId)
. '&Eck_Collection_Source_Vehicle_Dispatch_Eck_Collection_Camp_Collection_Camp_01.id=' . urlencode($collectionCampId)
. '&Camp_Institution_Data.Name_of_the_institution=' . urlencode($nameOfInstitution)
. '&Camp_Institution_Data.Address=' . urlencode($addressOfInstitution)
. '&Camp_Institution_Data.Email=' . urlencode($pocEmail)
. '&Camp_Institution_Data.Contact_Number=' . urlencode($pocContactNumber);

@nishant22029 nishant22029 deleted the feat/goonj-activities-crons branch December 6, 2024 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants