diff --git a/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/AfformFieldPrefillService.php b/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/AfformFieldPrefillService.php new file mode 100644 index 000000000..499721131 --- /dev/null +++ b/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/AfformFieldPrefillService.php @@ -0,0 +1,80 @@ +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; + } + + 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; + } + + $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."); + } + } + }); + + $angular->add($changeSet); + Civi::log()->debug('[AfformPrefill] ChangeSet injected successfully.'); +} + + public static function getFieldDefinition($afField) { + $existingFieldDefn = trim(pq($afField)->attr('defn') ?: ''); + + if ($existingFieldDefn && $existingFieldDefn[0] !== '{') { + return NULL; + } + + return $existingFieldDefn ? \CRM_Utils_JS::getRawProps($existingFieldDefn) : []; + } +} \ No newline at end of file diff --git a/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Form/InstitutionDroppingCenterLinks.php b/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Form/InstitutionDroppingCenterLinks.php index 7b2630fed..b332c17e7 100644 --- a/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Form/InstitutionDroppingCenterLinks.php +++ b/wp-content/civi-extensions/goonjcustom/CRM/Goonjcustom/Form/InstitutionDroppingCenterLinks.php @@ -95,36 +95,47 @@ public function setDefaultValues() { * * @return void */ - public function generateLinks($contactId): void { - $organization = $this->_organization; - $contact = $this->_contact; - - $nameOfInstitution = $organization['display_name']; - $address = $organization['address_primary.street_address']; - $pocEmail = $contact['email.email']; - $pocContactNumber = $contact['phone.phone']; - - // Generate dropping center links. - $links = [ - [ - 'label' => 'Vehicle Dispatch', - 'url' => self::createUrl( - '/institution-dropping-center-vehicle-dispatch', - "Camp_Vehicle_Dispatch.Institution_Dropping_Center={$this->_institutionDroppingCenterId}" . - "&Eck_Collection_Camp1={$this->_institutionDroppingCenterId}" . - "&Camp_Vehicle_Dispatch.To_which_PU_Center_material_is_being_sent={$this->_processingCenterId}" . - "&Camp_Vehicle_Dispatch.Filled_by={$contactId}" . - "&Camp_Institution_Data.Name_of_the_institution={$nameOfInstitution}" . - "&Camp_Institution_Data.Address=" . urlencode($address) . - "&Camp_Institution_Data.Email={$pocEmail}" . - "&Camp_Institution_Data.Contact_Number={$pocContactNumber}", - $contactId - ), - ], - ]; - - $this->assign('institutionDroppingCenterLinks', $links); - } + public function generateLinks($contactId): void { + $organization = $this->_organization; + $contact = $this->_contact; + + $nameOfInstitution = $organization['display_name']; + $address = $organization['address_primary.street_address']; + $pocEmail = $contact['email.email']; + $pocContactNumber = $contact['phone.phone']; + + // Create dispatch data array + $dispatchData = [ + 'Camp_Vehicle_Dispatch.Institution_Dropping_Center' => $this->_institutionDroppingCenterId, + 'Camp_Vehicle_Dispatch.Filled_by' => $contactId, + 'Camp_Vehicle_Dispatch.To_which_PU_Center_material_is_being_sent' => $this->_processingCenterId, + 'Camp_Vehicle_Dispatch.Goonj_Office_Name' => '', // optional if needed + 'Eck_Collection_Camp1' => $this->_institutionDroppingCenterId, + 'id' => $this->_institutionDroppingCenterId, + 'Camp_Institution_Data.Name_of_the_institution' => $nameOfInstitution, + 'Camp_Institution_Data.Address' => $address, + 'Camp_Institution_Data.Email' => $pocEmail, + 'Camp_Institution_Data.Contact_Number' => $pocContactNumber, + ]; + + // Encrypt token + $json = json_encode($dispatchData); + $encryptedToken = \CRM_Utils_Crypt::encrypt($json); + $tokenParam = 'token=' . urlencode($encryptedToken); + + // Authenticated token + $authUrl = self::createUrl('/institution-dropping-center-vehicle-dispatch', $tokenParam, $contactId); + + // Assign link + $links = [ + [ + 'label' => 'Vehicle Dispatch', + 'url' => $authUrl, + ], + ]; + + $this->assign('institutionDroppingCenterLinks', $links); +} /** * Generate an authenticated URL for viewing this form. @@ -153,7 +164,7 @@ public static function createUrl($path, $params, $contactId): string { $params = "{$params}&_authx={$bearerToken}&_authxSes=1"; // $url = \CRM_Utils_System::url($path, $params, TRUE, NULL, FALSE, TRUE); $config = CRM_Core_Config::singleton(); - $url = $config->userFrameworkBaseURL . $path . '#?' . $params; + $url = $config->userFrameworkBaseURL . $path . '?' . $params; return $url; } diff --git a/wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php b/wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php index d5eee758f..891eee601 100644 --- a/wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php +++ b/wp-content/civi-extensions/goonjcustom/Civi/DroppingCenterService.php @@ -543,12 +543,30 @@ 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, + 'Dropping_Centre.Where_do_you_wish_to_open_dropping_center_Address_' => 'Hardcoded test address for now' + ]; + + // Step 2: Convert to JSON and encrypt + $json = json_encode($dispatchData); + $token = \CRM_Utils_Crypt::encrypt($json); + + // Step 3: Generate encrypted URL + $vehicleDispatchFormUrl = $homeUrl . '/vehicle-dispatch/?token=' . urlencode($token); - $emailHtml = " - - + // Step 4: Compose HTML email + $emailHtml = " + +

