Добавляем в системный журнал функцию блокировки по ip

Код добавляет в записи системного журнала ссылку на блокировку адреса:

После клика на ссылке, адрес попадает в чёрный список (admin/user/rules).

/**
 * Implements hook_menu().
 */
function mymodule_menu() {
  $items = array();
 
  $items['ban/%'] = array(
    'page callback' => 'mymodule_ban_by_ip',
    'page arguments' => array(1),
    'access arguments' => array('administer permissions'),
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}
 
/**
 * Implements template_preprocess_page().
 */
function mymodule_preprocess_page(&$vars) {
  $item = menu_get_item();
  if ($item['path'] == 'admin/reports/event/%') {
    $link = '<a href="' . $GLOBALS['base_path'] . 'ban/$1?destination=' . $_GET['q'] . '">забанить</a>';
    $vars['content'] = preg_replace(
      '#<th>' . t('Hostname') . '</th><td>(.+?)</td>#',
      '<th>' . t('Hostname') . '</th><td>$1&nbsp;&nbsp;[ ' . $link . ' ]</td>',
      $vars['content']
    );
  }
}
 
/**
 * Ban user by IP.
 */
function mymodule_ban_by_ip($ip) {
  if ($ip != ip_address()) {
    db_query("INSERT INTO {access} (mask, type, status) VALUES ('%s', '%s', %d)", $ip, 'host', 0);
    drupal_set_message('Адрес заблокирован');
  }
 
  drupal_goto();
}

Написанное актуально для
Drupal 6.x

Добавлено: 27 Июля 2018 08:22:41 Добавил: Андрей Ковальчук

Как из php скрипта асинхронно выполнить другой скрипт (т.е. не дожидаясь его окончания)

Например есть скрипт a.php, в котором нужно вызвать тяжеловесный скрипт b.php и не дожидаясь его окончания продолжить работу. Одно из решений это сокеты:

// a.php
 
function exec_script($url, $params = array())
{
    $parts = parse_url($url);
 
    if (!$fp = fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80))
    {
        return false;
    }
 
    $data = http_build_query($params, '', '&');
 
    fwrite($fp, "POST " . (!empty($parts['path']) ? $parts['path'] : '/') . " HTTP/1.1\r\n");
    fwrite($fp, "Host: " . $parts['host'] . "\r\n");
    fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
    fwrite($fp, "Content-Length: " . strlen($data) . "\r\n");
    fwrite($fp, "Connection: Close\r\n\r\n");
    fwrite($fp, $data);
    fclose($fp);
 
    return true;
}
 
exec_script('http://example.com/b.php', array('foo' => 'bar'));

В примере, в скрипте b.php будет доступна переменная $_POST['foo'] со значением bar.

Добавлено: 27 Июля 2018 08:19:38 Добавил: Андрей Ковальчук

Как отдать пользователю данные в виде файла

В примере по кнопке "Экспорт" браузер предлагает сохранить текстовый файл состоящий из строчки Hello World!:

/**
 * Реализация hook_menu()
 */
function mymodule_menu() {
  $items['export'] = array(
    'title' => 'Export',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mymodule_export_form'),
    'access arguments' => array('administer site configuration'),
  );
  return $items;
}
 
/**
 * Форма экспорта
 */
function mymodule_export_form() {
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Экспорт',
  );
  return $form;
}
 
/**
 * Экспорт
 */
function mymodule_export_form_submit($form, &$form_state) {
  $data = 'Hello World!';
  header('Content-Disposition: attachment; filename=somename.txt');
  header('Content-Type: text/plain; charset=utf-8');
  echo $data;
  exit;
}

Добавлено: 27 Июля 2018 08:18:55 Добавил: Андрей Ковальчук

Как отправить POST запрос

Drupal 6:

$result = drupal_http_request(
  'http://example.com',
  array('Content-Type' => 'application/x-www-form-urlencoded'),
  'POST',
  'param1=value1&param2=value2'
);

Drupal 7:

$result = drupal_http_request('http://example.com', array(
  'headers' => array('Content-Type' => 'application/x-www-form-urlencoded'),
  'method' => 'POST',
  'data' => 'param1=value1&param2=value2',
));

Написанное актуально для
Drupal 6, Drupal 7

