Управление контентом в Orchard CMS

Orchard CMS можно назвать документоориентированной CMS: в Orchard есть понятие Content Item, что практически эквивалентно понятию Document в Umbraco.

Content Item — это единица контента. Например, запись в блоге, страница, статья. Соответственно, каждый Content Item является экземпляром какого-нибудь Content Type'а.

Content Type — определение типа Content Item'а, это аналог Document Template в Umbraco. Для управления Content Item'ами в Orchard CMS по-умолчанию есть один единственный грид:

Этот грид снабжён фильтром по Content Type, но каждый раз выставлять фильтр неудобно. Можно наделать себе в браузере закладок на страницы с фильтрами, но проще установить модуль Vandelay Industries. В этом модуле есть фича Vandelay Content Admin Menu, которая добавляет ссылки на cписки конкретных Сontent Item'ов.

Раскрывающееся меню контента
Раскрывающееся меню контента

На этом примере хорошо видно, что по-умолчанию Orchard CMS имеет довольно неудобную админку, и до приемлемого состояния её приходится доводить сторонними модулями.

Content Type может содержать Field'ы и Content Part'ы. Field — это минимальная часть контента. Content Part может содержать Field'ы и предоставлять свои собственные данные. Например, Content Part может получать посты из Twitter'a или из базы данных.

В Orchard CMS между Content Type'ами нет наследования, но отсутствие наследования можно легко обойти использованием Part'ов.

Например, мы хотим, чтобы News, BlogPost и Article имели короткое описание, которое бы выводилось красным цветом. Для этого нам нужно создать свой RedDescriptionPart и добавить его в желаемые Content Type'ы. Ещё нам понадобится один шаблон для отображения Part'a. Таким образом мы сможем управлять отображением Content Part'a нескольких Content Type'ов.

Важное отличие между Filed'ами и Part'ами в том, что Part'ы можно многократно использовать в разных Content Type'ах.

Допустим, у нас есть Content Part, который содержит один единственный Boolean Field "Approved" (одобрено). Этот Content Part добавлен в несколько Content Type'ов. В принципе, мы могли бы добавить этот Field прямо к Content Type'у и получили бы тот же результат. Но вот нам приходит требование добавить имя одобрившего. В случае с Content Part'ом мы просто добавляем ещё один Field в Part, а вот в случае с разрозненными Field'ами нам прийдётся много помучаться, чтобы найти все типы с полем Approved.

В базе все Field'ы ContentItem'a хранятся в таблице в сериализованном виде в таблице Orchard_Framework_ContentItemVersionRecord. Если у вас тип имеет пару Text'ов и булево полe, то, при необходимости получить только булево поле, Orchard вытащит ещё и данные из Text'ов.

Например, у меня есть тип News со своими Field'ами, и к типу News добавлен NewsPart у которого есть свои филды. В итоге XML будет выглядеть так:

<Data>
  <News>
     <TextFiled>News text here</TextFiled>
     <BoolField>True</BoolField>
  </News>
  <NewsPart>
     <NewsPartTextField>News part text here</NewsPartTextField>
     <NewsPartBoolField>True</NewsPartBoolField>
  </NewsPart>
</Data>

В Orchard CMS есть BodyPart с одним единственным текстовым полем, только данные из этого поля попадают в таблицу Common_BodyPartRecord.

Таблица Common_BodyPartRecord
Таблица Common_BodyPartRecord

Здесь надо заметить, что Orchard CMS при загрузке данных для Content Item'а использует Lazy Load и не будет загружать Part'ы, которые вы не собираетесь использовать.

В Orchard CMS нельзя добавить один и тот же Part в Content Type дважды. Потому не получится использовать один BodyPart для текста статьи, а другой BodyPart для краткого описания статьи.

Field — это не обязательно одна строчка или число. Вот, например, YouTubeField.

YouTubeField в админке
YouTubeField в админке

В базе ContentItem типа Video, содержащий только одно это поле будет выглядеть так:

