Счетчик последних материалов у пункта меню

Пример как выглядит счетчик постов в меню

Пример пункта меню

Довольно тривиальная задача стала передо мной недавно, нужно было вывести количество материалов за неделю, но на пути стоял drupal, а из коробки ничего похожего не нашел вот и пришлось писать модуль. Детали под катом.

Дело в том что эту задачу можно было легко решить с помощью views и агрегации, но мне нужно было сделать список ссылок со счетчиками разных типов материалов, а городить столько представлений нет никакого желания. Меньше слов, больше дела.

Сначала нужно придумать имя, думаю вполне подойдет menu_node_count. Уникально и описывает всю суть.

Ну раз имя уже есть тогда нужен info файл:

name = Menu Node Count
description = Provides a counter for menu items by content types and for a certain period.
core = 7.x

Больше тут ничего не нужно. Дальше нужно где-то сохранять счетчики, не каждый же раз заново считать, правда ведь? Если так то добавляем схему базы данных в install файл:

<?php
/**
 * @file
 * Create table schema with menu item id and configurations
 */

/**
 * Implements hook_schema().
 */
function menu_node_count_schema() {
  $schema = array();

  $schema['menu_node_count'] = array(
    'description' => 'Count of new nodes in menu link item',

    'fields' => array(
      'mlid' => array(
        'description' => 'The {menu_links}.mlid of this menu item.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'type' => array(
        'description' => 'The {node_type}.type of this node.',
        'type' => 'varchar',
        'length' => 32,
        'not null' => TRUE,
        'default' => '',
      ),
      'period' => array(
        'description' => 'Period of time for which a node is considered a new',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'number' => array(
        'description' => 'Number of new nodes from date period',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'foreign keys' => array(
      'mlid' => array(
        'table' => 'menu_links',
        'columns' => array('mlid' => 'mlid'),
      ),
    ),
  );

  return $schema;
}

Получается вот такая таблица:

mysql> DESCRIBE menu_node_count;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| mlid   | int(11)     | NO   |     | 0       |       |
| type   | varchar(32) | NO   |     |         |       |
| period | int(11)     | NO   |     | 0       |       |
| number | int(11)     | NO   |     | 0       |       |
+--------+-------------+------+-----+---------+-------+

Пояснения к полям есть в hook_schema, не вижу смысла их переводить.

А теперь осталось дописать пару хуков и в продакшн! 😀

В файле модуля сделал альтер формы настроек пункта меню и добавил туда форму с выбором типа материалов и текстовым полем с указанием периода. Еще добавил пару хуков чтобы при сохранении ноды происходил пересчет свежих материалов. Ну и в последнем хуке происходит подмена текста пункта меню, добавляем к тексту циферки. Итак сам файл модуля:

<?php
/**
 * @file
 * Provide menu item configuration of content type and date range and added
 * counter of node from that content type and date range.
 */

/**
 * Implements hook_preprocess_HOOK().
 */
function menu_node_count_preprocess_menu_link(&$variables) {
  $element = & $variables['element'];
  $count = db_select('menu_node_count', 'c')
    ->fields('c', array('number'))
    ->condition('mlid', $element['#original_link']['mlid'])
    ->execute()
    ->fetchField();
  if ($count) {
    $element['#title'] .= " <span class='added-new'>" . t('!count new', array('!count' => "<span class='added-new-number'>$count</span>")) . "</span>";
    $element['#localized_options']['html'] = TRUE;
    $element['#attributes']['class'][] = 'has-node-counter';
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Adds menu node count options to the edit menu item form.
 *
 * @see menu_edit_item()
 * @see _menu_node_count_form_alter()
 */
function menu_node_count_form_menu_edit_item_alter(&$form, $form_state) {
  $item = $form['original_item']['#value'];
  _menu_node_count_form_alter($form, $item, $form);
}

/**
 * Add the menu attributes to a menu item edit form.
 *
 * @param $form
 *   The menu item edit form passed by reference.
 * @param $item
 *   The optional existing menu item for context.
 *
 * @see hook_theme()
 */
function _menu_node_count_form_alter(array &$form, array $item = array(), array &$complete_form) {
  $form['node_count'] = array(
    '#type' => 'fieldset',
    '#title' => t('Node count settings'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#tree' => TRUE,
  );

  $values = db_select('menu_node_count', 'n')
    ->fields('n', array('type', 'period'))
    ->condition('mlid', $item['mlid'])
    ->execute()
    ->fetchAssoc();

  if (empty($values)) {
    $values = array(
      'type' => '',
      'period' => abs(strtotime('now') - strtotime('1 week')),
    );
  }

  $types = array();
  foreach (node_type_get_types() as $node_type) {
    $types[$node_type->type] = $node_type->name;
  }
  $form['node_count']['node_type'] = array(
    '#type' => 'select',
    '#title' => t('Content type'),
    '#options' => $types,
    '#empty_option' => t('None'),
    '#default_value' => $values['type']
  );

  $form['node_count']['time_range'] = array(
    '#type' => 'textfield',
    '#title' => t('Time range'),
    '#default_value' => format_interval($values['period'], 1),
  );

  $form['#submit'][] = '_menu_node_count_form_submit';
}

/**
 * Form submit handler for menu item links.
 *
 *  Save node count settings.
 */
function _menu_node_count_form_submit($form, &$form_state) {
  $values = $form_state['values'];
  if (isset($values['node_count']['node_type'])) {
    if (empty($values['node_count']['node_type'])) {
      db_delete('menu_node_count')
        ->condition('mlid', $values['mlid'])
        ->execute();
    }
    else {
      $type = $values['node_count']['node_type'];
      $period = abs(strtotime('now') - strtotime($values['node_count']['time_range']));
      db_merge('menu_node_count')
        ->key(array('mlid' => $values['mlid']))
        ->insertFields(array(
          'mlid' => $values['mlid'],
          'type' => $type,
          'period' => $period,
        ))
        ->updateFields(array(
          'type' => $type,
          'period' => $period,
        ))
        ->execute();
    }
  }
}

/**
 * Implements hook_menu_link_delete().
 */
function menu_node_count_menu_link_delete($link) {
  db_delete('menu_node_count')
    ->condition('mlid', $link['mlid'])
    ->execute();
}

/**
 * Implements hook_cron().
 *
 * @see hook_cron()
 */
function menu_node_count_cron() {
  _menu_node_count_recount();
}

/**
 * Implements hook_node_insert().
 */
function menu_node_count_node_insert($node) {
  _menu_node_count_recount($node);
}

/**
 * Implements hook_node_update().
 */
function menu_node_count_node_update($node) {
  _menu_node_count_recount($node);
}

/**
 * Implements hook_node_delete().
 */
function menu_node_count_node_delete($node) {
  _menu_node_count_recount($node);
}

/**
 * Update menu counters
 */
function _menu_node_count_recount($node = NULL) {
  if (!is_null($node)) {
    db_query('UPDATE {menu_node_count} AS m SET number = (SELECT COUNT(n.nid) FROM {node} AS n WHERE m.type = n.type AND n.created > ( UNIX_TIMESTAMP() - m.period ) AND n.status = :status) WHERE m.type = :type', array(':status' => NODE_PUBLISHED,':type' => $node->type));
  }
  else {
    db_query('UPDATE {menu_node_count} AS m SET number = (SELECT COUNT(n.nid) FROM {node} AS n WHERE m.type = n.type AND n.created > ( UNIX_TIMESTAMP() - m.period ) AND n.status = :status)', array(':status' => NODE_PUBLISHED));
  }
}

UPDATE 1: Залил модуль в песочницу на дорогуhttps://drupal.org/sandbox/zviryatko/2184221

Счетчик последних материалов у пункта меню: 2 комментария

    1. zviryatko Автор записи

      Только после запуска крона? А после сохранения формы пункта меню все нормально? Посмотри в эту функцию _menu_node_count_form_submit

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *