Skip to content

Send Feedback Email When Dropping Center is Closed or sent every 6 month#393

Merged
belwalshubham merged 17 commits intodevelopfrom
feat/dropping-center-cron
Oct 25, 2024
Merged

Send Feedback Email When Dropping Center is Closed or sent every 6 month#393
belwalshubham merged 17 commits intodevelopfrom
feat/dropping-center-cron

Conversation

@belwalshubham
Copy link
Copy Markdown
Collaborator

@belwalshubham belwalshubham commented Oct 23, 2024

  1. The feedback email is triggered automatically when the status of the Dropping Center is set to "Permanently Closed" during the cron job execution.
  2. Added the new custom field Feedback_Email_Delivered so that After the email is successfully sent, the Feedback_Email_Delivered status is updated to 1 (true) for that Dropping Center.
    This prevents the same email from being sent again in the next cron execution for the same center.
  3. Implemented a new cron job to send feedback emails to the dropping center every six months, based on the opening date.
image

Summary by CodeRabbit

  • New Features

    • Introduced automated feedback email system for Dropping Centers.
    • Added cron jobs for managing biannual and immediate feedback requests.
    • Implemented a service class to streamline the sending of feedback emails, including generating customized email content.
  • Bug Fixes

    • Enhanced error handling for email sending processes to ensure reliability.

@belwalshubham belwalshubham self-assigned this Oct 23, 2024
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 23, 2024

Warning

Rate limit exceeded

@belwalshubham has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 24 minutes and 52 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Files that changed from the base of the PR and between 995cce8 and 45a12e2.

Walkthrough

This pull request introduces functionalities for managing feedback related to Dropping Centers within the CiviCRM framework. It adds two cron jobs: one for sending feedback emails for closed dropping centers and another for processing biannual feedback. Additionally, a new service class is created to encapsulate the logic for sending feedback emails, including methods for processing status and generating email content. Error handling is implemented throughout to ensure smooth operation.

Changes

File Change Summary
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php - Added functions: _civicrm_api3_goonjcustom_feedback_dropping_center_cron_spec, civicrm_api3_goonjcustom_feedback_dropping_center_cron, sendFeedbackEmail for managing feedback emails for closed dropping centers.
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php - Added functions: civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron, _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec for processing biannual feedback.
wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php - Added class DroppingCenterFeedbackService with methods: processDroppingCenterStatus, sendFeedbackEmail, generateFeedbackEmailHtml, getContactDetails, and updateFeedbackLastSentDate for handling feedback email logic.

Possibly related PRs

Suggested reviewers

  • tarunnjoshi
  • pokhiii

🎉 In the realm of code, new features bloom,
Feedback flows like a sweet perfume.
Cron jobs ticking, emails sent with care,
Goonj's centers thrive, feedback everywhere!
With classes and functions, all neatly aligned,
A symphony of logic, beautifully designed! 🌟


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.

@belwalshubham belwalshubham changed the title Send Dropping Center Volunteer Feedback Form Send Feedback Email When Dropping Center is Closed Oct 23, 2024
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: 7

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between bdce920 and 9dcacb9.

📒 Files selected for processing (1)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1 hunks)
🧰 Additional context used
🔇 Additional comments (1)
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1)

73-73: Clarify the condition for feedback delivery status

The condition if (!$status) relies on $status being falsy when feedback has not been delivered. Ensure that $status accurately represents the delivery status, and consider making the condition more explicit for readability.

Confirm that $status is correctly set and reflects the intended logic.

Comment on lines +109 to +134
$homeUrl = \CRM_Utils_System::baseCMSURL();

// URL for the feedback form.
$volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;

$html = "
<p>Dear $organizingContactName,</p>

<p>Thank you for being such an outstanding representative of Goonj!
Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>

<p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center.
If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>

<p><a href='$volunteerFeedback'>Click here to access the feedback form.</a></p>

<p>We encourage you to share any highlights, suggestions, or challenges you’ve encountered. Together, we can refine and enhance this process even further.</p>

<p>We look forward to continuing this important journey with you!</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

Use a templating engine for email content

Constructing HTML emails by concatenating strings can lead to unreadable code and potential injection vulnerabilities. Consider using a templating engine like Smarty or CiviCRM's built-in template system for better maintainability and security.

This will separate the email content from the code logic and make it easier to manage.

Comment on lines +36 to +98
function civicrm_api3_goonjcustom_feedback_dropping_center_cron($params) {
$returnValues = [];

// Retrieve the Status option value.
$optionValues = OptionValue::get(FALSE)
->addWhere('option_group_id:name', '=', 'eck_sub_types')
->addWhere('name', '=', 'Status')
->addWhere('grouping', '=', 'Dropping_Center_Meta')
->setLimit(1)
->execute()->single();

$statusName = $optionValues['value'];

$droppingCenterMetas = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Closing_Date', 'Dropping_Center_Meta.Dropping_Center', 'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id', 'Status.Feedback_Email_Delivered:name')
->addWhere('subtype', '=', $statusName)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->execute();

[$defaultFromName, $defaultFromEmail] = CRM_Core_BAO_Domain::getNameAndEmail();
$from = "\"$defaultFromName\" <$defaultFromEmail>";

try {
foreach ($droppingCenterMetas as $meta) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];

// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => goonjcustom_volunteer_feedback_email_html($organizingContactName, $droppingCenterId),
];
$feedbackEmailSendResult = CRM_Utils_Mail::send($mailParams);

if ($feedbackEmailSendResult) {
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}

}
}
}
catch (Exception $e) {
error_log("Error processing: " . $e->getMessage());
}

return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'feedback_dropping_center_cron');
}
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 to adhere to Single Responsibility Principle

The function civicrm_api3_goonjcustom_feedback_dropping_center_cron handles multiple tasks: data retrieval, email preparation, sending emails, and updating records. For better maintainability, consider refactoring this function into smaller functions, each handling a specific responsibility.

Breaking down the function will improve readability and make future updates easier.

$droppingCenterMetas = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Closing_Date', 'Dropping_Center_Meta.Dropping_Center', 'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id', 'Status.Feedback_Email_Delivered:name')
->addWhere('subtype', '=', $statusName)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
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

Correct the typo in 'Parmanently_Closed'

The status value 'Parmanently_Closed' seems to be misspelled. The correct spelling is 'Permanently_Closed'.

Apply this diff to fix the typo:

->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
+->addWhere('Status.Status:name', '=', 'Permanently_Closed')

Committable suggestion was skipped due to low confidence.

Comment on lines +47 to +48
$statusName = $optionValues['value'];

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 check before accessing 'value' key

There is a risk of an undefined index error if $optionValues does not contain the 'value' key. It's best practice to verify that $optionValues is not null and has the expected key before accessing it.

Apply this diff to add a null check:

$optionValues = OptionValue::get(FALSE)
  ->addWhere('option_group_id:name', '=', 'eck_sub_types')
  ->addWhere('name', '=', 'Status')
  ->addWhere('grouping', '=', 'Dropping_Center_Meta')
  ->setLimit(1)
  ->execute()->single();

+ if (!$optionValues || !isset($optionValues['value'])) {
+   throw new Exception('OptionValue for Status not found.');
+ }

$statusName = $optionValues['value'];

Committable suggestion was skipped due to low confidence.

