Программное изменение поля типа «Список» с множественным значением в 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

Валидация форм на HTML5 по pattern и AJAX.

Как известно, HTML5 предоставляет нам много новых плюшек для контроля вводимых пользователем данных в текстовые поля форм. Это позволило сильно облегчить JS-валидацию форм в нашей системе интернет-банкинга. Однако, столкнулся с досадным моментом: каждый браузер по своему изменяет внешний вид полей при неправильном вводе. Кроме того, основная валидация происходит при вызове метода Submit формы. У нас же так исторически сложилось, что данные передаются в виде JSON’а через AJAX по нажатию на обычный button. В результате чего родилась вот такая функция:

function ChkHTMLValidation(obj)
{
	var field = document.getElementById(obj);
	if(!field.checkValidity()){
		document.getElementByID(obj).classList.add('StateError');
		document.getElementByID('button_next').disabled=true;
	}else{
		document.getElementByID(obj).classList.remove('StateError');
		document.getElementByID('button_next').disabled=false;
	}
}

На вход функция получает ИД текстового input’а, у которого определен pattern, и проверяет корректность введенных в него данных. Если данные некорректны, на поле навешивается CSS-класс ошибки и отключается кнопка отправки формы. Если же данные корректны — CSS-класс ошибки удаляется и кнопка включается.

Отправка XML методом POST в C#

Ох, что-то давно я ничего не писал… Дедлайн ближе — посты реже 🙂

В процессе прикручивания к интернет-банку платежной системы QIWI возникла необходимость слать XML POST-запросом на определенные URL и смотреть, что приходит в ответ. Чтобы не ворочать ради этого всю систему интернет-банкинга и не собирать нужную информацию по текстовым логам сервера, склепал на .Net небольшую софтинку в пару строк кода, которая шлет на указанный URL переданный XML методом POST используя класс WebRequest и выводит все, что получает в ответ. Исходник проекта берем тут.

P.S.: тулза писалась для личного использования в буквальном смысле на коленке, и по хорошему в нее надо добавить проверку ошибок (неправильный URL, URL не найден, ошибка DNS и т.д.).