diff --git a/includes/civipostcode_component.inc b/includes/civipostcode_component.inc deleted file mode 100755 index 3e42576..0000000 --- a/includes/civipostcode_component.inc +++ /dev/null @@ -1,55 +0,0 @@ - '', - 'form_key' => NULL, - 'pid' => 0, - 'weight' => 0, - 'value' => '', - 'mandatory' => 0, - 'extra' => array( - 'widget' => 'textfield', - 'attributes' => array(), - 'title_display' => 'before', - ), - ); -} - -function _webform_display_civipostcode($component, $value, $format = 'html') { - return array( - '#title' => $component['name'], - '#weight' => $component['weight'], - '#theme' => 'webform_display_textfield', - '#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'), - '#format' => $format, - '#value' => isset($value[0]) ? $value[0] : '', - '#translatable' => array('title', 'field_prefix', 'field_suffix'), - ); -} - -function _webform_render_civipostcode($component, $value = NULL, $filter = TRUE) { - - $element = array( - '#type' => 'textfield', - '#title' => $filter ? webform_filter_xss($component['name']) : $component['name'], - '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before', - '#required' => $component['required'], - '#weight' => $component['weight'], - '#attributes' => array(), - '#theme_wrappers' => array('webform_element'), - '#description' => $component['extra']['description'], - ); - - if (isset($value)) { - $element['#default_value'] = $value[0]; - } - return $element; -} \ No newline at end of file diff --git a/js/civipostcode_autocomplete.js b/js/civipostcode_autocomplete.js new file mode 100644 index 0000000..7136f0e --- /dev/null +++ b/js/civipostcode_autocomplete.js @@ -0,0 +1,76 @@ +(function (Drupal, $, once) { + Drupal.behaviors.civipostcodeAutocomplete = { + attach: function (context, settings) { + const $postcodeFields = $(once('civipostcodeAutocomplete', '.civipostcode-autocomplete', context)); + + $postcodeFields.each((index, element) => { + const $input = $(element); + const elementId = $input.attr('id'); + const { civicrmSeq, contactSeq } = extractSequences(elementId); + + if (!civicrmSeq || !contactSeq) return; + + $input.on('autocompleteselect', (event, ui) => { + if (!ui.item.id) return; + $.ajax({ + dataType: 'json', + data: {id: ui.item.id}, + url: drupalSettings.civipostcode.apiUrl, + success: function (data) { + populateAddress(data.address, civicrmSeq, contactSeq); + } + }); + + return false; + }); + }); + + /** + * Extract CiviCRM and Contact sequences from the element ID. + * Returns an object: { civicrmSeq, contactSeq } + */ + function extractSequences(id) { + const parts = id.split('-'); + let civicrmSeq = null; + let contactSeq = null; + + parts.forEach((part, index) => { + if (parts[index - 1] === 'civicrm') civicrmSeq = part; + if (parts[index - 1] === 'contact') contactSeq = part; + }); + + return { civicrmSeq, contactSeq }; + } + + /** + * Populate address fields based on CiviCRM and Contact sequence. + */ + function populateAddress(address, civicrmSeq, contactSeq) { + const fieldMap = { + street_address: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-street-address`, + supplemental_address_1: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-supplemental-address-1`, + supplemental_address_2: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-supplemental-address-2`, + supplemental_address_3: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-supplemental-address-3`, + city: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-city`, + postcode: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-postal-code`, + county: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-state-province-id`, + country_id: `civicrm-${civicrmSeq}-contact-${contactSeq}-address-country-id`, + }; + + Object.entries(fieldMap).forEach(([key, selector]) => { + const $field = $(`[id*="${selector}"]`); + // Always set a value — either the returned value or an empty string. + $field.val(address[key] ?? ''); + }); + + // Handle special cases if CiviCRM uses different keys + if (address.town) { + $(`[id*="civicrm-${civicrmSeq}-contact-${contactSeq}-address-city"]`).val(address.town); + } + if (address.state_province_abbreviation) { + $(`[id*="civicrm-${civicrmSeq}-contact-${contactSeq}-address-state-province-id"]`).val(address.state_province_abbreviation); + } + } + } + }; +})(Drupal, jQuery, once); diff --git a/js/civipostcode_component.js b/js/civipostcode_component.js deleted file mode 100644 index 2bd6fbe..0000000 --- a/js/civipostcode_component.js +++ /dev/null @@ -1,75 +0,0 @@ -jQuery(document).ready(function ($) { - var civiPostCodeLookupProvider = Drupal.settings.civiPostCodeLookupProvider; - var civiPostCodeFields = Drupal.settings.civiPostCodeFields; - var sourceUrl = Drupal.settings.baseUrl + "/civicrm/" + civiPostCodeLookupProvider + "/ajax/search?json=1"; - $.each(civiPostCodeFields, function (key, value) { - if ($('[id*="'+value+'"]').length) { // Check postcode element exists on page first - $('[id*="' + value + '"]').autocomplete({ - source: sourceUrl, - minLength: 3, - select: function (event, ui) { - var id = ui.item.id; - var sourceUrl = Drupal.settings.baseUrl + '/civicrm/' + civiPostCodeLookupProvider + '/ajax/get?json=1'; - var postcodeElementId = value; - var result = getCivicrmAndContactSequence(postcodeElementId); - - $.ajax({ - dataType: 'json', - data: {id: id}, - url: sourceUrl, - success: function (data) { - setAddress(data.address, result.civicrmSeq, result.contactSeq); - } - }); - return false; - }, - //optional (if other layers overlap autocomplete list) - open: function (event, ui) { - // show scrollbar - jQuery(".ui-autocomplete").css({ - 'overflow-y': 'auto', - 'max-height': '200px', - 'z-index': '1000', - }); - } - }); - } - }); - - // extract civicrm and contact sequence to form id - function getCivicrmAndContactSequence(str) { - var splittedIdArray = str.split('-'); - var previousValue = ""; - var result = []; - - $.each(splittedIdArray, function (index, value) { - if (previousValue == 'civicrm') { - result['civicrmSeq'] = value; - } - if (previousValue == 'contact') { - result['contactSeq'] = value; - } - previousValue = value; - }); - return result; - } - - // fill address in respective fields - function setAddress(address, civicrmSeq, contactSeq) { - var streetAddressElement = "civicrm-"+civicrmSeq+"-contact-"+contactSeq+"-address-street-address"; - var AddstreetAddressElement = "civicrm-"+civicrmSeq+"-contact-"+contactSeq+"-address-supplemental-address-1"; - var AddstreetAddressElement1 = "civicrm-"+civicrmSeq+"-contact-"+contactSeq+"-address-supplemental-address-2"; - var AddstreetAddressElement2 = "civicrm-"+civicrmSeq+"-contact-"+contactSeq+"-address-supplemental-address-3"; - var cityElement = "civicrm-"+civicrmSeq+"-contact-"+contactSeq+"-address-city"; - var postalCodeElement = "civicrm-"+civicrmSeq+"-contact-"+contactSeq+"-address-postal-code"; - var countyElement = "civicrm-"+civicrmSeq+"-contact-"+contactSeq+"-address-state-province-id"; - - $('[id *="' + streetAddressElement + '"]').val(address.street_address); - $('[id *="' + AddstreetAddressElement + '"]').val(address.supplemental_address_1); - $('[id *="' + AddstreetAddressElement1 + '"]').val(address.supplemental_address_2); - $('[id *="' + AddstreetAddressElement2 + '"]').val(address.supplemental_address_3); - $('[id *="' + cityElement + '"]').val(address.town); - $('[id *="' + postalCodeElement + '"]').val(address.postcode); - $('[id *="' + countyElement + '"]').val(address.state_province_abbreviation); - } -}); diff --git a/src/Controller/PostcodeAutocompleteController.php b/src/Controller/PostcodeAutocompleteController.php new file mode 100644 index 0000000..d83a9ef --- /dev/null +++ b/src/Controller/PostcodeAutocompleteController.php @@ -0,0 +1,76 @@ +httpClient = $http_client; + $this->utils = $utils; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('http_client'), + $container->get('webform_civicrm_postcode.utils') + ); + } + + /** + * Handle autocomplete request. + */ + public function handleAutocomplete(Request $request) { + $search = trim($request->query->get('q', '')); + + if (strlen($search) < 2) { + return new JsonResponse([]); + } + + $lookupProvider = $this->utils->getPostCodeLookupSettings()['provider']; + $lookupUrl = sprintf( + '%s/civicrm/%s/ajax/search?json=1&term=%s', + $request->getSchemeAndHttpHost(), $lookupProvider, urlencode($search) + ); + + try { + $response = $this->httpClient->get($lookupUrl, ['timeout' => 10]); + + if ($response->getStatusCode() !== 200) { + return new JsonResponse([]); + } + + $data = json_decode($response->getBody()->getContents(), TRUE); + if (!is_array($data)) { + return new JsonResponse([]); + } + + return new JsonResponse($data); + } + catch (\Exception $e) { + return new JsonResponse([]); + } + } + +} diff --git a/src/Plugin/WebformElement/Civipostcode.php b/src/Plugin/WebformElement/Civipostcode.php new file mode 100644 index 0000000..8c7a71a --- /dev/null +++ b/src/Plugin/WebformElement/Civipostcode.php @@ -0,0 +1,79 @@ +utils = $container->get('webform_civicrm_postcode.utils'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) { + // Always a textfield + input. + $element['#type'] = 'textfield'; + $element['#input'] = TRUE; + + // Only enable autocomplete if the extension is enabled. + if ($this->utils->isPostcodeLookupExtensionEnabled()) { + $provider = $this->utils->getPostCodeLookupSettings()['provider'] ?? NULL; + if ($provider) { + $host = \Drupal::request()->getSchemeAndHttpHost(); + + // Attach autocomplete processing handlers. + $element['#process'] = [ + ['Drupal\Core\Render\Element\Textfield', 'processAutocomplete'], + ['Drupal\Core\Render\Element\Textfield', 'processAjaxForm'], + ['Drupal\Core\Render\Element\Textfield', 'processPattern'], + ]; + + // Autocomplete route → your controller. + $element['#autocomplete_route_name'] = 'webform_civicrm_postcode.autocomplete'; + $element['#autocomplete_route_parameters'] = []; + + // Attach JS libraries. + $element['#attached']['library'][] = 'core/drupal.autocomplete'; + $element['#attached']['library'][] = 'webform_civicrm_postcode/civipostcode_autocomplete'; + + // Add class for JS behavior. + $element['#attributes']['class'][] = 'civipostcode-autocomplete'; + + // Provide JS settings (for the "get details" call). + $element['#attached']['drupalSettings']['civipostcode'] = [ + 'apiUrl' => "{$host}/civicrm/{$provider}/ajax/get?json=1", + ]; + } + } + + parent::prepare($element, $webform_submission); + } + +} diff --git a/src/Utils.php b/src/Utils.php new file mode 100644 index 0000000..1468ff8 --- /dev/null +++ b/src/Utils.php @@ -0,0 +1,31 @@ +initialize(); + } + + /** + * Check if the postcode extension is enabled. + * + * @return bool + */ + public function isPostcodeLookupExtensionEnabled(): bool { + return \CRM_Extension_System::singleton()->getMapper()->isActiveModule( + 'civicrmpostcodelookup' + ); + } + + /** + * Get postal code lookup settings. + * + * @return array + */ + public function getPostCodeLookupSettings() { + return unserialize(\Civi::settings()->get('api_details')); + } + +} diff --git a/webform_civicrm_postcode.info b/webform_civicrm_postcode.info deleted file mode 100644 index 5ca914a..0000000 --- a/webform_civicrm_postcode.info +++ /dev/null @@ -1,6 +0,0 @@ -name = Webform CiviPostcode Integration -description = Adds postcode lookup functionality (provided by CiviPostcode extension) by creating a webform component. -core = 7.x -version = 7.x-1.0 -package = CiviCRM -dependencies[] = webform_civicrm diff --git a/webform_civicrm_postcode.info.yml b/webform_civicrm_postcode.info.yml new file mode 100644 index 0000000..881d47c --- /dev/null +++ b/webform_civicrm_postcode.info.yml @@ -0,0 +1,9 @@ +name: 'Webform CiviPostcode Integration' +type: module +description: 'Adds postcode lookup functionality (provided by CiviPostcode extension) by creating a webform component.' +core_version_requirement: ^10 || ^11 +package: 'CiviCRM' +dependencies: + - drupal:webform_civicrm +version: '6.0.0' +project: 'webform_civicrm' diff --git a/webform_civicrm_postcode.libraries.yml b/webform_civicrm_postcode.libraries.yml new file mode 100644 index 0000000..876b15b --- /dev/null +++ b/webform_civicrm_postcode.libraries.yml @@ -0,0 +1,8 @@ +civipostcode_autocomplete: + version: 1.x + js: + js/civipostcode_autocomplete.js: {} + dependencies: + - core/jquery + - core/once + - core/drupal diff --git a/webform_civicrm_postcode.module b/webform_civicrm_postcode.module index 372c4dc..4a7cb37 100644 --- a/webform_civicrm_postcode.module +++ b/webform_civicrm_postcode.module @@ -1,83 +1,11 @@ webform_civicrm)) { - // attach javascript needed for postcode lookup when civipostcode extension is installed and enabled - if(isPostcodeLookupExtensionEnabled()) { - global $base_url; - - drupal_add_library('system', 'ui.autocomplete'); - - $civiPostcodeFields = getCivipostcodeFieldIds($form['#node']->webform['components']); - // Assign the postcode lookup provider to form, so that we can call the related function in AJAX - $settingsStr = CRM_Core_BAO_Setting::getItem('CiviCRM Postcode Lookup', 'api_details'); - $settingsArray = unserialize($settingsStr); - - $form['#attached']['js'][] = drupal_get_path('module', 'webform_civicrm_postcode') . '/js/civipostcode_component.js'; - $form['#attached']['js'][] = array( - // Pass PHP variables to Drupal.settings. - 'data' => array( - 'baseUrl' => $base_url, - 'civiPostCodeLookupProvider' => $settingsArray['provider'], - 'civiPostCodeFields' => $civiPostcodeFields, - ), - 'type' => 'setting', - ); - } - } -} /** - * Implements hook_webform_component_info(). - * - * @return array -*/ -function webform_civicrm_postcode_webform_component_info() { - $components = array(); - // when civipostcode extension is installed and enabled, civipostcode webform component will be made available - if(isPostcodeLookupExtensionEnabled()) { - $components['civipostcode'] = array( - 'label' => t('Civi Postcode'), - 'file' => 'includes/civipostcode_component.inc', - ); - } - - return $components; -} - -/** - * check if civipostcodelookup extension is enabled. - * - * @return boolean - */ -function isPostcodeLookupExtensionEnabled() { - civicrm_initialize(true); - $isPostcodeLookupEnabled = CRM_Core_DAO::getFieldValue( - 'CRM_Core_DAO_Extension', - 'uk.co.vedaconsulting.module.civicrmpostcodelookup', - 'is_active', - 'full_name' - ); - - return $isPostcodeLookupEnabled; -} - -/** - * Get list of fields which are of type 'civipostcode' - * - * @param array $components - * @return array $civiPostcodeFields + * Implements hook_webform_element_info_alter(). */ -function getCivipostcodeFieldIds($components) { - $civiPostcodeFields = array(); - foreach($components as $component) { - if($component['type'] == 'civipostcode') { - $civiPostcodeFields[] = str_replace("_", "-", $component['form_key']); - } +function webform_civicrm_postcode_webform_element_info_alter(&$types) { + $utils = \Drupal::service('webform_civicrm_postcode.utils'); + if (!$utils->isPostcodeLookupExtensionEnabled()) { + unset($types['civipostcode']); } - - return $civiPostcodeFields; } diff --git a/webform_civicrm_postcode.routing.yml b/webform_civicrm_postcode.routing.yml new file mode 100644 index 0000000..607a587 --- /dev/null +++ b/webform_civicrm_postcode.routing.yml @@ -0,0 +1,6 @@ +webform_civicrm_postcode.autocomplete: + path: '/civicrm-postcode/autocomplete' + defaults: + _controller: '\Drupal\webform_civicrm_postcode\Controller\PostcodeAutocompleteController::handleAutocomplete' + requirements: + _permission: 'access postcode lookup' diff --git a/webform_civicrm_postcode.services.yml b/webform_civicrm_postcode.services.yml new file mode 100644 index 0000000..b5938a1 --- /dev/null +++ b/webform_civicrm_postcode.services.yml @@ -0,0 +1,3 @@ +services: + webform_civicrm_postcode.utils: + class: Drupal\webform_civicrm_postcode\Utils