Comment on lines +93 to +95
catch (Exception $e) {
error_log("Error processing: " . $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.

🛠️ Refactor suggestion

Enhance exception handling in the catch block

Catching a generic Exception and merely logging it may not be sufficient. Consider rethrowing the exception or using a more robust error-handling mechanism to ensure that errors are properly propagated.

Apply this diff to improve error handling:

catch (Exception $e) {
- error_log("Error processing: " . $e->getMessage());
+ CRM_Core_Error::debug_log_message("Error in FeedbackDroppingCenterCron: " . $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
catch (Exception $e) {
error_log("Error processing: " . $e->getMessage());
}
catch (Exception $e) {
CRM_Core_Error::debug_log_message("Error in FeedbackDroppingCenterCron: " . $e->getMessage());
throw $e;
}

Comment on lines +71 to +72
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_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

Verify that $campAttendedBy is not null before use

If the contact retrieval does not return any results, accessing elements of $campAttendedBy will cause an error. Ensure that $campAttendedBy is not null before proceeding.

Apply this diff to add a null check:

$campAttendedBy = Contact::get(TRUE)
  ->addSelect('email.email', 'display_name')
  ->addJoin('Email AS email', 'LEFT')
  ->addWhere('id', '=', $initiatorId)
  ->execute()->single();

+ if (!$campAttendedBy) {
+   // Handle the case where the contact is not found.
+   continue;
+ }

$contactEmailId = $campAttendedBy['email.email'];

Committable suggestion was skipped due to low confidence.

Comment on lines +83 to +88
if ($feedbackEmailSendResult) {
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
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

Handle potential failure in EckEntity update

The update operation on Dropping_Center_Meta does not currently check for success. It's important to verify that the update was successful and handle any failures appropriately.

Apply this diff to check the update result:

if ($feedbackEmailSendResult) {
  $updateResult = EckEntity::update('Dropping_Center_Meta', TRUE)
    ->addValue('Status.Feedback_Email_Delivered:name', 1)
    ->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
    ->execute();

+  if (!$updateResult) {
+    // Log the failure or handle it as needed.
+    error_log("Failed to update Feedback_Email_Delivered for Dropping Center ID: $droppingCenterId");
+  }
}

Committable suggestion was skipped due to low confidence.

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: 3

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

11-22: Enhance API documentation.

While the function implementation is correct, consider adding more descriptive documentation about the cron job's purpose and execution frequency.

Add details about when this API is triggered and what it accomplishes:

 /**
  * Goonjcustom.FeedbackDroppingCenterCron API specification (optional)
  * This is used for documentation and validation.
+ * 
+ * This API is triggered by a cron job to send feedback request emails
+ * to contacts associated with permanently closed dropping centers.
+ * It ensures each contact receives only one feedback request.
  *
  * @param array $spec
  *   description of fields supported by this API call.
  *
  * @return void
  */

108-108: Rename function to match its purpose.

The function name sendFeedbackEmail suggests it sends an email, but it actually generates the email content.

-function sendFeedbackEmail($organizingContactName, $droppingCenterId) {
+function generateFeedbackEmailContent($organizingContactName, $droppingCenterId) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9dcacb9 and 058e6b7.

📒 Files selected for processing (1)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1 hunks)
🧰 Additional context used
🔇 Additional comments (1)
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1)

1-10: LGTM: File structure and imports are well-organized.

The file follows CiviCRM conventions with proper namespace imports for API v4.

Comment on lines +36 to +38
function civicrm_api3_goonjcustom_feedback_dropping_center_cron($params) {
$returnValues = [];

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

Return meaningful values from the API.

The API currently returns an empty array. Consider returning statistics about the processed dropping centers and sent emails.

 function civicrm_api3_goonjcustom_feedback_dropping_center_cron($params) {
-  $returnValues = [];
+  $returnValues = [
+    'processed_centers' => 0,
+    'emails_sent' => 0,
+  ];

Committable suggestion was skipped due to low confidence.

Comment on lines +108 to +113
function sendFeedbackEmail($organizingContactName, $droppingCenterId) {
$homeUrl = \CRM_Utils_System::baseCMSURL();

// URL for the feedback form.
$volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;

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 for function parameters.

The function should validate its input parameters before using them to generate the email content.

 function sendFeedbackEmail($organizingContactName, $droppingCenterId) {
+  if (empty($organizingContactName)) {
+    throw new InvalidArgumentException('Contact name cannot be empty');
+  }
+  if (!is_numeric($droppingCenterId) || $droppingCenterId <= 0) {
+    throw new InvalidArgumentException('Invalid dropping center ID');
+  }
+
   $homeUrl = \CRM_Utils_System::baseCMSURL();
   // URL for the  feedback form.
   $volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;
📝 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
function sendFeedbackEmail($organizingContactName, $droppingCenterId) {
$homeUrl = \CRM_Utils_System::baseCMSURL();
// URL for the feedback form.
$volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;
function sendFeedbackEmail($organizingContactName, $droppingCenterId) {
if (empty($organizingContactName)) {
throw new InvalidArgumentException('Contact name cannot be empty');
}
if (!is_numeric($droppingCenterId) || $droppingCenterId <= 0) {
throw new InvalidArgumentException('Invalid dropping center ID');
}
$homeUrl = \CRM_Utils_System::baseCMSURL();
// URL for the feedback form.
$volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;

Comment on lines +58 to +96
try {
foreach ($droppingCenterMetas as $meta) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];

// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => sendFeedbackEmail($organizingContactName, $droppingCenterId),
];
$feedbackEmailSendResult = CRM_Utils_Mail::send($mailParams);

if ($feedbackEmailSendResult) {
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}

}
}
}
catch (Exception $e) {
error_log("Error processing: " . $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

Wrap database operations in a transaction.

Multiple database operations (reading contacts and updating metadata) should be wrapped in a transaction to ensure data consistency.

   try {
+    $transaction = new CRM_Core_Transaction();
     foreach ($droppingCenterMetas as $meta) {
       // ... existing code ...
     }
+    $transaction->commit();
   }
   catch (Exception $e) {
+    $transaction->rollback();
     error_log("Error processing: " . $e->getMessage());
+    throw new CRM_Core_Exception('Failed to process dropping centers: ' . $e->getMessage());
   }
📝 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
try {
foreach ($droppingCenterMetas as $meta) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => sendFeedbackEmail($organizingContactName, $droppingCenterId),
];
$feedbackEmailSendResult = CRM_Utils_Mail::send($mailParams);
if ($feedbackEmailSendResult) {
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
}
}
catch (Exception $e) {
error_log("Error processing: " . $e->getMessage());
}
try {
$transaction = new CRM_Core_Transaction();
foreach ($droppingCenterMetas as $meta) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => sendFeedbackEmail($organizingContactName, $droppingCenterId),
];
$feedbackEmailSendResult = CRM_Utils_Mail::send($mailParams);
if ($feedbackEmailSendResult) {
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
}
$transaction->commit();
}
catch (Exception $e) {
$transaction->rollback();
error_log("Error processing: " . $e->getMessage());
throw new CRM_Core_Exception('Failed to process dropping centers: ' . $e->getMessage());
}

Comment on lines +60 to +72
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];

// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
Copy link
Copy Markdown
Member

@tarunnjoshi tarunnjoshi Oct 23, 2024

Choose a reason for hiding this comment

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

Could we consider relocating this logic to a dedicated service file? By creating a new service file specifically for dropping center feedback, we can simplify the current cron file and enhance its readability.

For reference please check the collectioncampcron.php

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

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: 10

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 058e6b7 and 7121d2c.

📒 Files selected for processing (3)
  • wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php
🧰 Additional context used

Comment on lines +56 to +57
return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');
}
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

Enhance API response with processing statistics.

The current implementation returns empty values, making it difficult to monitor the cron job's effectiveness.

Add processing statistics to the return values:

+ $returnValues = [
+   'processed' => count($droppingCenters),
+   'threshold_date' => $thresholdDate,
+   'timestamp' => date('Y-m-d H:i:s'),
+ ];
  return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');

Committable suggestion was skipped due to low confidence.

Comment on lines +11 to +15
/**
*
*/
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
}
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

API specification function needs implementation.

The empty spec function violates CiviCRM API v3 best practices. This function should define the expected parameters and their types for API documentation and validation.

Add parameter specifications:

 function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
+  // No required parameters for this API
+  $spec['limit'] = [
+    'title' => 'Limit',
+    'type' => CRM_Utils_Type::T_INT,
+    'description' => 'Maximum number of dropping centers to process',
+  ];
 }
📝 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
/**
*
*/
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
}
/**
*
*/
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
// No required parameters for this API
$spec['limit'] = [
'title' => 'Limit',
'type' => CRM_Utils_Type::T_INT,
'description' => 'Maximum number of dropping centers to process',
];
}

Comment on lines +23 to +28
$thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');

$droppingCenters = EckEntity::get('Collection_Camp', TRUE)
->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id')
->addWhere('Dropping_Centre.When_do_you_wish_to_open_center_Date_', '<=', $thresholdDate)
->execute();
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 adding configurable threshold and batch processing.

The 6-month threshold is hardcoded and the query fetches all matching records at once, which could cause memory issues with large datasets.

Consider these improvements:

- $thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');
+ $monthsThreshold = Civi::settings()->get('dropping_center_feedback_months_threshold') ?? 6;
+ $thresholdDate = (new \DateTime())->modify("-{$monthsThreshold} months")->format('Y-m-d');

  $droppingCenters = EckEntity::get('Collection_Camp', TRUE)
    ->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id')
    ->addWhere('Dropping_Centre.When_do_you_wish_to_open_center_Date_', '<=', $thresholdDate)
+   ->addWhere('Feedback_Email_Delivered', 'IS NULL')
+   ->setLimit($params['limit'] ?? 50)
    ->execute();

Committable suggestion was skipped due to low confidence.

Comment on lines +33 to +50
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
}
}
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

Improve error handling and logging granularity.

The current implementation catches errors at too high a level, potentially masking individual failures.

Implement per-record error handling:

  try {
    foreach ($droppingCenters as $center) {
+     try {
        $droppingCenterId = $center['id'];
        $initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

        $campAttendedBy = Contact::get(TRUE)
          ->addSelect('email.email', 'display_name')
          ->addJoin('Email AS email', 'LEFT')
          ->addWhere('id', '=', $initiatorId)
          ->execute()->single();

        if ($campAttendedBy) {
          $contactEmailId = $campAttendedBy['email.email'];
          $organizingContactName = $campAttendedBy['display_name'];

          DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
+         // Mark as processed
+         EckEntity::update('Collection_Camp', TRUE)
+           ->addValue('Feedback_Email_Delivered', 1)
+           ->addWhere('id', '=', $droppingCenterId)
+           ->execute();
        }
+     } catch (Exception $e) {
+       \CRM_Core_Error::debug_log_message(
+         "Error processing Dropping Center ID {$droppingCenterId}: " . $e->getMessage()
+       );
+       continue;
+     }
    }
  }
📝 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
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
}
}
try {
foreach ($droppingCenters as $center) {
try {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Mark as processed
EckEntity::update('Collection_Camp', TRUE)
->addValue('Feedback_Email_Delivered', 1)
->addWhere('id', '=', $droppingCenterId)
->execute();
}
} catch (Exception $e) {
\CRM_Core_Error::debug_log_message(
"Error processing Dropping Center ID {$droppingCenterId}: " . $e->getMessage()
);
continue;
}
}
}

Comment on lines +1 to +57
<?php

/**
* @file
*/

use Civi\Api4\Contact;
use Civi\Api4\EckEntity;
use Civi\DroppingCenterFeedbackCron;

/**
*
*/
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
}

/**
*
*/
function civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron($params) {
$returnValues = [];

$thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');

$droppingCenters = EckEntity::get('Collection_Camp', TRUE)
->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id')
->addWhere('Dropping_Centre.When_do_you_wish_to_open_center_Date_', '<=', $thresholdDate)
->execute();

[$defaultFromName, $defaultFromEmail] = CRM_Core_BAO_Domain::getNameAndEmail();
$from = "\"$defaultFromName\" <$defaultFromEmail>";

try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
}
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
}

return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codebase verification

Potential code duplication and responsibility overlap between feedback crons