Dear {$initiatorName},

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.

@@ -556,19 +574,21 @@ public static function sendDispatchEmail($email, $initiatorName, $droppingCenter This will help us to verify and acknowledge the materials as soon as they arrive.

We truly appreciate your cooperation and continued commitment to our cause.

Warm Regards,
Team Goonj..

- - - "; - $from = HelperService::getDefaultFromEmail(); - $mailParams = [ - 'subject' => 'Kindly fill the Dispatch Form for Material Pickup', - 'from' => $from, - 'toEmail' => $email, - 'html' => $emailHtml, - ]; + + + "; + + // 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); +} /** * @@ -659,13 +679,13 @@ public static function droppingCenterTabset($tabsetName, &$tabs, $context) { 'template' => 'CRM/Goonjcustom/Tabs/DroppingCenter/DonationBox.tpl', 'permissions' => ['goonj_chapter_admin', 'urbanops', 'urban_ops_admin', 'urban_ops_and_accounts_chapter_team'], ], - 'outcome' => [ - 'title' => ts('Outcome'), - 'module' => 'afsearchDroppingCenterOutcome', - 'directive' => 'afsearch-dropping-center-outcome', - 'template' => 'CRM/Goonjcustom/Tabs/DroppingCenter/Outcome.tpl', - 'permissions' => ['goonj_chapter_admin', 'urbanops', 'urban_ops_admin', 'urban_ops_and_accounts_chapter_team'], - ], + // 'outcome' => [ + // 'title' => ts('Outcome'), + // 'module' => 'afsearchDroppingCenterOutcome', + // 'directive' => 'afsearch-dropping-center-outcome', + // 'template' => 'CRM/Goonjcustom/Tabs/DroppingCenter/Outcome.tpl', + // 'permissions' => ['goonj_chapter_admin', 'urbanops', 'urban_ops_admin', 'urban_ops_and_accounts_chapter_team'], + // ], 'feedback' => [ 'title' => ts('Feedback'), 'module' => 'afsearchFeedback', diff --git a/wp-content/civi-extensions/goonjcustom/Civi/InstitutionDroppingCenterService.php b/wp-content/civi-extensions/goonjcustom/Civi/InstitutionDroppingCenterService.php index 493ba5e1c..96fe7fefd 100644 --- a/wp-content/civi-extensions/goonjcustom/Civi/InstitutionDroppingCenterService.php +++ b/wp-content/civi-extensions/goonjcustom/Civi/InstitutionDroppingCenterService.php @@ -670,24 +670,34 @@ public static function processDispatchEmail(string $op, string $objectName, $obj /** * */ - public static function sendDispatchEmail($nameOfInstitution, $address, $phone, $email, $initiatorName, $droppingCenterId, $contactId, $goonjOffice, $goonjOfficeName) { - $homeUrl = \CRM_Utils_System::baseCMSURL(); + public static function sendDispatchEmail($nameOfInstitution, $address, $phone, $email, $initiatorName, $droppingCenterId, $contactId, $goonjOffice, $goonjOfficeName) { + $homeUrl = \CRM_Utils_System::baseCMSURL(); + + // Step 1: Gather data + $dispatchData = [ + 'Camp_Vehicle_Dispatch.Institution_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, + 'Camp_Institution_Data.Name_of_the_institution' => $nameOfInstitution, + 'Camp_Institution_Data.Address' => $address, + 'Camp_Institution_Data.Email' => $email, + 'Camp_Institution_Data.Contact_Number' => $phone, + ]; + + // Step 2: Convert to JSON and encrypt + $json = json_encode($dispatchData); + $token = \CRM_Utils_Crypt::encrypt($json); - $baseUrl = '/institution-dropping-center-vehicle-dispatch/'; - - $vehicleDispatchFormUrl = $homeUrl . $baseUrl . '#?Camp_Vehicle_Dispatch.Institution_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 - . '&Camp_Institution_Data.Name_of_the_institution=' . $nameOfInstitution - . "&Camp_Institution_Data.Address=" . urlencode($address) - . "&Camp_Institution_Data.Email=" . $email - . "&Camp_Institution_Data.Contact_Number=" . $phone; - - $emailHtml = " - - + // Step 3: Generate encrypted URL + $vehicleDispatchFormUrl = $homeUrl . '/institution-dropping-center-vehicle-dispatch/?token=' . urlencode($token); + + // Step 4: Compose HTML email + $emailHtml = " + +

Dear {$initiatorName},

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.

@@ -695,19 +705,21 @@ public static function sendDispatchEmail($nameOfInstitution, $address, $phone, $ This will help us to verify and acknowledge the materials as soon as they arrive.

We truly appreciate your cooperation and continued commitment to our cause.

Warm Regards,
Team Goonj..

- - - "; - $from = HelperService::getDefaultFromEmail(); - $mailParams = [ - 'subject' => 'Kindly fill the Dispatch Form for Material Pickup', - 'from' => $from, - 'toEmail' => $email, - 'html' => $emailHtml, - ]; + + + "; + + // 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); +} /** * diff --git a/wp-content/civi-extensions/goonjcustom/goonjcustom.php b/wp-content/civi-extensions/goonjcustom/goonjcustom.php index 1a620fe63..9058088aa 100644 --- a/wp-content/civi-extensions/goonjcustom/goonjcustom.php +++ b/wp-content/civi-extensions/goonjcustom/goonjcustom.php @@ -33,6 +33,51 @@ function goonjcustom_civicrm_config(&$config): void { \Civi::dispatcher()->addSubscriber(new CRM_Goonjcustom_Token_IndividualGoonjActivities()); } +/** + * Implements hook_civicrm_alterAngular(). + */ +function goonjcustom_civicrm_alterAngular($angular) { + Civi::log()->debug('[AfformPrefill] alterAngular hook called'); + + $token = \CRM_Utils_Request::retrieve('token', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET'); + Civi::log()->debug('[AfformPrefill] Token: ' . ($token ? $token : '(none)')); + if (!$token) { + Civi::log()->debug('[AfformPrefill] No token, skipping prefill.'); + return; + } + + try { + $decrypted = \CRM_Utils_Crypt::decrypt($token); + Civi::log()->debug('[AfformPrefill] Decrypted: ' . $decrypted); + $data = json_decode($decrypted, true); + Civi::log()->debug('[AfformPrefill] Parsed array: ' . print_r($data, 1)); + } catch (\Exception $e) { + Civi::log()->error('[AfformPrefill] Token decryption failed: ' . $e->getMessage()); + return; + } + + if (!is_array($data)) { + Civi::log()->error('[AfformPrefill] Decrypted data is not an array.'); + return; + } + + $prefillData = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT); + \Civi::resources()->addScriptFile('goonjcustom', 'js/prefill.js', 10, 'html-header'); + + + \Civi::log()->debug('[AfformPrefill] JavaScript prefill payload: ' . $prefillData); + + \Civi::resources()->addScript(" + (function() { + if (window.CRM === undefined) window.CRM = {}; + window.CRM.afformPrefillData = $prefillData; + console.log('[AfformPrefill] Prefill data available on window.CRM.afformPrefillData:', window.CRM.afformPrefillData); + })(); + ", 10, 'html-header'); +} + + + /** * Implements hook_civicrm_install(). * @@ -42,6 +87,11 @@ function goonjcustom_civicrm_install(): void { _goonjcustom_civix_civicrm_install(); } + + + + + /** * Implements hook_civicrm_enable(). * diff --git a/wp-content/civi-extensions/goonjcustom/js/prefill.js b/wp-content/civi-extensions/goonjcustom/js/prefill.js new file mode 100644 index 000000000..549fc06c3 --- /dev/null +++ b/wp-content/civi-extensions/goonjcustom/js/prefill.js @@ -0,0 +1,163 @@ +(function () { + console.log('[AfformPrefill] Script with logs and EntityRef + dependent autofill loaded'); + + function setNested(obj, path, value) { + const parts = path.split('.'); + let current = obj; + for (let i = 0; i < parts.length - 1; i++) { + if (!current[parts[i]]) current[parts[i]] = {}; + current = current[parts[i]]; + } + current[parts[parts.length - 1]] = value; + } + + function flattenObject(obj, prefix = '', result = {}) { + Object.keys(obj).forEach(function (key) { + const value = obj[key]; + const newKey = prefix ? `${prefix}.${key}` : key; + if (value && typeof value === 'object' && !Array.isArray(value)) { + flattenObject(value, newKey, result); + } else { + result[newKey] = value; + } + }); + return result; + } + + function waitForAngularAndPrefill(retries = 100) { + if (!(window.angular && window.angular.module)) { + if (retries <= 0) return console.error('[AfformPrefill] Angular not found'); + return setTimeout(() => waitForAngularAndPrefill(retries - 1), 200); + } + pollForFormAndPrefill(); + } + + function pollForFormAndPrefill(retries = 100) { + const formElem = document.querySelector('[af-form], [af-fieldset], .crm-afform-form, .af-container'); + if (!formElem) { + if (retries <= 0) return console.error('[AfformPrefill] Form not found'); + return setTimeout(() => pollForFormAndPrefill(retries - 1), 200); + } + + const scope = angular.element(formElem).scope(); + if (!scope) { + if (retries <= 0) return console.error('[AfformPrefill] Scope not found'); + return setTimeout(() => pollForFormAndPrefill(retries - 1), 200); + } + + if (scope._afformPrefilled) { + console.log('[AfformPrefill] Already prefilled ✅'); + return; + } + + const data = window.CRM?.afformPrefillData; + if (!data) return console.warn('[AfformPrefill] No afformPrefillData found ❗'); + + const $timeout = angular.element(formElem).injector().get('$timeout'); + const $rootScope = angular.element(formElem).injector().get('$rootScope'); + + $timeout(function () { + const flattened = flattenObject(data); + const scopePaths = ['data', 'model', 'formData', 'entity']; + + console.log('[AfformPrefill] Flattened keys:', Object.keys(flattened)); + + // Set values in Angular scope + scopePaths.forEach(path => { + if (!scope[path]) scope[path] = {}; + Object.entries(flattened).forEach(([key, value]) => { + setNested(scope[path], key, value); + console.log(`[AfformPrefill] Set scope.${path}.${key} =`, value); + }); + }); + + scopePaths.forEach(path => { + Object.keys(scope[path]).forEach(entity => { + if (entity === 'Camp_Institution_Data' && typeof scope[path][entity] === 'object' && !Array.isArray(scope[path][entity])) { + console.log(`[AfformPrefill] Wrapping ${path}.${entity} as array for join compatibility`); + scope[path][entity] = [scope[path][entity]]; + } + }); + }); + + // Autofill standard input fields + const inputs = formElem.querySelectorAll('input, select, textarea'); + console.log('[AfformPrefill] Total fields to check:', inputs.length); + inputs.forEach((field) => { + const fieldName = field.name || field.getAttribute('name'); + const ngModel = field.getAttribute('ng-model'); + const id = field.id || ''; + const fieldKey = ngModel?.replace(/^.*?entity\./, '') || fieldName || id; + + const matchedKey = Object.keys(flattened).find(k => + k === fieldKey || k.endsWith('.' + fieldKey) || (fieldKey && k.includes(fieldKey)) + ); + + if (!matchedKey) { + console.warn(`[AfformPrefill] No match found for field:`, { fieldName, ngModel, id }); + return; + } + + const value = flattened[matchedKey]; + console.log(`[AfformPrefill] ✅ Matched ${matchedKey} →`, value); + + try { + if (field.tagName.toLowerCase() === 'select') { + field.value = value; + } else if (field.type === 'checkbox') { + field.checked = !!value; + } else if (field.type === 'radio') { + if (field.value == value) field.checked = true; + } else { + field.value = value; + } + + ['input', 'change', 'blur'].forEach(evt => { + const e = new Event(evt, { bubbles: true }); + field.dispatchEvent(e); + angular.element(field).triggerHandler(evt); + }); + + console.log(`[AfformPrefill] Value set on DOM field [${fieldKey}]`); + + } catch (err) { + console.error(`[AfformPrefill] Error setting field [${fieldKey}]`, err.message); + } + }); + + document.querySelectorAll('.ng-isolate-scope').forEach(function (afFieldElem) { + const fieldName = afFieldElem.getAttribute('name'); + const flatData = flattenObject(window.CRM.afformPrefillData); + if (!flatData[fieldName]) { + console.warn(`[EntityRef] No data to prefill for ${fieldName}`); + return; + } + + const value = flatData[fieldName]; + const isolateScope = angular.element(afFieldElem).isolateScope(); + const model = isolateScope?.getSetSelect; + + if (typeof model === 'function') { + console.log(`[EntityRef] Setting ${fieldName} →`, value); + model(value); + + if (typeof isolateScope?.$ctrl?.onSelectEntity === 'function') { + isolateScope.$ctrl.onSelectEntity(); + console.log(`[EntityRef] onSelectEntity() triggered for ${fieldName}`); + } + + isolateScope.$applyAsync?.(); + } else { + console.warn(`[EntityRef] getSetSelect not found for ${fieldName}`); + } + }); + + scope._afformPrefilled = true; + try { scope.$apply(); $rootScope.$apply(); } catch (_) {} + + console.log('[AfformPrefill]Autofill completed!'); + }, 1000); + } + + waitForAngularAndPrefill(); +})();