null

"Глобальная" навигация в Liferay 6.2

Обычно панель навигации в порталах на платформе Liferay формируется автоматически - на основании набора страниц текущего сайта. У такого подхода много преимуществ - при изменении структуры сайта навигация перестраивается автоматически, "прозрачно" учитываются права доступа пользователей к страницам, и т.д. В общем, если структура портала достаточно простая, дефолтная реализация навигации работает очень хорошо:

Навигация на портале формируется автоматически

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

Применительно к нашему сайту, дефолтная реализация навигации точно не подходит - т.к. блоги у нас располагаются на персональных страницах, получается, что на странице каждого блогера будет своя навигация, независимая от общей навигации сайта:

Плохо - в каждом блоге своя независимая навигация

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

Первый, и самый очевидный - прописать все необходимые ссылки "хардкодом" в шаблоне темы оформления. Плюс один - это сделать достаточно просто. Минусов больше - во-первых, при изменении структуры сайта тему придётся править, пересобирать и повторно деплоить; во-вторых, в vm-шаблоне темы придётся писать достаточно нетривиальную логику определения активного элемента навигации (т.е., той страницы, на которой пользователь находится в данный момент). В общем, несмотря на кажущуюся очевидность, от этого варианта решено было отказаться.

Второй - подменить где-нибудь внутри портала логику инициализации переменной Velocity $nav_items, по которой строится "динамическая" навигация. Этот путь более "красивый" (тему оформления править вообще не нужно), но есть одна проблема - классы, в которых инициализируется нужная нам переменная (com.liferay.portal.velocity.VelocityVariablesImpl в 6.1 и com.liferay.portal.template.TemplateContextHelper в 6.2) находятся уж очень глубоко "в недрах" портала, и добраться до них можно только с помощью ext-плагина (либо, вообще, пересобрав портал целиком). А ext-плагин - это такая штука, с которой, по опыту, без крайней необходимости лучше не связываться :)

В общем, был выбран третий путь - попытаться проинициализировать "правильную" навигацию прямо в шаблоне темы оформления и подменить ей стандартную коллекцию $nav_items. Получить набор страниц нужного нам сайта можно с помощью LayoutService. Код на Java, реализующий это, выглядит примерно так:

String selectedSiteName = "Guest"; // Имя группы сайта, из которого мы берём "глобальную" навигацию
Group selectedSiteGroup = 
    com.liferay.portal.service.GroupLocalService.getGroup(themeDisplay.getCompanyId(), selectedSiteName));
List<Layout> globalLayouts = 
    com.liferay.portal.service.LayoutService.getLayouts(selectedSiteGroup.groupId, 
        false, 0)); // "false" - публичные страницы, "0" - только страницы "верхнего" уровня

Если переписать приведённый код на Velocity, то на его основе уже можно сформировать нечто, похожее на нужную нам навигацию. Единственная проблема, в дефолтной навигации коллекция $nav_items состоит из элементов класса NavItem, а не Layout, вследствие чего, если не менять vm-шаблон, генерирующий навигацию, она будет работать "криво":

Всё очень плохо

Особую печаль вызывает "развалившаяся" логика генерации URL'ов элементов навигации, т.к. она достаточно нетривиальна (особенно, с учётом friendlyURL) и переписывать её на Velocity ну очень не хочется.

В общем, поправить этот шаблон можно, но лень, поэтому попробуем сконвертировать коллекцию Layout'ов в коллекцию NavItem'ов. Если посмотреть, как это делается в самом портале, то можно найти интересный метод непосредственно в классе NavItem:

public static List<NavItem> fromLayouts(
	HttpServletRequest request, List<Layout> layouts, Template template) {

	if (layouts == null) {
		return null;
	}

	List<NavItem> navItems = new ArrayList<NavItem>(layouts.size());

	for (Layout layout : layouts) {
		navItems.add(new NavItem(request, layout, template));
	}

	return navItems;
}

Видим, что этот метод статический, принимает на вход запрос (у нас в Velocity есть переменная $request), набор Layout'ов (то, что нужно!) и некий шаблон (Template). Осталось добраться до этого метода из Velocity и разобраться, откуда можно взять шаблон (и, вообще, нужно ли откуда-либо его брать). Первую задачу решим изящно - на любой странице портала у нас есть дефолтная навигация $nav_items с, как минимум, одним элементом. Т.е., сказав $nav_items.get(0).fromLayouts(...), мы доберёмся до нужного нам метода. Вторую проблему попробуем вообще не решать, т.к., судя по коду NavItem, шаблон нужен только тогда, когда он в явном виде вызывается через метод getTemplate(), а у нас в теме оформления этот метод не используется. В общем, добавим куда-нибудь в начало portal_normal.vm такой код:

#set ($globalNavSiteName = "Guest")
#set ($groupLocalService = $serviceLocator.findService("com.liferay.portal.service.GroupLocalService"))
#set ($group = $groupLocalService.getGroup($company_id, $globalNavSiteName))
#set ($layoutService = $serviceLocator.findService("com.liferay.portal.service.LayoutService"))
#set ($globalNavLayouts = $layoutService.getLayouts($group.groupId, false, 0))
#set ($nav_items = $nav_items.get(0).fromLayouts($request, $globalNavLayouts, null))

Пересобираем тему, деплоим, проверяем:

Навигация работает, проблема решена!

Всё работает!

Коротко о себе:

Работаю ведущим программистом в компании Tune IT и ассистентом кафедры Вычислительной техники в Университете ИТМО .

Занимаюсь проектами, связанными с разработкой разного рода веб-приложений (порталы, CRM-системы, системы электронного документооборота), а также, в рамках научной работы на кафедре, изучаю возможности применения семантического анализа в задачах САПР.