Drupal

A Drupal module to use the Sirene entreprise.data.gouv.fr API in a form

Published on 13 July 2021
Screenshot of the api.adresse.gouv.fr website
Here is the code for a module currently being contributed on drupal.org. Before proceeding, we are releasing the code and the process.

The project

On the occasion of the overhaul of CGT's unionization form, we wanted to offer users a simplified experience. The idea here is to take care of them by saving them time when entering their information. In fact, during the process, the form asks them to enter information regarding their employer’s identity.

If they know the SIREN or SIRET, users can fill in the appropriate field – Information regarding the identity and address of their employer will be automatically suggested and filled in by querying the API from the French Open Public Data Platform.

If they know their employer’s corporate name, users fill in the corresponding field – Information regarding their employer's address will be automatically suggested and entered by querying the API from the French Open Public Data Platform.

The module objective

The module is available here:
https://www.drupal.org/project/api_sirene_open_data_france -

The code is available here:
https://git.drupalcode.org/project/api_sirene_open_data_france/commit/4d7abc1 -

The module can be used to simplify any registration process requiring information on companies based in France.

We will soon be using it for registering professionals on a job board website. Only users representing an employer are authorized to post a job offer. There are many possible use cases. The connector to the France Open Data platform API is very straightforward to use and response times are very fast.

The sirene API

https://entreprise.data.gouv.fr/api_doc/sirene
As part of the Public Data Service, a large number of datasets are open. The SIRENE database can be queried freely and at no cost!

This service queries the Sirene API developed by Étalab.
This website and its API are open-source: you can download the code on GitHub.
Reference SIRENE data is available on Data.gouv.fr.

The demo

Auto-completion using the company’s SIREN or SIRET:

Auto-completion using the company’s corporate name:

Creating the route

We start by adding the Ajax callback route in the module’s .routing.yml file:

bluedrop_new.entreprise_data_autocomplete:
  path: '/client-action/entreprise-data-autocomplete'
  defaults:
    _controller: '\Drupal\bluedrop_entreprise\Controller\EntrepriseAjaxController::entreprise_data_autocomplete'
    _title: 'Search'
    _format: json
  requirements:
    _access: 'TRUE'

The manager file

We create a Manager file (to be extended); for now, it contains only the methods needed to search for addresses and auto-complete fields.
EntrepriseDataApiManager.php

error($msg);
     return;
    }
    $this->client = \Drupal::httpClient();
  }

  /**
   * Do CURL request with authorization.
   *
   * @param string $resource
   *   A request action of api.
   * @param string $method
   *   A method of curl request.
   * @param Array $inputs
   *   A data of curl request.
   *
   * @return array
   *   An associate array with respond data.
   */
  private function executeCurl($resource, $method, $inputs, $api) {
    if (!function_exists('curl_init')) {
     $msg = 'Entreprise Data API requires CURL module';
     \Drupal::logger('bluedropfr_syndicalisation_new')->error($msg);
     return NULL;
    }
    if(is_array($resource)){
     $api_url =  $api;
     foreach ($resource as $res){
       $api_url.="/".$res;
     }
    }else{
     $api_url = $api . "/" . $resource;
    }
    
    $options = [
     'headers' => [
       'Content-Type' => 'application/json'
     ],
    ];

    if (!empty($inputs)) {
     
     if($method == 'GET'){
       $api_url.= '?' . self::arrayKeyfirst($inputs) . '=' . array_shift($inputs);
       foreach($inputs as $param => $value){
         $api_url.= '&' . $param . '=' . $value;
       }
     }else{
       //POST request send data in array index form_params.
       //$options['body'] = $inputs;
     }
    }

    try {
     $clientRequest = $this->client->request($method, $api_url, $options);
     $body = $clientRequest->getBody();
    } catch (RequestException $e) {
     \Drupal::logger('bluedropfr_syndicalisation_new')->error('Curl error: @error', ['@error' => $e->getMessage()]);
    }

    return Json::decode($body);
  }

  /**
   * Get Request of API.
   *
   * @param string $resource
   *   A request action.
   * @param string $input
   *   A data of curl request.
   *
   * @return array
   *   A respond data.
   */
  public function curlGet($resource, $inputs, $api) {
    return $this->executeCurl($resource, "GET", $inputs, $api);
  }

  /**
   * Post Request of API.
   *
   * @param string $resource
   *   A request action.
   * @param string $inputs
   *   A data of curl request.
   *
   * @return array
   *   A respond data.
   */
  public function curlPost($resource, $inputs, $api) {
    return $this->executeCurl($resource, "POST", $inputs, $api);
  }

  public function searchBySiret($siret) {
    $resources = [
     "etablissements",
     $siret,
    ];
    return $this->curlGet($resources, [], self::API_URLv3);
  }

  public function searchBySirene($sirene) {
    $resources = [
     "unites_legales",
     $sirene,
    ];
    return $this->curlGet($resources, [], self::API_URLv3);
  }
 
  public function searchByName($name) {
    $resources = [
     "full_text",
     $name,
    ];
    return $this->curlGet($resources, [], self::API_URLv1);
  }
 
  /**
   * Function to return first element of the array, compatability with PHP 5, note that array_key_first is only available for PHP > 7.3.
   *
   * @param array $array
   *   Associative array.
   *
   * @return string
   *   The first key data.
   */
  public static function arrayKeyfirst($array){
    if (!function_exists('array_key_first')) {
       foreach($array as $key => $unused) {
         return $key;
       }
       return NULL;
    }else{
       return array_key_first($array);
    }
  }

}

