PHPackages                             dynamedia/wn-posts-plugin - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. dynamedia/wn-posts-plugin

ActiveWinter-plugin

dynamedia/wn-posts-plugin
=========================

Posts publishing plugin

v1.0.10(4y ago)2962[2 issues](https://github.com/Dynamedia/wn-posts-plugin/issues)1proprietaryPHPPHP &gt;=7.2

Since Oct 13Pushed 4y ago1 watchersCompare

[ Source](https://github.com/Dynamedia/wn-posts-plugin)[ Packagist](https://packagist.org/packages/dynamedia/wn-posts-plugin)[ Docs](https://github.com/dynamedia/wn-posts-plugin)[ RSS](/packages/dynamedia-wn-posts-plugin/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (4)Versions (13)Used By (1)

Introduction
------------

[](#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 which is a useful resource for developers looking to get a quick start. Additionally, a live and interactive demo site can be accessed at

On Installation
---------------

[](#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
-------------

[](#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

[](#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

[](#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 &amp; /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

[](#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

[](#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

[](#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

[](#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
----------

[](#components)

Several components are provided with the plugin.

#### displayPost

[](#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

[](#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

[](#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

[](#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

[](#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

[](#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

[](#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"
==

            Featured Posts
            {% component 'featuredPosts' %}

            Latest posts
            {% component 'latestPosts' %}

```

listPosts will inject the following variables:

- **posts** - a LengthAwarePaginator containing post items.

Anatomy of Objects
------------------

[](#anatomy-of-objects)

The following section describes the attributes available in the objects generated by the Posts plugin

### Post

[](#post)

Model `Dynamedia\Posts\Post`

Table `dynamedia_posts_posts`

##### Post Attributes

[](#post-attributes)

AttributeTypetitleStringslugStringexcerptText (html)bodyArrayimagesArrayurlstring \*contents\_listArray \*pagesArray \*show\_contentsBooleanis\_publishedBooleanpublished\_atDateTimeauthor\_idInteger (Backend\\Models\\User ID)editor\_idInteger (Backend\\Models\\User ID)primary\_category\_idInteger (Category ID)\* Appended attribute

To aid in development, some example dd dumps are provided below for the array attributes.

##### body attribute

[](#body-attribute)

```
^ array:4 [▼
  0 => array:2 [▼
    "block" => array:5 [▼
      "sId" => "first"
      "image" => array:4 [▼
        "alt" => ""
        "class" => ""
        "default" => ""
        "image_style" => "inline-left"
      ]
      "content" => "Construct your posts using as many or as few blocks as you like!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" => "Posts can, if you want, be written over multiple pages. It's all handled internally so just add a pagebreak block. Easy!"
      "heading" => "New page content"
      "in_contents" => "1"
    ]
    "_group" => "section"
  ]
]

```

##### pages attribute

[](#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" => "Construct your posts using as many or as few blocks as you like!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" => "Posts can, if you want, be written over multiple pages. It's all handled internally so just add a pagebreak block. Easy!"
        "heading" => "New page content"
        "in_contents" => "1"
      ]
      "_group" => "section"
      "page" => 2
    ]
  ]
]

```

##### images attribute

[](#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

[](#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

[](#post-relationships)

RelationTypeModelprimary\_categoryBelongsToCategoryauthorBelongsToBackend\\Models\\UsereditorBelongsToBackend\\Models\\UsercategoriesBelongsToManyCategorytagsBelongsToManyTag---

### Category

[](#category)

Model `Dynamedia\Posts\Category`

Table `dynamedia_posts_categories`

##### Category Attributes

[](#category-attributes)

AttributeTypenameStringslugStringexcerptText (html)bodyArrayurlString \*post\_list\_idsArray \*post\_list\_sortString \*post\_list\_per\_pageInteger \*computed\_cms\_layoutString \*path\_from\_rootArray \*path\_to\_rootArray \*subcategory\_idsArray \*\* Appended attribute

##### Category Relationships

[](#category-relationships)

RelationTypeModelpostsBelongsToManyPostselfNestedTreeCategory---

### Tag

[](#tag)

Model `Dynamedia\Posts\Tag`

Table `dynamedia_posts_tags`

##### Tag Attributes

[](#tag-attributes)

AttributeTypenameStringslugStringexcerptText (html)bodyArrayis\_approvedBooleanurlString \*post\_list\_sortString \*post\_list\_per\_pageInteger \*computed\_cms\_layoutString \*\* Appended attribute

##### Tag Relationships

[](#tag-relationships)

RelationTypeModelpostsBelongsToManyPost---

### Profile

[](#profile)

Model `Dynamedia\Posts\Profile`

Table `dynamedia_posts_profiles`

##### Profile Attributes

[](#profile-attributes)

AttributeTypeusernameStringwebsite\_urlStringfacebook\_handleStringtwitter\_handleStringinstagram\_handleStringmini\_biographyTextfull\_biographyTexturlString \*full\_nameString \*seo\_schemaArray \*user\_idInteger (Backend\\Models\\User ID)\* Appended attribute

##### Profile Relationships

[](#profile-relationships)

RelationTypeModeluserBelongsToManyBackend\\Models\\UserNOTE: This documentation will be updated and improved upon regularly. Please also see  for more information.

###  Health Score

22

—

LowBetter than 22% of packages

Maintenance0

Infrequent updates — may be unmaintained

Popularity14

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 99.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~17 days

Recently: every ~38 days

Total

11

Last Release

1493d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/c18d41147f76bc70268c830fd70af6f1e3466c17c7ba579e5eb6b5c5bcf02269?d=identicon)[dynamedia](/maintainers/dynamedia)

---

Top Contributors

[![robballantyne](https://avatars.githubusercontent.com/u/8018501?v=4)](https://github.com/robballantyne "robballantyne (311 commits)")[![damsfx](https://avatars.githubusercontent.com/u/282242?v=4)](https://github.com/damsfx "damsfx (2 commits)")

---

Tags

laravelblogpublishingpostscontent-marketingwinterwinter cms

### Embed Badge

![Health badge](/badges/dynamedia-wn-posts-plugin/health.svg)

```
[![Health](https://phpackages.com/badges/dynamedia-wn-posts-plugin/health.svg)](https://phpackages.com/packages/dynamedia-wn-posts-plugin)
```

###  Alternatives

[austintoddj/canvas

A Laravel publishing platform

3.4k120.6k](/packages/austintoddj-canvas)[microweber/microweber

New generation CMS with drag and drop

3.4k13.8k1](/packages/microweber-microweber)[binshops/laravel-blog

Simple blog package (with admin panel) for Laravel. Includes all views, controllers, routes and can add a blog to any existing Laravel app. Fully customisable blog (view, urls, and many other options). Includes image uploads and a pretty admin interface to manage your blog. Defaults to /blog but you can change it to anything.

48447.0k](/packages/binshops-laravel-blog)[winter/wn-blog-plugin

Blog plugin for Winter CMS

2042.1k3](/packages/winter-wn-blog-plugin)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