After reviewing the implementation, I've identified some concerning patterns:

  • The BiannualDroppingCenterFeedbackCron and FeedbackDroppingCenterCron APIs have overlapping responsibilities - both send feedback emails to dropping center contacts but with different triggers:

    • BiannualDroppingCenterFeedbackCron: Triggers based on 6-month threshold from opening date
    • FeedbackDroppingCenterCron: Triggers when centers are permanently closed
  • There's duplicated code for email preparation (getting default from email/name, contact details) that should be consolidated in the DroppingCenterFeedbackCron class

Recommendations:

  • Merge the two cron APIs into a single API with different trigger conditions to reduce code duplication
  • Move common email preparation logic from the APIs into DroppingCenterFeedbackCron class
  • Add proper error handling in BiannualDroppingCenterFeedbackCron similar to FeedbackDroppingCenterCron to track failures for specific centers

The current implementation violates the DRY principle and makes maintenance more difficult as changes need to be synchronized across multiple files.

🔗 Analysis chain

Verify integration with DroppingCenterFeedbackCron class.

The code relies heavily on the DroppingCenterFeedbackCron class. Let's verify its implementation.


Let's gather more information about the DroppingCenterFeedbackCron class implementation since we found its location.

This will help us verify:

  1. The implementation of sendFeedbackEmail method
  2. How Feedback_Email_Delivered field is used
  3. Any potential overlap between BiannualDroppingCenterFeedbackCron and FeedbackDroppingCenterCron
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for DroppingCenterFeedbackCron class implementation
echo "Searching for DroppingCenterFeedbackCron class definition..."
ast-grep --pattern 'class DroppingCenterFeedbackCron {
  $$$
  static function sendFeedbackEmail($$$) {
    $$$
  }
  $$$
}'

# Verify the Feedback_Email_Delivered field exists
echo "Verifying Feedback_Email_Delivered field definition..."
rg -l "Feedback_Email_Delivered"

Length of output: 9845


Script:

#!/bin/bash
# Get the implementation of DroppingCenterFeedbackCron class
echo "Content of DroppingCenterFeedbackCron class:"
cat wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackCron.php

# Get the implementation of FeedbackDroppingCenterCron API
echo -e "\nContent of FeedbackDroppingCenterCron API:"
cat wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php

Length of output: 6148

/**
* Class to manage sending feedback emails for Dropping Centers.
*/
class DroppingCenterFeedbackCron {
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 implementing an interface for better testability.

The class could benefit from implementing an interface (e.g., DroppingCenterFeedbackInterface) to allow for easier mocking in tests and better dependency injection.

Consider adding:

interface DroppingCenterFeedbackInterface {
  public static function processDroppingCenterStatus($meta, $from);
  public static function sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
  public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId);
}

Comment on lines +86 to +103
$html = "
<p>Dear $organizingContactName,</p>

<p>Thank you for being such an outstanding representative of Goonj!
Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>

<p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center.
If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>

<p><a href='$volunteerFeedback'>Click here to access the feedback form.</a></p>

<p>We encourage you to share any highlights, suggestions, or challenges you’ve encountered. Together, we can refine and enhance this process even further.</p>

<p>We look forward to continuing this important journey with you!</p>

<p>Warm Regards,<br>
Team Goonj</p>
";
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 using a template system for email content.

The email content is currently hardcoded in the method. Using a template system would improve maintainability and allow for easier content updates.

Consider:

  1. Moving the email content to a separate template file
  2. Using CiviCRM's template system or a similar solution
  3. Making the content configurable through the admin interface

Example implementation:

public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
  $template = \CRM_Core_Smarty::singleton();
  $template->assign('organizingContactName', $organizingContactName);
  $template->assign('volunteerFeedback', $volunteerFeedback);
  return $template->fetch('CRM/Goonj/FeedbackEmail.tpl');
}

Comment on lines +61 to +72
public static function sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
];

// Send the email.
\CRM_Utils_Mail::send($mailParams);
}
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 parameter validation and error handling for email sending.

The email sending logic needs validation and error handling to ensure reliable delivery.

Implement these improvements:

   public static function sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
+    // Validate input parameters
+    if (empty($organizingContactName) || empty($droppingCenterId) || 
+        empty($contactEmailId) || empty($from)) {
+      throw new \CRM_Core_Exception("Missing required parameters for sending feedback email");
+    }
+
+    if (!filter_var($contactEmailId, FILTER_VALIDATE_EMAIL) || 
+        !filter_var($from, FILTER_VALIDATE_EMAIL)) {
+      throw new \CRM_Core_Exception("Invalid email format");
+    }
+
     $mailParams = [
       'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
       'from' => $from,
       'toEmail' => $contactEmailId,
       'replyTo' => $from,
       'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
     ];

-    // Send the email.
-    \CRM_Utils_Mail::send($mailParams);
+    try {
+      $result = \CRM_Utils_Mail::send($mailParams);
+      if (!$result) {
+        throw new \CRM_Core_Exception("Failed to send feedback email");
+      }
+    } catch (\Exception $e) {
+      \Civi::log()->error('Failed to send feedback email: ' . $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 sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
];
// Send the email.
\CRM_Utils_Mail::send($mailParams);
}
public static function sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
// Validate input parameters
if (empty($organizingContactName) || empty($droppingCenterId) ||
empty($contactEmailId) || empty($from)) {
throw new \CRM_Core_Exception("Missing required parameters for sending feedback email");
}
if (!filter_var($contactEmailId, FILTER_VALIDATE_EMAIL) ||
!filter_var($from, FILTER_VALIDATE_EMAIL)) {
throw new \CRM_Core_Exception("Invalid email format");
}
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
];
try {
$result = \CRM_Utils_Mail::send($mailParams);
if (!$result) {
throw new \CRM_Core_Exception("Failed to send feedback email");
}
} catch (\Exception $e) {
\Civi::log()->error('Failed to send feedback email: ' . $e->getMessage());
throw $e;
}
}

Comment on lines +82 to +106
public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
$homeUrl = \CRM_Utils_System::baseCMSURL();
$volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;

$html = "
<p>Dear $organizingContactName,</p>

<p>Thank you for being such an outstanding representative of Goonj!
Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>

<p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center.
If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>

<p><a href='$volunteerFeedback'>Click here to access the feedback form.</a></p>

<p>We encourage you to share any highlights, suggestions, or challenges you’ve encountered. Together, we can refine and enhance this process even further.</p>

<p>We look forward to continuing this important journey with you!</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.

⚠️ Potential issue

Secure the HTML generation by escaping user input.

The HTML generation needs proper escaping to prevent XSS vulnerabilities.

Apply these security improvements:

   public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
+    // Validate input parameters
+    if (empty($organizingContactName) || empty($droppingCenterId)) {
+      throw new \CRM_Core_Exception("Missing required parameters for HTML generation");
+    }
+
+    // Escape user input
+    $organizingContactName = htmlspecialchars($organizingContactName, ENT_QUOTES, 'UTF-8');
+    $droppingCenterId = (int) $droppingCenterId;
+
     $homeUrl = \CRM_Utils_System::baseCMSURL();
-    $volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;
+    $volunteerFeedback = htmlspecialchars(
+      $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId,
+      ENT_QUOTES,
+      'UTF-8'
+    );

     $html = "
       <p>Dear $organizingContactName,</p>
📝 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 generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
$homeUrl = \CRM_Utils_System::baseCMSURL();
$volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;
$html = "
<p>Dear $organizingContactName,</p>
<p>Thank you for being such an outstanding representative of Goonj!
Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>
<p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center.
If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>
<p><a href='$volunteerFeedback'>Click here to access the feedback form.</a></p>
<p>We encourage you to share any highlights, suggestions, or challenges you’ve encountered. Together, we can refine and enhance this process even further.</p>
<p>We look forward to continuing this important journey with you!</p>
<p>Warm Regards,<br>
Team Goonj</p>
";
return $html;
}
public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
// Validate input parameters
if (empty($organizingContactName) || empty($droppingCenterId)) {
throw new \CRM_Core_Exception("Missing required parameters for HTML generation");
}
// Escape user input
$organizingContactName = htmlspecialchars($organizingContactName, ENT_QUOTES, 'UTF-8');
$droppingCenterId = (int) $droppingCenterId;
$homeUrl = \CRM_Utils_System::baseCMSURL();
$volunteerFeedback = htmlspecialchars(
$homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId,
ENT_QUOTES,
'UTF-8'
);
$html = "
<p>Dear $organizingContactName,</p>
<p>Thank you for being such an outstanding representative of Goonj!
Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>
<p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center.
If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>
<p><a href='$volunteerFeedback'>Click here to access the feedback form.</a></p>
<p>We encourage you to share any highlights, suggestions, or challenges you've encountered. Together, we can refine and enhance this process even further.</p>
<p>We look forward to continuing this important journey with you!</p>
<p>Warm Regards,<br>
Team Goonj</p>
";
return $html;
}

Comment on lines +23 to +47
public static function processDroppingCenterStatus($meta, $from) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];

// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