Добавлено: 27 Июля 2018 08:17:38 Добавил: Андрей Ковальчук

Как программно заполнить поле расширенного профиля (Profile)

Код, который заполняет поле profile_phone значением 8905 987-21-32:

$uid = 1;                                  // id пользователя
$fieldName = 'profile_phone';              // имя поля
$fieldCategory = 'Контактная информация';  // категория поля
$fieldValue = '8905 987-21-32';            // новое значение поля
 
$field = db_fetch_object(db_query("
  SELECT fid, type FROM {profile_fields}
  WHERE name = '%s' AND category = '%s'
", $fieldName, $fieldCategory));
 
if (_profile_field_serialize($field->type)) {
  $fieldValue = serialize($fieldValue);
}
 
db_query("
  UPDATE {profile_values} SET value = '%s'
  WHERE fid = %d AND uid = %d
", $fieldValue, $field->fid, $uid);

Написанное актуально для
Drupal 6.x

Добавлено: 27 Июля 2018 08:16:52 Добавил: Андрей Ковальчук

Как программно удалить ошибку валидации установленную с помощью form_set_error()

Бывает нужно избавиться от ошибки валидации, которая проходила в другом модуле. Код ниже, удаляет ошибку валидации поля some_field в форме some_form:

/**
 * Реализация hook_form_FORM_ID_alter()
 */
function mymodule_form_some_form_alter(&$form) {
    // вешаем на форму свой валидатор.
    $form['#validate'][] = 'mymodule_some_form_validate';
}
 
/**
 * Валидация формы some_form
 */
function mymodule_some_form_validate($form, &$form_state) {
    $errors = form_set_error(); // получаем список ошибок
    if (isset($errors['mail'])) {
        // удаляем сообщение об ошибке
        unset($_SESSION['messages']['error'][array_search($errors['mail'], $_SESSION['messages']['error'])]);
 
        // удаляем саму ошибку
        unset($errors['mail']);
 
        // сбрасываем список ошибок
        form_set_error(NULL, '', TRUE); 
 
        // заполняем список по новой
        foreach ($errors as $key => $value) {
            form_set_error($key, $value);
        }
 
        // удаляем дубли ошибок, добавленных прошлым шагом
        $_SESSION['messages']['error'] = array_values(array_unique($_SESSION['messages']['error']));
    }
}

Написанное актуально для
Drupal 6.x

Добавлено: 27 Июля 2018 08:16:20 Добавил: Андрей Ковальчук

Как добавить новое поле в существующую таблицу БД

Урезанный, но рабочий пример из модуля FileField Paths, который добавляет в таблицу files новое поле origname:

/**
 * Implements hook_schema_alter().
 */
function filefield_paths_schema_alter(&$schema) {
  $schema['files']['fields']['origname'] = array(
    'description' => 'Original name of the file.',
    'type' => 'varchar',
    'length' => 255,
    'not null' => TRUE,
    'default' => '',
  );
}
 
/**
 * Implements hook_install().
 */
function filefield_paths_install() {
  db_add_field($ret = array(), 'files', 'origname', array(
    'description' => 'Original name of the file.',
    'type' => 'varchar',
    'length' => 255,
    'not null' => TRUE,
    'default' => '',
  ));
}
 
/**
 * Implements hook_uninstall().
 */
function filefield_paths_uninstall() {
  db_drop_field($ret = array(), 'files', 'origname');
}

Код находится в файле filefield_paths.install.

А вот более универсальный вариант для добавления нескольких полей:

/**
 * Implements hook_schema_alter()
 */
function mymodule_schema_alter(&$schema) {
  if (isset($schema['table_name'])) {
    $schema['table_name']['fields']['field_1'] = array(
      'type' => 'varchar',
      'length' => 100,
      'not null' => TRUE,
      'default' => '',
    );
    $schema['table_name']['fields']['field_2'] = array(
      'type' => 'int',
      'unsigned' => TRUE,
      'not null' => TRUE,
      'size' => 'tiny',
      'default' => 0,
    );
  }
}
 
/**
 * Implements hook_install()
 */
function mymodule_install() {
  $schema = array('table_name' => array());
  mymodule_schema_alter($schema);
  foreach ($schema['table_name']['fields'] as $field => $spec) {
    db_add_field($ret = array(), 'table_name', $field, $spec);
  }
}
 
/**
 * Implements hook_uninstall()
 */
function mymodule_uninstall() {
  $schema = array('table_name' => array());
  mymodule_schema_alter($schema);
  foreach ($schema['table_name']['fields'] as $field => $spec) {
    db_drop_field($ret = array(), 'table_name', $field);
  }
}

Написанное актуально для
Drupal 6.x

Добавлено: 27 Июля 2018 08:15:41 Добавил: Андрей Ковальчук

Как получить доступ к доп. полям профиля пользователя

global $user;
profile_load_profile($user);
$phone = $user->profile_phone;

Ф-я profile_load_profile() добавляет в объект $user значения кастомных полей, созданных с помощью модуля Profile.

Написанное актуально для
Drupal 6.x

Добавлено: 27 Июля 2018 08:14:54 Добавил: Андрей Ковальчук

Как правильно делать JOIN для таблиц с CCK полями

Если вам захочется сделать выборку нод вместе с CCK полями, то первым желанием будет посмотреть схему таблиц и набросать что-нибудь в духе:

SELECT n.title, f.field_name_value FROM {node} n
INNER JOIN {content_type_story} f ON n.nid = f.nid AND n.vid = f.vid
WHERE n.type = 'story'
ORDER BY n.title

(в примере выбираются заголовки нод типа story и соответствующее им значение cck поля field_name)

Однако тут есть подводный камень — если поле используется только в одном типе материалов и может иметь только одно значение, то оно хранится в таблице с именем content_type_TYPE (где TYPE это тип материала), иначе же значения хранятся в таблице content_FIELD_NAME (где FIELD_NAME это название поля). Это значит, что как только мы добавим поле field_name в другой материал, или дадим ему возможность иметь несколько значений, запрос перестанет работать.

Поэтому правильней будет — сначала с помощью CCK API узнать название таблицы, и только потом джойнить её в SQL запросе:

$field = content_fields('field_name');
$db_info = content_database_info($field);
 
$result = db_query("
  SELECT title, %s FROM {node} n
  INNER JOIN {%s} f ON n.nid = f.nid AND n.vid = f.vid
  WHERE n.type = 'story'
  ORDER BY n.title
", $db_info['columns']['value']['column'], $db_info['table']);

Написанное актуально для
CCK 6.x-2.x

Добавлено: 27 Июля 2018 08:14:12 Добавил: Андрей Ковальчук

Как из Javascript-a подключить Lightbox к определённым элементам

Код заставляет открываться в Lightbox2 все ссылки с вложенным тегом <img />:

Drupal 6:

$(function(){
  $('a:has(img)').attr('rel', 'lightbox');
  Lightbox.initList();
});

Drupal 7:

jQuery(function(){
  jQuery('a:has(img)').attr('rel', 'lightbox');
  Lightbox.initList();
});

Добавлено: 27 Июля 2018 08:13:24 Добавил: Андрей Ковальчук

Как создать пэйджер для данных не из БД

В примере создаётся пэйджер на 5 страниц без использования функции pager_query():

global $pager_page_array, $pager_total;
 
// настройки пейджера
$per_page = 10;   // строк на страницу
$total_rows = 50; // всего строк
$element = 0;     // номер пэйджера
 
// инициализация пэйджера
$pager_page_array[$element] = isset($_GET['page']) ? $_GET['page'] : 0;
$pager_total[$element] = ceil($total_rows / $per_page);
 
// генерация пэйджера
$pager = theme('pager', null, $per_page, $element);

Написанное актуально для
Drupal 6.x

Добавлено: 27 Июля 2018 08:12:22 Добавил: Андрей Ковальчук

Как переопределить функцию темизации в своём модуле

Есть два способа переопределения функций темизаций в модуле. Первый, это реализация хука hook_theme:

/**
 * Реализация hook_theme()
 * Переопределение функции theme_breadcrumb()
 */
function mymodule_theme() {
  return array(
    'breadcrumb' => array(
      'function' => 'mymodule_breadcrumb',
      'arguments' => array('breadcrumb' => NULL),
    ),
  );
}
 
/**
 * Функция темизации хлебных крошек
 */
function mymodule_breadcrumb($breadcrumb) {
  if (!empty($breadcrumb)) {
    return '<div class="breadcrumb">' . implode(' → ', $breadcrumb) . '</div>';
  }
}

этот код будет работать только если функция theme_breadcrumb не переопределена в template.php или в модуле с бОльшим весом.

Следующий код будет работать даже если функция переопределена в template.php

/**
 * Реализация hook_theme_registry_alter()
 * Переопределение функции theme_breadcrumb()
 */
function mymodule_theme_registry_alter(&$theme_registry) {
  $theme_registry['breadcrumb']['function'] = 'mymodule_breadcrumb';
}
 
/**
 * Функция темизации хлебных крошек
 */
function mymodule_breadcrumb($breadcrumb) {
  if (!empty($breadcrumb)) {
    return '<div class="breadcrumb">' . implode(' → ', $breadcrumb) . '</div>';
  }
}

Написанное актуально для
Drupal 6.x

Добавлено: 27 Июля 2018 08:10:46 Добавил: Андрей Ковальчук

Уведомление администратора о новых комментариях в семёрке

Код отправляет уведомление о новых комментариях на ящик admin_mail@gmail.com

/**
 * Implements hook_comment_insert().
 */
function mymodule_comment_insert($comment) {
  if ($comment->uid != 1) {
    drupal_mail('mymodule', 'notify_of_new_comment', 'admin_mail@gmail.com', language_default(), $comment);
  }
}
 
/**
 * Implements hook_mail().
 */
function mymodule_mail($key, &$message, $params) {
  if ($key == 'notify_of_new_comment') {
    $node = node_load($params->nid);
    $body = $params->name . ' оставил комментарий в теме "' . $node->title . '":' . "\n\n";
    $body .= trim($params->comment_body['und'][0]['value']) . "\n\n";
    $body .= url('node/' . $params->nid, array('absolute' => true));
    $message['subject'] = 'Уведомление о новом комментарии';
    $message['body'][] = $body;
  }
}

Написанное актуально для
Drupal 7

Добавлено: 27 Июля 2018 08:09:11 Добавил: Андрей Ковальчук

Добавляем контекстные ссылки для комментариев

В семёрке контекстные ссылки (contextual links) есть у материалов и блоков, но отчего-то их нет у комментариев.

Код ниже добавляет к каждому комментарию ссылку на редактирование и удаление:

/**
 * Реализация hook_menu_alter()
 */
function mymodule_menu_alter(&$items)
{
    $items['comment/%comment/edit']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
    $items['comment/%/delete']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
}
 
/**
 * Реализация hook_comment_view()
 */
function mymodule_comment_view($comment, $view_mode, $langcode)
{
    if ($view_mode == 'full')
    {
        $comment->content['#contextual_links']['comment'] = array('comment', array($comment->cid));
    }
}

Обычные ссылки за ненадобностью можно скрыть — в template.php добавить:

function THEMENAME_preprocess_comment(&$vars)
{
    hide($vars['content']['links']);
}

Не забываем сбросить кэш.

Написанное актуально для
Drupal 7.x

Добавлено: 26 Июля 2018 22:16:38 Добавил: Андрей Ковальчук

Как программно создать блок

Пример создания простейшего блока в Drupal 7:

/**
 * Реализация hook_block_info().
 * Информация о блоке и дефолтные настройки.
 */
function mymodule_block_info() {
  $blocks['hello-message'] = array('info' => t('Message'));
  return $blocks;
}
 
/**
 * Реализация hook_block_view().
 * Генерация заголовка блока и его контента.
 */
function mymodule_block_view($delta = '') {
  $block = array();
 
  if ($delta == 'hello-message') {
    $block['subject'] = t('Message');
    $block['content'] = t('Hello World!');
  }
 
  return $block;
}

Пример создания простейшего блока в Drupal 6:
/**
 * Реализация hook_block().
 */
function mymodule_block($op = 'list', $delta = 0, $edit = array()) {
  if ($op == 'list') {
    $blocks[0] = array('info' => 'Message');
    return $blocks;
  }
  else if ($op == 'view') {
    if ($delta == 0) {
      $block = array(
        'subject' => t('Message'), 
        'content' => t('Hello World!'),
      );
    }
    return $block;
  }
}

Написанное актуально для
Drupal 6, Drupal 7

Добавлено: 26 Июля 2018 22:15:57 Добавил: Андрей Ковальчук