Toon ontbrekende nodes van een taxonomy_term view in de andere taal in tweetalige website

English flagDrupal 9

Als de modules views en taxonomy zijn ingeschakeld is in views altijd het pagina overzicht taxonomy_term aanwezig. Het is altijd het mogelijk om de taal aan te geven zodat alleen de pagina’s (nodes) worden getoond die bij die taal horen. Maar wat als er een dominante taal is waarin ‘alle’ pagina’s zijn aangemaakt en een secundaire taal waarin slechts een deel van die pagina’s is vertaald. En je wilt dat als de view wordt weergegeven in de secundaire taal de ontbrekende pagina’s ook worden getoond, maar dan in de dominante taal? Daarin voorziet Drupal / views niet.

De instellings pagina van taxonomy_term

Als voorbeeld nemen we een website met Engels als dominante taal en Nederlands als secundaire taal. Hieronder staat een tabelletje van de node id’s met de taalcodes van de verschillende pagina’s van de term ‘Test’.

node id taalcode
15 en
15 nl
22 en
23 en
37 en
37 nl

Standaard zal de view in de Engelse taal vier resultaten laten zien en in het Nederlands slechts twee.
Een extra complicatie is dat een node in een tweetalige website vier verschillende ‘taalaanduidingen’ kan hebben: en, nl, und en zxx ofwel Engels, Nederlands, niet gespecificeerd (not specified) en niet van toepassing (not applicable). En met die twee extra ‘taalaanduidingen’ moet ook rekening worden gehouden bij de taalinstelling van views.

Na het aanklikken van ‘Content:Vertalingstaal’ (midden links) kunnen we de talen kiezen:

De momenteel geselecteerde taal/talen

Hier kan de taal instelling worden aangepast met o.a. ‘Niet gespecificeerd’ en/of ‘Niet van toepassing’.

We hebben een functie geschreven met de volgende specificaties:

  • in ieder van beide talen worden alle pagina’s getoond, waar mogelijk alleen in de op dat moment gevraagde taal;
  • in het programma is het mogelijk om één taal uit te sluiten, bijvoorbeeld als alle pagina’s altijd in die taal aanwezig zijn of het niet relevant is om de pagina’s in de andere taal weer te geven;
  • het werkt alleen als er precies twee talen gedefinieerd zijn;
  • er wordt gecontroleerd of de view alleen en exact de oorspronkelijke velden bevat voor de selectie.

De code is:

<?php
 
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\query;
use Drupal\views\Plugin\views\query\QueryPluginBase;
 
 
/**
 * Implements hook_views_query_alter().
 *
 * Wijzigt de taxonomy_term view.
 * Werkt alleen in een tweetalige website.
 * Voegt de nodes van de andere taal toe aan de view als deze een andere node-id (= nid) hebben.
 *
 * @param \Drupal\views\ViewExecutable $view
 * @param \Drupal\views\Plugin\views\query\QueryPluginBase $query
 */
