monitoring-8.x-1.x-dev/src/Sensor/SensorManager.php
src/Sensor/SensorManager.php
<?php
/**
* @file
* Contains \Drupal\monitoring\Sensor\SensorManager.
*/
namespace Drupal\monitoring\Sensor;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\monitoring\Attribute\SensorPlugin;
use Drupal\monitoring\Entity\SensorConfig;
use Drupal\search_api\Entity\Index;
/**
* Manages sensor definitions and settings.
*
* Provides list of enabled sensors.
* Sensors can be listed by category.
*
* Maintains a (non persistent) info cache.
* Enables and disables sensors.
*
*/
class SensorManager extends DefaultPluginManager {
use StringTranslationTrait;
/**
* List of sensor definitions.
*
* @var \Drupal\monitoring\Entity\SensorConfig[]
*/
protected $sensor_config;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $config;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a sensor manager.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ConfigFactoryInterface $config, EntityTypeManagerInterface $entity_type_manager, MessengerInterface $messenger) {
parent::__construct('Plugin/monitoring/SensorPlugin', $namespaces, $module_handler, '\Drupal\monitoring\SensorPlugin\SensorPluginInterface', SensorPlugin::class, 'Drupal\monitoring\Annotation\SensorPlugin');
$this->alterInfo('monitoring_sensor_plugins');
$this->setCacheBackend($cache_backend, 'monitoring_sensor_plugins');
$this->config = $config;
$this->entityTypeManager = $entity_type_manager;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public function createInstance($plugin_id, array $configuration = array()) {
// Configuration contains SensorConfig object. Extracting
// it to use for sensor object creation.
$sensor_config = $configuration['sensor_config'];
$definition = $this->getDefinition($plugin_id);
// SensorPlugin class from the sensor definition.
/** @var \Drupal\monitoring\SensorPlugin\SensorPluginInterface $class */
$class = $definition['class'];
// Creating instance of the sensor. Refer SensorPlugin.php for arguments.
return $class::create(\Drupal::getContainer(), $sensor_config, $plugin_id, $definition);
}
/**
* Returns monitoring sensor config.
*
* @return \Drupal\monitoring\Entity\SensorConfig[]
* List of SensorConfig instances.
*/
public function getAllSensorConfig() {
$sensors = SensorConfig::loadMultiple();
// Sort the sensors by category and label.
uasort($sensors, "\Drupal\monitoring\Entity\SensorConfig::sort");
return $sensors;
}
/**
* Returns monitoring sensor config for enabled sensors.
*
* @return \Drupal\monitoring\Entity\SensorConfig[]
* List of SensorConfig instances.
*/
public function getEnabledSensorConfig() {
$enabled_sensors = array();
foreach ($this->getAllSensorConfig() as $sensor_config) {
if ($sensor_config->isEnabled()) {
$enabled_sensors[$sensor_config->id()] = $sensor_config;
}
}
return $enabled_sensors;
}
/**
* Returns monitoring sensor config for a given sensor.
*
* Directly use SensorConfig::load($name) if sensor existence assured.
*
* @param string $sensor_name
* Sensor id.
*
* @return \Drupal\monitoring\Entity\SensorConfig
* A single SensorConfig instance.
*
* @throws \Drupal\monitoring\Sensor\NonExistingSensorException
* Thrown if the requested sensor does not exist.
*/
public function getSensorConfigByName($sensor_name) {
$sensor_config = SensorConfig::load($sensor_name);
if ($sensor_config == NULL) {
throw new NonExistingSensorException(new FormattableMarkup('Sensor @sensor_name does not exist', array('@sensor_name' => $sensor_name)));
}
return $sensor_config;
}
/**
* Gets sensor config grouped by categories.
*
* @todo: The enabled flag is strange, FALSE should return all?
*
* @param bool $enabled
* Sensor isEnabled flag.
*
* @return \Drupal\monitoring\Entity\SensorConfig[][]
* Sensor config.
*/
public function getSensorConfigByCategories($enabled = TRUE) {
$config_by_categories = array();
foreach ($this->getAllSensorConfig() as $sensor_name => $sensor_config) {
if ($sensor_config->isEnabled() != $enabled) {
continue;
}
$config_by_categories[$sensor_config->getCategory()][$sensor_name] = $sensor_config;
}
return $config_by_categories;
}
/**
* Reset the static cache.
*/
public function resetCache() {
$this->sensor_config = array();
}
/**
* Enable a sensor.
*
* Checks if the sensor is enabled and enables it if not.
*
* @param string $sensor_name
* Sensor name to be enabled.
*
* @throws \Drupal\monitoring\Sensor\NonExistingSensorException
* Thrown if the requested sensor does not exist.
*/
public function enableSensor($sensor_name) {
$sensor_config = $this->getSensorConfigByName($sensor_name);
if (!$sensor_config->isEnabled()) {
$sensor_config->status = TRUE;
$sensor_config->save();
$available_sensors = \Drupal::state()->get('monitoring.available_sensors', array());
if (!isset($available_sensors[$sensor_name])) {
// Use the watchdog message as the disappeared sensor does when new
// sensors are detected.
\Drupal::logger('monitoring')->notice('@count new sensor/s added: @names',
array('@count' => 1, '@names' => $sensor_name));
}
$available_sensors[$sensor_name]['enabled'] = TRUE;
$available_sensors[$sensor_name]['name'] = $sensor_name;
\Drupal::state()->set('monitoring.available_sensors', $available_sensors);
}
}
/**
* Disable a sensor.
*
* Checks if the sensor is enabled and if so it will disable it and remove
* from the active sensor list.
*
* @param string $sensor_name
* Sensor name to be disabled.
*
* @throws \Drupal\monitoring\Sensor\NonExistingSensorException
* Thrown if the requested sensor does not exist.
*/
public function disableSensor($sensor_name) {
$sensor_config = $this->getSensorConfigByName($sensor_name);
if ($sensor_config->isEnabled()) {
$sensor_config->status = FALSE;
$sensor_config->save();
$available_sensors = \Drupal::state()->get('monitoring.available_sensors', array());
$available_sensors[$sensor_name]['enabled'] = FALSE;
$available_sensors[$sensor_name]['name'] = $sensor_name;
\Drupal::state()->set('monitoring.available_sensors', $available_sensors);
}
}
/**
* Rebuild the sensor list.
*
* Automatically updates sensors based on currently installed modules and
* their requirement hooks.
*/
public function rebuildSensors($display_message = TRUE) {
if ($this->config->get('monitoring.settings')->get('disable_sensor_autocreate')) {
return;
}
// Declaring a flag for updated sensors.
$updated_sensors = FALSE;
// Load .install files
if (file_exists(DRUPAL_ROOT . '/core/includes/install.inc')) {
include_once DRUPAL_ROOT . '/core/includes/install.inc';
}
drupal_load_updates();
$storage = $this->entityTypeManager->getStorage('monitoring_sensor_config');
// Iterate through the installed implemented modules to see if
// there are any new requirements hook updates and initialize them.
$requirement_providing_modules = [];
\Drupal::moduleHandler()->invokeAllWith('requirements', function (callable $hook, string $module) use ($storage, &$updated_sensors, &$requirement_providing_modules, $display_message) {
$requirement_providing_modules[] = $module;
if (!$storage->load('core_requirements_' . $module)) {
if ($this->initializeRequirementsSensor($module)) {
if ($display_message) {
$this->messenger->addMessage($this->t('The sensor @sensor has been added.', ['@sensor' => $storage->load('core_requirements_' . $module)->label()]));
}
$updated_sensors = TRUE;
}
}
});
// Iterate through the installed implemented modules to see if
// there are any new requirements hook updates and initialize them.
\Drupal::moduleHandler()->invokeAllWith('runtime_requirements', function (callable $hook, string $module) use ($storage, &$updated_sensors, &$requirement_providing_modules, $display_message) {
$requirement_providing_modules[] = $module;
if (!$storage->load('core_requirements_' . $module)) {
if ($this->initializeRequirementsSensor($module)) {
if ($display_message) {
$this->messenger->addMessage($this->t('The sensor @sensor has been added.', ['@sensor' => $storage->load('core_requirements_' . $module)->label()]));
}
$updated_sensors = TRUE;
}
}
});
// Delete any updated sensors that are not implemented in the requirements
// hook anymore.
$sensor_ids = $storage->getQuery()
->condition('plugin_id', 'core_requirements')
->accessCheck(FALSE)
->execute();
/** @var \Drupal\monitoring\SensorConfigInterface $sensor */
foreach ($storage->loadMultiple($sensor_ids) as $sensor) {
$module = $sensor->getSetting('module');
if (!in_array($module, $requirement_providing_modules)) {
$this->messenger->addMessage($this->t('The sensor @sensor has been removed.', ['@sensor' => $sensor->label()]));
$sensor->delete();
$updated_sensors = TRUE;
// Remove the sensor from the list of available sensors.
$available_sensors = \Drupal::state()->get('monitoring.available_sensors', []);
unset($available_sensors[$sensor->id()]);
\Drupal::state()->set('monitoring.available_sensors', $available_sensors);
}
}
/** @var \Drupal\Core\Config\StorageInterface[] $config_storages */
$config_storages[] = new FileStorage($this->moduleHandler->getModule('monitoring')->getPath() . '/config/install');
$config_storages[] = new FileStorage($this->moduleHandler->getModule('monitoring')->getPath() . '/config/optional');
// Rebuilds all non-addable sensors.
foreach ($this->getDefinitions() as $sensor_definition) {
if (!$sensor_definition['addable']) {
if ($sensor_definition['id'] !== 'update_status') {
$config_ids = [$sensor_definition['id']];
}
else {
$config_ids = ['update_core', 'update_contrib'];
}
foreach ($config_ids as $config_id) {
// Checks if the sensor is not created.
if (!$storage->load($config_id)) {
// Check the two directories install and optional for sensors that need to be created.
foreach ($config_storages as $config_storage) {
if ($data = $config_storage->read('monitoring.sensor_config.' . $config_id)) {
$storage->create($data)->trustData()->save();
$this->messenger->addMessage($this->t('The sensor @sensor has been created.', ['@sensor' => (string) $sensor_definition['label']]));
$updated_sensors = TRUE;
break;
}
}
}
}
}
}
// Declares initial set of search api sensors (if module exists).
if (\Drupal::moduleHandler()->moduleExists('search_api')) {
foreach (Index::loadMultiple() as $index) {
if (!$storage->load('search_api_' . $index->id())) {
$sensor = $storage->create([
'id' => 'search_api_' . $index->id(),
'label' => new FormattableMarkup('Search index queue size of @index', ['@index' => $index->label()]),
'plugin_id' => 'search_api_unindexed',
'value_type' => 'number',
'value_label' => 'Unindexed items',
'category' => 'Search API',
// Cache for 15 minutes.
'caching_time' => 900,
'status' => TRUE,
'settings' => array(
'index_id' => $index->id(),
),
'dependencies' => ['module' => 'search_api'],
]);
$sensor->save();
$updated_sensors = TRUE;
}
}
}
// Set message to inform the user that there were no updated sensors.
if (!$updated_sensors && $display_message) {
$this->messenger->addMessage($this->t('No changes were made.'));
}
}
/**
* Initialize core requirements sensors.
*
* @param string $module
* Name of the module to create a requirements sensor for.
*
* @return bool
* TRUE if a sensor was created, FALSE otherwise.
*/
protected function initializeRequirementsSensor($module) {
// Skip update module as there is a separate sensors for core and contrib.
if ($module == 'update') {
return FALSE;
}
$sensor = SensorConfig::create([
'id' => 'core_requirements_' . $module,
'label' => new FormattableMarkup('Module @module', ['@module' => $module]),
'description' => new FormattableMarkup('Requirements of the @module module', ['@module' => $module]),
'plugin_id' => 'core_requirements',
'value_type' => 'no_value',
'category' => 'Requirements',
'caching_time' => 3600,
'status' => TRUE,
'settings' => [
'module' => $module,
// List requirements keys which reports will be suppressed.
'exclude_keys' => [],
],
'dependencies' => ['module' => $module],
]);
// Ignore the cron key for system requirements, as we have a separate
// sensor for this.
if ($module == 'system') {
$sensor->settings['exclude_keys'][] = 'cron';
}
$sensor->save();
return TRUE;
}
/**
* Returns if an array is flat.
*
* @param array $array
* The array to check.
*
* @return bool
* TRUE if the array has no values that are arrays again.
*/
protected function isFlatArray(array $array) {
foreach ($array as $value) {
if (is_array($value)) {
return FALSE;
}
}
return TRUE;
}
}