The controller

The controller acts as a callback for the ajax call -
EntrepriseAjaxController.php

request->get('siret_sirene');
    if(strlen($search_string) > 9) $search = "siret";
    
    if($search_string == "") {
     $search_string = \Drupal::request()->request->get('query_name');
     $search = "full_text";
    }
    //$type = "housenumber"; // can be housenumber, street, locality or municipality.

    $edataApi = new EntrepriseDataApiManager();
    switch($search){
     case 'sirene':
       $return = $edataApi->searchBySirene($search_string);
       break;
     case 'siret':
       $return = $edataApi->searchBySiret($search_string);
       break;
     case 'full_text':
       $return = $edataApi->searchByName($search_string);
       $return = $return['etablissement'];
       break;
    }
    //echo "etsttt 
";     //print_r($return);     //exit;     return new JsonResponse(json_encode($return), 200, [], true);   }   }

The javascript component for user interactions

A javascript file will manage interactions with users – This file is still specific to the project in question. A future commit will soon make it evolve.
What remains is to add dynamic auto-completion to the form fields. By creating a configuration page in the back office, the user will be able to match their site’s form fields with the API data. Using "Drupal behaviors", we will pass this information to the javascript component.

//--> search by Siret or Siren
    $('#edit-siret-siren').autocomplete({
        source : function(request, response){ // the two arguments represent the data needed for the plugin
        $.ajax({
             url : Drupal.url('cgt-syndicalisation/entreprise-data-autocomplete'), // we call the JSON script
             dataType : 'json', // we explicitly specify the data type is JSON
             type: "POST",
             data : {
                 // variable sent with the request to the server
                 siret_sirene : $('#edit-siret-siren').val(), // the string entered in the search field
             },
             success : function(data){
                 // 'data' is the variable received from the server with the results
                 response($.map(data, function(obj){
                     var label = "";
                     var value = "";
                     var nom = "";
                     var numero_voie = "";
                     var type_voie = "";
                     var libelle_voie = "";
                     var code_postal = "";
                     var libelle_commune = "";
                     if(typeof obj.unite_legale === 'object' && obj.unite_legale != null) {
                         //console.log("in obj unite_legale");
                         label = obj.unite_legale.denomination + " (" + obj.code_postal + ")";
                         nom = obj.unite_legale.denomination;
                         value = obj.siret;
                         numero_voie = obj.numero_voie;
                         type_voie = obj.type_voie;
                         libelle_voie = obj.libelle_voie;
                         code_postal = obj.code_postal;
                         libelle_commune = obj.libelle_commune;
                     }else{
                         //console.log("else, not in obj unite_legale");
                         label = obj.denomination + " (" + obj.etablissement_siege.code_postal + ")";
                         value = obj.siren;
                         nom = obj.denomination;
                         numero_voie = obj.etablissement_siege.numero_voie;
                         type_voie = obj.etablissement_siege.type_voie;
                         libelle_voie = obj.etablissement_siege.libelle_voie;
                         code_postal = obj.etablissement_siege.code_postal;
                         libelle_commune = obj.etablissement_siege.libelle_commune;
                     }
                     // we return this suggestion format with these data
                     return {
                         'label': label,
                         'value': value,
                         'nom': nom,
                         'numero_voie': numero_voie,
                         'type_voie': type_voie,
                         'libelle_voie': libelle_voie,
                         'code_postal': code_postal,
                         'libelle_commune': libelle_commune,
                     }; 
                 }));
             }
        });
    }
    });

    $('#edit-siret-siren').on( "autocompleteselect", function( event, ui ) {
        var nom = ui.item.nom;
        var numero_voie = ui.item.numero_voie;
        var type_voie = ui.item.type_voie;
        var libelle_voie = ui.item.libelle_voie;
        var code_postal = ui.item.code_postal;
        var libelle_commune = ui.item.libelle_commune;
        $('#edit-entreprise').val(nom);
        $('#edit-adresse-rue-entreprise').val(numero_voie + " " + type_voie + " " + libelle_voie);
        $('#edit-code-postal-entreprise').val(code_postal);
        $('#edit-ville-entreprise').val(libelle_commune);
    });

    //---> search by full_text: company name
    $('#edit-entreprise').autocomplete({
        source : function(request, response){ // the two arguments represent the data needed for the plugin
        $.ajax({
             url : Drupal.url('cgt-syndicalisation/entreprise-data-autocomplete'), // we call the JSON script
             dataType : 'json', // we explicitly specify the data type is JSON
             type: "POST",
             data : {
                 // variable sent with the request to the server
                 query_name : $('#edit-entreprise').val(), // the string entered in the search field
             },
             success : function(data){
                 // 'data' is the variable received from the server with the results
                 response($.map(data, function(obj){
                     var label = "";
                     var value = "";
                     var type_voie = "";
                     var numero_voie = "";
                     var libelle_voie = "";
                     var code_postal = "";
                     var libelle_commune = "";
                    
                     label = obj.nom_raison_sociale + " (" + obj.code_postal + ")";
                     value = obj.nom_raison_sociale;
                     numero_voie = obj.numero_voie;
                     type_voie = obj.type_voie;
                     libelle_voie = obj.libelle_voie;
                     code_postal = obj.code_postal;
                     libelle_commune = obj.libelle_commune;
                    
                     // we return this suggestion format with these data
                     return {
                         'label': label,
                         'value': value,
                         'type_voie': type_voie,
                         'numero_voie': numero_voie,
                         'libelle_voie': libelle_voie,
                         'code_postal': code_postal,
                         'libelle_commune': libelle_commune,
                     }; 
                 }));
             }
        });
    }
    });

    $('#edit-entreprise').on( "autocompleteselect", function( event, ui ) {
        var type_voie = ui.item.type_voie;
        var numero_voie = ui.item.numero_voie;
        var libelle_voie = ui.item.libelle_voie;
        var code_postal = ui.item.code_postal;
        var libelle_commune = ui.item.libelle_commune;
        $('#edit-adresse-rue-entreprise').val(numero_voie + " " + type_voie + " " + libelle_voie);
        $('#edit-code-postal-entreprise').val(code_postal);
        $('#edit-ville-entreprise').val(libelle_commune);
    });
 });

Try it out!
API sirene Open Data France Drupal Module

Big thanks to @elie for this contribution!!

?>

Read more articles on Drupal