<Data>
  <Video>
    <YoutubeFiled Identifier="oHg5SJYRHA0" Width="640" Height="390" />
  </Video>
</Data>

Отношения между объектами

Content Picker Field

В Orchard 1.5 появился Content Picker Field, который позволяет выбрать один или несколько ContentItem'ов. Этим решается задача связки Content Item'ов, однако фича довольно сырая. В Orchard 1.5.1 она глючит и позволяет добавлять элементы только по одному, те для добавления 10 элементов пикер прийдётся открывать 10 раз. Возможно, в Orchard 1.5.2 это поправят.

Content Picker Field с выбранными элементами
Content Picker Field с выбранными элементами

Сторонняя реализация похожей функциональности, говорят, есть в модуле Science Project: Mechanics, но этот модуль не совместим с Orchard 1.5.

Query и Projection

В Orchard CMS есть возможность организовать выборки Content Item'ов, эта функция называется Query.

Здесь можете посмотреть пример использования:

Как вы могли увидеть, в Query можно делать сортировку и фильтрацию по Field'ам. Для этого Orchard не десериализует XML, а пользуется таблицами вроде Orchard_Projections_StringFieldIndexRecord.

Query приучает не использовать Field'ы с одинаковой функциональностью для разных Content Type'ов. Вот, что получается в Edit Query, если в Content Type'ы Article, News, BlogPost добавлено по филду Teaser:

Три Field c одинаковой функциональностью
Три Field c одинаковой функциональностью

В этом примере правильно было бы создать один Content Part с Field'ом Teaser и добавить его в Article, News, BlogPost.

Ваши собственные Content Part'ы тоже могут добавить условия в Query, но для этого вам прийдётся реализовать свой IFilterProvider, а если нужна сортировка, то и ISortCriterionProvider.

Taxonomies

Для категоризации контента есть модуль Taxonomies, видео про этот модуль можно посмотреть здесь.

Этот модуль может заменить механизм тегов, который поставляется вместе с Orchard.

Сразу скажу, что с помощью этого модуля можно организовать связь вроде "Страна → Статья". Но Taxonomy не может иметь дерево из Term'ов разного типа, так что создать иерархию "Страна", "Город" и уже к ним привязывать статью, у вас не выйдет, прийдётся писать свой модуль код.

Container и Containable

Есть возможность создавать списки из Content Item'ов, как написано в статье Creating lists. Но из-за того, что Part одного типа можно добавить в Content Type только один раз, нельзя добавить ContainablePart дважды. Потому невозможно создать список любимых статей и список всех статей, так чтобы эти два списка имели общие элементы: статья будет либо в одном, либо в другом списке. Из-за этого ограничения нельзя создать Content Type, который будет содержать в себе два списка.

Модуль List является deprecated, но он делает управление Container Part'ом очень удобным. Добавление и удаление нескольких элементов становится намного удобнее, чем в случае Content Picker Field'a, описанного выше.

Управление списком с помощью модуля List

Навигация (Menu)

Навигация позволяет вам создавать списки из Content Item'ов, но, в отличии от Container и Containable, один Content Item может быть одновременно в нескольких списках.

В Orchard 1.5 появился приемлемый модуль навигации.

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

Самописные отношения и списки

В Orchard CMS создавать свои собственные ContentPart'ы и реализовывать связи между ними довольно просто и быстро, если знать как это делать. Потому если вам нужна нетривиальная связь, то смело открывайте Visual Studio и создавайте свои собственные ContentPart'ы, реализующие сущности и списки.

Я применяю такую стратегию: делаю простые сущности вроде ArticlePart и CategoryPart. CategoryPart не имеет никаких полей, а ArticlePart имеет только ссылку на CategoryPart. Затем реализую два Content Type'a: Category и Article, и уже к этим типам добавляю Part'ы вроде TitlePart, BodyPart и Field'ы вроде Favorite. Это даёт возможность легко использовать Query, например, чтобы выбирать любимые статьи.