FALSE, 'content_types' => [], ] + parent::defaultConfiguration(); } public function blockForm($form, FormStateInterface $form_state) { $form = parent::blockForm($form, $form_state); $types = NodeType::loadMultiple(); $options = []; foreach ($types as $type) { $options[$type->id()] = $type->label(); } $form['content_types'] = [ '#type' => 'checkboxes', '#title' => $this->t('Content types to include'), '#options' => $options, '#default_value' => isset($this->configuration['content_types']) ? $this->configuration['content_types'] : [], '#description' => $this->t('Select one or more content types to include in the archive tree.'), '#required' => TRUE, ]; $form['expand_years'] = [ '#type' => 'checkbox', '#title' => $this->t('Expand all years by default'), '#default_value' => $this->configuration['expand_years'], '#description' => $this->t('If checked, all years will be expanded by default.'), ]; return $form; } public function blockSubmit($form, FormStateInterface $form_state) { parent::blockSubmit($form, $form_state); $this->configuration['expand_years'] = $form_state->getValue('expand_years'); $selected_types = array_keys(array_filter($form_state->getValue('content_types'))); $this->configuration['content_types'] = $selected_types; } public function build(): array { $storage = \Drupal::entityTypeManager()->getStorage('node'); $types = array_filter($this->configuration['content_types']); if (empty($types)) { // If no types selected, return empty block. return [ '#markup' => $this->t('No content types selected.'), ]; } // 1. Get node IDs with access check. $query = $storage->getQuery() ->condition('type', $types, 'IN') ->condition('status', 1) ->sort('created', 'DESC') ->accessCheck(TRUE); $nids = $query->execute(); $tree = []; if (!empty($nids)) { // 2. Fetch only nid and created fields for those nodes. $connection = \Drupal::database(); $result = $connection->select('node_field_data', 'n') ->fields('n', ['nid', 'created']) ->condition('n.nid', $nids, 'IN') ->execute(); foreach ($result as $row) { $created = $row->created; $year = date('Y', $created); $month = date('m', $created); if (!isset($tree[$year])) { $tree[$year] = ['count' => 0, 'months' => []]; } if (!isset($tree[$year]['months'][$month])) { $tree[$year]['months'][$month] = ['count' => 0]; } $tree[$year]['count']++; $tree[$year]['months'][$month]['count']++; } } krsort($tree); // Descending years foreach ($tree as $year => $data) { krsort($tree[$year]['months']); // Descending months } $output = ''; $expand = !empty($this->configuration['expand_years']); $type_arg = implode(',', $types); foreach ($tree as $year => $data) { $year_url = '/archive-tree/' . $year . '/' . $type_arg; $open = $expand ? ' open' : ''; $output .= ''; $output .= '' . $year . ' (' . $data['count'] . ')'; $output .= ''; if (!empty($data['months'])) { foreach ($data['months'] as $month => $month_data) { $month_url = '/archive-tree/' . $year . '/' . $month . '/' . $type_arg; $output .= '
'; $output .= '' . $month . ' (' . $month_data['count'] . ')'; $output .= '
'; } } $output .= ''; } return [ '#markup' => '
' . $output . '
', '#allowed_tags' => ['details', 'summary', 'a', 'div'], '#attached' => [ 'library' => [ 'archive_tree/archive_tree', ], ], '#cache' => [ 'contexts' => ['user', 'languages'], 'tags' => ['node_list'], 'max-age' => -1, ], ]; } }