if (!$status) {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
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 and error handling.

The method has several potential issues:

  1. No validation of required meta fields
  2. Missing error handling for email operations
  3. Status update could fail silently after email send

Consider implementing these improvements:

 public static function processDroppingCenterStatus($meta, $from) {
+    // Validate required fields
+    $requiredFields = [
+      'Dropping_Center_Meta.Dropping_Center',
+      'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id',
+      'Status.Feedback_Email_Delivered:name'
+    ];
+    foreach ($requiredFields as $field) {
+      if (!isset($meta[$field])) {
+        throw new \CRM_Core_Exception("Missing required field: $field");
+      }
+    }
+
     $droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
     $initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
     $status = $meta['Status.Feedback_Email_Delivered:name'];

     // Get recipient email and name.
     $campAttendedBy = Contact::get(TRUE)
       ->addSelect('email.email', 'display_name')
       ->addJoin('Email AS email', 'LEFT')
       ->addWhere('id', '=', $initiatorId)
       ->execute()->single();

+    if (empty($campAttendedBy['email.email'])) {
+      throw new \CRM_Core_Exception("No email found for contact ID: $initiatorId");
+    }

     $contactEmailId = $campAttendedBy['email.email'];
     $organizingContactName = $campAttendedBy['display_name'];

     if (!$status) {
+      try {
         self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
 
         // Update status if the email is sent.
-        EckEntity::update('Dropping_Center_Meta', TRUE)
+        $result = EckEntity::update('Dropping_Center_Meta', TRUE)
           ->addValue('Status.Feedback_Email_Delivered:name', 1)
           ->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
           ->execute();
+        if (empty($result)) {
+          throw new \CRM_Core_Exception("Failed to update feedback email status");
+        }
+      } catch (\Exception $e) {
+        \Civi::log()->error('Failed to process dropping center feedback: ' . $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 processDroppingCenterStatus($meta, $from) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
public static function processDroppingCenterStatus($meta, $from) {
// Validate required fields
$requiredFields = [
'Dropping_Center_Meta.Dropping_Center',
'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id',
'Status.Feedback_Email_Delivered:name'
];
foreach ($requiredFields as $field) {
if (!isset($meta[$field])) {
throw new \CRM_Core_Exception("Missing required field: $field");
}
}
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if (empty($campAttendedBy['email.email'])) {
throw new \CRM_Core_Exception("No email found for contact ID: $initiatorId");
}
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
try {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update status if the email is sent.
$result = EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
if (empty($result)) {
throw new \CRM_Core_Exception("Failed to update feedback email status");
}
} catch (\Exception $e) {
\Civi::log()->error('Failed to process dropping center feedback: ' . $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: 7

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

3-6: Enhance the file-level PHPDoc block for better documentation

The file-level docblock is currently minimal. Providing a detailed description of the file's purpose and functionality will improve code readability and maintainability.

Consider expanding the docblock as follows:

 /**
+ * Implements a cron job to send biannual feedback emails to dropping centers.
+ *
+ * This script retrieves dropping centers that meet certain criteria and sends
+ * feedback emails to associated contacts. It updates the last feedback sent date
+ * to prevent duplicate emails.
  * @file
  */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7121d2c and 0cab4e0.

📒 Files selected for processing (1)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (1 hunks)

$droppingCenters = EckEntity::get('Collection_Camp', TRUE)
->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id', 'Dropping_Centre.last_feedback_sent_date')
->addWhere('Dropping_Centre.When_do_you_wish_to_open_center_Date_', '<=', $thresholdDate)
->addWhere('Collection_Camp_Core_Details.Status:name', '=', 'Authorized')
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

Resolve inconsistent status checks between 'Authorized' and 'Permanently_Closed'

The query filters dropping centers with status 'Authorized' (line 31), but later checks if the status is 'Permanently_Closed' (line 58). A dropping center cannot be both 'Authorized' and 'Permanently_Closed' simultaneously. This inconsistency may result in no centers being processed.

To align the logic, consider updating the status check:

-   ->addWhere('Collection_Camp_Core_Details.Status:name', '=', 'Authorized')
+   ->addWhere('Collection_Camp_Core_Details.Status:name', '=', 'Permanently_Closed')

Alternatively, if the intent is to process centers that are not permanently closed, adjust the later check accordingly.

Also applies to: 58-58

$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
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

Correct the typo in the status name 'Parmanently_Closed'

The status name 'Parmanently_Closed' appears to have a typo. It should be 'Permanently_Closed'. This error may prevent the query from matching any records.

Apply this diff to fix the typo:

-          ->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
+          ->addWhere('Status.Status:name', '=', 'Permanently_Closed')
📝 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
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->addWhere('Status.Status:name', '=', 'Permanently_Closed')

\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
}

return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');
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

Return meaningful processing statistics

The $returnValues array is empty, so the API response does not provide any insight into what was processed. Including statistics will improve monitoring and debugging.

Populate $returnValues with processing details:

 $returnValues = [];
+ $returnValues['processed_centers'] = count($droppingCenters);
+ $returnValues['timestamp'] = date('Y-m-d H:i:s');

 return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');

Committable suggestion was skipped due to low confidence.

$returnValues = [];

// Set threshold date to 6 months ago.
$thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');
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

Externalize the threshold date for flexibility

The threshold of 6 months is hard-coded, which reduces flexibility and reusability. Moving this value to a configuration setting or making it a parameter enhances adaptability.

Consider retrieving the threshold from a configuration setting:

- $thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');
+ $monthsThreshold = Civi::settings()->get('dropping_center_feedback_months_threshold') ?? 6;
+ $thresholdDate = (new \DateTime())->modify("-{$monthsThreshold} months")->format('Y-m-d');

Committable suggestion was skipped due to low confidence.

Comment on lines +61 to +75
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];

// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
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

Prevent multiple emails due to duplicate meta records

If multiple Dropping_Center_Meta records exist for a single dropping center, the nested loop may send multiple feedback emails to the same contact. This could lead to spamming the user.

To ensure only one email is sent per dropping center, consider restructuring the code:

  • Move the email-sending logic outside the inner loop.
  • Check if at least one meta record meets the criteria before sending the email.

Example adjustment:

         foreach ($droppingCenterMeta as $meta) {
           $status = $meta['Status.Feedback_Email_Delivered'];

-          if (!$status) {
-            // Send the feedback email.
-            DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

-            // Update the last_feedback_sent_date to the current date after the email is sent.
-            EckEntity::update('Collection_Camp', TRUE)
-              ->addWhere('id', '=', $droppingCenterId)
-              ->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
-              ->execute();
-          }
+          if ($status) {
+            // Feedback email already sent; no action needed.
+            continue;
+          }
+          // Meta record requires feedback email.
+          $needsEmail = true;
+          break; // Exit loop if email is needed.
         }

+        if (isset($needsEmail)) {
+          // Send the feedback email.
+          DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

+          // Update the last_feedback_sent_date to the current date after the email is sent.
+          EckEntity::update('Collection_Camp', TRUE)
+            ->addWhere('id', '=', $droppingCenterId)
+            ->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
+            ->execute();
+        }
📝 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
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
if ($status) {
// Feedback email already sent; no action needed.
continue;
}
// Meta record requires feedback email.
$needsEmail = true;
break; // Exit loop if email is needed.
}
if (isset($needsEmail)) {
// Send the feedback email.
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}

Comment on lines +38 to +81
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->execute();

foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];

// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
}
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $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

Enhance error handling within the processing loop

Catching exceptions at the high level may cause the entire process to halt if an error occurs with one dropping center. This violates the robustness principle and affects the reliability of the cron job.

Implement per-dropping center error handling to ensure one failure doesn't impact others:

   try {
     foreach ($droppingCenters as $center) {
+      try {
         // Existing processing logic...
+      } catch (\Exception $e) {
+        \CRM_Core_Error::debug_log_message('Error processing Dropping Center ID ' . $center['id'] . ': ' . $e->getMessage());
+        // Optionally continue to the next center
+        continue;
+      }
     }
-  }
-  catch (Exception $e) {
-    \CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
   }
📝 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
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->execute();
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
}
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
}
try {
foreach ($droppingCenters as $center) {
try {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->execute();
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
}
} catch (\Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center ID ' . $center['id'] . ': ' . $e->getMessage());
// Optionally continue to the next center
continue;
}
}
}

/**
* Class to manage sending feedback emails for Dropping Centers.
*/
class DroppingCenterFeedbackCron {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we update the name of the file so that we identify it's an service file

*
*/
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Add comment here // There are no parameters for the Goonjcustom cron. so that it wont blank

// Get all dropping centers where the center's open date is older than 6 months
// and the last feedback email was either never sent or sent more than 6 months ago.
$droppingCenters = EckEntity::get('Collection_Camp', TRUE)
->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id', 'Dropping_Centre.last_feedback_sent_date')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id', 'Dropping_Centre.last_feedback_sent_date')
->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id', 'Dropping_Centre.last_feedback_sent_date')

remove When_do_you_wish_to_open_center_Date_ ,id as we are not using those anywhere

Comment on lines +35 to +36
[$defaultFromName, $defaultFromEmail] = CRM_Core_BAO_Domain::getNameAndEmail();
$from = "\"$defaultFromName\" <$defaultFromEmail>";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We have created a helper function for this please check on this file /wp-content/civi-extensions/goonjcustom/Civi/HelperService.php

Comment on lines +40 to +58
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move logic to service file

Comment on lines +40 to +58
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the spelling of Parmanently_Closed

Comment on lines +65 to +75
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackCron::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we return it before is there were status is there

$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Closing_Date', 'Dropping_Center_Meta.Dropping_Center', 'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id', 'Status.Feedback_Email_Delivered:name')
->addWhere('subtype', '=', $statusName)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Check the spelling

Comment on lines +55 to +56
[$defaultFromName, $defaultFromEmail] = CRM_Core_BAO_Domain::getNameAndEmail();
$from = "\"$defaultFromName\" <$defaultFromEmail>";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use helper function for those