function MY_MODULE_views_query_alter(ViewExecutable $view, QueryPluginBase $query){
  // Test of dit een taxonomy_term view is en het pagina 1 is (het pagina overzicht).
  if ($view->id() == 'taxonomy_term' && $view->current_display == 'page_1') {
    // Test het aantal gedefinieerde talen.
    $languages = \Drupal::languageManager()->getLanguages();
    if (count($languages) != 2) {
      return;
    }
    $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
    // Als de view NIET gewijzigd moet worden voor één specifieke taal, verwijder dan de volgende drie regels.
    if ($language == 'en') {
      return;
    }
 
    // Stap 1.
    // Verzamel de query info van de huidige view.
    // Controleer of de view de originele is want de view kan aangepast zijn door:
    // - een beheerder;
    // - drupal kan de implementatie in een toekomstige versie wijzigen;
    // - een andere hook_..._alter kan de query aanpassen.
    $values = $errors = [];
    $in_operator = 'IN';
    $in_reverse = [
      'IN' => 'NOT IN',
      'NOT IN' => 'IN',
    ];
    $expected_conditions = [
      // fieldname => expected operator.
      'node_field_data.langcode' => 'in',
      'taxonomy_index.tid' => 'formula',
      'taxonomy_index.status' => '=',
    ];
    // Doorloop het 'where' deel van de query en sla relevante gegevens op.
    foreach ($query->where as $condition_group) {
      foreach ($condition_group['conditions'] as $condition) {
        // De $condition['field'] kan diverse 'words' bevatten.
        $parts = explode(' ', $condition['field']);
        $fieldname = $parts[0];
        // Wordt deze veldnaam verwacht?
        if (isset($expected_conditions[$fieldname])) {
          $parts = explode('.', $fieldname);
          // Voor het gemak gebruiken we de korte tabel namen.
          $values[$parts[1]] = $condition['value'];
          if (strcasecmp($expected_conditions[$fieldname], $condition['operator']) != 0) {
            $error = TRUE;
            if ($expected_conditions[$fieldname] == 'in') {
              if (strtolower($condition['operator']) == 'not in') {
                $expected_conditions[$fieldname] = $condition['operator'];
                $error = FALSE;
              }
              $in_operator = strtoupper($expected_conditions[$fieldname]);
            }
            if ($error) {
              $errors[] = t('Unexpected operator %cond in field: %fnme. Expected: %expect',
                ['%cond' => $condition['operator'], '%fnme' => $fieldname,
                  '%expect' => $expected_conditions[$fieldname]]);
            }
          }
        }
        else {
          $errors[] = t('Unexpected condition field: %fnme'. ['%fnme' => $fieldname]);
        }
      }
    }
 
    // Stap 2.
    // Alle waarden worden omgevormd voor de queries.
    // Alle verwachtte conditions gevonden?
    // Status kan afwezig zijn / maak het query deel voor status.
    if (!isset($values['status'])) {
      $values['status'] = '';
    }
    else {
      $values['status'] = " AND taxonomy_index.status = '" . $values['status'] . "'";
    }
    if (count($expected_conditions) != count($values)) {
      $errors[] = t('One or more conditions are missing.');
    }
    // Als fouten gevonden, log deze en quit. Better safe then sorry.
    if (!empty($errors)) {
      $message = t('Error in function my_module_alters_views_query_alter() to alter view taxonomy_term.');
      foreach ($errors as $err) {
        $message .= '<br />- ' . $err;
      }
      \Drupal::logger('my_module_alters')->critical($message);
      return;
    }
 
    // Lees de huidige taxonomy tid waarde voor de queries.
    $values['tid'] = array_shift($values['tid']);
    // Vervang taal placeholder met huidige taal.
    foreach ($values['langcode'] as &$lc) {
      $lc = str_replace('***LANGUAGE_language_interface***', $language, $lc);
    }
 
    // Stap 3.
    // Bepaal de extra nids van de andere taal.
    // Zoek alle nids van de huidige node taal.
    $db = \Drupal::database();
    $current_lang = $db->query("SELECT node_field_data.nid AS nid
      FROM {node_field_data} node_field_data
      LEFT JOIN {taxonomy_index} taxonomy_index ON node_field_data.nid = taxonomy_index.nid
      WHERE taxonomy_index.tid = :tid AND node_field_data.langcode "
        . $in_operator . " (:langcodes[])" . $values['status'],
      [':tid' => $values['tid'], ':langcodes[]' => $values['langcode']])
      ->fetchCol();
    // Doe dit ook voor de andere taal.
    $missing_lang = $db->query("SELECT node_field_data.nid AS nid
      FROM {node_field_data} node_field_data
      LEFT JOIN {taxonomy_index} taxonomy_index ON node_field_data.nid = taxonomy_index.nid
      WHERE taxonomy_index.tid = :tid AND node_field_data.langcode "
        . $in_reverse[$in_operator] . " (:langcodes[])" . $values['status'],
      [':tid' => $values['tid'], ':langcodes[]' => $values['langcode']])
      ->fetchCol();
    // Bepaal de missende nids.
    $missing = array_diff($missing_lang, $current_lang);
    // Geen nids toe te voegen? Klaar.
    if (empty($missing)) {
      return;
    }
 
    // Stap 4.
    // Maak de where clause van de query.
    $query->where = [];
    // Maak een OR group met een query van de oorspronkelijke inhoud.
    $group_id = $query->setWhereGroup('OR');
    $snippet = "taxonomy_index.tid = :tid AND node_field_data.langcode "
      . $in_operator . " (:langcodes[])" . $values['status'];
    $query->addWhereExpression($group_id, $snippet,
      [':tid' => $values['tid'], ':langcodes[]' => $values['langcode']]);
    // Vul het tweede OR deel met de extra query.
    $snippet = "taxonomy_index.tid = :tid AND node_field_data.nid IN (:missing[]) AND node_field_data.langcode "
      . $in_reverse[$in_operator] . " (:langcodes[])";
    $query->addWhereExpression($group_id, $snippet, [':tid' => $values['tid'],
      ':missing[]' => $missing, ':langcodes[]' => $values['langcode']]);
  }
}