sgd_dashboard-1.0.0-beta1/sgd_dashboard.module
sgd_dashboard.module
<?php
/**
* @file
* Module for the implementation of common hooks and general helper functions.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Url;
use Drupal\Node\NodeInterface;
use Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\query\QueryPluginBase;
/**
* Implements hook_theme().
*
* {@inheritDoc}
*/
function sgd_dashboard_theme($existing, $type, $theme, $path) {
return [
'sgd_php_data' => [
'variables' => [
'data' => NULL,
],
],
'sgd_user_data' => [
'variables' => [
'data' => NULL,
],
],
'sgd_benchmark_data' => [
'variables' => [
'data' => NULL,
],
],
'sgd_watchdog_summary' => [
'variables' => [
'data' => NULL,
],
],
'sgd_core_data' => [
'variables' => [
'status' => NULL,
'exposed_form' => NULL,
'project_list' => NULL,
],
'template' => 'sgd-core-data',
],
];
}
/**
* Implements hook_toolbar_alter().
*/
function sgd_dashboard_toolbar_alter(&$items) {
$items['administration']['#attached']['library'][] = 'sgd_dashboard/sgd_dashboard';
}
/**
* Implements hook_entity_operation().
*
* Adds the 'refresh' operation to website nodes in views/lists.
*
* {@inheritdoc}
*/
function sgd_dashboard_entity_operation(EntityInterface $entity) {
$operations = [];
// If it's website node.
if ($entity->getEntityTypeId() == 'node' && $entity->bundle() == 'website') {
/** @var \Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle $website */
$website = $entity;
// Add a 'Refresh' operation if user has permission.
if (\Drupal::currentUser()->hasPermission('refresh websites')) {
$destination = Url::fromUserInput(\Drupal::destination()->get());
$operations['refresh'] = [
'title' => t('Refresh'),
'url' => Url::fromRoute('entity.node.refresh_website',
[
'node' => $website->id(),
],
[
'query' => [
'destination' => Url::fromRoute($destination->getRouteName(), $destination->getRouteParameters())->toString(),
],
],
),
'weight' => -1,
];
}
// Add a 'Visit site' operation.
$uri = $website->getBaseUrl();
$operations['site_link'] = [
'title' => t('Visit site'),
'url' => Url::fromUri($uri),
'weight' => -2,
];
}
// Add link to project on Drupal.org for enabled project entities.
elseif ($entity->getEntityTypeId() == 'sgd_enabled_project') {
$uri = 'https://www.drupal.org/project/' . $entity->get('name')->value;
$operations['drupal_project_link'] = [
'title' => t('Visit on Drupal.org'),
'url' => Url::fromUri($uri),
'weight' => -2,
];
}
return $operations;
}
/**
* Implements hook_ENTITY_TYPE_insert().
*
* {@inheritdoc}
*/
function sgd_dashboard_node_insert(NodeInterface $node): void {
if ($node->bundle() == 'website') {
// Create a website data entity for the website.
$websiteData = \Drupal::entityTypeManager()->getStorage('sgd_website_data')->create([
'label' => $node->getTitle(),
'uid' => '1',
'status' => 1,
]);
$websiteData->save();
// Update the website node with the website data entity ID.
$node->set('field_sgd_website_data', [
'entity' => $websiteData,
'target_id' => $websiteData->id(),
]);
$node->save();
// Create the cron job for the website.
$cronService = \Drupal::service('siteguardian.CronService');
$cronService->addCronJob($node);
}
}
/**
* Implements hook_ENTITY_TYPE_update().
*
* {@inheritdoc}
*/
function sgd_dashboard_node_update(NodeInterface $node): void {
if ($node->bundle() == 'website') {
$dataService = \Drupal::service('siteguardian.DataService');
$cronService = \Drupal::service('siteguardian.CronService');
// Update the website data storage node name.
$dataService->updateWebsiteDataName($node);
// Update the Cron job.
if (!$cronService->updateCronJob($node)) {
$cronService->addCronJob($node);
}
}
}
/**
* Implements hook_ENTITY_TYPE_delete().
*
* {@inheritdoc}
*/
function sgd_dashboard_node_delete(NodeInterface $node): void {
if ($node->bundle() == 'website') {
$dataService = \Drupal::service('siteguardian.DataService');
$cronService = \Drupal::service('siteguardian.CronService');
// Delete the cron job.
$cronService->deleteCronJob($node);
// Delete the website data entity for the website.
$dataService->deleteWebsiteData($node);
// Delete all enabled project storage entities for the website.
$dataService->deleteProjects($node);
}
}
/**
* Implements hook_form_views_exposed_form_alter().
*
* Redirects the projects by name exposed form to the layout page containing
* the view.
*
* {@inheritdoc}
*/
function sgd_dashboard_form_views_exposed_form_alter(array &$form, FormStateInterface $form_state): void {
// Redirect to website projects page.
if ($form['#id'] == 'views-exposed-form-website-projects-projects-by-name') {
$url = Url::fromRoute('sgd_dashboard.projects', []);
$form['#action'] = $url->toString();
}
if ($form['#id'] == 'views-exposed-form-website-projects-projects-by-name' ||
$form['#id'] == 'views-exposed-form-website-projects-projects-by-website-and-name') {
$form['exact_match'] = [
'#type' => 'radios',
'#title' => t('Exact match'),
'#description' => t('Only show projects whose title or machine name exactly match the search term.'),
'#weight' => 1,
'#options' => ['yes' => 'Yes', 'no' => 'No'],
'#default_value' => 'yes',
];
if (empty($form_state->getUserInput()['name'])) {
$form['exact_match']['#attributes'] = ['data-bef-auto-submit-exclude' => ''];
}
// Set the order of the fields.
$form['name']['#weight'] = 0;
$form['exact_match']['#weight'] = 1;
$form['sg_pr_status']['#weight'] = 2;
}
}
/**
* Implements hook_views_query_alter().
*
* Modify the view filter values.
*
* {@inheritdoc}
*/
function sgd_dashboard_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
// If the website projects view and either of the displays.
if ($view->id() == 'website_projects' && ($view->current_display == 'projects_by_name' || $view->current_display == 'projects_by_website_and_name')) {
$queryOptions = \Drupal::request()->query->all();
$searchValue = $queryOptions['name'] ?? NULL;
$exactMatch = $queryOptions['exact_match'] ?? NULL;
// Only the 'Name' field is exposed in the view so we need to add the
// search condition to the 'title' field as well.
$query->where[2]['conditions'][1] = [
'field' => 'sgd_enabled_project.title',
'value' => $query->where[2]['conditions'][0]["value"],
'operator' => 'REGEXP',
];
// If 'name' (the search value) has value and we are doing an exact match
// search then we want to exact match search both the title and the machine
// name fields so we need the regex to force exact match on both.
if (!empty($searchValue)) {
if (($exactMatch == 'yes')) {
$query->where[2]['conditions'][0]["value"] = '(?:^|\W)' . $searchValue . '(?:$|\W)';
$query->where[2]['conditions'][1]["value"] = '(?:^|\W)' . $searchValue . '(?:$|\W)';
}
}
else {
$query->where[2]['conditions'][0]["value"] = '(?:^|\W).*(?:$|\W)';
$query->where[2]['conditions'][1]["value"] = '(?:^|\W).*(?:$|\W)';
}
// If the search value requests a status of 'Unsupported' then
// we need to make sure the query searches for states 2 or 3.
// As well as for any other status selected.
$statusValues = $queryOptions['sg_pr_status'] ?? NULL;
if (is_array($statusValues)) {
// If 3 is selected then add 4 to the search condition.
if (!empty($statusValues["3"])) {
$query->where[2]['conditions'][] = [
"field" => '"sgd_enabled_project.status_id"',
"value" => '2',
"operator" => '=',
];
}
}
}
}
/**
* Implements hook_preprocess_HOOK().
*
* {@inheritdoc }
*/
function sgd_dashboard_preprocess_views_view_field(array &$variables): void {
$modulePath = \Drupal::service('extension.list.module')->getPath('sgd_dashboard');
$view_field = $variables['field']->field;
if ($view_field == 'core_secure' || $view_field == 'contrib_secure') {
/** @var \Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle $website */
$website = $variables['row']->_entity;
// Get tim ewhen site was last checked.
$lastChecked = $website->field_site_last_checked->value;
if (!empty($website->field_site_last_checked->value)) {
$security_status = $variables['field']->getValue($variables['row']);
$classes = $security_status ? "status-5" : "status-1";
$security_status_message = $security_status ? t("Secure") : t("Not secure");
$variables['output'] = Markup::create('<span class="' . $classes . ' ">' . $security_status_message . '</span>');
}
// Never been successfully scanned so status is unknown.
else {
$classes = "status-1";
$security_status_message = t("Unknown");
$variables['output'] = Markup::create('<span class="' . $classes . ' ">' . $security_status_message . '</span>');
}
}
elseif ($view_field == 'status_id') {
$status_id = $variables['field']->getValue($variables['row']);
$status_str = match($status_id) {
'5' => t('Up-to-date'),
'4' => t('Updates available'),
'3' => t('Unsupported'),
'2' => t('Unsupported'),
'1' => t('Security updates'),
'' => t('Features made'),
'-1' => t('Unknown'),
'-2' => t('Unknown'),
'-3' => t('Unknown'),
'-4' => t('Unknown'),
};
$classes = "status-$status_id";
$variables['output'] = Markup::create('<span class="' . $classes . ' ">' . $status_str . '</span>');
}
elseif ($view_field == 'drupal_version') {
/** @var \Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle $website */
$website = $variables['row']->_entity;
$drupal_version_support_status = $website->getDataEntity()->getDrupalSupportStatus();
$drupal_version_support_status_message = $website->getDataEntity()->getDrupalSupportStatusMessage();
$eol_hourglass_colour = '';
if (!empty($drupal_version_support_status)) {
$eol_hourglass_colour = match($drupal_version_support_status->render()) {
'danger' => 'red',
'warning' => 'yellow',
'ok' => 'green',
};
}
$eol_hourglass_image = '<img src="/' . $modulePath . '/assets/hourglass-' . $eol_hourglass_colour . '-192x192.png" title="' . $drupal_version_support_status_message . '" class="eol-icon"/>';
$drupal_version = $variables['field']->getValue($variables['row']);
$version_output = !empty($drupal_version) ? $drupal_version : t('n/a');
$variables['output'] = Markup::create($eol_hourglass_image . '<span class="drupal-version-support-status">' . $version_output . '</span>');
}
elseif ($view_field == 'php_version') {
/** @var \Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle $website */
$website = $variables['row']->_entity;
$php_status = $website->getDataEntity()->getPhpSupportStatus();
$php_status_message = $website->getDataEntity()->getPhpSupportStatusMessage();
$eol_hourglass_colour = match($php_status->render()) {
'danger' => 'red',
'warning' => 'yellow',
'ok' => 'green',
};
$eol_hourglass_image = '<img src="/' . $modulePath . '/assets/hourglass-' . $eol_hourglass_colour . '-192x192.png" title="' . $php_status_message . '" class="eol-icon"/>';
$output = (!empty($variables['output'])) ? $variables['output']->__toString() : t('n/a');
$variables['output'] = Markup::create($eol_hourglass_image . '<span class="php-security-support-status">' . $output . '</span>');
}
elseif ($view_field == 'db_version') {
/** @var \Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle $website */
$website = $variables['row']->_entity;
$db_status = $website->getDataEntity()->getDbSupportStatus();
$db_status_message = $website->getDataEntity()->getDbSupportStatusMessage();
$eol_hourglass_colour = match($db_status->render()) {
'danger' => 'red',
'warning' => 'yellow',
'ok' => 'green',
};
$eol_hourglass_image = '<img src="/' . $modulePath . '/assets/hourglass-' . $eol_hourglass_colour . '-192x192.png" title="' . $db_status_message . '" class="eol-icon"/>';
$output = (!empty($variables['output'])) ? $variables['output']->__toString() : t('n/a');
$variables['output'] = Markup::create($eol_hourglass_image . '<span class="db-version-support-status">' . $output . '</span>');
}
elseif ($view_field == 'field_site_last_checked') {
/** @var \Drupal\sgd_dashboard\Entity\Bundle\WebsiteBundle $website */
$website = $variables['row']->_entity;
// Get values we need and work out delta.
$lastChecked = $website->field_site_last_checked->value;
$now = time();
$delta = $now - $lastChecked;
// Workout status class value.
// If last checked more than 7 days ago then error.
if ($lastChecked < $now - (60 * 60 * 24 * 7)) {
$status = 'danger';
}
// If last checked more than a day ago then warning.
elseif ($lastChecked < $now - (60 * 60 * 24)) {
$status = 'warning';
}
// All good.
else {
$status = 'ok';
}
$classes = "status-last-checked-$status";
// Workout the tooltip/status message.
$lastCheckedDate = date("l jS \of F Y \a\\t H:i:s", $website->field_site_last_checked->value);
$statusMessage = t("Website last checked on @date.", ['@date' => $lastCheckedDate]);
// If less than a minute ago.
if ($delta < 60) {
$output = t('< 1 minute ago');
}
// If delta less than 1 hour then show mins since run.
elseif ($delta < (60 * 60)) {
$output = t('@time minutes ago', ['@time' => round($delta / 60, 0, PHP_ROUND_HALF_DOWN)]);
}
// If delta less than 1 day then show hours since run.
elseif ($delta < (60 * 60 * 24)) {
$output = t('@time hours ago', ['@time' => round($delta / (60 * 60), 0, PHP_ROUND_HALF_DOWN)]);
}
// Otherwise show days since run.
elseif ($lastChecked > 0) {
$output = t('@time days ago', ['@time' => round($delta / (60 * 60 * 24), 0, PHP_ROUND_HALF_DOWN)]);
}
else {
$statusMessage = t("Website has never been checked.");
$output = t('Never');
}
// Build the final output for the view field.
$variables['output'] = Markup::create('<span class="' . $classes . ' " title="' . $statusMessage . '">' . $output . '</span>');
}
}
/**
* Implements hook_views_pre_render().
*/
function sgd_dashboard_views_pre_render(ViewExecutable $view) {
if (isset($view)) {
if ($view->storage->id() == 'websites' || $view->storage->id() == 'website_projects') {
$view->element['#attached']['library'][] = 'sgd_dashboard/sgd_views';
}
}
}
/**
* Implements hook_entity_bundle_info_alter().
*/
function sgd_dashboard_entity_bundle_info_alter(array &$bundles): void {
if (isset($bundles['node']['website'])) {
$bundles['node']['website']['class'] = WebsiteBundle::class;
}
}