Comment on lines +38 to +47
if (!$status) {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can we return it before?

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: 6

🧹 Outside diff range and nitpick comments (1)
wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (1)

1-11: Consider splitting responsibilities into separate services.

The class currently handles multiple responsibilities: email generation, sending, and status management. Consider splitting these into separate services:

  • DroppingCenterEmailService for email operations
  • DroppingCenterStatusService for status management
    This would better align with the Single Responsibility Principle.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0cab4e0 and eb3084c.

📒 Files selected for processing (3)
  • wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php
🔇 Additional comments (7)
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (7)

35-36: Enhance email sender information retrieval

Currently, the code retrieves the default domain name and email but doesn't handle the possibility of missing data, which could cause issues.

Consider adding a fallback or validation:

[$defaultFromName, $defaultFromEmail] = CRM_Core_BAO_Domain::getNameAndEmail();
+if (empty($defaultFromEmail)) {
+  throw new Exception('Default From Email is not configured.');
+}
$from = "\"$defaultFromName\" <$defaultFromEmail>";

9-9: Ensure the DroppingCenterFeedbackService class follows single responsibility

The DroppingCenterFeedbackService class should focus solely on sending feedback emails. Verify that it doesn't contain unrelated logic, which could violate the single responsibility principle.

Run the following script to check the class methods:

✅ Verification successful

Let me try a different approach to verify the content of this class.


DroppingCenterFeedbackService follows single responsibility principle - no issues found

After reviewing the implementation, the DroppingCenterFeedbackService class strictly adheres to the single responsibility principle. It has three well-focused methods that work together for the sole purpose of managing feedback emails for Dropping Centers:

  • processDroppingCenterStatus: Handles the business logic for determining when to send feedback
  • sendFeedbackEmail: Manages the email sending mechanics
  • generateFeedbackEmailHtml: Focuses on email content generation

The class is well-documented, cohesive, and doesn't contain any unrelated functionality. Each method has a clear, specific purpose that contributes to the main responsibility of managing feedback emails.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: List all methods in DroppingCenterFeedbackService class.

# Test: Extract method names. Expect: Only methods related to feedback email.

ast-grep --pattern 'class DroppingCenterFeedbackService {
  $$$
  function $_($$) {
    $$$
  }
  $$$
}' wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php

Length of output: 184


Script:

#!/bin/bash
# Let's first check if the file exists and see its content
cat wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php

# If the file is not found at that location, let's search for it
fd DroppingCenterFeedbackService.php

# Let's also try to find any references to this class in the codebase
rg "DroppingCenterFeedbackService" -A 3

Length of output: 5922


58-58: ⚠️ Potential issue

Correct the typo in status name to prevent logic errors

There's a typo in the status name 'Parmanently_Closed'. It should be 'Permanently_Closed'. This error may prevent the query from matching any records.

Apply this correction:

->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
+->addWhere('Status.Status:name', '=', 'Permanently_Closed')

Likely invalid or redundant comment.


24-24: 🛠️ Refactor suggestion

Externalize the threshold date for flexibility and maintainability

The threshold of 6 months is hard-coded, which reduces flexibility. Moving this value to a configuration setting or making it a parameter enhances adaptability and makes future changes easier.

Consider retrieving the threshold from a configuration setting:

-$thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');
+$monthsThreshold = Civi::settings()->get('dropping_center_feedback_months_threshold') ?? 6;
+$thresholdDate = (new \DateTime())->modify("-{$monthsThreshold} months")->format('Y-m-d');

Likely invalid or redundant comment.


54-60: 🛠️ Refactor suggestion

Consolidate status checks to improve readability

The status check for the dropping center is split between lines 31 and 58, causing confusion.

Consider consolidating the status checks into the main query to streamline logic:

$droppingCenters = EckEntity::get('Collection_Camp', TRUE)
  ->addSelect('Dropping_Centre.When_do_you_wish_to_open_center_Date_', 'id', 'Collection_Camp_Core_Details.Contact_Id', 'Dropping_Centre.last_feedback_sent_date')
  ->addWhere('Dropping_Centre.When_do_you_wish_to_open_center_Date_', '<=', $thresholdDate)
- ->addWhere('Collection_Camp_Core_Details.Status:name', '=', 'Authorized')
+ ->addWhere('Collection_Camp_Core_Details.Status:name', 'NOT IN', ['Permanently_Closed'])
  ->addClause('OR', ['Dropping_Centre.last_feedback_sent_date', 'IS NULL'], ['Dropping_Centre.last_feedback_sent_date', '<=', $thresholdDate])
  ->execute();

Likely invalid or redundant comment.


83-83: 🛠️ Refactor suggestion

Return meaningful processing statistics

The $returnValues array is empty, so the API response does not provide insight into the cron job's effectiveness. Including statistics will improve monitoring and debugging.

Populate $returnValues with processing details:

 $returnValues = [];
+$returnValues['processed_centers'] = count($droppingCenters);
+$returnValues['timestamp'] = date('Y-m-d H:i:s');

 return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');

Likely invalid or redundant comment.


31-32: ⚠️ Potential issue

Resolve inconsistent status checks to ensure correct data retrieval

The query filters dropping centers with status 'Authorized' (line 31), but later checks if the status is 'Parmanently_Closed' (line 58). A dropping center cannot be both 'Authorized' and 'Parmanently_Closed' simultaneously. This inconsistency may result in no centers being processed.

To align the logic, consider updating the status check:

->addWhere('Collection_Camp_Core_Details.Status:name', '=', 'Authorized')
+->addWhere('Collection_Camp_Core_Details.Status:name', '=', 'Permanently_Closed')

Also, correct the typo 'Parmanently_Closed' to 'Permanently_Closed' in line 58.

Likely invalid or redundant comment.

Comment on lines +82 to +106
public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
$homeUrl = \CRM_Utils_System::baseCMSURL();
$volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;

$html = "
<p>Dear $organizingContactName,</p>

<p>Thank you for being such an outstanding representative of Goonj!
Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>

<p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center.
If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>

<p><a href='$volunteerFeedback'>Click here to access the feedback form.</a></p>

<p>We encourage you to share any highlights, suggestions, or challenges you’ve encountered. Together, we can refine and enhance this process even further.</p>

<p>We look forward to continuing this important journey with you!</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.

⚠️ Potential issue

Move email template to a separate file and add security measures.

Several improvements needed:

  1. Move HTML template to a separate file
  2. Add XSS protection for user input
  3. Make URL path configurable

[security]
Here's a suggested improvement:

+ const FEEDBACK_URL_PATH = 'volunteer-feedback/#?Eck_Collection_Camp1=';

  public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
    $homeUrl = \CRM_Utils_System::baseCMSURL();
-   $volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;
+   $volunteerFeedback = $homeUrl . self::FEEDBACK_URL_PATH . (int)$droppingCenterId;
+   $organizingContactName = \CRM_Utils_String::htmlEscape($organizingContactName);

-   $html = "
-     <p>Dear $organizingContactName,</p>
-     ...
-   ";
+   $templatePath = \Civi::paths()->getPath('[civicrm.root]/templates/dropping-center-feedback.html');
+   $template = file_get_contents($templatePath);
+   $html = str_replace(
+     ['{{name}}', '{{feedback_url}}'],
+     [$organizingContactName, $volunteerFeedback],
+     $template
+   );

    return $html;
  }

Create a new template file at templates/dropping-center-feedback.html:

<p>Dear {{name}},</p>

<p>Thank you for being such an outstanding representative of Goonj! 
Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>

<p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center. 
If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>

<p><a href='{{feedback_url}}'>Click here to access the feedback form.</a></p>

<p>We encourage you to share any highlights, suggestions, or challenges you've encountered. Together, we can refine and enhance this process even further.</p>

<p>We look forward to continuing this important journey with you!</p>

<p>Warm Regards,<br>
Team Goonj</p>

Comment on lines +61 to +72
public static function sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
];

// Send the email.
\CRM_Utils_Mail::send($mailParams);
}
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

Enhance email sending robustness and configurability.

The email sending logic needs improvements:

  1. Add parameter validation
  2. Add error handling for mail sending
  3. Move email subject to configuration

Here's a suggested improvement:

+ const EMAIL_SUBJECT = 'Your Feedback on Managing the Goonj Dropping Center';

  public static function sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
+   if (empty($contactEmailId) || empty($from)) {
+     throw new \InvalidArgumentException('Missing required email parameters');
+   }

+   try {
      $mailParams = [
-       'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
+       'subject' => self::EMAIL_SUBJECT,
        'from' => $from,
        'toEmail' => $contactEmailId,
        'replyTo' => $from,
        'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
      ];

-     \CRM_Utils_Mail::send($mailParams);
+     $sent = \CRM_Utils_Mail::send($mailParams);
+     if (!$sent) {
+       throw new \RuntimeException('Failed to send feedback email');
+     }
+     return $sent;
+   } catch (\Exception $e) {
+     \Civi::log()->error('Failed to send feedback email: ' . $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 sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
$mailParams = [
'subject' => 'Your Feedback on Managing the Goonj Dropping Center',
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
];
// Send the email.
\CRM_Utils_Mail::send($mailParams);
}
const EMAIL_SUBJECT = 'Your Feedback on Managing the Goonj Dropping Center';
public static function sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from) {
if (empty($contactEmailId) || empty($from)) {
throw new \InvalidArgumentException('Missing required email parameters');
}
try {
$mailParams = [
'subject' => self::EMAIL_SUBJECT,
'from' => $from,
'toEmail' => $contactEmailId,
'replyTo' => $from,
'html' => self::generateFeedbackEmailHtml($organizingContactName, $droppingCenterId),
];
$sent = \CRM_Utils_Mail::send($mailParams);
if (!$sent) {
throw new \RuntimeException('Failed to send feedback email');
}
return $sent;
} catch (\Exception $e) {
\Civi::log()->error('Failed to send feedback email: ' . $e->getMessage());
throw $e;
}
}

Comment on lines +23 to +47
public static function processDroppingCenterStatus($meta, $from) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];

// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

