sgd_dashboard-1.0.0-beta1/src/Services/SiteGuardianDataService.php
src/Services/SiteGuardianDataService.php
<?php
namespace Drupal\sgd_dashboard\Services;
use Drupal\Core\Database\Connection;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Node\NodeInterface;
use Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle;
use Drupal\sgd_dashboard\SgdCompanionPluginManager;
use Psr\Log\LoggerInterface;
/**
* SiteGuardian dashboard data service.
*
* Provides functions for maintaining the SGD data model.
*/
class SiteGuardianDataService {
/**
* The SGD API Client service.
*
* @var \Drupal\sgd_dashboard\Services\SiteGuardianAPIClientService
*/
protected $apiService;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected Connection $database;
/**
* The Site Guardian Dashboard Companion plugin manager.
*
* @var \Drupal\sdg_dashboard\SgdCompanionPluginManager
*/
protected $pluginManager;
/**
* Logger.
*
* @var Psr\Log\LoggerInterface
*/
protected $logger;
/**
* {@inheritDoc}
*/
public function __construct(SiteGuardianAPIClientService $apiService, EntityTypeManagerInterface $entityTypeManager, Connection $database, SgdCompanionPluginManager $pluginManager, LoggerInterface $logger) {
$this->apiService = $apiService;
$this->entityTypeManager = $entityTypeManager;
$this->database = $database;
$this->pluginManager = $pluginManager;
$this->logger = $logger;
}
/**
* Helper function to refresh non-user-added Website node fields.
*/
public function refreshWebsiteFields(NodeInterface $website, $saveNode = TRUE): void {
// Get the API data from the target website.
$websiteData = $this->apiService->getStatus($website);
$enabledProjects = $this->apiService->getEnabledProjects($website);
// If the website data does not have a 'sgd_update_last_checked' entry then
// an old version of the API is being used.
// In this case we get the value in the project data and inject it into the
// status data.
if (empty($websiteData['sgd_updates_last_checked'])) {
$websiteData['sgd_updates_last_checked'] = [
'value' => $enabledProjects['last_checked'],
];
}
// In any case we dont want the last checked value in the project data.
unset($enabledProjects['last_checked']);
// Update the database as required.
$this->updateWebsite($website, $enabledProjects, $saveNode);
$this->updateWebsiteData($website, $websiteData, $enabledProjects);
$this->updateProjects($website, $enabledProjects);
}
/**
* Update fields on the website node with data from the remote website.
*/
public function updateWebsite(NodeInterface $website, $enabledProjects, $saveNode): void {
$coreIsSecure = $enabledProjects['drupal']['status'] != 1;
$contribIsSecure = TRUE;
foreach ($enabledProjects as $moduleName => $moduleData) {
if (!in_array($moduleName, ['drupal'])) {
if ($moduleData['status'] == 1) {
$contribIsSecure = FALSE;
break;
}
}
}
$siteLastChecked = new DrupalDateTime('1 second ago');
$website->set('field_core_security', $coreIsSecure);
$website->set('field_contrib_security', $contribIsSecure);
$website->set('field_site_last_checked', $siteLastChecked->getTimestamp());
if ($saveNode) {
$website->save();
}
}
/**
* Update name of the website data storage entity.
*/
public function updateWebsiteDataName(WebsiteBundle $website): void {
// Get the website data for this website.
$websiteData = $website->field_sgd_website_data->entity;
if ($websiteData) {
// Update the name to match the website node.
$websiteData->set('label', $website->getTitle());
// Save the website data.
$websiteData->save();
}
}
/**
* Update fields on the website data storage entity.
*
* Website data entity is updated with data from the remote website
* by calling each companionion plugin which will update the data it knows
* about.
*/
public function updateWebsiteData(WebsiteBundle $website, array $statusReport, array $enabledProjects): void {
// Get the website data for this website.
$websiteData = $website->field_sgd_website_data->entity;
if ($websiteData) {
// Always update the name to match the website node.
$websiteData->set('label', $website->getTitle());
// If we have status data then update the status fields.
if ($statusReport) {
// Find all Companion plugins and iterate through them calling their
// saveData function so each can extract the info they understand from
// the status report data and save it as required.
// Get all the companion modules.
$plugins = $this->pluginManager->getDefinitions();
// Iterate over them all.
foreach ($plugins as $plugin) {
$instance = $this->pluginManager->createInstance($plugin['id']);
// If the plugin can see data it knows how to handle in the status
// report.
if ($instance->canProcessStatus($statusReport)) {
// Call the 'savedata' function passing the websitedata entity so
// the plugin can save the data it knows about.
// By passing the website as well as the status report data the
// plugin can store the data as it requires.
$instance->saveStatus($websiteData, $statusReport, $enabledProjects);
}
}
// Save the website data.
$websiteData->save();
}
}
else {
$this->logger->error("No website data storage entity was found for %website", [
'%website' => $website->getTitle(),
]);
}
}
/**
* Delete website data storage entity for remote website.
*/
public function deleteWebsiteData(NodeInterface $website): void {
$websiteData = $website->field_sgd_website_data->entity;
if ($websiteData) {
$websiteData->delete();
}
}
/**
* Updates project storage entities with projects from the remote website.
*/
public function updateProjects(NodeInterface $website, array $enabledProjects): void {
// If the website node already exists then it may have project data so
// delete it first.
if ($website->isNew() == FALSE) {
$this->deleteProjects($website);
}
$enabledProjectStorage = $this->entityTypeManager->getStorage('sgd_enabled_project');
// Now for each project add a record to the DB.
foreach ($enabledProjects as $projectName => $project) {
$enabledProject = $enabledProjectStorage->create([
'name' => $projectName,
'website' => $website,
'status_id' => $project['status'],
'info' => isset($project['info']) ? json_encode($project['info']) : NULL,
'datestamp' => $project['datestamp'],
'includes' => isset($project['includes']) ? json_encode($project['includes']) : NULL,
'project_type' => $project['project_type'],
'project_status' => $project['project_status'] == 'unsupported' ?: var_export($project['project_status'], TRUE),
'existing_version' => $project['existing_version'],
'existing_major' => $project['existing_major'],
'install_type' => $project['install_type'],
'title' => $project['title'] ?? NULL,
'link' => isset($project['link']) ? ['url' => $project['link'], 'label' => $projectName] : NULL,
'latest_version' => $project['latest_version'] ?? NULL,
'releases' => isset($project['releases']) ? json_encode($project['releases']) : NULL,
'recommended' => $project['recommended'] ?? '',
]);
$enabledProject->save();
}
}
/**
* Deletes project storage entities for a website.
*/
public function deleteProjects(NodeInterface $website): void {
$enabledProjectStorage = $this->entityTypeManager->getStorage('sgd_enabled_project');
// Get all the websites projects in the DB.
$projectIds = $enabledProjectStorage->getQuery()
->accessCheck(TRUE)
->condition('website', $website->id())
->execute();
// Delete them all.
foreach ($projectIds as $projectId) {
$project_already_in_db = $enabledProjectStorage->load($projectId);
$project_already_in_db->delete();
}
}
/**
* Checks if the passed Site Guardian key is unique across all websites.
*/
public function isSiteGuardianKeyUnique($key): bool {
// See if site guardian key has more then 1 entry from the website nodes.
$query = $this->database->select('node__field_site_guardian_key', 'sgk')
->condition('sgk.field_site_guardian_key_value', $key, '=')
->fields('sgk', ['field_site_guardian_key_value'])
->countQuery();
$unique = (bool) ($query->execute()->fetchField() < 2);
return $unique;
}
}
