Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

use Civi\Core\Event\GenericHookEvent;
use Civi\Core\Service\AutoService;

/**
* Provides ability to prefill afform fields from encrypted token.
*
* @service goonjcustom.afform_prefill
*/
class CRM_Goonjcustom_AfformFieldPrefillService extends AutoService {

public static function preprocess(GenericHookEvent $event) {
Civi::log()->debug('[AfformPrefill] Preprocess started.');

$request = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET');

if (!$request) {
Civi::log()->warning('[AfformPrefill] No token found in URL.');
return;
}

// Decrypt token
try {
$decrypted = CRM_Utils_Crypt::decrypt($request);
$data = json_decode($decrypted, true);
Civi::log()->debug('[AfformPrefill] Token decrypted: ' . print_r($data, true));
} catch (\Exception $e) {
Civi::log()->error('[AfformPrefill] Token decryption failed: ' . $e->getMessage());
return;
}

if (!is_array($data)) {
Civi::log()->error('[AfformPrefill] Decrypted token is not valid JSON.');
return;
}

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 security validations for the token

The token is accepted without any validation of its length, format, or structure. This could be exploited with malformed tokens.

Add validation for the token and decrypted data:

   $request = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET');

   if (!$request) {
     Civi::log()->warning('[AfformPrefill] No token found in URL.');
     return;
   }
+
+  // Validate token length to prevent DoS
+  if (strlen($request) > 10000) {
+    Civi::log()->error('[AfformPrefill] Token exceeds maximum allowed length.');
+    return;
+  }

   // Decrypt token
   try {
     $decrypted = CRM_Utils_Crypt::decrypt($request);
     $data = json_decode($decrypted, true);
+    
+    // Validate expected structure
+    $expectedKeys = ['Camp_Vehicle_Dispatch.Dropping_Center', 'Camp_Vehicle_Dispatch.Filled_by'];
+    foreach ($expectedKeys as $key) {
+      if (!array_key_exists($key, $data)) {
+        throw new \Exception("Missing required key: {$key}");
+      }
+    }
+    
     Civi::log()->debug('[AfformPrefill] Token decrypted: ' . print_r($data, true));
📝 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
$request = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET');
if (!$request) {
Civi::log()->warning('[AfformPrefill] No token found in URL.');
return;
}
// Decrypt token
try {
$decrypted = CRM_Utils_Crypt::decrypt($request);
$data = json_decode($decrypted, true);
Civi::log()->debug('[AfformPrefill] Token decrypted: ' . print_r($data, true));
} catch (\Exception $e) {
Civi::log()->error('[AfformPrefill] Token decryption failed: ' . $e->getMessage());
return;
}
if (!is_array($data)) {
Civi::log()->error('[AfformPrefill] Decrypted token is not valid JSON.');
return;
}
$request = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET');
if (!$request) {
Civi::log()->warning('[AfformPrefill] No token found in URL.');
return;
}
// Validate token length to prevent DoS
if (strlen($request) > 10000) {
Civi::log()->error('[AfformPrefill] Token exceeds maximum allowed length.');
return;
}
// Decrypt token
try {
$decrypted = CRM_Utils_Crypt::decrypt($request);
$data = json_decode($decrypted, true);
// Validate expected structure
$expectedKeys = [
'Camp_Vehicle_Dispatch.Dropping_Center',
'Camp_Vehicle_Dispatch.Filled_by',
];
foreach ($expectedKeys as $key) {
if (!array_key_exists($key, $data)) {
throw new \Exception("Missing required key: {$key}");
}
}
Civi::log()->debug('[AfformPrefill] Token decrypted: ' . print_r($data, true));
} catch (\Exception $e) {
Civi::log()->error('[AfformPrefill] Token decryption failed: ' . $e->getMessage());
return;
}
if (!is_array($data)) {
Civi::log()->error('[AfformPrefill] Decrypted token is not valid JSON.');
return;
}
🤖 Prompt for AI Agents
In
wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/AfformFieldPrefillService.php
around lines 16 to 36, the token retrieved from the request lacks validation for
length, format, or structure, which poses a security risk. Add checks to
validate the token string before decryption, ensuring it meets expected length
and format criteria (e.g., alphanumeric, specific length). After decryption,
validate that the decoded data is an array and contains the required keys or
structure expected for further processing. Return early and log warnings or
errors if validations fail.


$changeSet = \Civi\Angular\ChangeSet::create('injectPrefillValues')
->alterHtml(';\\.aff\\.html$;', function($doc, $path) use ($data) {
Civi::log()->debug("[AfformPrefill] Altering HTML template: {$path}");

foreach (pq('af-field', $doc) as $afField) {
$fieldName = $afField->getAttribute('name');

if (!$fieldName) {
continue;
}

if (array_key_exists($fieldName, $data)) {
$fieldDefn = self::getFieldDefinition($afField);

if (!$fieldDefn) {
Civi::log()->warning("[AfformPrefill] No field definition for: {$fieldName}");
continue;
}

// Inject default value and make read-only
$fieldDefn['afform_default'] = $data[$fieldName];
$fieldDefn['readonly'] = true;

pq($afField)->attr('defn', htmlspecialchars(\CRM_Utils_JS::writeObject($fieldDefn), ENT_COMPAT));
Civi::log()->info("[AfformPrefill] Prefilled field '{$fieldName}' with value: " . json_encode($data[$fieldName]));
} else {
Civi::log()->debug("[AfformPrefill] Field '{$fieldName}' not in token data, skipping.");
}
}
});

$event->angular->add($changeSet);
Civi::log()->debug('[AfformPrefill] ChangeSet injected successfully.');

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

Reduce logging verbosity and avoid logging sensitive data

The service has excessive debug logging that could impact performance and logs decrypted data (line 27) which may contain sensitive information.

Remove verbose debug logs and sensitive data logging:

-    Civi::log()->debug('[AfformPrefill] Preprocess started.');
-
     $request = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET');

     if (!$request) {
-      Civi::log()->warning('[AfformPrefill] No token found in URL.');
+      Civi::log()->debug('[AfformPrefill] No token found in URL.');
       return;
     }

     // Decrypt token
     try {
       $decrypted = CRM_Utils_Crypt::decrypt($request);
       $data = json_decode($decrypted, true);
-      Civi::log()->debug('[AfformPrefill] Token decrypted: ' . print_r($data, true));
+      Civi::log()->info('[AfformPrefill] Token decrypted successfully');

Also remove the debug logs on lines 40, 64, and 70 as they don't add value in production.

📝 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
Civi::log()->debug('[AfformPrefill] Preprocess started.');
$request = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET');
if (!$request) {
Civi::log()->warning('[AfformPrefill] No token found in URL.');
return;
}
// Decrypt token
try {
$decrypted = CRM_Utils_Crypt::decrypt($request);
$data = json_decode($decrypted, true);
Civi::log()->debug('[AfformPrefill] Token decrypted: ' . print_r($data, true));
} catch (\Exception $e) {
Civi::log()->error('[AfformPrefill] Token decryption failed: ' . $e->getMessage());
return;
}
if (!is_array($data)) {
Civi::log()->error('[AfformPrefill] Decrypted token is not valid JSON.');
return;
}
$changeSet = \Civi\Angular\ChangeSet::create('injectPrefillValues')
->alterHtml(';\\.aff\\.html$;', function($doc, $path) use ($data) {
Civi::log()->debug("[AfformPrefill] Altering HTML template: {$path}");
foreach (pq('af-field', $doc) as $afField) {
$fieldName = $afField->getAttribute('name');
if (!$fieldName) {
continue;
}
if (array_key_exists($fieldName, $data)) {
$fieldDefn = self::getFieldDefinition($afField);
if (!$fieldDefn) {
Civi::log()->warning("[AfformPrefill] No field definition for: {$fieldName}");
continue;
}
// Inject default value and make read-only
$fieldDefn['afform_default'] = $data[$fieldName];
$fieldDefn['readonly'] = true;
pq($afField)->attr('defn', htmlspecialchars(\CRM_Utils_JS::writeObject($fieldDefn), ENT_COMPAT));
Civi::log()->info("[AfformPrefill] Prefilled field '{$fieldName}' with value: " . json_encode($data[$fieldName]));
} else {
Civi::log()->debug("[AfformPrefill] Field '{$fieldName}' not in token data, skipping.");
}
}
});
$event->angular->add($changeSet);
Civi::log()->debug('[AfformPrefill] ChangeSet injected successfully.');
// (Removed overly-verbose startup debug)
$request = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET');
if (!$request) {
Civi::log()->debug('[AfformPrefill] No token found in URL.');
return;
}
// Decrypt token
try {
$decrypted = CRM_Utils_Crypt::decrypt($request);
$data = json_decode($decrypted, true);
Civi::log()->info('[AfformPrefill] Token decrypted successfully');
} catch (\Exception $e) {
Civi::log()->error('[AfformPrefill] Token decryption failed: ' . $e->getMessage());
return;
}
if (!is_array($data)) {
Civi::log()->error('[AfformPrefill] Decrypted token is not valid JSON.');
return;
}
$changeSet = \Civi\Angular\ChangeSet::create('injectPrefillValues')
->alterHtml(';\.aff\.html$;', function($doc, $path) use ($data) {
Civi::log()->debug("[AfformPrefill] Altering HTML template: {$path}");
foreach (pq('af-field', $doc) as $afField) {
$fieldName = $afField->getAttribute('name');
if (!$fieldName) {
continue;
}
if (array_key_exists($fieldName, $data)) {
$fieldDefn = self::getFieldDefinition($afField);
if (!$fieldDefn) {
Civi::log()->warning("[AfformPrefill] No field definition for: {$fieldName}");
continue;
}
// Inject default value and make read-only
$fieldDefn['afform_default'] = $data[$fieldName];
$fieldDefn['readonly'] = true;
pq($afField)
->attr('defn', htmlspecialchars(\CRM_Utils_JS::writeObject($fieldDefn), ENT_COMPAT));
Civi::log()->info("[AfformPrefill] Prefilled field '{$fieldName}' with value: " . json_encode($data[$fieldName]));
} else {
Civi::log()->debug("[AfformPrefill] Field '{$fieldName}' not in token data, skipping.");
}
}
});
$event->angular->add($changeSet);
Civi::log()->debug('[AfformPrefill] ChangeSet injected successfully.');
🤖 Prompt for AI Agents
In
wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/AfformFieldPrefillService.php
from lines 14 to 70, reduce logging verbosity by removing debug logs that output
decrypted token data and other non-essential debug messages. Specifically,
remove the debug log on line 27 that prints decrypted token contents, and also
remove debug logs on lines 40, 64, and 70. Keep only warning, error, and info
logs that are necessary for monitoring and troubleshooting, avoiding logging any
sensitive data.

}

public static function getFieldDefinition($afField) {
$existingFieldDefn = trim(pq($afField)->attr('defn') ?: '');

if ($existingFieldDefn && $existingFieldDefn[0] !== '{') {
return NULL;
}

return $existingFieldDefn ? \CRM_Utils_JS::getRawProps($existingFieldDefn) : [];
}
Comment on lines +71 to +79

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 error handling for field definition parsing

The CRM_Utils_JS::getRawProps() call could throw exceptions that aren't handled, potentially breaking the entire prefill process.

-  return $existingFieldDefn ? \CRM_Utils_JS::getRawProps($existingFieldDefn) : [];
+  if (!$existingFieldDefn) {
+    return [];
+  }
+  
+  try {
+    return \CRM_Utils_JS::getRawProps($existingFieldDefn);
+  } catch (\Exception $e) {
+    Civi::log()->error('[AfformPrefill] Failed to parse field definition: ' . $e->getMessage());
+    return [];
+  }
📝 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 getFieldDefinition($afField) {
$existingFieldDefn = trim(pq($afField)->attr('defn') ?: '');
if ($existingFieldDefn && $existingFieldDefn[0] !== '{') {
return NULL;
}
return $existingFieldDefn ? \CRM_Utils_JS::getRawProps($existingFieldDefn) : [];
}
public static function getFieldDefinition($afField) {
$existingFieldDefn = trim(pq($afField)->attr('defn') ?: '');
if ($existingFieldDefn && $existingFieldDefn[0] !== '{') {
return NULL;
}
if (!$existingFieldDefn) {
return [];
}
try {
return \CRM_Utils_JS::getRawProps($existingFieldDefn);
} catch (\Exception $e) {
Civi::log()->error('[AfformPrefill] Failed to parse field definition: ' . $e->getMessage());
return [];
}
}
🤖 Prompt for AI Agents
In
wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/AfformFieldPrefillService.php
around lines 71 to 79, the call to CRM_Utils_JS::getRawProps() may throw
exceptions that are currently unhandled. Wrap this call in a try-catch block to
catch any exceptions, and handle them gracefully by returning an empty array or
null to prevent the prefill process from breaking.

}
Original file line number Diff line number Diff line change
Expand Up @@ -543,32 +543,51 @@ public static function processDispatchEmail(string $op, string $objectName, $obj
*
*/
public static function sendDispatchEmail($email, $initiatorName, $droppingCenterId, $contactId, $goonjOffice, $goonjOfficeName) {
$homeUrl = \CRM_Utils_System::baseCMSURL();
$vehicleDispatchFormUrl = $homeUrl . '/vehicle-dispatch/#?Camp_Vehicle_Dispatch.Dropping_Center=' . $droppingCenterId . '&Camp_Vehicle_Dispatch.Filled_by=' . $contactId . '&Camp_Vehicle_Dispatch.To_which_PU_Center_material_is_being_sent=' . $goonjOffice . '&Camp_Vehicle_Dispatch.Goonj_Office_Name=' . $goonjOfficeName . '&Eck_Collection_Camp1=' . $droppingCenterId;
$homeUrl = \CRM_Utils_System::baseCMSURL();

// Step 1: Gather data
$dispatchData = [
'Camp_Vehicle_Dispatch.Dropping_Center' => $droppingCenterId,
'Camp_Vehicle_Dispatch.Filled_by' => $contactId,
'Camp_Vehicle_Dispatch.To_which_PU_Center_material_is_being_sent' => $goonjOffice,
'Camp_Vehicle_Dispatch.Goonj_Office_Name' => $goonjOfficeName,
'Eck_Collection_Camp1' => $droppingCenterId,
'id' => $droppingCenterId,
];
Comment on lines +549 to +557

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

Validate input parameters before encryption

The method assumes all parameters are valid without verification. This could lead to issues if any required data is missing or malformed.

Add validation before constructing the dispatch data:

+  // Validate required parameters
+  if (empty($droppingCenterId) || empty($contactId) || empty($goonjOffice)) {
+    \Civi::log()->error('Missing required parameters for dispatch email');
+    return;
+  }
+
   // Step 1: Gather data
   $dispatchData = [
     'Camp_Vehicle_Dispatch.Dropping_Center' => $droppingCenterId,
🤖 Prompt for AI Agents
In wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php around
lines 549 to 556, the code constructs the $dispatchData array without validating
input parameters, which risks errors if data is missing or invalid. Add
validation checks for each input parameter (like $droppingCenterId, $contactId,
$goonjOffice, and $goonjOfficeName) before using them to ensure they are set and
correctly formatted. If any required parameter is missing or invalid, handle the
error appropriately before proceeding to construct $dispatchData.


// Step 2: Convert to JSON and encrypt
$json = json_encode($dispatchData);
$token = \CRM_Utils_Crypt::encrypt($json);
Comment on lines +548 to +561

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 encryption failures

The encryption operation could fail, but there's no error handling. If CRM_Utils_Crypt::encrypt() throws an exception or returns false, the email will be sent with a malformed URL.

Wrap the encryption in a try-catch block:

-  // Step 2: Convert to JSON and encrypt
-  $json = json_encode($dispatchData);
-  $token = \CRM_Utils_Crypt::encrypt($json);
+  // Step 2: Convert to JSON and encrypt
+  $json = json_encode($dispatchData);
+  try {
+    $token = \CRM_Utils_Crypt::encrypt($json);
+  } catch (\Exception $e) {
+    \Civi::log()->error('Failed to encrypt dispatch data: ' . $e->getMessage());
+    throw new \CRM_Core_Exception('Failed to generate dispatch email');
+  }
🤖 Prompt for AI Agents
In wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php around
lines 548 to 560, the encryption call to CRM_Utils_Crypt::encrypt() lacks error
handling, risking malformed URLs if encryption fails. Wrap the encryption call
in a try-catch block to catch exceptions and check if the result is false;
handle these cases by logging the error or aborting the operation to prevent
sending invalid data.


$emailHtml = "
<html>
<body>
// Step 3: Generate encrypted URL
$vehicleDispatchFormUrl = $homeUrl . '/vehicle-dispatch/?token=' . urlencode($token);

// Step 4: Compose HTML email
$emailHtml = "
<html>
<body>
<p>Dear {$initiatorName},</p>
<p>Thank you so much for your invaluable efforts in running the Goonj Dropping Center.
Your dedication plays a crucial role in our work, and we deeply appreciate your continued support.</p>
<p>Please fill out this Dispatch Form – <a href='{$vehicleDispatchFormUrl}'>Link</a> once the vehicle is loaded and ready to head to Goonj’s processing center.
This will help us to verify and acknowledge the materials as soon as they arrive.</p>
<p>We truly appreciate your cooperation and continued commitment to our cause.</p>
<p>Warm Regards,<br>Team Goonj..</p>
</body>
</html>
";
$from = HelperService::getDefaultFromEmail();
$mailParams = [
'subject' => 'Kindly fill the Dispatch Form for Material Pickup',
'from' => $from,
'toEmail' => $email,
'html' => $emailHtml,
];
</body>
</html>
";
Comment on lines +567 to +579

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

Extract email HTML to a template file

Inline HTML violates the separation of concerns principle and makes the code harder to maintain. Consider moving this to a Smarty template for better maintainability and localization support.

Create a new template file templates/CRM/Goonjcustom/Email/DispatchForm.tpl and use CiviCRM's template system:

-  // Step 4: Compose HTML email
-  $emailHtml = "
-  <html>
-  <body>
-    <p>Dear {$initiatorName},</p>
-    <p>Thank you so much for your invaluable efforts in running the Goonj Dropping Center. 
-    Your dedication plays a crucial role in our work, and we deeply appreciate your continued support.</p>
-    <p>Please fill out this Dispatch Form – <a href='{$vehicleDispatchFormUrl}'>Link</a> once the vehicle is loaded and ready to head to Goonj's processing center. 
-    This will help us to verify and acknowledge the materials as soon as they arrive.</p>
-    <p>We truly appreciate your cooperation and continued commitment to our cause.</p>
-    <p>Warm Regards,<br>Team Goonj..</p>
-  </body>
-  </html>
-  ";
+  // Step 4: Compose HTML email
+  $tplParams = [
+    'initiatorName' => $initiatorName,
+    'vehicleDispatchFormUrl' => $vehicleDispatchFormUrl,
+  ];
+  $emailHtml = CRM_Core_Smarty::singleton()->fetchWith('CRM/Goonjcustom/Email/DispatchForm.tpl', $tplParams);
📝 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
$emailHtml = "
<html>
<body>
<p>Dear {$initiatorName},</p>
<p>Thank you so much for your invaluable efforts in running the Goonj Dropping Center.
Your dedication plays a crucial role in our work, and we deeply appreciate your continued support.</p>
<p>Please fill out this Dispatch Form – <a href='{$vehicleDispatchFormUrl}'>Link</a> once the vehicle is loaded and ready to head to Goonj’s processing center.
This will help us to verify and acknowledge the materials as soon as they arrive.</p>
<p>We truly appreciate your cooperation and continued commitment to our cause.</p>
<p>Warm Regards,<br>Team Goonj..</p>
</body>
</html>
";
$from = HelperService::getDefaultFromEmail();
$mailParams = [
'subject' => 'Kindly fill the Dispatch Form for Material Pickup',
'from' => $from,
'toEmail' => $email,
'html' => $emailHtml,
];
</body>
</html>
";
// Step 4: Compose HTML email
$tplParams = [
'initiatorName' => $initiatorName,
'vehicleDispatchFormUrl'=> $vehicleDispatchFormUrl,
];
$emailHtml = CRM_Core_Smarty::singleton()
->fetchWith('CRM/Goonjcustom/Email/DispatchForm.tpl', $tplParams);
🤖 Prompt for AI Agents
In wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php around
lines 566 to 578, the email HTML is hardcoded inline, which reduces
maintainability and localization support. To fix this, extract the HTML content
into a new Smarty template file named
templates/CRM/Goonjcustom/Email/DispatchForm.tpl. Then, update the PHP code to
load and render this template using CiviCRM's template system, passing necessary
variables like initiatorName and vehicleDispatchFormUrl to the template for
dynamic content rendering.


// Step 5: Send email
$from = HelperService::getDefaultFromEmail();
$mailParams = [
'subject' => 'Kindly fill the Dispatch Form for Material Pickup',
'from' => $from,
'toEmail' => $email,
'html' => $emailHtml,
];

\CRM_Utils_Mail::send($mailParams);
}
\CRM_Utils_Mail::send($mailParams);
}

/**
*
Expand Down