About Posts for October CMS
Posts is a powerful publishing platform for user generated content.
It's a plugin that can be used by content creators of all sizes, from individual bloggers to large publishing houses. Posts is powerful, but it's also really simple to use.
It's a good idea to check out the interactive demo website. You can also find more information about the plugin here.
Posts requires a Commercial License if you plan to deploy it on a for-profit/commercial website.
Key Features at a Glance
- Automatic URL redirection when structures change
- Full SEO support including schema.org markup generation
- Multi-language support for content & URLs
- Clean, semantic URL's
- Multi-type content body options
- Structured post types defined by the active theme. Create anything!
- Per post theme layout with inheritance
- ACL's ensure multi-user safety
- RSS & sitemap generation
Structure
A post is an article, such as a blog post or a news item. Posts are often grouped by category and/or by tag, and they are associated with their author & editor.
In Posts, everything is content. Categories and tags are more than simple lists of posts. Each category and tag can be built up with content in just the same way as a post.
Automatic Redirects
Often, as a content-rich website evolves, it becomes necessary to add new categories and move posts to more suitable categories. This can be a headache for developers and SEO's.
All Posts objects (posts, categories & tags) have a single, definitive URL which is based on the object's type and slug identifier. In the case of a post, the URL is also derived from its primary category and that category's parent categories (if any).
Posts tracks changes to object slugs and can detect when a stale link has been used, automatically redirecting the visitor to the correct path thereby removing the need to set manual redirects and eliminating duplicate content concerns.
SEO & Schema.org Markup
Posts takes full responsibility for adding SEO related meta tags and schema markup to your posts pages.
Several sections are injected into the frontend theme which allow the site administrator to define the site owner and publisher type.
Each post object has a section where further SEO details can be added including Opengraph and Twitter titles along with SEO page title and meta descriptions. Where details are missing, sensible defaults will be used as a fallback.
In the case of 'structured posts', which are defined by the active theme, it's possible to further extend the Schema markup. Two examples are provided in the demo theme for product reviews and recipes.
Multi-Language Support
Posts, categories & tags can all be fully translated into any language which has been defined in the Rainlab Translate plugin. Content and URL's can all be translated and benefit from redirection as detailed above.
A post may have its default language set as any of the available languages. This is useful if you have content which should not be published in the default language/locale of the main webiste.
Multi-Type Content Body
Currently there are four available content body types for posts and three for categories and tags.
Repeater Body
The repeater body allows users to create content in a methodical fashion, adding headings, content sections and media part-by-part. CMS content files and partials can be added here.
Richeditor Body
Create content with the standard WYSIWYG editor
Markdown Body
Create content with the standard Markdown editor
Structured Posts (posts only)
Structured posts are defined not by the plugin, but by the active theme.
The theme should supply a yaml file containing a form definition and a partial file for rendering the post. A structured post could be very simple, or very complex depending on the theme developer's vision for the structure. The linked demo theme provides an example structured post for recipes and product reviews including the relevant Schema.org markup for Google rich results.
Per Post Theme Layout
Posts, categories and tags can specify a theme layout
The main plugin settings allow you to define a default layout, which can be either inherited or overridden by a group of posts, or by an individual post as required.
This is useful for large sites that want to maintain a familiar look, but whose categories, for example, might need to look quite different.
User Permissions (ACLs)
Advanced permissions and user roles can be granted so that site owners have full control of the website while allowing for other users to simply login and write. Some users might be granted editorial or management permission, but whichever way you choose to allocate permissions, you can be confident that your users have all the privileges they need and none they don't.
URL Configuration
Component settings have been kept as simple as possible, with the majority of configuration options being available in the main plugin settings. It is important to visit the settings to choose the CMS page to use for displaying posts, categories, tags and user profile.
It is recommended to use the same URL for both the post display and category display pages. The plugin handles the logic to resolve the correct page in the event of a URL clash. This allows for semantic URL's which can even begin at the project root.
Post Display Page.
title = "Post Display Page" url = "/:postsFullPath*" layout = "default" meta_title = "Post Display Page" is_hidden = 0 [displayPost] == {% component 'displayPost' %}
Category Display Page
title = "Category Display Page" url = "/:postsFullPath*" layout = "default" meta_title = "Category Display Page" is_hidden = 0 [displayCategory] includeSubcategories = 1 postsPerPage = 11 noPostsMessage = "No posts found" sortOrder = "published_at desc" == {% component 'displayCategory' %}
By using the above recommended settings, the following would all be valid:
- /a-category
- /an-uncategorized-post
- /a-category/a-post-in-a-category
- /a-category/a-sub-category/a-post-in-the-subcategory
You can, of course refer to the documentation and define your site structure as you please.
The following plugin is required
The following plugin extends or depends on the plugin
The following theme uses this plugin
Introduction
This plugin provides functionality to write posts (blog posts, news articles etc). To facilitate this, you will need several CMS pages containing relevant plugin components.
To properly implement this plugin into your website, you will likely want to be able to display the content of your posts and have a way to list posts according to various criteria, perhaps by date posted or by the associated category, tag or author.
A demonstration theme is available at https://github.com/Dynamedia/oc-posts-demo-theme which is a useful resource for developers looking to get a quick start. Additionally, a live and interactive demo site can be accessed at https://playground.posts-plugin.dynamedia.uk
On Installation
Backend Users will have a 'profile' created where additional information can be entered. Each user will have a configurable username generated which is used in the frontend to avoid exposing the login name.
Configuration
The majority of the plugin configuration can be found in the backend settings area in the Dynamedia Posts Settings section. Some settings are absolute, whereas others allow you to define a default which will either be inherited or overridden by a post or group (category, tag) of posts.
Publisher Tab
These settings are to assist with Search Engine Optimization and are required for the plugin to generate the correct outputs in conjunction with the theme.
-
Publisher Name - Should contain the name of the organization or individual who is publishing the content, for example Dynamedia Limited.
-
Publisher Type - Dropdown to select either Individual or Organisation.
-
Publisher URL - The URL of the publisher. This may or may not be the same as the current website URL.
-
Publisher Logo - The logo of the publisher.
Posts Tab
This section is for configuring the CMS pages used to display post content.
-
Post Display Page (With Categories) - Select the CMS page which contains the displayPost component. It is recommended to select a page which also has the displayCategory component attached although this isn't required if you prefer a /categories & /posts URL structure.
-
Post Display Page (No Category) - Select the CMS page with the displayPost component when not following the above advice. You can choose a separate CMS page for displaying uncategorized posts but it is advised against.
Further information is available in the component section of this documentation.
Categories Tab
This section is for configuring the CMS pages used to display category content and the associated posts list.
-
Category Display Page - Select the CMS page which contains the displayCategory component. As above, it is recommended to utilise both the displayPost and displayCategory components in the same CMS page.
-
Default Posts Sort Order - Choose a default sorting order for posts in the category. This value can be overridden in the settings section of each individual category.
-
List Posts from Sub-Categories - Choose whether the posts list for this category should contain posts from sub categories. This value can be overridden in the settings section of each individual category.
-
Posts Per Page - Used to select how many posts should be shown per page. This value can be overridden in the settings section of each individual category. The values for this dropdown are derived from config/config.php.
Tags Tab
This section is for configuring the CMS page used to display tag content and the associated posts list.
-
Default Posts Sort Order - Defines a default sorting order for posts in the category. This value can be overridden in the settings section of each individual category.
-
Posts Per Page - Used to select how many posts should be shown per page. This value can be overridden in the settings section of each individual category. The values for this dropdown are derived from config/config.php.
Users Tab
This section is for configuring the CMS page used to display user profiles and the associated posts list.
-
Default Posts Sort Order - Defines a default sorting order for posts by the user. This value can be overridden in the settings section of each individual category.
-
Posts Per Page - Used to select how many posts should be shown per page. The values for this dropdown are derived from config/config.php.
CMS Layouts Tab
The plugin allows for on-the-fly theme layout changing so you are not restricted to a single layout file per CMS page (post, category, tag). This gives great flexibility when designing themes. An example use case for this is when designing a large website which covers several different topics. You may want to have a different appearance for different sections. Naturally, the options available in this section will depend entirely on how many layouts have been made available in the theme.
-
Default Post Layout - The default for posts where the category or post has not specified a separate layout. Posts can specify a layout file or can inherit from their primary category.
-
Default Category Layout - The default for categories where the category has not specified a separate layout.
-
Default Tag Layout - The default for tags where the tag has not specified a separate layout.
Components
Several components are provided with the plugin.
displayPost
This component is for displaying the contents of a post. The component has no configurable options and should be present on a CMS page which contains either of the two available URL parameters.
Both of the available parameters ultimately fetch the post from the post slug but several are provided to help with defining the ideal URL structure.
:postsPostSlug
represents only the slug, eg /my-post
:postsFullPath*
represents the category and post, eg /a-category/a-subcategory/my-post
although this can also match a category slug or a post without categories when used
alongside the displayCategory component.
The following snippet demonstrates how you might implement this component, standalone on a CMS page with the full category path as part of the URL.
title = "Posts Page" url = "/posts/:postsCategoryPath*/:postsPostSlug" [displayPost] == {% component 'displayPost' %}
If you want to implement without showing the category you might do as follows
title = "Posts Page Without Category" url = "/posts/:postsPostSlug" [displayPost] == {% component 'displayPost' %}
displayPost will inject the following variables:
-
post - The Post object
-
paginator - a LengthAwarePaginator to use with multi-page posts
displayCategory
This component is for displaying the contents of a category and its associated list of posts. The component has no configurable options and should be present on a CMS page which contains either of the two available URL parameters.
:postsCategorySlug
represents the category slug.
:postsCategoryPath*
represents the category part of the path, eg. /a-category or
/a-category/a-sub-category
:postsFullPath*
represents the category and post, eg /a-category/a-subcategory/my-post
although this can also match a category slug or a post without categories when used
alongside the displayCategory component.
The following snippet demonstrates how you might implement this component, standalone on a CMS page with the full category path as part of the URL.
title = "Category Display Page" url = "/posts/category/:postsCategoryPath*" [displayCategory] includeSubcategories = 1 postsPerPage = 10 == {% component 'displayCategory' %}
Or with just the category slug but without the full path
title = "Category Display Page" url = "/posts/category/:postsCategorySlug" [displayCategory] includeSubcategories = 1 postsPerPage = 10 == {% component 'displayCategory' %}
displayCategory will inject the following variables:
-
category - The Category object.
-
posts - a LengthAwarePaginator containing post items.
Combining displayPost and displayCategory
It is recommended to use the same URL for both the post display and category display pages. The plugin handles the logic to resolve the correct page in the event of a URL clash. This allows for semantic URL's which can even begin at the project root.
You can achieve this my utilising the :postsFullPath*
parameter in the url
In this case, you could use the following snippets for both pages.
Post Display Page.
title = "Post Display Page" url = "/:postsFullPath*" layout = "default" meta_title = "Post Display Page" is_hidden = 0 [displayPost] == {% component 'displayPost' %}
Category Display Page
title = "Category Display Page" url = "/:postsFullPath*" layout = "default" meta_title = "Category Display Page" is_hidden = 0 [displayCategory] includeSubcategories = 1 postsPerPage = 11 noPostsMessage = "No posts found" sortOrder = "published_at desc" == {% component 'displayCategory' %}
This would match, for example:
https://example.org/my-uncategorized-post
https://example.org/a-category
https://example.org/a-category/my-categorized-post
It would, however, not match:
https://example.org/my-static-page
Of course, you do not need to implement from the URL root,
you could just as easily use url = '/posts/:postsFullPath*\'
Note: URL's
The above components are both used in URL generation. When you have created
your preferred structure and selected the relevant pages in the plugin
settings, post and category objects will have the url attribute
available. You can access these at {{ post.url }}
and {{ category.url }}
If your category structure changes, users will always be redirected to the correct URL.
As an example, if you have a post with the slug 'my-interesting-post',
and it has no category, its URL might be
https://example.org/posts/my-interesting-post
You may later add a category called 'my-stuff' and choose to add the post
so it's URL now becomes
https://example.org/posts/my-stuff/my-interesting-post
In this case, https://example.org/posts/my-interesting-post
will redirect
to https://example.org/posts/my-stuff/my-interesting-post
.
displayTag
This component is for displaying the contents of a tag and its associated list of posts. The component has no configurable options.
The tag will be identified using the :postsTagSlug
URL parameter.
Example implementation
title = "Tags" url = "/tags/:postsTagSlug" [displayTag] postsPerPage = 10 == {% component 'displayTag' %}
displayCategory will inject the following variables:
-
tag - The Tag object.
-
posts - a LengthAwarePaginator containing post items.
displayUser
This component is responsible for displayng a user profile and the posts of the user.
The user will be identified using the :postsUsername
URL parameter.
It is important to note that the username is not the backend login name. A username will be generated by the Profile model for each user, but this can be changed in the user admin settings.
Example implementation
title = "User Posts" url = "/user/:postsUsername" [displayUser] == {% component 'displayUser' %}
displayCategory will inject the following variables:
-
user - The backend User object.
-
posts - a LengthAwarePaginator containing post items.
searchPosts
This component is responsible for displayng search results for Posts.
The search query should be passed with the q
URL parameter, for example
https://example.org/search?q=keyword
This component requires some configuration
-
postsLimit - The total number of posts that can be fetched.
-
postsPerPage - When this is greater than postsLimit or postsLimit is not set, the results will be paginated.
-
sortOrder - Dropdown to choose the order in which results should be returned.
Example implementation
title = "Search" url = "/search" [searchPosts] postsPerPage = 10 == {% component 'searchPosts' %}
searchPosts will inject the following variables:
-
searchQuery - The search string.
-
posts - a LengthAwarePaginator containing post items.
listPosts
This component is responsible for displayng a customised list of posts.
This component requires some configuration
-
categoryFilter - Posts from the selected category.
-
includeSubcategories - Include posts from the selected category's subcategories.
-
tagFilter - Posts tagged with the selected tag.
-
postIds - Return only posts with the given IDs, in the order specified.
-
notPostIds - Exclude posts with the given IDs.
-
notCategoryIds - Exclude posts from categories with the given IDs.
-
notTagIds - Exclude posts with tags matching with the given IDs.
-
postsLimit - The total number of posts that can be fetched.
-
postsPerPage - When this is greater than postsLimit or postsLimit is not set, the results will be paginated.
-
sortOrder - Choose the order in which results should be returned.
Example implementation, perhaps suitable for a website homepage
title = "Home" url = "/" [listPosts featuredPosts] postIds = "1,2" postsLimit = 2 postsPerPage = 2 sortOrder = "published_at desc" [listPosts latestPosts] notPostIds = "1,2" notCategoryIds = 4 notTagIds = 1 postsLimit = 5 postsPerPage = 5 sortOrder = "published_at desc" == <div class="content"> <div class="row"> <div class="col-12"> <h2 class="mt-4 mb-4">Featured Posts</h2> {% component 'featuredPosts' %} <h2 class="mt-4 mb-4">Latest posts</h2> {% component 'latestPosts' %} </div> </div> </div>
listPosts will inject the following variables:
- posts - a LengthAwarePaginator containing post items.
Anatomy of Objects
The following section describes the attributes available in the objects generated by the Posts plugin
Post
Model Dynamedia\Posts\Post
Table dynamedia_posts_posts
Post Attributes
Attribute | Type |
---|---|
title | String |
slug | String |
excerpt | Text (html) |
body | Array |
images | Array |
url | string * |
contents_list | Array * |
pages | Array * |
show_contents | Boolean |
is_published | Boolean |
published_at | DateTime |
author_id | Integer (Backend\Models\User ID) |
editor_id | Integer (Backend\Models\User ID) |
primary_category_id | Integer (Category ID) |
* Appended attribute
To aid in development, some example dd dumps are provided below for the array attributes.
body attribute
^ array:4 [▼ 0 => array:2 [▼ "block" => array:5 [▼ "sId" => "first" "image" => array:4 [▼ "alt" => "" "class" => "" "default" => "" "image_style" => "inline-left" ] "content" => "<p>Construct your posts using as many or as few blocks as you like!</p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incidid ▶" "heading" => "A content block" "in_contents" => "1" ] "_group" => "section" ] 1 => array:2 [▶] 2 => array:1 [▼ "_group" => "pagebreak" ] 3 => array:2 [▼ "block" => array:5 [▼ "sId" => "third" "image" => array:4 [▶] "content" => "<p>Posts can, if you want, be written over multiple pages. It's all handled internally so just add a pagebreak block. Easy!</p>" "heading" => "New page content" "in_contents" => "1" ] "_group" => "section" ] ]
pages attribute
This is derived from the body and separates body sections by pagebreaks
^ array:2 [▼ 0 => array:2 [▼ 0 => array:3 [▼ "block" => array:5 [▼ "sId" => "first" "image" => array:4 [▶] "content" => "<p>Construct your posts using as many or as few blocks as you like!</p><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incidid ▶" "heading" => "A content block" "in_contents" => "1" ] "_group" => "section" "page" => 1 ] 1 => array:3 [▶] ] 1 => array:1 [▼ 0 => array:3 [▼ "block" => array:5 [▼ "sId" => "third" "image" => array:4 [▶] "content" => "<p>Posts can, if you want, be written over multiple pages. It's all handled internally so just add a pagebreak block. Easy!</p>" "heading" => "New page content" "in_contents" => "1" ] "_group" => "section" "page" => 2 ] ] ]
images attribute
This contains the main images, which are separate from the post body images.
URL's should be used with the | media
twig filter.
^ array:3 [▼ "list" => array:3 [▼ "alt" => "The List Image" "class" => "optional-class" "default" => "/list-image.png" ] "banner" => array:3 [▼ "alt" => "The Banner Image" "class" => "optional-class" "default" => "/banner-image.png" ] "social" => array:2 [▼ "twitter" => "/twitter-image.png" "facebook" => "/facebook-image.png" ] ]
contents_list attribute
Post sections with in_contents set to true
^ array:3 [▼ 0 => array:4 [▼ "title" => "A content block" "page" => 1 "url" => "http://octobercms.local/uncategorized/first-blog-post#first" 0 => "contents_list" ] 1 => array:4 [▼ "title" => "Another content block" "page" => 1 "url" => "http://octobercms.local/uncategorized/first-blog-post#second" 0 => "contents_list" ] 2 => array:4 [▼ "title" => "New page content" "page" => 2 "url" => "http://octobercms.local/uncategorized/first-blog-post?page=2#third" 0 => "contents_list" ] ]
Post Relationships
Relation | Type | Model |
---|---|---|
primary_category | BelongsTo | Category |
author | BelongsTo | Backend\Models\User |
editor | BelongsTo | Backend\Models\User |
categories | BelongsToMany | Category |
tags | BelongsToMany | Tag |
Category
Model Dynamedia\Posts\Category
Table dynamedia_posts_categories
Category Attributes
Attribute | Type |
---|---|
name | String |
slug | String |
excerpt | Text (html) |
body | Array |
url | String * |
post_list_ids | Array * |
post_list_sort | String * |
post_list_per_page | Integer * |
computed_cms_layout | String * |
path_from_root | Array * |
path_to_root | Array * |
subcategory_ids | Array * |
* Appended attribute
Category Relationships
Relation | Type | Model |
---|---|---|
posts | BelongsToMany | Post |
self | NestedTree | Category |
Tag
Model Dynamedia\Posts\Tag
Table dynamedia_posts_tags
Tag Attributes
Attribute | Type |
---|---|
name | String |
slug | String |
excerpt | Text (html) |
body | Array |
is_approved | Boolean |
url | String * |
post_list_sort | String * |
post_list_per_page | Integer * |
computed_cms_layout | String * |
* Appended attribute
Tag Relationships
Relation | Type | Model |
---|---|---|
posts | BelongsToMany | Post |
Profile
Model Dynamedia\Posts\Profile
Table dynamedia_posts_profiles
Profile Attributes
Attribute | Type |
---|---|
username | String |
website_url | String |
facebook_handle | String |
twitter_handle | String |
instagram_handle | String |
mini_biography | Text |
full_biography | Text |
url | String * |
full_name | String * |
seo_schema | Array * |
user_id | Integer (Backend\Models\User ID) |
* Appended attribute
Profile Relationships
Relation | Type | Model |
---|---|---|
user | BelongsToMany | Backend\Models\User |
NOTE: This documentation will be updated and improved upon regularly. Please also see https://posts-plugin.dynamedia.uk for more information.
-
LCT TECH
Found the plugin useful on 1 Dec, 2021
Best posts plugin
-
1.0.5 |
Replace depreciated getTranslateAttribute() usage May 23, 2022 |
---|---|
1.0.4 |
All backend fields are now translatable. Fix settings defaults Jan 14, 2022 |
1.0.3 |
Theme templates can be inherited from parent theme. Core version constraint. Nov 11, 2021 |
1.0.2 |
No locale prefix where only one language (i.e. Translate unused) Oct 22, 2021 |
1.0.1 |
Fix issue when saving models before settings have been completed Oct 16, 2021 |
1.0.0 |
First Version of Posts Oct 13, 2021 |