if (!$status) {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
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 and error handling.

Several improvements needed for robustness:

  1. Validate input parameters before processing
  2. Add error handling for API calls
  3. Handle potential null email addresses
  4. Replace magic number with constant

Here's a suggested improvement:

+ const STATUS_EMAIL_DELIVERED = 1;

  public static function processDroppingCenterStatus($meta, $from) {
+   if (empty($meta) || empty($from)) {
+     throw new \InvalidArgumentException('Missing required parameters');
+   }
    
    $droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
    $initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
    $status = $meta['Status.Feedback_Email_Delivered:name'];

+   try {
      $campAttendedBy = Contact::get(TRUE)
        ->addSelect('email.email', 'display_name')
        ->addJoin('Email AS email', 'LEFT')
        ->addWhere('id', '=', $initiatorId)
        ->execute()->single();

+     if (empty($campAttendedBy['email.email'])) {
+       throw new \RuntimeException("No email found for contact $initiatorId");
+     }

      $contactEmailId = $campAttendedBy['email.email'];
      $organizingContactName = $campAttendedBy['display_name'];

      if (!$status) {
        self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

        EckEntity::update('Dropping_Center_Meta', TRUE)
-         ->addValue('Status.Feedback_Email_Delivered:name', 1)
+         ->addValue('Status.Feedback_Email_Delivered:name', self::STATUS_EMAIL_DELIVERED)
          ->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
          ->execute();
      }
+   } catch (\Exception $e) {
+     \Civi::log()->error('Failed to process dropping center status: ' . $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 processDroppingCenterStatus($meta, $from) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
// Get recipient email and name.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
const STATUS_EMAIL_DELIVERED = 1;
public static function processDroppingCenterStatus($meta, $from) {
if (empty($meta) || empty($from)) {
throw new \InvalidArgumentException('Missing required parameters');
}
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
try {
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if (empty($campAttendedBy['email.email'])) {
throw new \RuntimeException("No email found for contact $initiatorId");
}
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
if (!$status) {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', self::STATUS_EMAIL_DELIVERED)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
} catch (\Exception $e) {
\Civi::log()->error('Failed to process dropping center status: ' . $e->getMessage());
throw $e;
}
}

Comment on lines +14 to +15
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
}
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

Implement API specification function for better validation

The _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec function is currently empty. According to CiviCRM API v3 best practices, this function should define the expected parameters and their data types. This enhances validation and documentation.

Consider adding parameter specifications, even if the API does not require any parameters:

 function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
+  // No parameters required for this API.
+  $spec['limit'] = [
+    'title' => 'Limit',
+    'type' => CRM_Utils_Type::T_INT,
+    'description' => 'Maximum number of dropping centers to process',
+    'api.default' => 50,
+  ];
 }
📝 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
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
}
function _civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron_spec(&$spec) {
// No parameters required for this API.
$spec['limit'] = [
'title' => 'Limit',
'type' => CRM_Utils_Type::T_INT,
'description' => 'Maximum number of dropping centers to process',
'api.default' => 50,
];
}

Comment on lines +61 to +75
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];

// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
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

Prevent multiple emails due to duplicate meta records

If multiple Dropping_Center_Meta records exist for a single dropping center, the inner loop may send multiple feedback emails to the same contact, leading to spamming.

To ensure only one email is sent per dropping center, restructure the code:

foreach ($droppingCenterMeta as $meta) {
  $status = $meta['Status.Feedback_Email_Delivered'];

-  if (!$status) {
-    // Send the feedback email.
-    DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
-
-    // Update the last_feedback_sent_date to the current date after the email is sent.
-    EckEntity::update('Collection_Camp', TRUE)
-      ->addWhere('id', '=', $droppingCenterId)
-      ->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
-      ->execute();
-  }
+  if ($status) {
+    // Feedback email already sent; no action needed.
+    continue;
+  }
+  // Meta record requires feedback email.
+  $needsEmail = true;
+  break; // Exit loop if email is needed.
}

+if (isset($needsEmail)) {
+  // Send the feedback email.
+  DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
+
+  // Update the last_feedback_sent_date to the current date after the email is sent.
+  EckEntity::update('Collection_Camp', TRUE)
+    ->addWhere('id', '=', $droppingCenterId)
+    ->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
+    ->execute();
+}
📝 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
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
if ($status) {
// Feedback email already sent; no action needed.
continue;
}
// Meta record requires feedback email.
$needsEmail = true;
break; // Exit loop if email is needed.
}
if (isset($needsEmail)) {
// Send the feedback email.
DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}

Comment on lines +38 to +81
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();

if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];

// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->execute();

foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];

// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
}
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $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

Enhance error handling within the processing loop

Catching exceptions at a high level may cause the entire process to halt if an error occurs with one dropping center. Implement per-dropping center error handling to ensure one failure doesn't impact others.

Modify the try-catch structure:

 try {
   foreach ($droppingCenters as $center) {
+    try {
     // Existing processing logic...
+    } catch (\Exception $e) {
+      \CRM_Core_Error::debug_log_message('Error processing Dropping Center ID ' . $center['id'] . ': ' . $e->getMessage());
+      // Optionally continue to the next center
+      continue;
+    }
   }
-}
-catch (Exception $e) {
-  \CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
 }
📝 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
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->execute();
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
}
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
}
try {
foreach ($droppingCenters as $center) {
try {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
// Fetch the email and name of the initiator.
$campAttendedBy = Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
if ($campAttendedBy) {
$contactEmailId = $campAttendedBy['email.email'];
$organizingContactName = $campAttendedBy['display_name'];
// Check the status of the dropping center
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name','Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Parmanently_Closed')
->execute();
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered'];
// Send email only if not delivered and not permanently closed
if (!$status) {
// Send the feedback email.
DroppingCenterFeedbackService::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
}
} catch (\Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center ID ' . $center['id'] . ': ' . $e->getMessage());
// Optionally continue to the next center
continue;
}
}
}

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: 7

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

21-23: Enhance API specification documentation.

While it's valid to have no parameters, it would be helpful to explicitly document why parameters aren't needed for this cron job.

 function _civicrm_api3_goonjcustom_feedback_dropping_center_cron_spec(&$spec) {
-  // There are no parameters for the Goonjcustom cron.
+  // This cron job runs automatically without requiring any input parameters
+  // as it processes all permanently closed dropping centers that haven't received feedback emails.
 }
wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (1)

29-30: Rename $contactEmailId to $contactEmail for clarity.

The variable $contactEmailId holds an email address, not an identifier. Renaming it to $contactEmail improves readability and reduces confusion.

Apply this diff:

-       $contactEmailId = $contactDetails['email.email'];
+       $contactEmail = $contactDetails['email.email'];

And update all occurrences of $contactEmailId to $contactEmail.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between eb3084c and 8777556.

📒 Files selected for processing (3)
  • wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1 hunks)
🔇 Additional comments (3)
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (2)

48-49: ⚠️ Potential issue

Add null check before accessing array key.

The code assumes the 'value' key exists in $optionValues. This could lead to undefined index errors.

+  if (empty($optionValues) || !isset($optionValues['value'])) {
+    throw new CRM_Core_Exception('Required Status option value not found');
+  }
   $statusName = $optionValues['value'];

Likely invalid or redundant comment.


37-39: 🛠️ Refactor suggestion

Return meaningful statistics from the cron job.

The current implementation returns an empty array, making it difficult to monitor the cron job's effectiveness.

 function civicrm_api3_goonjcustom_feedback_dropping_center_cron($params) {
-  $returnValues = [];
+  $returnValues = [
+    'processed' => [],    // Successfully processed dropping centers
+    'failed' => [],       // Failed dropping centers with error messages
+    'total_processed' => 0,
+    'total_failed' => 0,
+    'execution_time' => 0,
+  ];
+  $startTime = microtime(true);

And at the end of the function:

+  $returnValues['total_processed'] = count($returnValues['processed']);
+  $returnValues['total_failed'] = count($returnValues['failed']);
+  $returnValues['execution_time'] = microtime(true) - $startTime;
   return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'feedback_dropping_center_cron');

Likely invalid or redundant comment.

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

66-66: ⚠️ Potential issue

Add Global Namespace Prefix to Exception

To ensure consistency and avoid potential namespace issues, add the global namespace prefix to the Exception class in the catch block.

Apply this change:

