From 375aa55bc2cce792cb2ce76fb09bd9e4acc41efd Mon Sep 17 00:00:00 2001 From: Nicolas Tostin <tostinni@13041.no-reply.drupal.org> Date: Fri, 4 Apr 2025 17:37:00 -0400 Subject: [PATCH] Issue #2960620: Support for translations of menu items --- src/Controller/MenuLinksController.php | 333 ++++++++++++++----------- src/Form/MenuSyncForm.php | 10 +- 2 files changed, 197 insertions(+), 146 deletions(-) diff --git a/src/Controller/MenuLinksController.php b/src/Controller/MenuLinksController.php index 354b4c1..1b0fd77 100755 --- a/src/Controller/MenuLinksController.php +++ b/src/Controller/MenuLinksController.php @@ -12,6 +12,11 @@ */ class MenuLinksController extends ControllerBase { + /** + * Structure sync config. + * + * @var \Drupal\Core\Config\Config + */ private $config; /** @@ -36,6 +41,8 @@ private function getEditableConfig() { */ public function exportMenuLinks(array $form = NULL, FormStateInterface $form_state = NULL) { StructureSyncHelper::logMessage('Menu links export started'); + $languages_enabled = \Drupal::languageManager()->getLanguages(); + $languagesPriority = []; if (is_object($form_state) && $form_state->hasValue('export_menu_list')) { $menu_list = $form_state->getValue('export_menu_list'); @@ -60,24 +67,39 @@ public function exportMenuLinks(array $form = NULL, FormStateInterface $form_sta $customMenuLinks = []; foreach ($menuLinks as $menuLink) { - $customMenuLinks[] = [ - 'menu_name' => $menuLink->menu_name->value, - 'title' => $menuLink->title->value, - 'parent' => $menuLink->parent->value, - 'uri' => $menuLink->link->uri, - 'link_title' => $menuLink->link->title, - 'description' => $menuLink->description->value, - 'enabled' => $menuLink->enabled->value, - 'expanded' => $menuLink->expanded->value, - 'weight' => $menuLink->weight->value, - 'langcode' => $menuLink->langcode->value, - 'uuid' => $menuLink->uuid(), - ]; - - if (array_key_exists('drush', $form) && $form['drush'] === TRUE) { - drush_log('Exported "' . $menuLink->title->getValue()[0]['value'] . '" of menu "' . $menuLink->menu_name->getValue()[0]['value'] . '"', 'ok'); + $customMenuLink = []; + $field_defs = $menuLink->getFieldDefinitions(); + $mid = $menuLink->id(); + + // Sort the languages to be exported by priority. + $default_language = $menuLink->get('langcode')->value; + $languagesPriority = $languages_enabled; + unset($languagesPriority[$default_language]); + array_unshift($languagesPriority, $languages_enabled[$default_language]); + + foreach ($languagesPriority as $language) { + $language_id = $language->getId(); + if ($menuLink->hasTranslation($language_id)) { + $customMenuLink[$mid][$language_id]['id'] = $menuLink->id(); + foreach ($field_defs as $field_name => $value) { + if (!array_key_exists($field_name, $customMenuLink[$mid][$language_id])) { + if ($field_name == 'link' || $field_name == 'link_override') { + // For link we export all its properties. + // Also adding support for translatable_menu_link_uri module through link_override field. + $customMenuLink[$mid][$language_id][$field_name] = $menuLink->getTranslation($language_id)->{$field_name}->getValue()[0]; + } + elseif ($field_name == 'bundle') { + $customMenuLink[$mid][$language_id][$field_name] = $menuLink->bundle(); + } + else { + $customMenuLink[$mid][$language_id][$field_name] = $menuLink->getTranslation($language_id)->{$field_name}->value; + } + } + } + StructureSyncHelper::logMessage('Exported "' . $customMenuLink[$mid][$language_id]['title'] . '" of menu "' . $customMenuLink[$mid][$language_id]['menu_name'] . '" (' . $language_id . ')'); + } } - StructureSyncHelper::logMessage('Exported "' . $menuLink->title->value . '" of menu "' . $menuLink->menu_name->value . '"'); + $customMenuLinks[] = $customMenuLink[$mid]; } $this->config->set('menus', $customMenuLinks)->save(); @@ -217,23 +239,67 @@ public function importMenuLinks(array $form, FormStateInterface $form_state = NU */ public static function deleteDeletedMenuLinks($menus, &$context) { $uuidsInConfig = []; + $midLangConfig = []; + $midLangDb = []; + $midLangToDelete = []; foreach ($menus as $menuLink) { - $uuidsInConfig[] = $menuLink['uuid']; + foreach ($menuLink as $language_id => $link) { + $midLangConfig[] = $link['id'] . '.' . $language_id; + $uuidsInConfig[] = $link['uuid']; + } } - if (!empty($uuidsInConfig)) { - $query = StructureSyncHelper::getEntityQuery('menu_link_content'); - $query->condition('uuid', $uuidsInConfig, 'NOT IN'); - $ids = $query->execute(); - $controller = StructureSyncHelper::getEntityManager() - ->getStorage('menu_link_content'); - $entities = $controller->loadMultiple($ids); - $controller->delete($entities); + // Remove duplicates. + $uuidsInConfig = array_unique($uuidsInConfig); + + // Completely delete terms that are not in the exported configuration. + if (!empty($uuidsInConfig) && count($uuidsInConfig) > 0) { + $query = StructureSyncHelper::getEntityQuery('menu_link_content'); + $query->condition('uuid', $uuidsInConfig, 'NOT IN'); + $ids = $query->execute(); + $controller = StructureSyncHelper::getEntityManager() + ->getStorage('menu_link_content'); + $entities = $controller->loadMultiple($ids); + $controller->delete($entities); + + $query = \Drupal::database()->select('menu_link_content_data', 'mlcd'); + $query->fields('mlcd', ['id', 'langcode']); + $result = $query->execute(); + while ($record = $result->fetchAssoc()) { + $midLangDb[] = $record['id'] . '.' . $record['langcode']; + } + $midLangToDelete = array_diff($midLangDb, $midLangConfig); + // Delete translations of the term. + if (count($midLangToDelete) > 0) { + foreach ($midLangToDelete as $value) { + $divkey = explode('.', $value); + $mid = $divkey[0]; + $language_id = $divkey[1]; + $link_loaded = \Drupal::entityTypeManager() + ->getStorage('menu_link_content') + ->load($mid); + if ($link_loaded->hasTranslation($language_id)) { + if ($link_loaded->getUntranslated()->content_translation_source->value == $link_loaded->getTranslation($language_id)->content_translation_source->value) { + if (array_key_exists('drush', $context) && $context['drush'] === TRUE) { + Drush::logger() + ->notice('You can not delete the origin "' . $language_id . '" and keep the translation from link id "' . $mid . '"', 'warning'); + } + } + else { + $link_loaded->removeTranslation($language_id); + $link_loaded->save(); + } + } + } + } } if (array_key_exists('drush', $context) && $context['drush'] === TRUE) { drush_log('Deleted menu links that were not in config', 'ok'); } + else { + self::deleteMenuLinks($context); + } StructureSyncHelper::logMessage('Deleted menu links that were not in config'); } @@ -246,16 +312,18 @@ public static function deleteDeletedMenuLinks($menus, &$context) { public static function importMenuLinksFull($menus, &$context) { $uuidsInConfig = []; foreach ($menus as $menuLink) { - $uuidsInConfig[] = $menuLink['uuid']; + foreach ($menuLink as $language_id => $link) { + $uuidsInConfig[] = $link['uuid']; + } } $entities = []; if (!empty($uuidsInConfig)) { - $query = StructureSyncHelper::getEntityQuery('menu_link_content'); - $query->condition('uuid', $uuidsInConfig, 'IN'); - $ids = $query->execute(); - $controller = StructureSyncHelper::getEntityManager() - ->getStorage('menu_link_content'); - $entities = $controller->loadMultiple($ids); + $query = StructureSyncHelper::getEntityQuery('menu_link_content'); + $query->condition('uuid', $uuidsInConfig, 'IN'); + $ids = $query->execute(); + $controller = StructureSyncHelper::getEntityManager() + ->getStorage('menu_link_content'); + $entities = $controller->loadMultiple($ids); } $parents = array_column($menus, 'parent'); @@ -277,81 +345,78 @@ public static function importMenuLinksFull($menus, &$context) { $context['sandbox']['progress'] = 0; while ($firstRun || count($idsLeft) > 0) { foreach ($menus as $menuLink) { - $query = StructureSyncHelper::getEntityQuery('menu_link_content'); - $query->condition('uuid', $menuLink['uuid']); - $ids = $query->execute(); - - $currentParent = $menuLink['parent']; - if (!is_null($currentParent)) { - if (($pos = strpos($currentParent, ":")) !== FALSE) { - $currentParent = substr($currentParent, $pos + 1); - } - } - - if (!in_array($menuLink['uuid'], $idsDone) - && ($menuLink['parent'] === NULL - || !in_array($menuLink['parent'], $parents) - || in_array($currentParent, $idsDone)) - ) { - if (count($ids) <= 0) { - MenuLinkContent::create([ - 'title' => $menuLink['title'], - 'link' => [ - 'uri' => $menuLink['uri'], - 'title' => $menuLink['link_title'], - ], - 'menu_name' => $menuLink['menu_name'], - 'expanded' => $menuLink['expanded'] === '1' ? TRUE : FALSE, - 'enabled' => $menuLink['enabled'] === '1' ? TRUE : FALSE, - 'parent' => $menuLink['parent'], - 'description' => $menuLink['description'], - 'weight' => $menuLink['weight'], - 'langcode' => $menuLink['langcode'], - 'uuid' => $menuLink['uuid'], - ])->save(); + foreach ($menuLink as $language_id => $link) { + $query = StructureSyncHelper::getEntityQuery('menu_link_content'); + $query->condition('uuid', $link['uuid']); + $query->condition('langcode', $language_id); + $ids = $query->execute(); + + $currentParent = $link['parent']; + if (!is_null($currentParent)) { + if (($pos = strpos($currentParent, ":")) !== FALSE) { + $currentParent = substr($currentParent, $pos + 1); + } } - else { - foreach ($entities as $entity) { - if ($menuLink['uuid'] === $entity->uuid()) { - $customMenuLink = MenuLinkContent::load($entity->id()); - if (!empty($customMenuLink)) { - $customMenuLink - ->set('title', $menuLink['title']) - ->set('link', [ - 'uri' => $menuLink['uri'], - 'title' => $menuLink['link_title'], - ]) - ->set('expanded', $menuLink['expanded'] === '1' ? TRUE : FALSE) - ->set('enabled', $menuLink['enabled'] === '1' ? TRUE : FALSE) - ->set('parent', $menuLink['parent']) - ->set('description', $menuLink['description']) - ->set('weight', $menuLink['weight']) - ->save(); + if (!in_array($link['uuid'], $idsDone) + && ($link['parent'] === NULL + || !in_array($link['parent'], $parents) + || in_array($currentParent, $idsDone)) + ) { + if (count($ids) <= 0) { + $query = StructureSyncHelper::getEntityQuery('menu_link_content'); + $query->condition('uuid', $link['uuid']); + // Add translation. + if (count($query->execute()) >= 1) { + MenuLinkContent::load($link['id'])->addTranslation($language_id, $link)->save(); + StructureSyncHelper::logMessage('Imported link translation (' . $language_id . ') "' . $link['title'] . '" into "' . $link['menu_name'] . '"'); + } + // Create a new menu link. + else { + MenuLinkContent::create($link)->save(); + StructureSyncHelper::logMessage('Imported link "' . $link['title'] . '" into "' . $link['menu_name'] . '"'); + } + } + // Update links. + else { + foreach ($entities as $entity) { + if ($link['uuid'] === $entity->uuid()) { + $link_loaded = MenuLinkContent::load($entity->id()); + foreach ($link as $field_name => $field_value) { + if ($link_loaded->get($field_name)->getFieldDefinition()->isTranslatable()) { + // Update link field translation. + $link_loaded->getTranslation($language_id)->{$field_name}->setValue($field_value); + } + else { + // Update link field. + $link_loaded->{$field_name}->setValue($field_value); + } + } + // Save link. + $link_loaded->save(); + + StructureSyncHelper::logMessage('Updated link (' . $language_id . ') "' . $link['title'] . '" into "' . $link['menu_name'] . '"'); } break; } } } - - $idsDone[] = $menuLink['uuid']; - - if (in_array($menuLink['uuid'], $idsLeft)) { - unset($idsLeft[$menuLink['uuid']]); + else { + $idsLeft[$link['uuid']] = $link['uuid']; } + } - if (array_key_exists('drush', $context) && $context['drush'] === TRUE) { - drush_log('Imported "' . $menuLink['title'] . '" into ' . $menuLink['menu_name'], 'ok'); - } - StructureSyncHelper::logMessage('Imported "' . $menuLink['title'] . '" into ' . $menuLink['menu_name']); + $idsDone[] = $link['uuid']; + if (in_array($link['uuid'], $idsLeft)) { + unset($idsLeft[$link['uuid']]); + } - $context['sandbox']['progress']++; - if ($context['sandbox']['progress'] != $context['sandbox']['max']) { - $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; - } + $context['sandbox']['progress']++; + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; } - else { - $idsLeft[$menuLink['uuid']] = $menuLink['uuid']; + if (array_key_exists('drush', $context) && $context['drush'] === TRUE) { + drush_log('Imported "' . $menuLink['title'] . '" into ' . $menuLink['menu_name'], 'ok'); } } @@ -371,35 +436,27 @@ public static function importMenuLinksSafe($menus, &$context) { ->getStorage('menu_link_content') ->loadMultiple(); - foreach ($entities as $entity) { - for ($i = 0; $i < count($menus); $i++) { - if ($entity->uuid() === $menus[$i]['uuid']) { - unset($menusFiltered[$i]); + foreach ($entities as $key => $entity) { + foreach ($menusFiltered as $key => $menuLink) { + foreach ($menuLink as $language_id => $link) { + if ($entity->uuid() === $link['uuid']) { + if ($entity->hasTranslation($language_id)) { + unset($menusFiltered[$key][$language_id]); + } + } + } + if (isset($menusFiltered[$key]) && empty($menusFiltered[$key])) { + unset($menusFiltered[$key]); } } } - foreach ($menusFiltered as $menuLink) { - MenuLinkContent::create([ - 'title' => $menuLink['title'], - 'link' => [ - 'uri' => $menuLink['uri'], - 'title' => $menuLink['link_title'], - ], - 'menu_name' => $menuLink['menu_name'], - 'expanded' => $menuLink['expanded'] === '1' ? TRUE : FALSE, - 'enabled' => $menuLink['enabled'] === '1' ? TRUE : FALSE, - 'parent' => $menuLink['parent'], - 'description' => $menuLink['description'], - 'weight' => $menuLink['weight'], - 'langcode' => $menuLink['langcode'], - 'uuid' => $menuLink['uuid'], - ])->save(); - - if (array_key_exists('drush', $context) && $context['drush'] === TRUE) { - drush_log('Imported "' . $menuLink['title'] . '" into "' . $menuLink['menu_name'] . '" menu', 'ok'); - } - StructureSyncHelper::logMessage('Imported "' . $menuLink['title'] . '" into "' . $menuLink['menu_name'] . '" menu'); + // Import new links and translation links. + if (count($menusFiltered) > 0) { + self::importMenuLinksFull($menusFiltered, $context); + } + else { + StructureSyncHelper::logMessage('Not found new links to import'); } } @@ -424,27 +481,19 @@ public static function deleteMenuLinks(&$context) { * Function to import (create) all menu links that need to be imported. */ public static function importMenuLinksForce($menus, &$context) { - foreach ($menus as $menuLink) { - MenuLinkContent::create([ - 'title' => $menuLink['title'], - 'link' => [ - 'uri' => $menuLink['uri'], - 'title' => $menuLink['link_title'], - ], - 'menu_name' => $menuLink['menu_name'], - 'expanded' => $menuLink['expanded'] === '1' ? TRUE : FALSE, - 'enabled' => $menuLink['enabled'] === '1' ? TRUE : FALSE, - 'parent' => $menuLink['parent'], - 'description' => $menuLink['description'], - 'weight' => $menuLink['weight'], - 'langcode' => $menuLink['langcode'], - 'uuid' => $menuLink['uuid'], - ])->save(); - - if (array_key_exists('drush', $context) && $context['drush'] === TRUE) { - drush_log('Imported "' . $menuLink['title'] . '" into "' . $menuLink['menu_name'] . '" menu', 'ok'); + foreach ($menuLink as $language_id => $link) { + if ($link['content_translation_source'] === 'und') { + MenuLinkContent::create($link)->save(); + StructureSyncHelper::logMessage('Imported link "' . $link['title'] . '" into "' . $link['menu_name'] . '" menu'); + } + else { + MenuLinkContent::load($link['id'])->addTranslation($language_id, $link)->save(); + + if (array_key_exists('drush', $context) && $context['drush'] === TRUE) { + drush_log('Imported "' . $menuLink['title'] . '" into "' . $menuLink['menu_name'] . '" menu', 'ok'); + } + StructureSyncHelper::logMessage('Imported link translation (' . $language_id . ') "' . $link['title'] . '" into "' . $link['menu_name'] . '" menu'); } - StructureSyncHelper::logMessage('Imported "' . $menuLink['title'] . '" into "' . $menuLink['menu_name'] . '" menu'); } } diff --git a/src/Form/MenuSyncForm.php b/src/Form/MenuSyncForm.php index 8aab39d..f8df94c 100755 --- a/src/Form/MenuSyncForm.php +++ b/src/Form/MenuSyncForm.php @@ -138,10 +138,12 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $menu_list = []; - foreach ($menus as $menu) { - $menuName = $this->config('system.menu.' . $menu['menu_name']) - ->get('label'); - $menu_list[$menu['menu_name']] = $menuName; + foreach ($menus as $languages) { + foreach ($languages as $key => $menu) { + $menuName = $this->config('system.menu.' . $menu['menu_name']) + ->get('label'); + $menu_list[$menu['menu_name']] = $menuName; + } } $form['import']['import_menu_list'] = [ -- GitLab