taxonomy_menu_sync-1.0.4/src/TaxonomyMenuSyncHelper.php
src/TaxonomyMenuSyncHelper.php
<?php
namespace Drupal\taxonomy_menu_sync;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Menu\MenuParentFormSelectorInterface;
use Drupal\menu_link_content\MenuLinkContentInterface;
use Drupal\node\NodeInterface;
use Drupal\taxonomy\TermInterface;
use Drupal\taxonomy_menu_sync\Entity\TaxonomyMenuSync;
/**
* Autogenerated menu utility service.
*/
class TaxonomyMenuSyncHelper {
/**
* Logger name.
*/
const string LOGGER_NAME = 'taxonomy_menu_sync';
/**
* Menu configuration id.
*
* @var string
*/
protected string $menuConfigId;
/**
* Menu name to be used for sync-in.
*
* @var string
*/
protected string $menuName;
/**
* Parent menu content item used as parent when sync-in.
*
* @var string
*/
protected string $parentMenuItem;
/**
* Content type use for pull-in data(i.e. label, URL)
*
* @var string
*/
protected string $bundleName;
/**
* Checkbox to use node.
*
* @var bool
*/
protected bool $useNode = FALSE;
/**
* Use term weight to set order of menu.
*
* @var bool
*/
protected bool $useTermWeight = FALSE;
/**
* Disable/enable menu item.
*
* @var bool
*/
protected bool $hideEmptyTerms = FALSE;
/**
* DB stored mapping data.
*
* @var array
*/
protected array $existingMapping = [];
/**
* Enabled languages.
*
* @var array
*/
protected array $enabledLanguages = [];
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* @var \Drupal\Core\Menu\MenuParentFormSelectorInterface
*/
protected MenuParentFormSelectorInterface $menuParentFormSelector;
/**
* @var \Drupal\Core\Database\Connection
*/
protected Connection $connection;
/**
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected LoggerChannelFactoryInterface $loggerFactory;
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected ModuleHandlerInterface $moduleHandler;
/**
* Autogenerated menu constructor.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
* @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_form_selector
* Menu parent form selector service.
* @param \Drupal\Core\Database\Connection $connection
* Database connection service.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* Logging service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* Module handle service.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
MenuParentFormSelectorInterface $menu_parent_form_selector,
Connection $connection,
LoggerChannelFactoryInterface $logger_factory,
ModuleHandlerInterface $module_handler,
) {
$this->entityTypeManager = $entity_type_manager;
$this->menuParentFormSelector = $menu_parent_form_selector;
$this->connection = $connection;
$this->loggerFactory = $logger_factory;
$this->moduleHandler = $module_handler;
}
/**
* Entity load.
*
* @return \Drupal\Core\Entity\EntityInterface|null
* An object of entity or null.
*/
public function entityLoad(string $entity_type, string|int $entity_id):EntityInterface|null {
try {
$entity_storage = $this->entityTypeManager->getStorage($entity_type);
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
return NULL;
}
return $entity_storage->load($entity_id);
}
/**
* Menu(s) list.
*
* @return array
* An array id, label pair.
*/
public function getMenuList():array {
$menus = [];
try {
$menu_storage = $this->entityTypeManager->getStorage('menu');
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
return [];
}
foreach ($menu_storage->loadMultiple() as $menu) {
$menus[$menu->id()] = $menu->label();
}
return $menus;
}
/**
* Menu(s) link content list.
*
* @param array|null $menus
* An array(id, label pair) of menu lists.
* @param string $menu_id
* Menu link content id.
*
* @return array
* An array id, label pair.
*/
public function getMenuLinkContentList(?array $menus = NULL, string $menu_id = '',):array {
return $this->menuParentFormSelector->getParentSelectOptions($menu_id, $menus);
}
/**
* Find taxonomy term id.
*
* @param string $vocab
* A string of taxonomy vocabulary machine name.
* @param string|int $term_id
* Taxonomy term id.
*
* @return bool
*/
public function verifyTermId(string $vocab, string|int $term_id): bool {
try {
return (bool) $this->entityTypeManager->getStorage('taxonomy_term')->getQuery()
->accessCheck(FALSE)
->condition('vid', $vocab)
->condition('tid', $term_id)
->execute();
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
return FALSE;
}
}
/**
* Lists of enabled languages.
*
* @return array|int[]|string[]
* An array of langcodes.
*/
public function getLanguages(): array {
try {
$language_storage = $this->entityTypeManager->getStorage('configurable_language');
return $this->enabledLanguages = array_keys($language_storage->loadByProperties(['locked' => FALSE]));
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
return [];
}
}
/**
* Set the initial requirement to autogenerate menu(s).
*
* @param string $id
* Menu configuration id.
*
* @return void
*/
public function sync(string $id): void {
try {
/** @var \Drupal\taxonomy_menu_sync\Entity\TaxonomyMenuSync $entity */
if (!empty($entity = $this->entityLoad('taxonomy_menu_sync', $id))) {
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Taxonomy terms -> Menu sync initiated for taxonomy menu sync, id: %id.', ['%id' => $id]);
$this->getLanguages();
$this->menuConfigId = $entity->id();
$this->menuName = $entity->get('target_menu')->target_id ?? '';
$this->parentMenuItem = $entity->get('target_menu_item')->value ?? '';
$this->bundleName = $entity->get('source_bundle')->target_id ?? '';
$this->useNode = (bool) $entity->get('use_entity')->value;
$this->useTermWeight = (bool) $entity->get('use_term_weight')->value;
$this->hideEmptyTerms = (bool) $entity->get('hide_empty_term')->value;
$this->existingMapping = $entity->getMapping();
$vocabulary = $entity->get('source_vocabulary')->target_id ?? '';
$parent_tid = $entity->get('source_term')->target_id ?? 0;
$depth = $entity->get('depth')->value ?? NULL;
$include_source_term = (bool) $entity->get('include_source_term')->value;
if ($this->menuName && $this->bundleName && $vocabulary) {
$terms = [];
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Taxonomy terms -> Menu sync requirements found for taxonomy menu sync, id: %id.', ['%id' => $id]);
/** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
$term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
// Include source taxonomy term.
if ($include_source_term && !empty($parent_tid)) {
$current_term = $term_storage->load($parent_tid);
if ($current_term instanceof TermInterface) {
$terms[] = $current_term;
}
}
$childrens = $term_storage->loadTree($vocabulary, $parent_tid, $depth, TRUE);
$data = array_merge($terms, $childrens);
$this->generateMenuLinks($data, $entity);
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Taxonomy terms -> Menu sync completed for taxonomy menu sync, id: %id.', ['%id' => $id]);
}
}
else {
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Taxonomy terms -> Menu sync found no records for taxonomy menu sync, id: %id.', ['%id' => $id]);
}
}
catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Taxonomy terms -> Menu sync ran into issues for taxonomy menu sync, id: %id. Message: @message', [
'%id' => $id,
'@message' => $e->getMessage(),
]);
}
}
/**
* Generate menu(s) using the configurations provided.
*
* @param array $terms
* An array of term object.
* @param \Drupal\taxonomy_menu_sync\Entity\TaxonomyMenuSync $storeEntity
* Taxonomy menu sync object.
*
* @return void
*/
public function generateMenuLinks(array $terms, TaxonomyMenuSync $storeEntity): void {
$mapping = $db_mapping = [];
$disable_title_sync = (bool) $storeEntity->get('disable_title_sync')->value;
$disable_parent_sync = (bool) $storeEntity->get('disable_parent_sync')->value;
$parent_menu = preg_replace('/^' . preg_quote("$this->menuName:") . '/', '', $this->parentMenuItem);
try {
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Generate menu(s) initiated for taxonomy menu sync, id: %id.', ['%id' => $this->menuConfigId]);
/** @var \Drupal\menu_link_content\MenuLinkContentStorageInterface $menu_link_storage */
$menu_link_storage = $this->entityTypeManager->getStorage('menu_link_content');
/** @var \Drupal\taxonomy\TermInterface $term */
foreach ($terms as $term) {
if ($term instanceof TermInterface) {
$entity = $term;
$tid = $term->id();
$parent_id = !$term->get('parent')->isEmpty() ? $term->get('parent')->getString() : NULL;
$menu_link = 'entity:taxonomy_term/' . $tid;
if ($this->useNode) {
$select = $this->connection->select('taxonomy_index', 'tn');
$select->fields('tn', ['nid']);
$select->join('node', 'n', 'n.nid = tn.nid');
$select->condition('tn.tid', $tid);
$select->condition('n.type', $this->bundleName);
$results = $select->execute()->fetchCol();
if (!empty($results)) {
$menu_link = 'entity:node/' . $results[0];
$node = $this->entityTypeManager->getStorage('node')->load($results[0]);
if ($node instanceof NodeInterface) {
$entity = $node;
}
}
}
$menu_link_content = !empty($this->existingMapping[$tid]['mlid']) ? $menu_link_storage->load($this->existingMapping[$tid]['mlid']) : NULL;
$prefill_data = [
'link' => ['uri' => $menu_link],
'title' => $entity->label(),
'parent' => $mapping[$parent_id] ?? $parent_menu,
'enabled' => 1,
'expanded' => TRUE,
];
if ($this->hideEmptyTerms) {
if (!$this->termUseCount($tid)) {
$prefill_data['enabled'] = 0;
}
}
if ($this->useTermWeight) {
$prefill_data['weight'] = $term->getWeight();
}
if ($menu_link_content instanceof MenuLinkContentInterface) {
foreach ($prefill_data as $key => $value) {
if (($disable_title_sync && $key === 'title') || ($disable_parent_sync && $key === 'parent')) {
continue;
}
$menu_link_content->set($key, $value);
}
}
else {
$prefill_data['menu_name'] = $this->menuName;
$menu_link_content = $menu_link_storage->create($prefill_data);
}
foreach ($this->enabledLanguages as $langcode) {
if ($langcode !== 'en' && $entity->hasTranslation($langcode)) {
$entity = $entity->getTranslation($langcode);
if ($menu_link_content->hasTranslation($langcode)) {
if (!$disable_title_sync) {
$menu_link_content->getTranslation($langcode)->set('title', $entity->label());
}
}
else {
$menu_link_content->addTranslation($langcode, ['title' => $entity->label()]);
}
}
}
// Alter `$menu_link_content` using `taxonomy_menu_sync` hooks.
$this->moduleHandler->alter('taxonomy_menu_sync', $menu_link_content, $entity);
if ($menu_link_content->save()) {
$mapping[$tid] = $menu_link_content->getPluginId();
$db_mapping[$tid] = ['mlid' => $menu_link_content->id(), 'uuid' => $menu_link_content->uuid()];
}
}
}
if (count($terms) === count($db_mapping)) {
$json_mapping = json_encode($db_mapping);
$storeEntity->set('is_synced', TRUE);
$storeEntity->set('mapping', $json_mapping);
if ($storeEntity->save()) {
// Delete unmapped menu items.
if (!empty($unused_menus = array_diff_key($this->existingMapping, $db_mapping))) {
$mlid_ids = array_column($unused_menus, 'mlid');
if (!empty($loaded_entities = $menu_link_storage->loadMultiple($mlid_ids))) {
$menu_link_storage->delete($loaded_entities);
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Unused menu(s) deleted for taxonomy menu sync, id: %id. Mapping(tid:menu_id): @message', [
'%id' => $this->menuConfigId,
'@message' => json_encode($unused_menus),
]);
}
}
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Generate menu(s) completed for taxonomy menu sync, id: %id. Mapping(tid:menu_id): @message', [
'%id' => $this->menuConfigId,
'@message' => $json_mapping,
]);
}
}
}
catch (InvalidPluginDefinitionException | PluginNotFoundException | EntityStorageException | \Exception $e) {
$this->loggerFactory->get(self::LOGGER_NAME)->notice('Generate menu(s) ran into issues for taxonomy menu sync, id: %id. Message: @message', [
'%id' => $this->menuConfigId,
'@message' => $e->getMessage(),
]);
}
}
/**
* Get the number of nodes for given term.
*
* @param int $tid
* A taxonomy term id.
*
* @return bool
* The number of nodes the given term is on.
*/
public function termUseCount(int $tid): bool {
try {
$status = FALSE;
$result = $this->connection->select('taxonomy_index', 'tn');
$result->condition('tid', $tid);
$result->condition('n.type', $this->bundleName);
$result->join('node', 'n', 'n.nid = tn.nid');
$result->addExpression('COUNT(n.nid)', 'term_count');
$temp = $result->execute();
$temp = $temp->fetchObject();
if ($temp->term_count > 0) {
$status = TRUE;
}
return $status;
}
catch (\Exception $e) {
return FALSE;
}
}
}