- catch (Exception $e) {
+ catch (\Exception $e) {

Likely invalid or redundant comment.

Comment on lines +50 to +54
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Closing_Date', 'Dropping_Center_Meta.Dropping_Center', 'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id', 'Status.Feedback_Email_Delivered:name')
->addWhere('subtype', '=', $statusName)
->addWhere('Status.Status:name', '=', 'Permanently_Closed')
->execute();
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

Add pagination to prevent memory issues with large datasets.

The query fetches all dropping centers at once, which could cause memory issues if there are many records.

   $droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
     ->addSelect('Status.Status:name', 'Status.Closing_Date', 'Dropping_Center_Meta.Dropping_Center', 'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id', 'Status.Feedback_Email_Delivered:name')
     ->addWhere('subtype', '=', $statusName)
     ->addWhere('Status.Status:name', '=', 'Permanently_Closed')
+    ->setLimit(50)  // Process in batches of 50
     ->execute();
📝 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
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Closing_Date', 'Dropping_Center_Meta.Dropping_Center', 'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id', 'Status.Feedback_Email_Delivered:name')
->addWhere('subtype', '=', $statusName)
->addWhere('Status.Status:name', '=', 'Permanently_Closed')
->execute();
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Closing_Date', 'Dropping_Center_Meta.Dropping_Center', 'Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id', 'Status.Feedback_Email_Delivered:name')
->addWhere('subtype', '=', $statusName)
->addWhere('Status.Status:name', '=', 'Permanently_Closed')
->setLimit(50) // Process in batches of 50
->execute();

Comment on lines +58 to +71
try {
foreach ($droppingCenterMeta as $meta) {

$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];

DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center ID: ' . $meta['Dropping_Center_Meta.Dropping_Center']);
}

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

Improve error handling and add transaction management.

The current error handling only logs the error without proper cleanup or reporting. Additionally, database operations should be wrapped in a transaction.

+  $transaction = new CRM_Core_Transaction();
   try {
     foreach ($droppingCenterMeta as $meta) {
+      try {
         $droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
         $initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
         $status = $meta['Status.Feedback_Email_Delivered:name'];

         DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
+        $returnValues['processed'][] = $droppingCenterId;
+      } catch (Exception $e) {
+        $returnValues['failed'][] = [
+          'id' => $droppingCenterId,
+          'error' => $e->getMessage()
+        ];
+        \CRM_Core_Error::debug_log_message(
+          "Error processing Dropping Center ID {$droppingCenterId}: " . $e->getMessage()
+        );
+      }
     }
+    $transaction->commit();
   }
   catch (Exception $e) {
-    \CRM_Core_Error::debug_log_message('Error processing Dropping Center ID: ' . $meta['Dropping_Center_Meta.Dropping_Center']);
+    $transaction->rollback();
+    throw new CRM_Core_Exception('Critical error in feedback cron: ' . $e->getMessage());
   }
📝 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
try {
foreach ($droppingCenterMeta as $meta) {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center ID: ' . $meta['Dropping_Center_Meta.Dropping_Center']);
}
$transaction = new CRM_Core_Transaction();
try {
foreach ($droppingCenterMeta as $meta) {
try {
$droppingCenterId = $meta['Dropping_Center_Meta.Dropping_Center'];
$initiatorId = $meta['Dropping_Center_Meta.Dropping_Center.Collection_Camp_Core_Details.Contact_Id'];
$status = $meta['Status.Feedback_Email_Delivered:name'];
DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
$returnValues['processed'][] = $droppingCenterId;
} catch (Exception $e) {
$returnValues['failed'][] = [
'id' => $droppingCenterId,
'error' => $e->getMessage()
];
\CRM_Core_Error::debug_log_message(
"Error processing Dropping Center ID {$droppingCenterId}: " . $e->getMessage()
);
}
}
$transaction->commit();
}
catch (Exception $e) {
$transaction->rollback();
throw new CRM_Core_Exception('Critical error in feedback cron: ' . $e->getMessage());
}

Comment on lines +21 to +70
function civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron($params) {
$returnValues = [];

// Set threshold date to 6 months ago.
$thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');

// Get all dropping centers where the center's open date is older than 6 months
// and the last feedback email was either never sent or sent more than 6 months ago.
$droppingCenters = EckEntity::get('Collection_Camp', TRUE)
->addSelect('Collection_Camp_Core_Details.Contact_Id', 'Dropping_Centre.last_feedback_sent_date')
->addWhere('Dropping_Centre.When_do_you_wish_to_open_center_Date_', '<=', $thresholdDate)
->addWhere('Collection_Camp_Core_Details.Status:name', '=', 'Authorized')
->addClause('OR', ['Dropping_Centre.last_feedback_sent_date', 'IS NULL'], ['Dropping_Centre.last_feedback_sent_date', '<=', $thresholdDate])
->execute();

$from = HelperService::getDefaultFromEmail();

try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Permanently_Closed')
->execute();

foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered:name'];

}

// Send email only if not delivered and not permanently closed
// Send the feedback email.
DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);

// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
}

return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');
}
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 Function for Single Responsibility

The civicrm_api3_goonjcustom_biannual_dropping_center_feedback_cron function handles multiple responsibilities, including data retrieval, processing logic, sending emails, and updating records. Refactoring the code to separate these concerns can enhance readability and maintainability.

Consider the following refactoring steps:

  • Data Retrieval: Extract the logic for fetching dropping centers into a separate method or service.
  • Processing Logic: Create a dedicated method to process each dropping center, handling status checks and email sending.
  • Email Sending: Leverage the DroppingCenterFeedbackService to encapsulate email-related operations.
  • Error Handling: Implement per-dropping center error handling to prevent one failure from affecting the entire process.

This adheres to the Single Responsibility Principle and makes future modifications more manageable.

Comment on lines +50 to +53
$status = $meta['Status.Feedback_Email_Delivered: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

Ensure Correct Handling of $status Variable

The $status variable is assigned within the foreach loop over $droppingCenterMeta (lines 50-53), but it's used outside the loop at line 57. If $droppingCenterMeta contains multiple records, $status will retain the value from the last iteration, which may not be the intended behavior. This could lead to incorrect processing in processDroppingCenterStatus().

Consider processing each meta record individually or aggregating statuses appropriately. Here's a possible fix:

foreach ($droppingCenterMeta as $meta) {
  $status = $meta['Status.Feedback_Email_Delivered:name'];
+  // Send email only if not delivered and not permanently closed
+  DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
}

- // Send email only if not delivered and not permanently closed
- // Send the feedback email.
- DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);

Committable suggestion was skipped due to low confidence.

Comment on lines +23 to +42
public static function processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from) {

// Get recipient email and name.
$contactDetails = self::getContactDetails($initiatorId);

if ($contactDetails) {
$contactEmailId = $contactDetails['email.email'];
$organizingContactName = $contactDetails['display_name'];

if (!$status) {
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
}
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 error handling for critical operations.

The method processDroppingCenterStatus performs database updates and email sending without error handling. If any of these operations fail, the system may not behave as expected.

Consider wrapping the email sending and status update in try-catch blocks or checking the return values to handle exceptions and ensure the system remains consistent.

Comment on lines +16 to +22
* @param array $meta
* Meta data for the Dropping Center.
* @param string $from
* Email address for 'from'.
*
* @throws \CRM_Core_Exception
*/
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

Update method documentation to reflect actual parameters.

The PHPDoc for processDroppingCenterStatus does not match the method's parameters. The documentation refers to $meta, but the method accepts $droppingCenterId, $initiatorId, $status, and $from.

Apply this diff to correct the PHPDoc:

 /**
-   * Send feedback email if it hasn't been sent yet.
+   * Process the dropping center status and send feedback email if it hasn't been sent yet.
  *
-   * @param array $meta
-   *   Meta data for the Dropping Center.
+   * @param int $droppingCenterId
+   *   ID of the Dropping Center.
+   * @param int $initiatorId
+   *   ID of the initiator.
+   * @param bool $status
+   *   Feedback email delivery status.
   * @param string $from
     *   Email address for 'from'.
   *
   * @throws \CRM_Core_Exception
   */
📝 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
* @param array $meta
* Meta data for the Dropping Center.
* @param string $from
* Email address for 'from'.
*
* @throws \CRM_Core_Exception
*/
/**
* Process the dropping center status and send feedback email if it hasn't been sent yet.
*
* @param int $droppingCenterId
* ID of the Dropping Center.
* @param int $initiatorId
* ID of the initiator.
* @param bool $status
* Feedback email delivery status.
* @param string $from
* Email address for 'from'.
*
* @throws \CRM_Core_Exception
*/

$contactEmailId = $contactDetails['email.email'];
$organizingContactName = $contactDetails['display_name'];

if (!$status) {
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

Use more descriptive variable name for $status.

The variable $status is ambiguous. Renaming it to $feedbackEmailDelivered or $isFeedbackEmailSent makes the code more understandable.

Apply this diff:

- public static function processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from) {
+ public static function processDroppingCenterStatus($droppingCenterId, $initiatorId, $isFeedbackEmailSent, $from) {

...

-         if (!$status) {
+         if (!$isFeedbackEmailSent) {

And update any other references accordingly.

Committable suggestion was skipped due to low confidence.

@belwalshubham belwalshubham changed the title Send Feedback Email When Dropping Center is Closed Send Feedback Email When Dropping Center is Closed or sent every 6 month Oct 24, 2024
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: 3

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

3-5: Add proper PHPDoc documentation blocks

The file and function documentation blocks are empty. Add comprehensive documentation to improve code maintainability.

 /**
+ * BiannualDroppingCenterFeedbackCron.php
+ * 
+ * Implements a cron job that sends feedback emails to dropping centers
+ * every six months based on their opening date.
  */

 /**
+ * API function specification for biannual dropping center feedback cron.
+ * 
+ * This API doesn't require any parameters as it processes all eligible
+ * dropping centers based on predefined criteria.
+ * 
+ * @return array API specification
  */

Also applies to: 11-13


21-69: Refactor for Single Responsibility Principle

The function handles multiple responsibilities: data retrieval, processing logic, email sending, and record updates. This violates the Single Responsibility Principle.

Consider extracting these responsibilities into separate methods in the DroppingCenterFeedbackService class:

  1. getEligibleDroppingCenters() - Data retrieval
  2. processDroppingCenter() - Individual center processing
  3. updateFeedbackSentDate() - Record updates

This would make the code more maintainable and testable.

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

11-11: Consider improving architecture with better separation of concerns.

The class currently handles multiple responsibilities:

  1. Email content generation
  2. Email sending
  3. Status management
  4. Contact details retrieval

Consider splitting these into separate services:

  • DroppingCenterEmailService: For email generation and sending
  • DroppingCenterStatusService: For status management
  • ContactDetailsService: For contact information retrieval

This would improve maintainability and testability.

Would you like me to provide a detailed implementation plan for this refactoring?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8777556 and 557edc6.

📒 Files selected for processing (3)
  • wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/FeedbackDroppingCenterCron.php
🔇 Additional comments (5)
wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (3)

68-68: 🛠️ Refactor suggestion

Enhance API response with processing statistics

The current implementation returns empty values, making it difficult to monitor the cron job's effectiveness.

+ $returnValues = [
+   'processed' => count($droppingCenters),
+   'threshold_date' => $thresholdDate,
+   'timestamp' => date('Y-m-d H:i:s'),
+ ];
  return civicrm_api3_create_success($returnValues, $params, 'Goonjcustom', 'biannual_dropping_center_feedback_cron');

Likely invalid or redundant comment.


48-55: ⚠️ Potential issue

Fix status variable scope and processing logic

The status variable is set inside a loop but used outside, leading to potential bugs. Additionally, the processing logic should be inside the loop.

 foreach ($droppingCenterMeta as $meta) {
   $status = $meta['Status.Feedback_Email_Delivered:name'];
+  if (!$status) {
+    // Send email only if not delivered
+    DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
+    break;  // Process only once per dropping center
+  }
 }

- // Send email only if not delivered and not permanently closed
- // Send the feedback email.
- DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);

Likely invalid or redundant comment.


24-25: 🛠️ Refactor suggestion

Make the threshold period configurable

The 6-month threshold is hardcoded, reducing flexibility. Move this to a configuration setting.

- $thresholdDate = (new \DateTime())->modify('-6 months')->format('Y-m-d');
+ $monthsThreshold = Civi::settings()->get('dropping_center_feedback_months_threshold') ?? 6;
+ $thresholdDate = (new \DateTime())->modify("-{$monthsThreshold} months")->format('Y-m-d');

Likely invalid or redundant comment.

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

74-85: Previous review comments are still applicable.

The existing review comments about enhancing email sending robustness and configurability are still valid and should be addressed.


95-119: Previous review comments are still applicable.

The existing review comments about moving the email template to a separate file and adding security measures are still valid and should be addressed.

Comment on lines +37 to +66
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];

$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Permanently_Closed')
->execute();

foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered:name'];

}

// Send email only if not delivered and not permanently closed
// Send the feedback email.
DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);

// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $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

Improve error handling granularity

The current error handling catches exceptions at too high a level, potentially masking individual failures.

 try {
   foreach ($droppingCenters as $center) {
+    try {
       $droppingCenterId = $center['id'];
       // ... existing processing logic ...
+    } catch (\Exception $e) {
+      \CRM_Core_Error::debug_log_message(
+        "Error processing Dropping Center ID {$droppingCenterId}: " . $e->getMessage()
+      );
+      continue;  // Continue with next center
+    }
   }
-} catch (Exception $e) {
-  \CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
 }
📝 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
try {
foreach ($droppingCenters as $center) {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Permanently_Closed')
->execute();
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered:name'];
}
// Send email only if not delivered and not permanently closed
// Send the feedback email.
DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
}
}
catch (Exception $e) {
\CRM_Core_Error::debug_log_message('Error processing Dropping Center feedback: ' . $e->getMessage());
}
foreach ($droppingCenters as $center) {
try {
$droppingCenterId = $center['id'];
$initiatorId = $center['Collection_Camp_Core_Details.Contact_Id'];
$droppingCenterMeta = EckEntity::get('Dropping_Center_Meta', TRUE)
->addSelect('Status.Status:name', 'Status.Feedback_Email_Delivered:name')
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->addWhere('Status.Status:name', '=', 'Permanently_Closed')
->execute();
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered:name'];
}
// Send email only if not delivered and not permanently closed
// Send the feedback email.
DroppingCenterFeedbackService::processDroppingCenterStatus($droppingCenterId, $initiatorId, $status, $from);
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
} catch (\Exception $e) {
\CRM_Core_Error::debug_log_message(
"Error processing Dropping Center ID {$droppingCenterId}: " . $e->getMessage()
);
continue; // Continue with next center
}
}

