Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
106 / 106
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
VisitorsPopularBlock
100.00% covered (success)
100.00%
106 / 106
100.00% covered (success)
100.00%
10 / 10
20
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 defaultConfiguration
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 blockAccess
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 blockForm
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
1
 entityTypes
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 blockSubmit
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 build
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 getCacheTags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 entityLabelList
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace Drupal\visitors\Plugin\Block;
4
5use Drupal\Core\Access\AccessResult;
6use Drupal\Core\Block\Attribute\Block;
7use Drupal\Core\Block\BlockBase;
8use Drupal\Core\Config\ConfigFactoryInterface;
9use Drupal\Core\Entity\EntityRepositoryInterface;
10use Drupal\Core\Entity\EntityTypeManagerInterface;
11use Drupal\Core\Form\FormStateInterface;
12use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
13use Drupal\Core\Render\RendererInterface;
14use Drupal\Core\Session\AccountInterface;
15use Drupal\Core\StringTranslation\TranslatableMarkup;
16use Drupal\visitors\VisitorsCounterInterface;
17use Symfony\Component\DependencyInjection\ContainerInterface;
18
19/**
20 * Provides a 'Popular content' block.
21 */
22#[Block(
23  id: "visitors_popular_block",
24  admin_label: new TranslatableMarkup("Popular content")
25)]
26final class VisitorsPopularBlock extends BlockBase implements ContainerFactoryPluginInterface {
27
28  /**
29   * The entity type manager.
30   *
31   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
32   */
33  protected $entityTypeManager;
34
35  /**
36   * The entity repository service.
37   *
38   * @var \Drupal\Core\Entity\EntityRepositoryInterface
39   */
40  protected $entityRepository;
41
42  /**
43   * The storage for visitor counter.
44   *
45   * @var \Drupal\visitors\VisitorsCounterInterface
46   */
47  protected $statisticsStorage;
48
49  /**
50   * The renderer interface.
51   *
52   * @var \Drupal\Core\Render\RendererInterface
53   */
54  protected $renderer;
55
56  /**
57   * The configuration factory.
58   *
59   * @var \Drupal\Core\Config\ConfigFactoryInterface
60   */
61  protected $configFactory;
62
63  /**
64   * Constructs a StatisticsPopularBlock object.
65   *
66   * @param array $configuration
67   *   A configuration array containing information about the plugin instance.
68   * @param string $plugin_id
69   *   The plugin_id for the plugin instance.
70   * @param mixed $plugin_definition
71   *   The plugin implementation definition.
72   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
73   *   The entity type manager.
74   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
75   *   The entity repository service.
76   * @param \Drupal\visitors\VisitorsCounterInterface $statistics_storage
77   *   The storage for statistics.
78   * @param \Drupal\Core\Render\RendererInterface $renderer
79   *   The renderer configuration array.
80   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
81   *   The configuration factory.
82   */
83  public function __construct(
84    array $configuration,
85    $plugin_id,
86    $plugin_definition,
87    EntityTypeManagerInterface $entity_type_manager,
88    EntityRepositoryInterface $entity_repository,
89    VisitorsCounterInterface $statistics_storage,
90    RendererInterface $renderer,
91    ConfigFactoryInterface $config_factory,
92  ) {
93    parent::__construct($configuration, $plugin_id, $plugin_definition);
94    $this->entityTypeManager = $entity_type_manager;
95    $this->entityRepository = $entity_repository;
96    $this->statisticsStorage = $statistics_storage;
97    $this->renderer = $renderer;
98    $this->configFactory = $config_factory;
99  }
100
101  /**
102   * {@inheritdoc}
103   */
104  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
105    return new self(
106      $configuration,
107      $plugin_id,
108      $plugin_definition,
109      $container->get('entity_type.manager'),
110      $container->get('entity.repository'),
111      $container->get('visitors.counter'),
112      $container->get('renderer'),
113      $container->get('config.factory'),
114    );
115  }
116
117  /**
118   * {@inheritdoc}
119   */
120  public function defaultConfiguration() {
121    return [
122      'top_day_num' => 0,
123      'top_all_num' => 0,
124      'top_last_num' => 0,
125      'entity_type' => 'node',
126    ];
127  }
128
129  /**
130   * {@inheritdoc}
131   */
132  protected function blockAccess(AccountInterface $account) {
133    $settings = $this->configFactory->get('visitors.settings');
134    $entity_type = $this->configuration['entity_type'] ?? '';
135    $allowed_entity_types = $settings->get('counter.entity_types');
136    $disabled_or_entity_type_not_allowed = !$settings->get('counter.enabled') || !in_array($entity_type, $allowed_entity_types);
137    if ($disabled_or_entity_type_not_allowed) {
138      return AccessResult::forbidden();
139    }
140
141    return AccessResult::allowedIfHasPermission($account, 'access content');
142  }
143
144  /**
145   * {@inheritdoc}
146   */
147  public function blockForm($form, FormStateInterface $form_state) {
148    // Popular content block settings.
149    $numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40];
150    $numbers = ['0' => $this->t('Disabled')] + array_combine($numbers, $numbers);
151    $form['statistics_block_top_day_num'] = [
152      '#type' => 'select',
153      '#title' => $this->t("Number of day's top views to display"),
154      '#default_value' => $this->configuration['top_day_num'],
155      '#options' => $numbers,
156      '#description' => $this->t('How many content items to display in "day" list.'),
157    ];
158    $form['statistics_block_top_all_num'] = [
159      '#type' => 'select',
160      '#title' => $this->t('Number of all time views to display'),
161      '#default_value' => $this->configuration['top_all_num'],
162      '#options' => $numbers,
163      '#description' => $this->t('How many content items to display in "all time" list.'),
164    ];
165    $form['statistics_block_top_last_num'] = [
166      '#type' => 'select',
167      '#title' => $this->t('Number of most recent views to display'),
168      '#default_value' => $this->configuration['top_last_num'],
169      '#options' => $numbers,
170      '#description' => $this->t('How many content items to display in "recently viewed" list.'),
171    ];
172    $form['entity_type'] = [
173      '#type' => 'radios',
174      '#title' => $this->t('Entity types'),
175      '#options' => $this->entityTypes(),
176      '#default_value' => $this->configuration['entity_type'] ?? 'node',
177      '#description' => $this->t('Select entity types to display popular content.'),
178    ];
179
180    return $form;
181  }
182
183  /**
184   * Returns a list of entity types.
185   */
186  protected function entityTypes() {
187    $allowed_entity_types = $this->configFactory->get('visitors.settings')->get('counter.entity_types');
188    $entity_types_list = [];
189    $entity_definitions = $this->entityTypeManager->getDefinitions();
190    foreach ($entity_definitions as $entity_name => $entity_definition) {
191      if (!in_array($entity_name, $allowed_entity_types)) {
192        continue;
193      }
194      $entity_types_list[$entity_name] = (string) $entity_definition->getLabel();
195    }
196    asort($entity_types_list);
197
198    return $entity_types_list;
199  }
200
201  /**
202   * {@inheritdoc}
203   */
204  public function blockSubmit($form, FormStateInterface $form_state) {
205    $this->configuration['top_day_num'] = $form_state->getValue('statistics_block_top_day_num');
206    $this->configuration['top_all_num'] = $form_state->getValue('statistics_block_top_all_num');
207    $this->configuration['top_last_num'] = $form_state->getValue('statistics_block_top_last_num');
208    $this->configuration['entity_type'] = $form_state->getValue('entity_type');
209  }
210
211  /**
212   * {@inheritdoc}
213   */
214  public function build() {
215    $content = [];
216    $entity_type = $this->configuration['entity_type'] ?? 'node';
217    if ($this->configuration['top_day_num'] > 0) {
218      $ids = $this->statisticsStorage->fetchAll($entity_type, 'today', $this->configuration['top_day_num']);
219      if ($ids) {
220        $content['top_day'] = $this->entityLabelList($ids, $this->t("Today's:"));
221        $content['top_day']['#suffix'] = '<br />';
222      }
223    }
224
225    if ($this->configuration['top_all_num'] > 0) {
226      $ids = $this->statisticsStorage->fetchAll($entity_type, 'total', $this->configuration['top_all_num']);
227      if ($ids) {
228        $content['top_all'] = $this->entityLabelList($ids, $this->t('All time:'));
229        $content['top_all']['#suffix'] = '<br />';
230      }
231    }
232
233    if ($this->configuration['top_last_num'] > 0) {
234      $ids = $this->statisticsStorage->fetchAll($entity_type, 'timestamp', $this->configuration['top_last_num']);
235      $content['top_last'] = $this->entityLabelList($ids, $this->t('Last viewed:'));
236      $content['top_last']['#suffix'] = '<br />';
237    }
238
239    return $content;
240  }
241
242  /**
243   * {@inheritdoc}
244   */
245  public function getCacheTags() {
246    return ['config:visitors.settings'];
247  }
248
249  /**
250   * Generates the ordered array of entity links for build().
251   *
252   * @param int[] $ids
253   *   An ordered array of entity ids.
254   * @param string $title
255   *   The title for the list.
256   *
257   * @return array
258   *   A render array for the list.
259   */
260  protected function entityLabelList(array $ids, $title) {
261    $entity_type = $this->configuration['entity_type'] ?? 'node';
262    $entities = $this->entityTypeManager->getStorage($entity_type)->loadMultiple($ids);
263
264    $items = [];
265    foreach ($ids as $id) {
266      $entity = $this->entityRepository->getTranslationFromContext($entities[$id]);
267      $item = $entity->toLink()->toRenderable();
268      $this->renderer->addCacheableDependency($item, $entity);
269      $items[] = $item;
270    }
271
272    return [
273      '#theme' => 'item_list',
274      '#items' => $items,
275      '#title' => $title,
276      '#cache' => [
277        'tags' => $this->entityTypeManager->getDefinition($entity_type)->getListCacheTags(),
278      ],
279    ];
280  }
281
282}