Created
April 25, 2023 18:24
-
-
Save ultimike/15fbe4483de3d34213794f34fe35df9a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Drupal\drupaleasy_repositories; | |
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher; | |
use Drupal\Component\Plugin\PluginManagerInterface; | |
use Drupal\Core\Config\ConfigFactoryInterface; | |
use Drupal\Core\Entity\EntityInterface; | |
use Drupal\Core\Entity\EntityTypeManagerInterface; | |
use Drupal\Core\StringTranslation\StringTranslationTrait; | |
use Drupal\drupaleasy_repositories\Event\RepoUpdatedEvent; | |
use Drupal\node\NodeInterface; | |
/** | |
* Service description. | |
*/ | |
class DrupaleasyRepositoriesService { | |
use StringTranslationTrait; | |
/** | |
* The plugin manager interface. | |
* | |
* @var \Drupal\Component\Plugin\PluginManagerInterface | |
*/ | |
protected PluginManagerInterface $pluginManagerDrupaleasyRepositories; | |
/** | |
* The configuration factory interface. | |
* | |
* @var \Drupal\Core\Config\ConfigFactoryInterface | |
*/ | |
protected ConfigFactoryInterface $configFactory; | |
/** | |
* The entity type manager interface. | |
* | |
* @var \Drupal\Core\Entity\EntityTypeManagerInterface | |
*/ | |
protected EntityTypeManagerInterface $entityTypeManager; | |
/** | |
* The dry-run parameter. | |
* | |
* When set to "true", no nodes are created, updated, or deleted. | |
* | |
* @var bool | |
*/ | |
protected bool $dryRun = FALSE; | |
/** | |
* The event dispatcher service. | |
* | |
* @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher | |
*/ | |
protected ContainerAwareEventDispatcher $eventDispatcher; | |
/** | |
* Constructs a DrupaleasyRepositories object. | |
* | |
* @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager_drupaleasy_repositories | |
* The plugin manager. | |
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory | |
* The configuration factory. | |
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager | |
* The entity type manager. | |
* @param bool $dry_run | |
* The dry run parameter that specifies whether or not to save node changes. | |
* @param \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher $event_dispatcher | |
* The event dispatcher service. | |
*/ | |
public function __construct(PluginManagerInterface $plugin_manager_drupaleasy_repositories, ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, bool $dry_run, ContainerAwareEventDispatcher $event_dispatcher) { | |
$this->pluginManagerDrupaleasyRepositories = $plugin_manager_drupaleasy_repositories; | |
$this->configFactory = $config_factory; | |
$this->entityTypeManager = $entity_type_manager; | |
$this->dryRun = $dry_run; | |
$this->eventDispatcher = $event_dispatcher; | |
} | |
/** | |
* Get repository URL help text from each enabled plugin. | |
* | |
* @return string | |
* The help text. | |
*/ | |
public function getValidatorHelpText(): string { | |
$repository_plugins = []; | |
// Determine which of our plugins are enabled. | |
$enabled_repository_plugin_ids = $this->configFactory->get('drupaleasy_repositories.settings')->get('repositories') ?? []; | |
// Instantiate our enabled plugins. | |
foreach ($enabled_repository_plugin_ids as $enabled_repository_plugin_id) { | |
if (!empty($enabled_repository_plugin_id)) { | |
$repository_plugins[] = $this->pluginManagerDrupaleasyRepositories->createInstance($enabled_repository_plugin_id); | |
} | |
} | |
$help = []; | |
// Loop around enabled plugins, calling each's validateHelpText() method. | |
/** @var \Drupal\drupaleasy_repositories\DrupaleasyRepositories\DrupaleasyRepositoriesInterface $repository_plugin */ | |
foreach ($repository_plugins as $repository_plugin) { | |
$help[] = $repository_plugin->validateHelpText(); | |
} | |
// Check to see if the "count" is necessary after hook_form_alter is | |
// implemented. | |
if (count($help)) { | |
return implode(' ', $help); | |
} | |
// Return the help text. | |
return ''; | |
} | |
/** | |
* Validate repository URLs. | |
* | |
* Validate the URLs are valid based on the enabled plugins and ensure they | |
* haven't been added by another user. | |
* | |
* @param array<string, mixed> $urls | |
* The urls to be validated. | |
* @param int $uid | |
* The user id of the user submitting the URLs. | |
* | |
* @return string | |
* Errors reported by plugins. | |
*/ | |
public function validateRepositoryUrls(array $urls, int $uid): string { | |
$errors = []; | |
$repository_plugins = []; | |
// Get IDs all DrupaleasyRepository plugins (enabled or not). | |
$repository_plugin_ids = $this->configFactory->get('drupaleasy_repositories.settings')->get('repositories') ?? []; | |
// Instantiate each enabled DrupaleasyRepository plugin (and confirm that | |
// at least one is enabled). | |
$atLeastOne = FALSE; | |
foreach ($repository_plugin_ids as $repository_plugin_id) { | |
if (!empty($repository_plugin_id)) { | |
$atLeastOne = TRUE; | |
$repository_plugins[] = $this->pluginManagerDrupaleasyRepositories->createInstance($repository_plugin_id); | |
} | |
} | |
if (!$atLeastOne) { | |
return 'There are no enabled repository plugins'; | |
} | |
// Loop around each Repository URL and attempt to validate. | |
foreach ($urls as $url) { | |
if (is_array($url)) { | |
if ($uri = trim($url['uri'])) { | |
$validated = FALSE; | |
// Check to see if the URI is valid for any enabled plugins. | |
/** @var \Drupal\drupaleasy_repositories\DrupaleasyRepositories\DrupaleasyRepositoriesInterface $repository_plugin */ | |
foreach ($repository_plugins as $repository_plugin) { | |
if ($repository_plugin->validate($uri)) { | |
$validated = TRUE; | |
$repo_metadata = $repository_plugin->getRepo($uri); | |
if ($repo_metadata) { | |
if (!$this->isUnique($repo_metadata, $uid)) { | |
$errors[] = $this->t('The repository at %uri has already been added by another user.', ['%uri' => $uri]); | |
} | |
} | |
else { | |
$errors[] = $this->t('The repository at the url %uri was not found.', ['%uri' => $uri]); | |
} | |
} | |
} | |
if (!$validated) { | |
$errors[] = $this->t('The repository url %uri is not valid.', ['%uri' => $uri]); | |
} | |
} | |
} | |
} | |
if ($errors) { | |
return implode(' ', $errors); | |
} | |
// No errors found. | |
return ''; | |
} | |
/** | |
* Update the repository nodes for a given account. | |
* | |
* @param \Drupal\Core\Entity\EntityInterface $account | |
* The user account whose repositories to update. | |
* | |
* @return bool | |
* TRUE if successful. | |
*/ | |
public function updateRepositories(EntityInterface $account): bool { | |
$repos_metadata = []; | |
$repository_plugin_ids = $this->configFactory->get('drupaleasy_repositories.settings')->get('repositories') ?? []; | |
foreach ($repository_plugin_ids as $repository_plugin_id) { | |
if (!empty($repository_plugin_id)) { | |
/** @var \Drupal\drupaleasy_repositories\DrupaleasyRepositories\DrupaleasyRepositoriesInterface $repository_location */ | |
$repository_location = $this->pluginManagerDrupaleasyRepositories->createInstance($repository_plugin_id); | |
// Loop through repository URLs. | |
foreach ($account->field_repository_url ?? [] as $url) { | |
// Check if the URL validates for this repository. | |
if ($repository_location->validate($url->uri)) { | |
// Confirm the repository exists and get metadata. | |
if ($repo_metadata = $repository_location->getRepo($url->uri)) { | |
$repos_metadata += $repo_metadata; | |
} | |
} | |
} | |
} | |
} | |
return $this->updateRepositoryNodes($repos_metadata, $account) && | |
$this->deleteRepositoryNodes($repos_metadata, $account); | |
} | |
/** | |
* Update repository nodes for a given user. | |
* | |
* @param array<string, array<string, string>> $repos_info | |
* Repository info from API call. | |
* @param \Drupal\Core\Entity\EntityInterface $account | |
* The user account whose repositories to update. | |
* | |
* @return bool | |
* TRUE if successful. | |
*/ | |
protected function updateRepositoryNodes(array $repos_info, EntityInterface $account): bool { | |
if (!$repos_info) { | |
return TRUE; | |
} | |
// Prepare the storage and query stuff. | |
/** @var \Drupal\Core\Entity\EntityStorageInterface $node_storage */ | |
$node_storage = $this->entityTypeManager->getStorage('node'); | |
foreach ($repos_info as $key => $info) { | |
// Calculate hash value. | |
$hash = md5(serialize($info)); | |
// Look for repository nodes from this user with matching | |
// machine_name. | |
/** @var \Drupal\Core\Entity\Query\QueryInterface $query */ | |
$query = $node_storage->getQuery(); | |
$query->condition('type', 'repository') | |
->condition('uid', $account->id()) | |
->condition('field_machine_name', $key) | |
->condition('field_source', $info['source']) | |
->accessCheck(FALSE); | |
$results = $query->execute(); | |
if ($results) { | |
/** @var \Drupal\node\Entity\Node $node */ | |
$node = $node_storage->load(reset($results)); | |
if ($hash != $node->get('field_hash')->value) { | |
// Something changed, update node. | |
$node->setTitle($info['label']); | |
$node->set('field_description', $info['description']); | |
$node->set('field_machine_name', $key); | |
$node->set('field_number_of_issues', $info['num_open_issues']); | |
$node->set('field_source', $info['source']); | |
$node->set('field_url', $info['url']); | |
$node->set('field_hash', $hash); | |
if (!$this->dryRun) { | |
$node->save(); | |
$this->repoUpdated($node, 'updated'); | |
} | |
} | |
} | |
else { | |
// Repository node doesn't exist - create a new one. | |
/** @var \Drupal\node\Entity\Node $node */ | |
$node = $node_storage->create([ | |
'uid' => $account->id(), | |
'type' => 'repository', | |
'title' => $info['label'], | |
'field_description' => $info['description'], | |
'field_machine_name' => $key, | |
'field_number_of_issues' => $info['num_open_issues'], | |
'field_source' => $info['source'], | |
'field_url' => $info['url'], | |
'field_hash' => $hash, | |
]); | |
if (!$this->dryRun) { | |
$node->save(); | |
$this->repoUpdated($node, 'created'); | |
} | |
} | |
} | |
return TRUE; | |
} | |
/** | |
* Delete repository nodes deleted from the source for a given user. | |
* | |
* @param array<string, array<string, string>> $repos_info | |
* Repository info from API call. | |
* @param \Drupal\Core\Entity\EntityInterface $account | |
* The user account whose repositories to update. | |
* | |
* @return bool | |
* TRUE if successful. | |
*/ | |
protected function deleteRepositoryNodes(array $repos_info, EntityInterface $account): bool { | |
// Prepare the storage and query stuff. | |
/** @var \Drupal\Core\Entity\EntityStorageInterface $node_storage */ | |
$node_storage = $this->entityTypeManager->getStorage('node'); | |
/** @var \Drupal\Core\Entity\Query\QueryInterface $query */ | |
$query = $node_storage->getQuery(); | |
$query->condition('type', 'repository') | |
->condition('uid', $account->id()) | |
->accessCheck(FALSE); | |
// We can't chain this above because $repos_info might be empty. | |
if ($repos_info) { | |
$query->condition('field_machine_name', array_keys($repos_info), 'NOT IN'); | |
} | |
$results = $query->execute(); | |
if ($results) { | |
$nodes = $node_storage->loadMultiple($results); | |
/** @var \Drupal\node\Entity\Node $node */ | |
foreach ($nodes as $node) { | |
if (!$this->dryRun) { | |
$node->delete(); | |
$this->repoUpdated($node, 'deleted'); | |
} | |
} | |
} | |
return TRUE; | |
} | |
/** | |
* Check to see if a given repository is unique. | |
* | |
* @param array<string, array<string, string>> $repo_info | |
* The repository to check. | |
* @param int $uid | |
* The UID of the user submitting the repository URL. | |
* | |
* @return bool | |
* Return true if the given repository is unique. | |
* | |
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException | |
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException | |
*/ | |
protected function isUnique(array $repo_info, int $uid): bool { | |
/** @var \Drupal\node\NodeStorageInterface $node_storage */ | |
$node_storage = $this->entityTypeManager->getStorage('node'); | |
$repo_metadata = array_pop($repo_info); | |
$query = $node_storage->getQuery(); | |
$results = $query->condition('type', 'repository') | |
->condition('field_url', $repo_metadata['url']) | |
->condition('uid', $uid, '<>') | |
->accessCheck(FALSE) | |
->execute(); | |
return !count($results); | |
} | |
/** | |
* Dispatch event whenever a repository node is changed. | |
* | |
* @param \Drupal\node\NodeInterface $node | |
* The node that was changed. | |
* @param string $action | |
* The action that was performed. | |
*/ | |
protected function repoUpdated(NodeInterface $node, string $action): void { | |
$event = new RepoUpdatedEvent($node, $action); | |
$this->eventDispatcher->dispatch($event, RepoUpdatedEvent::REPO_UPDATED); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment