PageRenderTime 99ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Plugin/monitoring/SensorPlugin/ContentEntityAggregatorSensorPlugin.php

https://gitlab.com/Drulenium-bot/monitoring
PHP | 628 lines | 383 code | 72 blank | 173 comment | 32 complexity | 2d50d0a02a9f0bbb7ae0f26cd6c73904 MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\monitoring\Plugin\monitoring\SensorPlugin\ContentEntityAggregatorSensorPlugin.
  5. */
  6. namespace Drupal\monitoring\Plugin\monitoring\SensorPlugin;
  7. use Drupal\Component\Utility\SafeMarkup;
  8. use Drupal\Core\Database\Database;
  9. use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  10. use Drupal\Core\Entity\DependencyTrait;
  11. use Drupal\Core\Entity\Entity;
  12. use Drupal\Core\Entity\EntityManagerInterface;
  13. use Drupal\Core\Entity\FieldableEntityInterface;
  14. use Drupal\Core\Entity\Query\QueryAggregateInterface;
  15. use Drupal\Core\Entity\Query\QueryFactory;
  16. use Drupal\Core\Form\FormStateInterface;
  17. use Drupal\Core\TypedData;
  18. use Drupal\monitoring\Entity\SensorConfig;
  19. use Drupal\monitoring\Result\SensorResultInterface;
  20. use Drupal\monitoring\SensorPlugin\DatabaseAggregatorSensorPluginBase;
  21. use Drupal\monitoring\SensorPlugin\ExtendedInfoSensorPluginInterface;
  22. use Symfony\Component\DependencyInjection\ContainerInterface;
  23. /**
  24. * Content entity database aggregator.
  25. *
  26. * It utilises the entity query aggregate functionality.
  27. *
  28. * @SensorPlugin(
  29. * id = "entity_aggregator",
  30. * label = @Translation("Content Entity Aggregator"),
  31. * description = @Translation("Utilises the entity query aggregate functionality."),
  32. * addable = TRUE
  33. * )
  34. */
  35. class ContentEntityAggregatorSensorPlugin extends DatabaseAggregatorSensorPluginBase implements ExtendedInfoSensorPluginInterface {
  36. use DependencySerializationTrait;
  37. use DependencyTrait;
  38. /**
  39. * Local variable to store the field that is used as aggregate.
  40. *
  41. * @var string
  42. * Field name.
  43. */
  44. protected $aggregateField;
  45. /**
  46. * Local variable to store \Drupal::entityManger().
  47. *
  48. * @var \Drupal\Core\Entity\EntityManagerInterface
  49. */
  50. protected $entityManager;
  51. /**
  52. * Local variable to store the query factory.
  53. *
  54. * @var \Drupal\Core\Entity\Query\QueryFactory
  55. */
  56. protected $entityQueryFactory;
  57. /**
  58. * Allows plugins to control if the entity type can be configured.
  59. *
  60. * @var bool
  61. */
  62. protected $configurableEntityType = TRUE;
  63. /**
  64. * Builds the entity aggregate query.
  65. *
  66. * @return \Drupal\Core\Entity\Query\QueryAggregateInterface
  67. * The entity query object.
  68. */
  69. protected function getEntityQueryAggregate() {
  70. $entity_info = $this->entityManager->getDefinition($this->sensorConfig->getSetting('entity_type'), TRUE);
  71. // Get aggregate query for the entity type.
  72. $query = $this->entityQueryFactory->getAggregate($this->sensorConfig->getSetting('entity_type'));
  73. $this->aggregateField = $entity_info->getKey('id');
  74. $this->addAggregate($query);
  75. // Add conditions.
  76. foreach ($this->getConditions() as $condition) {
  77. if (empty($condition['field'])) {
  78. continue;
  79. }
  80. $query->condition($condition['field'], $condition['value'], isset($condition['operator']) ? $condition['operator'] : NULL);
  81. }
  82. // Apply time interval on field.
  83. if ($this->getTimeIntervalField() && $this->getTimeIntervalValue()) {
  84. $query->condition($this->getTimeIntervalField(), REQUEST_TIME - $this->getTimeIntervalValue(), '>');
  85. }
  86. return $query;
  87. }
  88. /**
  89. * Builds the entity query for verbose output.
  90. *
  91. * Similar to the aggregate query, but without aggregation.
  92. *
  93. * @return \Drupal\Core\Entity\Query\QueryInterface
  94. * The entity query object.
  95. *
  96. * @see getEntityQueryAggregate()
  97. */
  98. protected function getEntityQuery() {
  99. $entity_info = $this->entityManager->getDefinition($this->sensorConfig->getSetting('entity_type'), TRUE);
  100. // Get query for the entity type.
  101. $query = $this->entityQueryFactory->get($this->sensorConfig->getSetting('entity_type'));
  102. // Add conditions.
  103. foreach ($this->getConditions() as $condition) {
  104. if (empty($condition['field'])) {
  105. continue;
  106. }
  107. $query->condition($condition['field'], $condition['value'], isset($condition['operator']) ? $condition['operator'] : NULL);
  108. }
  109. // Apply time interval on field.
  110. if ($this->getTimeIntervalField() && $this->getTimeIntervalValue()) {
  111. $query->condition($this->getTimeIntervalField(), REQUEST_TIME - $this->getTimeIntervalValue(), '>');
  112. }
  113. // Order by most recent or id.
  114. if ($this->getTimeIntervalField()) {
  115. $query->sort($this->getTimeIntervalField(), 'DESC');
  116. }
  117. else {
  118. $query->sort($entity_info->getKey('id'), 'DESC');
  119. }
  120. return $query;
  121. }
  122. /**
  123. * {@inheritdoc}
  124. */
  125. public function __construct(SensorConfig $sensor_config, $plugin_id, $plugin_definition, EntityManagerInterface $entityManager, QueryFactory $query_factory) {
  126. parent::__construct($sensor_config, $plugin_id, $plugin_definition);
  127. $this->entityManager = $entityManager;
  128. $this->entityQueryFactory = $query_factory;
  129. }
  130. /**
  131. * {@inheritdoc}
  132. */
  133. public static function create(ContainerInterface $container, SensorConfig $sensor_config, $plugin_id, $plugin_definition) {
  134. return new static(
  135. $sensor_config,
  136. $plugin_id,
  137. $plugin_definition,
  138. $container->get('entity.manager'),
  139. $container->get('entity.query')
  140. );
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function getDefaultConfiguration() {
  146. $default_config = array(
  147. 'settings' => array(
  148. 'entity_type' => 'node',
  149. ),
  150. );
  151. return $default_config;
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function runSensor(SensorResultInterface $result) {
  157. $query_result = $this->getEntityQueryAggregate()->execute();
  158. $entity_type = $this->sensorConfig->getSetting('entity_type');
  159. $entity_info = $this->entityManager->getDefinition($entity_type);
  160. if (isset($query_result[0][$entity_info->getKey('id') . '_count'])) {
  161. $records_count = $query_result[0][$entity_info->getKey('id') . '_count'];
  162. }
  163. else {
  164. $records_count = 0;
  165. }
  166. $result->setValue($records_count);
  167. }
  168. /**
  169. * {@inheritdoc}
  170. */
  171. public function resultVerbose(SensorResultInterface $result) {
  172. $output = [];
  173. if ($this->sensorConfig->getSetting('verbose_fields')) {
  174. $this->verboseResultUnaggregated($output);
  175. }
  176. return $output;
  177. }
  178. /**
  179. * Adds unaggregated verbose output to the render array $output.
  180. *
  181. * @param array &$output
  182. * Render array where the result will be added.
  183. */
  184. public function verboseResultUnaggregated(array &$output) {
  185. $output = [];
  186. /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */
  187. $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
  188. Database::startLog('monitoring_ceasp');
  189. // Fetch the last 10 matching entries, unaggregated.
  190. $entity_ids = $this->getEntityQuery()
  191. ->range(0, 10)
  192. ->execute();
  193. // Show query.
  194. $query_log = Database::getLog('monitoring_ceasp')[0];
  195. // Load entities.
  196. $entity_type_id = $this->sensorConfig->getSetting('entity_type');;
  197. $entities = $this->entityManager
  198. ->getStorage($entity_type_id)
  199. ->loadMultiple($entity_ids);
  200. // Get the fields to display from the settings.
  201. $fields = $this->sensorConfig->getSetting('verbose_fields', ['id', 'label']);
  202. // Render entities.
  203. $rows = [];
  204. /* @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
  205. foreach ($entities as $id => $entity) {
  206. $row = [];
  207. foreach ($fields as $field) {
  208. switch ($field) {
  209. case 'id':
  210. $row[] = $entity->id();
  211. break;
  212. case $this->getTimeIntervalField():
  213. $row[] = \Drupal::service('date.formatter')->format($entity->get($this->getTimeIntervalField())[0]->value, 'short');
  214. break;
  215. case 'label':
  216. $row[] = $entity->hasLinkTemplate('canonical') ? $entity->link() : $entity->label();
  217. break;
  218. default:
  219. // Make sure the field exists on this entity.
  220. if ($entity instanceof FieldableEntityInterface && $entity->hasField($field)) {
  221. try {
  222. // Get the main property as a fallback if the field can not be
  223. // viewed.
  224. $field_type = $entity->getFieldDefinition($field)->getFieldStorageDefinition()->getType();
  225. // If the field type has a default formatter, try to view it.
  226. if (isset($field_type_manager->getDefinition($field_type)['default_formatter'])) {
  227. $value = $entity->$field->view(['label' => 'hidden']);
  228. $row[] = \Drupal::service('renderer')->renderPlain($value);
  229. }
  230. else {
  231. // Fall back to the main property.
  232. $property = $entity->getFieldDefinition($field)->getFieldStorageDefinition()->getMainPropertyName();
  233. $row[] = SafeMarkup::checkPlain($entity->$field->$property);
  234. }
  235. } catch (\Exception $e) {
  236. // Catch any exception and display as an error.
  237. drupal_set_message(t('Error while trying to display %field: @error', ['%field' => $field, '@error' => $e->getMessage()]), 'error');
  238. $row[] = '';
  239. }
  240. }
  241. else {
  242. $row[] = '';
  243. }
  244. break;
  245. }
  246. }
  247. $rows[] = array(
  248. 'data' => $row,
  249. 'class' => 'entity',
  250. );
  251. }
  252. $header = $this->sensorConfig->getSetting('verbose_fields', [
  253. 'id',
  254. 'label'
  255. ]);
  256. $output['entities'] = array(
  257. '#type' => 'verbose_table_result',
  258. '#header' => $header,
  259. '#rows' => $rows,
  260. '#empty' => t('No matching entities were found.'),
  261. '#query' => $query_log['query'],
  262. '#query_args' => $query_log['args'],
  263. );
  264. return $output;
  265. }
  266. /**
  267. * {@inheritdoc}
  268. */
  269. public function calculateDependencies() {
  270. $entity_type_id = $this->sensorConfig->getSetting('entity_type');
  271. if (!$entity_type_id) {
  272. throw new \Exception(SafeMarkup::format('Sensor @id is missing the required entity_type setting.', array('@id' => $this->id())));
  273. }
  274. $entity_type = $this->entityManager->getDefinition($this->sensorConfig->getSetting('entity_type'));
  275. $this->addDependency('module', $entity_type->getProvider());
  276. return $this->dependencies;
  277. }
  278. /**
  279. * Adds UI for variables entity_type, conditions and verbose_fields.
  280. */
  281. public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
  282. $form = parent::buildConfigurationForm($form, $form_state);
  283. $settings = $this->sensorConfig->getSettings();
  284. $entity_types = $this->entityManager->getEntityTypeLabels();
  285. $options = [];
  286. foreach ($entity_types as $id => $label) {
  287. $class = $this->entityManager->getDefinition($id)->getClass();
  288. if (is_subclass_of($class, '\Drupal\Core\Entity\FieldableEntityInterface')) {
  289. $options[$id] = $label;
  290. };
  291. }
  292. $form['entity_type'] = array(
  293. '#type' => 'select',
  294. '#default_value' => $this->sensorConfig->getSetting('entity_type', 'node'),
  295. '#maxlength' => 255,
  296. '#options' => $options,
  297. '#title' => t('Entity Type'),
  298. '#ajax' => array(
  299. 'callback' => array($this, 'fieldsReplace'),
  300. 'wrapper' => 'selected-output',
  301. 'method' => 'replace',
  302. ),
  303. '#access' => $this->configurableEntityType,
  304. );
  305. if (!isset($settings['entity_type'])) {
  306. $form['entity_type']['#required'] = TRUE;
  307. }
  308. // Add conditions.
  309. // Fieldset for sensor list elements.
  310. $form['conditions_table'] = array(
  311. '#type' => 'fieldset',
  312. '#title' => t('Conditions'),
  313. '#prefix' => '<div id="selected-conditions">',
  314. '#suffix' => '</div>',
  315. '#tree' => FALSE,
  316. );
  317. // Table for included sensors.
  318. $form['conditions_table']['conditions'] = array(
  319. '#type' => 'table',
  320. '#tree' => TRUE,
  321. '#header' => array(
  322. 'field' => t('Field'),
  323. 'operator' => t('Operator'),
  324. 'value' => t('Value'),
  325. ),
  326. '#empty' => t(
  327. 'Add conditions to filter the results.'
  328. ),
  329. );
  330. // Fill the sensors table with form elements for each sensor.
  331. $conditions = array_values($this->sensorConfig->getSetting('conditions', []));
  332. if (empty($conditions)) {
  333. $conditions = [];
  334. }
  335. if (!$form_state->has('conditions_rows')) {
  336. $form_state->set('conditions_rows', count($conditions) + 1);
  337. }
  338. for ($i = 0; $i < $form_state->get('conditions_rows'); $i++) {
  339. $condition = isset($conditions[$i]) ? $conditions[$i] : array();
  340. $condition += array(
  341. 'field' => '',
  342. 'value' => '',
  343. 'operator' => '=',
  344. );
  345. // See operators https://api.drupal.org/api/drupal/includes%21entity.inc/function/EntityFieldQuery%3A%3AaddFieldCondition/7
  346. $operators = array(
  347. '=' => t('='),
  348. '!=' => t('!='),
  349. '<' => t('<'),
  350. '=<' => t('=<'),
  351. '>' => t('>'),
  352. '>=' => t('>='),
  353. 'STARTS_WITH' => t('STARTS_WITH'),
  354. 'CONTAINS' => t('CONTAINS'),
  355. //'BETWEEN' => t('BETWEEN'), // Requires
  356. //'IN' => t('IN'),
  357. //'NOT IN' => t('NOT IN'),
  358. //'EXISTS' => t('EXISTS'),
  359. //'NOT EXISTS' => t('NOT EXISTS'),
  360. //'LIKE' => t('LIKE'),
  361. //'IS NULL' => t('IS NULL'),
  362. //'IS NOT NULL' => t('IS NOT NULL'),
  363. );
  364. $form['conditions_table']['conditions'][$i] = array(
  365. 'field' => array(
  366. '#type' => 'textfield',
  367. '#default_value' => $condition['field'],
  368. '#title' => t('Field'),
  369. '#title_display' => 'invisible',
  370. '#size' => 20,
  371. //'#required' => TRUE,
  372. ),
  373. 'operator' => array(
  374. '#type' => 'select',
  375. '#default_value' => $condition['operator'],
  376. '#title' => t('Operator'),
  377. '#title_display' => 'invisible',
  378. '#options' => $operators,
  379. //'#required' => TRUE,
  380. ),
  381. 'value' => array(
  382. '#type' => 'textfield',
  383. '#default_value' => $condition['value'],
  384. '#title' => t('Value'),
  385. '#title_display' => 'invisible',
  386. '#size' => 40,
  387. //'#required' => TRUE,
  388. ),
  389. );
  390. }
  391. // Select element for available conditions.
  392. $form['conditions_table']['condition_add_button'] = array(
  393. '#type' => 'submit',
  394. '#value' => t('Add another condition'),
  395. '#ajax' => array(
  396. 'wrapper' => 'selected-conditions',
  397. 'callback' => array($this, 'conditionsReplace'),
  398. 'method' => 'replace',
  399. ),
  400. '#submit' => array(array($this, 'addConditionSubmit')),
  401. );
  402. // Fill the sensors table with form elements for each sensor.
  403. $form['verbose_fields'] = array(
  404. '#type' => 'details',
  405. '#title' => t('Verbose Output configuration'),
  406. '#prefix' => '<div id="selected-output">',
  407. '#suffix' => '</div>',
  408. '#open' => TRUE,
  409. );
  410. $entity_type = $this->entityManager->getDefinition($this->sensorConfig->getSetting('entity_type'));
  411. $available_fields = array_merge(['id', 'label'], array_keys($this->entityManager->getBaseFieldDefinitions($entity_type->id())));
  412. sort($available_fields);
  413. $form['verbose_fields']['#description'] = t('Available Fields for entity type %type: %fields.', [
  414. '%type' => $entity_type->getLabel(),
  415. '%fields' => implode(', ', $available_fields)
  416. ]);
  417. // Fill the sensors table with form elements for each sensor.
  418. $fields = $this->sensorConfig->getSetting('verbose_fields', ['id', 'label']);
  419. if (!$form_state->has('fields_rows')) {
  420. $form_state->set('fields_rows', count($fields) + 1);
  421. }
  422. for ($i = 0; $i < $form_state->get('fields_rows'); $i++) {
  423. $form['verbose_fields'][$i] = [
  424. '#type' => 'textfield',
  425. '#default_value' => isset($fields[$i]) ? $fields[$i] : '',
  426. '#maxlength' => 256,
  427. '#required' => FALSE,
  428. '#tree' => TRUE,
  429. ];
  430. }
  431. $form['verbose_fields']['field_add_button'] = array(
  432. '#type' => 'submit',
  433. '#value' => t('Add another field'),
  434. '#ajax' => array(
  435. 'wrapper' => 'selected-output',
  436. 'callback' => array($this, 'fieldsReplace'),
  437. 'method' => 'replace',
  438. ),
  439. '#submit' => array(array($this, 'addFieldSubmit')),
  440. );
  441. return $form;
  442. }
  443. /**
  444. * {@inheritdoc}
  445. */
  446. public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
  447. parent::submitConfigurationForm($form, $form_state);
  448. /** @var \Drupal\monitoring\Form\SensorForm $sensor_form */
  449. $sensor_form = $form_state->getFormObject();
  450. /** @var \Drupal\monitoring\SensorConfigInterface $sensor_config */
  451. $sensor_config = $sensor_form->getEntity();
  452. $settings = $sensor_config->getSettings();
  453. // Cleanup conditions, remove empty.
  454. $settings['conditions'] = [];
  455. foreach ($form_state->getValue('conditions') as $key => $condition) {
  456. if (!empty($condition['field'])) {
  457. $settings['conditions'][] = $condition;
  458. }
  459. }
  460. $verbose_fields = [];
  461. foreach ($form_state->getValue('settings')['verbose_fields'] as $key => $field) {
  462. if (!empty($field)) {
  463. $verbose_fields[] = $field;
  464. }
  465. };
  466. $settings['verbose_fields'] = array_unique($verbose_fields);
  467. $sensor_config->set('settings', $settings);
  468. }
  469. /**
  470. * Returns the updated 'conditions' fieldset for replacement by ajax.
  471. *
  472. * @param array $form
  473. * The updated form structure array.
  474. * @param FormStateInterface $form_state
  475. * The form state structure.
  476. *
  477. * @return array
  478. * The updated form component for the selected fields.
  479. */
  480. public function conditionsReplace(array $form, FormStateInterface $form_state) {
  481. return $form['plugin_container']['settings']['conditions_table'];
  482. }
  483. /**
  484. * Adds sensor to entity when 'Add another condition' button is pressed.
  485. *
  486. * @param array $form
  487. * The form structure array.
  488. * @param FormStateInterface $form_state
  489. * The form state structure.
  490. */
  491. public function addConditionSubmit(array $form, FormStateInterface $form_state) {
  492. $form_state->setRebuild();
  493. $form_state->set('conditions_rows', $form_state->get('conditions_rows') + 1);
  494. drupal_set_message(t('Condition added.'), 'status');
  495. }
  496. /**
  497. * Returns the updated 'verbose_fields' fieldset for replacement by ajax.
  498. *
  499. * @param array $form
  500. * The updated form structure array.
  501. * @param FormStateInterface $form_state
  502. * The form state structure.
  503. *
  504. * @return array
  505. * The updated form component for the selected fields.
  506. */
  507. public function fieldsReplace(array $form, FormStateInterface $form_state) {
  508. return $form['plugin_container']['settings']['verbose_fields'];
  509. }
  510. /**
  511. * Adds sensor to entity when 'Add another field' button is pressed.
  512. *
  513. * @param array $form
  514. * The form structure array.
  515. * @param FormStateInterface $form_state
  516. * The form state structure.
  517. */
  518. public function addFieldSubmit(array $form, FormStateInterface $form_state) {
  519. $form_state->setRebuild();
  520. $form_state->set('fields_rows', $form_state->get('fields_rows') + 1);
  521. drupal_set_message(t('Field added.'), 'status');
  522. }
  523. /**
  524. * {@inheritdoc}
  525. */
  526. public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
  527. parent::validateConfigurationForm($form, $form_state);
  528. $field_name = $form_state->getValue(array(
  529. 'settings',
  530. 'aggregation',
  531. 'time_interval_field',
  532. ));
  533. $entity_type_id = $form_state->getValue(array('settings', 'entity_type'));
  534. if (!empty($field_name) && !empty($entity_type_id)) {
  535. // @todo instead of validate, switch to a form select.
  536. $entity_info = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
  537. $data_type = NULL;
  538. if (!empty($entity_info[$field_name])) {
  539. $data_type = $entity_info[$field_name]->getPropertyDefinition('value')->getDataType();
  540. }
  541. if ($data_type != 'timestamp') {
  542. $form_state->setErrorByName('settings][aggregation][time_interval_field',
  543. t('The specified time interval field %name does not exist or is not type timestamp.', array('%name' => $field_name)));
  544. }
  545. }
  546. }
  547. /**
  548. * Add aggregation to the query.
  549. *
  550. * @param \Drupal\Core\Entity\Query\QueryAggregateInterface $query
  551. * The query.
  552. */
  553. protected function addAggregate(QueryAggregateInterface $query) {
  554. $query->aggregate($this->aggregateField, 'COUNT');
  555. }
  556. }