Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ class CRM_Goonjcustom_Engine {
/**
*
*/
public static function processQueue($params) {
$returnValues = [];
public static function processQueue(int $maxSeconds = 60): array {
$returnValues = [
'processed' => 0,
'results' => [],
];

$queue = \CRM_Queue_Service::singleton()->create([
'name' => self::QUEUE_NAME,
Expand All @@ -24,7 +27,8 @@ public static function processQueue($params) {
'errorMode' => CRM_Queue_Runner::ERROR_CONTINUE,
]);

$maxRunTime = time() + 30;
$maxSeconds = max(1, (int) $maxSeconds);
$maxRunTime = time() + $maxSeconds;
$continue = TRUE;

// Loop to process the queue items.
Expand All @@ -35,10 +39,18 @@ public static function processQueue($params) {
$continue = FALSE;
}

$returnValues[] = $result;
if (isset($result['exception']) && $result['exception'] instanceof \Throwable) {
$result['exception'] = [
'type' => get_class($result['exception']),
'message' => $result['exception']->getMessage(),
];
}

$returnValues['results'][] = $result;
$returnValues['processed']++;
}

return civicrm_api3_create_success($returnValues, $params, 'SendAuthorizationQueue', 'Run');
return $returnValues;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -745,8 +745,9 @@ public static function aclCollectionCamp($entity, &$clauses, $userId, $condition
->addWhere('status', '=', 'Added')
->addWhere('group_id.Chapter_Contact_Group.Use_Case', '=', 'chapter-team')
->execute();

$teamGroupContact = $teamGroupContacts->first();
error_log("teamGroupContact: " . print_r($teamGroupContact, TRUE));


if (!$teamGroupContact) {
// @todo we should handle it in a better way.
Expand All @@ -763,8 +764,13 @@ public static function aclCollectionCamp($entity, &$clauses, $userId, $condition
->addWhere('id', '=', $groupId)
->execute();


$group = $chapterGroups->first();
error_log("group: " . print_r($group, TRUE));

$statesControlled = $group['Chapter_Contact_Group.States_controlled'];
error_log("statesControlled: " . print_r($statesControlled, TRUE));


if (empty($statesControlled)) {
// Handle the case when the group is not controlling any state.
Expand Down
189 changes: 189 additions & 0 deletions wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1742,4 +1742,193 @@ public static function generateInvoiceNumber(string $op, string $objectName, int
}
}

/**
* Send camp outcome acknowledgement email after 5 days.
*/
public static function sendCampOutcomeAckEmailAfter5Days($collectionCamp) {
$campId = $collectionCamp['id'];

// Fetch Event Volunteer
$volunteeringActivities = Activity::get(FALSE)
->addSelect('activity_contact.contact_id')
->addJoin('ActivityContact AS activity_contact', 'LEFT')
->addWhere('activity_type_id:name', '=', 'Volunteering')
->addWhere('Volunteering_Activity.Collection_Camp', '=', $campId)
->addWhere('activity_contact.record_type_id', '=', 3)
->execute();

// Collect volunteer contact IDs
$volunteerContactIds = [];
foreach ($volunteeringActivities as $volunteer) {
$volunteerContactIds[] = $volunteer['activity_contact.contact_id'];
}

// Fetch volunteer emails (if any volunteers found)
$eventVolunteerEmails = [];
if (!empty($volunteerContactIds)) {
$volunteerContacts = Contact::get(FALSE)
->addSelect('email.email')
->addJoin('Email AS email', 'LEFT')
->addWhere('id', 'IN', $volunteerContactIds)
->execute();

foreach ($volunteerContacts as $contact) {
if (!empty($contact['email.email'])) {
$eventVolunteerEmails[] = $contact['email.email'];
}
}
}

// Convert to comma-separated string for CC
$eventVolunteerCC = implode(',', $eventVolunteerEmails);

$collectionCamps = EckEntity::get('Collection_Camp', FALSE)
->addSelect('Collection_Camp_Intent_Details.Location_Area_of_camp', 'Core_Contribution_Details.Number_of_unique_contributors', 'Camp_Outcome.Rate_the_camp', 'Camp_Outcome.Total_Fundraised_form_Activity', 'Collection_Camp_Intent_Details.Start_Date', 'title', 'Collection_Camp_Intent_Details.End_Date')
->addWhere('id', '=', $campId)
->execute()->single();

$contribution = Contribution::get(FALSE)
->addSelect('total_amount')
->addWhere('contribution_status_id:name', '=', 'Completed')
->addWhere('Contribution_Details.Source', '=', $campId)
->execute();

$totalAmount = 0;

foreach ($contribution as $c) {
$totalAmount += $c['total_amount'];
}

$collectionSourceVehicleDispatche = EckEntity::get('Collection_Source_Vehicle_Dispatch', FALSE)
->addSelect('Acknowledgement_For_Logistics.No_of_bags_received_at_PU_Office')
->addWhere('Camp_Vehicle_Dispatch.Collection_Camp', '=', $campId)
->execute();

$materialGeneratedList = [];
foreach ($collectionSourceVehicleDispatche as $dispatch) {
$materialGeneratedList[] = $dispatch['Acknowledgement_For_Logistics.No_of_bags_received_at_PU_Office'];
}

$materialGeneratedHtml = '';
if (!empty($collectionSourceVehicleDispatche)) {
// Outer bullet
$materialGeneratedHtml .= "<li>Material generated:<br>";
// Inner numbered list with inline style for emails
$materialGeneratedHtml .= "<ol style='margin:0; padding-left:20px;'>";
foreach ($collectionSourceVehicleDispatche as $dispatch) {
$materialGeneratedHtml .= "<li>" . $dispatch['Acknowledgement_For_Logistics.No_of_bags_received_at_PU_Office'] . "</li>";
}
$materialGeneratedHtml .= "</ol></li>";
}

$uniqueContributors = $collectionCamps['Core_Contribution_Details.Number_of_unique_contributors'];

$campRating = $collectionCamps['Camp_Outcome.Rate_the_camp'];
$fundsGenerated = $collectionCamps['Camp_Outcome.Total_Fundraised_form_Activity'];
$collectionCampTitle = $collectionCamps['title'];

$campAddress = $collectionCamps['Collection_Camp_Intent_Details.Location_Area_of_camp'];

$campStartDate = $collectionCamps['Collection_Camp_Intent_Details.Start_Date'];
$campEndDate = $collectionCamps['Collection_Camp_Intent_Details.End_Date'];

$campCompletionDate = $collectionCamp['Camp_Outcome.Camp_Status_Completion_Date'];
$campOrganiserId = $collectionCamp['Collection_Camp_Core_Details.Contact_Id'];

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

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

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

$mailParams = [
'subject' => $attendeeName . ' thankyou for organizing the camp! A quick snapshot.',
'from' => self::getFromAddress(),
'toEmail' => $attendeeEmail,
'replyTo' => self::getFromAddress(),
'html' => self::getCampOutcomeAckEmailAfter5Days($attendeeName, $campAddress, $campStartDate, $totalAmount, $materialGeneratedHtml, $uniqueContributors, $campRating, $fundsGenerated, $campId, $campEndDate, $campOrganiserId),
'cc' => $eventVolunteerCC,
];

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

if ($emailSendResult) {
\Civi::log()->info("Camp status email sent for collection camp: $campId");
try {
EckEntity::update('Collection_Camp', FALSE)
->addValue('Camp_Outcome.Five_Day_Email_Sent', 1)
->addWhere('id', '=', $campId)
->execute();

}
catch (\CiviCRM_API4_Exception $ex) {
\Civi::log()->debug("Exception while creating update the email sent " . $ex->getMessage());
}
try {
$results = Activity::create(FALSE)
->addValue('subject', $collectionCampTitle)
->addValue('activity_type_id:name', 'Camp summary email')
->addValue('status_id:name', 'Authorized')
->addValue('activity_date_time', date('Y-m-d H:i:s'))
->addValue('source_contact_id', $campOrganiserId)
->addValue('target_contact_id', $campOrganiserId)
->addValue('Collection_Camp_Data.Collection_Camp_ID', $campId)
->execute();

}
catch (\CiviCRM_API4_Exception $ex) {
\Civi::log()->debug("Exception while creating Camp summary email activity: " . $ex->getMessage());
}

}

}
Comment on lines +1748 to +1892

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 | 🟠 Major

sendCampOutcomeAckEmailAfter5Days does too much and has several issues.

This single method fetches volunteers, fetches camp details, sums contributions, builds dispatch HTML, fetches organiser contact, composes and sends the email, updates a flag, and creates an activity. Consider extracting helper methods for readability and testability.

Concrete issues:

  1. Unused variable (Line 1835): $campCompletionDate is assigned but never read.
  2. Unused variable (Line 1875): $results from Activity::create is never read.
  3. Double iteration (Lines 1807-1822): $collectionSourceVehicleDispatche is iterated once to build $materialGeneratedList (which is itself never used), then iterated again to build $materialGeneratedHtml. Remove the first loop.
  4. No top-level try/catch: If Contact::get on Line 1838 throws or returns no result, the method will throw an unhandled exception before the email flag is set.
  5. Inconsistent indentation (Lines 1813-1822): Mixed tabs and spaces.
Proposed fix for the unused code and double iteration
-    $materialGeneratedList = [];
-    foreach ($collectionSourceVehicleDispatche as $dispatch) {
-        $materialGeneratedList[] = $dispatch['Acknowledgement_For_Logistics.No_of_bags_received_at_PU_Office'];
-    }
-
     $materialGeneratedHtml = '';
-	    if (!empty($collectionSourceVehicleDispatche)) {
+    if (!empty($collectionSourceVehicleDispatche)) {
-      $campCompletionDate = $collectionCamp['Camp_Outcome.Camp_Status_Completion_Date'];
-        $results = Activity::create(FALSE)
+        Activity::create(FALSE)
🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 1835-1835: Avoid unused local variables such as '$campCompletionDate'. (undefined)

(UnusedLocalVariable)


[warning] 1875-1875: Avoid unused local variables such as '$results'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In `@wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php` around
lines 1748 - 1892, sendCampOutcomeAckEmailAfter5Days is doing too much and
contains unused vars, double iteration, and missing top-level error handling;
remove the unused $campCompletionDate assignment and drop capturing $results
from Activity::create (call it without assigning), remove the first loop that
builds $materialGeneratedList (only keep the loop that builds
$materialGeneratedHtml), wrap the block that fetches organiser contact,
composes/sends the email, updates 'Camp_Outcome.Five_Day_Email_Sent' and creates
the Activity in a single try/catch to handle Contact::get failures and other
exceptions (log errors and avoid throwing), and normalize indentation in the
method (replace mixed tabs/spaces) to improve readability; refer to
sendCampOutcomeAckEmailAfter5Days, $campCompletionDate, $materialGeneratedList,
$materialGeneratedHtml, Activity::create, Contact::get, EckEntity::update when
making these changes.


/**
*
*/
public static function getCampOutcomeAckEmailAfter5Days($attendeeName, $campAddress, $campStartDate, $totalAmount, $materialGeneratedHtml, $uniqueContributors, $campRating, $fundsGenerated, $campId, $campEndDate, $campOrganiserId) {
$homeUrl = \CRM_Utils_System::baseCMSURL();
$campVolunteerFeedback = $homeUrl . 'volunteer-camp-feedback/#?Collection_Source_Feedback.Collection_Camp_Code=' . $campId . '&Collection_Source_Feedback.Collection_Camp_Address=' . urlencode($campAddress) . '&Collection_Source_Feedback.Filled_By=' . $campOrganiserId;
// Conditionally include funds raised
$fundsGeneratedHtml = '';
if (!empty($fundsGenerated)) {
$fundsGeneratedHtml = "<li>Funds raised through activities: $fundsGenerated</li>";
}

$formattedCampStartDate = date('d-m-Y', strtotime($campStartDate));
$formattedCampEndDate = date('d-m-Y', strtotime($campEndDate));

// Conditional date text
if ($formattedCampStartDate === $formattedCampEndDate) {
$campDateText = "on <strong>$formattedCampStartDate</strong>";
} else {
$campDateText = "from <strong>$formattedCampStartDate</strong> to <strong>$formattedCampEndDate</strong>";
}

$html = "
<p>Dear $attendeeName,</p>
<p>Thank you for organising the recent collection drive at <strong>$campAddress</strong> $campDateText! Your effort brought people together and added strength to this movement of mindful giving.</p>
<p>Here’s a quick snapshot of the camp:</p>
<ul>
$materialGeneratedHtml
<li>Footfall: $uniqueContributors</li>
$fundsGeneratedHtml
</ul>
<p>If you haven’t filled the feedback form yet, you can share your thoughts here: <a href='$campVolunteerFeedback'>Feedback Form</a></p>
<p>We would also love to hear about any highlights, challenges, or ideas you’d like us to know. Your reflections will help us make future drives even more impactful.</p>
<p>Looking forward to many more such collaborations ahead!</p>
<p>Warm regards,<br>Team Goonj</p>
";

return $html;
}
Comment on lines +1897 to +1932

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 | 🟡 Minor

Unused parameters $totalAmount and $campRating in getCampOutcomeAckEmailAfter5Days.

These parameters are accepted but never used in the HTML body. Either incorporate them into the email template or remove them from the signature (and the call site on Line 1856).

🧰 Tools
🪛 PHPMD (2.15.0)

[warning] 1897-1897: Avoid unused parameters such as '$totalAmount'. (undefined)

(UnusedFormalParameter)


[warning] 1897-1897: Avoid unused parameters such as '$campRating'. (undefined)

(UnusedFormalParameter)

🤖 Prompt for AI Agents
In `@wp-content/civi-extensions/goonjcustom/Civi/CollectionCampService.php` around
lines 1897 - 1932, The function getCampOutcomeAckEmailAfter5Days currently
accepts unused parameters $totalAmount and $campRating; remove these two
parameters from the method signature and from every invocation of
getCampOutcomeAckEmailAfter5Days (update the call site(s) that pass $totalAmount
and $campRating), and adjust any associated docblocks/tests to reflect the new
signature; alternatively, if you prefer to keep them, add $totalAmount and
$campRating into the generated $html (e.g., include as list items or summary
lines) and ensure variable names $totalAmount and $campRating are correctly
interpolated in the template.


}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@
*
* @throws \CRM_Core_Exception
*/
function _civicrm_api3_goonjcustom_action_process_queue_spec(&$spec) {
$spec['max_seconds'] = [
'title' => 'Max Seconds',
'description' => 'Maximum number of seconds to process queue items.',
'type' => CRM_Utils_Type::T_INT,
'api.default' => 60,
];
}

function civicrm_api3_goonjcustom_action_process_queue($params) {
$returnValues = CRM_Goonjcustom_Engine::processQueue(60);
$maxSeconds = isset($params['max_seconds']) ? (int) $params['max_seconds'] : 60;
$returnValues = CRM_Goonjcustom_Engine::processQueue($maxSeconds);
return civicrm_api3_create_success($returnValues, $params, 'GoonjcustomQueue', 'Process');
}
Loading