Comment on lines +54 to +60
protected static function getContactDetails($initiatorId) {
return Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->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

Add error handling and parameter validation.

The method should validate input and handle potential API exceptions.

Apply this improvement:

   protected static function getContactDetails($initiatorId) {
+    if (empty($initiatorId) || !is_numeric($initiatorId)) {
+      throw new \InvalidArgumentException('Invalid initiator ID provided');
+    }
+    
+    try {
       return Contact::get(TRUE)
         ->addSelect('email.email', 'display_name')
         ->addJoin('Email AS email', 'LEFT')
         ->addWhere('id', '=', $initiatorId)
         ->execute()->single();
+    } catch (\Exception $e) {
+      \Civi::log()->error('Failed to retrieve contact details: ' . $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
protected static function getContactDetails($initiatorId) {
return Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
}
protected static function getContactDetails($initiatorId) {
if (empty($initiatorId) || !is_numeric($initiatorId)) {
throw new \InvalidArgumentException('Invalid initiator ID provided');
}
try {
return Contact::get(TRUE)
->addSelect('email.email', 'display_name')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', '=', $initiatorId)
->execute()->single();
} catch (\Exception $e) {
\Civi::log()->error('Failed to retrieve contact details: ' . $e->getMessage());
throw $e;
}
}

Comment on lines +27 to +43
if ($contactDetails) {
$contactEmailId = $contactDetails['email.email'];
$organizingContactName = $contactDetails['display_name'];

if ($status) {
return;
}

self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
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 explicit handling for missing contact details.

The method silently continues if contactDetails is null. This could lead to dropped emails without any logging or notification.

Consider adding explicit error handling:

   if ($contactDetails) {
     $contactEmailId = $contactDetails['email.email'];
     $organizingContactName = $contactDetails['display_name'];

     if ($status) {
       return;
     }

     self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);

     // Update status if the email is sent.
     EckEntity::update('Dropping_Center_Meta', TRUE)
       ->addValue('Status.Feedback_Email_Delivered:name', 1)
       ->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
       ->execute();
+  } else {
+    \Civi::log()->warning("Could not send feedback email - contact details not found for initiator ID: $initiatorId");
   }
📝 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
if ($contactDetails) {
$contactEmailId = $contactDetails['email.email'];
$organizingContactName = $contactDetails['display_name'];
if ($status) {
return;
}
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
}
}
if ($contactDetails) {
$contactEmailId = $contactDetails['email.email'];
$organizingContactName = $contactDetails['display_name'];
if ($status) {
return;
}
self::sendFeedbackEmail($organizingContactName, $droppingCenterId, $contactEmailId, $from);
// Update status if the email is sent.
EckEntity::update('Dropping_Center_Meta', TRUE)
->addValue('Status.Feedback_Email_Delivered:name', 1)
->addWhere('Dropping_Center_Meta.Dropping_Center', '=', $droppingCenterId)
->execute();
} else {
\Civi::log()->warning("Could not send feedback email - contact details not found for initiator ID: $initiatorId");
}
}

Comment on lines +48 to +52
foreach ($droppingCenterMeta as $meta) {
$status = $meta['Status.Feedback_Email_Delivered:name'];

}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we need this loop here?

Comment on lines +57 to +61
// Update the last_feedback_sent_date to the current date after the email is sent.
EckEntity::update('Collection_Camp', TRUE)
->addWhere('id', '=', $droppingCenterId)
->addValue('Dropping_Centre.last_feedback_sent_date', (new \DateTime())->format('Y-m-d'))
->execute();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move this logic to service file

->addWhere('option_group_id:name', '=', 'eck_sub_types')
->addWhere('name', '=', 'Status')
->addWhere('grouping', '=', 'Dropping_Center_Meta')
->setLimit(1)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
->setLimit(1)

Should we need this here? If you are using ->single(); then we don't need ->setLimit(1) please remove it and test the feature


try {
foreach ($droppingCenterMeta as $meta) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

remove the extra line

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: 0

🧹 Outside diff range and nitpick comments (1)
wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (1)

105-129: Enhance readability by using HEREDOC syntax for HTML content.

Embedding the HTML content using HEREDOC syntax improves readability and maintainability of the code.

Apply this diff to use HEREDOC syntax:

  public static function generateFeedbackEmailHtml($organizingContactName, $droppingCenterId) {
    $homeUrl = \CRM_Utils_System::baseCMSURL();
    $volunteerFeedback = $homeUrl . 'volunteer-feedback/#?Eck_Collection_Camp1=' . $droppingCenterId;

-   $html = "
+   $html = <<<HTML
      <p>Dear $organizingContactName,</p>
      
      <p>Thank you for being such an outstanding representative of Goonj! 
      Your dedication, time, and passion are truly making a difference as we work to bring essential materials to remote villages across the country.</p>
      
      <p>As part of our commitment to constant improvement, we would greatly appreciate hearing about your experience managing the Dropping Center. 
      If you could spare a few moments to complete our feedback form, your input would be invaluable to us!</p>
      
      <p><a href='$volunteerFeedback'>Click here to access the feedback form.</a></p>
      
      <p>We encourage you to share any highlights, suggestions, or challenges you’ve encountered. Together, we can refine and enhance this process even further.</p>
      
      <p>We look forward to continuing this important journey with you!</p>
      
      <p>Warm Regards,<br>
      Team Goonj</p>
-   ";
+   HTML;

    return $html;
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 557edc6 and b91842c.

📒 Files selected for processing (2)
  • wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (1 hunks)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wp-content/civi-extensions/goonjcustom/api/v3/Goonjcustom/BiannualDroppingCenterFeedbackCron.php
🔇 Additional comments (4)
wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterFeedbackService.php (4)

23-43: Method processDroppingCenterStatus adheres to best practices.

The method effectively processes the Dropping Center status and sends feedback emails as required, maintaining clarity and single responsibility.


54-60: Method getContactDetails is clear and efficient.

The method retrieves contact details appropriately and follows best coding practices.


65-70: Method updateFeedbackLastSentDate is concise and accurate.

The method correctly updates the last_feedback_sent_date, aligning with coding standards.


84-95: Method sendFeedbackEmail correctly constructs and sends the email.

The method sets up email parameters effectively and utilizes the CRM utility to send emails appropriately.

@belwalshubham
Copy link
Copy Markdown
Collaborator Author

@tarunnjoshi done with feedback comment.

@belwalshubham belwalshubham merged commit 3661e82 into develop Oct 25, 2024
@belwalshubham belwalshubham deleted the feat/dropping-center-cron branch October 25, 2024 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants