Drupal 8 Views: убрать некоторые значения из раскрытого фильтра

Недавно на понадобилось сделать фильтр записей по полю типа «список», у которого может быть очень много возможных значений (список значений и сами записи импортируются из внешней системы). Если делать обычный Exposed Filter, то появляются два неудобства:

  • Из-за большого количества возможных значений фильтр не очень красиво выглядит на экране и пользователю сложно искать нужное значение
  • Если выбрать очень много пунктов за раз (или сразу все), то можно получить ошибку сервера из-за слишком большого GET-запроса (URL too long), поскольку Drupal передает все выбранные значения фильтров в URL не очень «коротким» способом.

Один из возможных выходов: не дать возможности пользователю выбрать значения фильтра, которые не встречаются в нодах. Сделал я это путем реализации хука form_alter() для модификации формы фильтра:

function customs_form_alter(&$form, $form_state, $form_id)
  {
    //Убедимся, что имеем дело с нужной формой
    if ($form_id == 'views_exposed_form' && $form['#id'] == 'views-exposed-form-vacancy-page-1')
    {
        //Соберем все ДОСТУПНЫЕ значения
        $nids = \Drupal::entityQuery('node')
          ->condition('status', 1)
          ->condition('type', 'vakancy')
          ->condition('field_razdel_saita', 59)
          ->execute();
        $nodes = Node::loadMultiple($nids);
        $depts = array();
        foreach ($nodes as $node) {
          $depts[] = $node->get('field_strukturnoe_podrazdelenie')->value;
        }
        //Уберем дубликаты
        $depts=array_unique($depts);
        //Присвоим массиву с возможными значениями поля только те элементы, значения которых есть в $depts
        $form['field_department_value']['#options']=array_intersect($form['field_department_value']['#options'], $depts); 
    }

Раскрытый фильтр по дате в Drupal 8: прошедшие/текущие/будущие события

Небольшой пример того, как я сделал раскрытый фильтр на Drupal 8 Views по полям «Начало мероприятия» и «Конец мероприятия» типа «Дата» для вывода прошедших, текущих и предстоящих мероприятий, используя hook_views_query_alter() для модификации запроса на выборку.
Читать тут.

Программное изменение поля типа «Список» с множественным значением в Drupal 8

Если задать значение обычного поля в Drupal 8 можно с помощью метода setValue() ($node->field_name->setValue(«Some value»);), то для выбора значений из списка нужно воспользоваться методом appendItem(). Вот как я сделал это при создании ноды из стороннего XML:

foreach($plan->Exams->Exam as $exam)
{
  $node->field_exams->appendItem($exam->ExamName);
}

Drupal 8 Views — выводить пустую страницу, если не заполнены раскрытые фильтры

Недавно столкнулся со странным поведением Views в Drupal 8 (а как показал гугл — и в ранних версиях): на сайте есть лента новостей с категориями, выведенная через Views. Пользователь может отметить чекбоксами (с помощью Detter Exposed Filter) интересующие его категории. Так вот, если он отмечает категорию, в которой новостей нет, то в результате получает пустую страницу — тут все хорошо. Но если он вообще не отметит ни одной категории, то Views возвращает вообще все новости, хотя, по идее, должен так же вернуть пустую страницу. Всякие танцы с бубном вокруг обязательных фильтров и PHP-кодов в валидации параметров Global:Null особо не помогли. В результате нагуглил вот такие варианты.

Воспользоваться решил вторым вариантом, предложенным господином drupby, а именно реализовать hook_views_querry_alter(). Однако, согласно документации Drupal 8 его формат несколько изменился. Вот мой вариант:

function inno_hooks_views_query_alter($view, $query) {
 if ($view->name == 'My_View' && $view->current_display == 'page') {
    $filter_set = FALSE;
    foreach ($view->filter as $filter) {
      // Check if we've found a filter identifier that is set.
      if ($filter->options['exposed'] && array_key_exists($filter->options['expose']['identifier'], $_GET)) {
        $filter_set = TRUE;
        break;
      }
    }
 
    // If the filter isn't set, add a WHERE clause to the query that
    // cannot be TRUE. This ensures the view returns no results.
    if (!$filter_set) {
      $query->addWhere(0, 'FALSE');
      // To display a different message (or no message at all) you
      // also might want to adjust the Views' empty text. 
      $view->display_handler->options['empty'] = '';
    }
  }
}

Интеграция пользователей Drupal 8 со сторонней системой

На данный момент я занимаюсь разработкой нескольких сайтов на Drupal 8 для одной крупной организации, у которой уже есть свои внутренние ресурсы. Одним из таких ресурсов является что-то вроде  электронной системы учета сотрудников, разработанная силами своих программистов.

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

Разработчики системы учета сотрудников предоставили что-то вроде API: ссылка, на которую надо отправить логин/пароль пользователя в их системе, на что она ответит XML-ом с информацией о пользователе, если такой найдется. А далее все просто: ищем на сайте пользователя, доп. поле «external_uid» которого равно userId внешней системы, если не нашли, то создаем, а затем его авторизуем.

Для этого реализовал отдельный модуль, который рисует форму логина и шлет ее по тому самому адресу. Весь модуль «светить» здесь не буду, ибо он является только первой ступенью в большой коммерческой разработке, да и в обмене данными в PHP нет ничего хитрого. Приведу код обработчика ответа:

 public function login()
    {
        $context  = stream_context_create(array('http' => array('header' => 'Accept: application/xml')));
        $url = 'http://www.***.com/get_user';
        $xml = file_get_contents($url, false, $context);
        $user_info = new \SimpleXMLElement($xml);
        $query = \Drupal::entityQuery('user')
            ->condition('field_extarnal_uid', $user_info->userId);
        $users = $query->execute();
        //Если не нашли пользователя с таким External UID, то создаем его
        if(empty($users))
        {
            $user = User::create();
            $user->setUsername($user_info->firstname." ".$user_info->lastname);
            $user->setEmail($user_info->email);
            $user->field_external_uid = $user_info->userId;
            $user->activate();
            $result = $user->save();
            /*Немного говнокода, чтобы узнать UID вновь добавленного пользователя.
            //Наверняка можно сделать проще, надо будет в будущем разобраться*/
            $query = \Drupal::entityQuery('user')
                ->condition('field_external_uid', $user_info->userId);
            $users = $query->execute();
        }
        $user = User::load(array_keys($users)[0]); //Загружаем пользователя с нужным UID
        user_login_finalize($user); //Авторизуем пользователя на сайте
        //Рисуем страницу приветсвия
        $output['#title'] = $user_info->firstname;
        $output['#markup'] = "Добро пожаловать, ".$user_info->firstName."!";
        return $output;
    }

Как видно из приведенного выше кода, пользователь создается без пароля, поэтому войти ему можно только через эту самую внешнюю систему. Конкретно для моего случая это именно то, что нужно, однако, можно реализовать поиск пользователя не по полю External UID, а, например, по e-mail’у. И в таком случае можно «привязать» его аккаунт в сторонней системе к профилю друпала.

Получение нод определенного типа в Drupal 8

На днях понадобилось написать модуль, который бы обрабатывал определенные типы нод согласно введенным данным. Отсюда возникла задача: изначально неведомо, какие у этих нод nid, и сразу использовать Node::load($nid) не получится. На Drupal 7 я бы просто обратился к БД, используя db_query(). Но на Drupal 8 все можно сделать гораздо удобнее благодаря Entity Query, а именно:

$query = \Drupal::entityQuery('node')
    ->condition('status', 1)
    ->condition('type','user_type');
$nids = $query->execute();

Аналогично с помощью condition() можно добавлять и другие условия. Подробнее можно узнать тут (англ.):

Drupal 8 загрузка ноды по AJAX

Предлагаю «каркас» модуля для Drupal 8, который позволит вам получать ноды по их ID через AJAX. В приведенном ниже коде реализовано получение ноды с помощью метода Node::load, который пришел на замену функции node_load() и вывод заголовка и содержания ноды в виде JSON-объекта. Аналогично можно добавлять и другие поля ноды.

Собственно, код модуля:

< ?php
namespace Drupal\ajax_loader\Controller;
 
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\node\Entity\Node;
 
class Ajax_loaderController extends ControllerBase
{
    public function load()
    {
        $nid = intval($_GET['nid']);
	$node = Node::load($nid);
	$response = new JsonResponse();
        $response->setData(array('title' => $node->getTitle(), 'body' => $node->get("body")->value));
        return $response;
    }
}

Весь цимес заключается в том, что метод контроллера обязательно должен возвращать объект ответа, просто строку вернуть не получится. Однако, при возврате обычного ответа Drupal оборачивает его в HTML-шаблон, который при использовании AJAX нам не нужен. Для этого и используется JsonResponse::setData(), который возвращает JSON, сформированный из переданного массива.

UPD: Для получения ID ноды по ее алиасу можно использовать метод \Drupal::service(‘path.alias_manager’)->getPathByAlias($alias), который вернет системный путь вида node/XX

RSForm! Pro не работает при включенном кэше Joomla

Некоторое время назад столкнулся со странностями на одном сайтов, сделанных на Joomla. Странность заключалась в следующем: на одной из страниц в текст материала была вставлена форма компонента RSForm. Эта форма прекрасно отображалась, но при попытке ее отправить в браузере клиента просто снова открывалась эта же страница, никакого сообщения об удачной отправки данных не появлялось. Письма администратору и клиенту так же не отправлялись, и в БД не было вообще никаких упоминаний о том, что эта форма когда-либо была заполнена.
После небольшого гугления выяснилось, что во всем виновато кэширование. Вот что по этому поводу говорится на официальном сайте компонента RS Form:

Чтобы понять, почему так происходит, нужно разобрать, как работает кэш Joomla (для примера будем использовать случай, когда форма встроена в текст статьи):

  • Загружается статья;
  • Плагин RSForm запускается и заменяет плейсхолдер в тексте статьи на соответствующую форму;
  • Система кэширования Joomla «запоминает», как выглядит страница и сохраняет это представление в кэше.
  • При следующих обращениях к этой странице первые два этапа пропускаются, и страница сразу загружается из кэша. Это ускоряет работу сайта, но так же это значит, что страница не будет загружать плагин RSForm. Форма будет отображаться на странице, так как ее внешний вид сохранен в кэше, но ее функционал — нет.

Единственное решение, которое предлагается — отключить кэширование.

Однако, как быть, если совсем отключать кэширование нельзя? Ведь при этом может сильно возрасти нагрузка на хостинг.  Мне известны следующие варианты:

  • Добавить в index.php шаблона строки
    $cache = &amp;JFactory::getCache('com_rsform');
    $cache-&gt;clean();

    В таком случае при каждом обращении к сайту будет происходить очистка кэша RSForm. На мой взгляд, это не очень оптимальный метод, ведь форма все рано будет кэшироваться, а потом каш будет очищаться — получаем лишние действия. Еще один минус: этот код сработает только в том случае, когда доступ к форме осуществляется по адресу вида index.php?option=com_rsform.Если на сайте используются SEF-ссылки, этот метод не сработает.

  • Более сложный вариант, найденный мной у «ИТ Осминогов»: в файле /plugins/system/cache.php в каждой функции добавили условие, которое запрещает выполнение, если запрошена страница, содержащая форму (для проверки страницы использовался $_SERVER[‘REQUEST_URI’] ). Этот вариант более предпочтительный, однако требует некоторых знаний PHP и принципов работы CMS Joomla.

Вывод содержание загруженной FB2 книги в Drupal

Продолжаю цикл статей о создании электронной библиотеки на Drupal 7. В прошлой статье я рассказал о модуле, который позволяет загружать на сайт книги в формате FB2 и создает из них ноды. Сегодня же я покажу, как можно вывести содержание загруженной тем модулем книги.
Кому интересно, прошу под кат.

Создание ноды в Drupal по загруженной FB2 книге.

Потихоньку работаю над проектом электронной библиотеки книг в формате FB2, и решил использовать для нее Drupal. Собственно, вся логика библиотеки будет заключаться в пакете модулей «FB2». Модули пишу строго под нужды конкретного проекта, поэтому пока не планирую выкладывать их целиком, а лишь опишу принцип их работы и приведу наиболее интересные и важные участки кода.
А первым на очереди у нас модуль, который позволяет пользователям загружать на сайт файлы FB2, разбирает их и создает соответствующие ноды, где заголовок ноды — это название книги а тело ноды — аннотация. Плюс, дополнительно созданные поля «autor» и «file» для хранения авторов книги и ссылки на сам FB2.
Узнать подробнее можно по ссылке: Загрузка книг в формате FB2 и создание из них нод в Drupal