B3 blog

Summary: Easy to install and use wiki blogging system
Version: 20230131
Prerequisites: PmWiki 2.2.56 or newer
Status: Experimental
Maintainer: Petko
License: GPLv3+
Users: +4 (view / edit)
Discussion: B3-Talk

Use PmWiki as a simple news/blog engine.


This is a simple blogging script that allows easy creation of news/blog articles with PmWiki markup.

  • The system facilitates the creation and the listing of the articles. Creating an article resembles creating a wiki page with an Edit template, similar to creating new pages here on the Cookbook, or in the PITS.
  • A blog entry (article) can have any core or custom PmWiki markup. It can be public or hidden (draft).
  • Articles can have reader comments open, moderated (hidden until approved), closed or disabled, and partial wiki markup can be allowed. The comment form can have Captcha protection.
  • Listings and trails automatically link between posts.
  • The posts can have different tags (similar to categories), and listings can be configured to show entries from specified author(s) or tag(s). Listings can be automatically paginated.
  • Draft posts and unapproved comments are only shown to editors. "Publishing" a "draft" post is as easy as following a link.
  • JavaScript is not required to read or to create blog entries, or to post comments. A small script enhances the display of the dates/times of posts and comments.
  • The recipe offers RSS feeds for all posts or for individual category listings to allow easy subscription and notification.

The recipe was written specifically for the News section of pmwiki.org where you can see actual articles and listings.

Demo: PmWeekly, official development blog.

Test/sandbox: TestBlog (the edit password is b345).


The recipe assumes all your blog entries will be in a specified WikiGroup, say "Blog" or "News".

  1. Get b3.zipΔ, place cookbook/b3.php in your pmwiki/cookbook directory, the pub/b3 directory in your pmwiki/pub directory.
  2. Add to local/Blog.php or to local/config.php such a line:
  3. Edit the page Blog.Blog and insert:
(:b3-list newpostform=1:)
Optional: Place captcha.phpΔ from the Captcha recipe in your pmwiki/cookbook directory. If that file is in your installation, it will be automatically included by B3.


Creating a new post, or editing an existing one

You can edit an existing blog post like you edit a wiki page. Just keep in place the anchors and the attributes, outlined below.

Near the top of a page listing, fill in the title of the new post, eg "My first post", then press "Add post". You can select a page name (in the same group), if empty, it will be calculated from the title field. You can select a date, either by typing a date like 2024-04-17 19:01 or a string that can be parsed into a date, eg. now or tomorrow or next Monday 14:00 (in English).

A new page will be opened for editing, pre-filled with an edit template, a few attributes near the top and a few empty lines where to type the blog entry:

Title: My first post
Author: Petko
Date: 2017-06-10 13:53:29 +0200
Tags: Uncategorized
Comments: Open

Describe My first post here.


You can edit the "attributes" or "variables" near the top of the page if needed, then type the page text using any wiki markup you like, above the [[#b3comments]] line.

Note: The "Title:" line is not required if your page has a (:title ...:) directive.

The post attributes, all without wiki markup, are as follows:

  • Title: the post title (also page title). Any text without wiki markup.
  • Author: the name of the author. In the post header and in listings, it will be converted to a link to the Profile page of the author.
  • Date: the date of the post. The date format must be parsable by PHP. If the date is in the future, the post is considered hidden (or draft), and will not appear in listings and in trails.
    • A public post (as long as the Date attribute is in the past) is open for reading, appears at listings, and can accept comments.
    • A draft post (the Date attribute is in the future) it is only visible to "editors", people with "edit" permissions. Note: PmWiki.Drafts does not need to be enabled, only the "Date:" line is read.
    • There is no "indefinite" Draft status, but you could select a date 1000 years in the future like 3024-04-17 19:01 just in case your wiki survives that long.
  • Tags: comma-separated list of tags or categories, eg Cookbook, Core. The tags appear in the post header and lead to pages in the current group which can be configured to list only pages in the selected category.
  • Comments: can be:
    • Open: Anyone can post a comment which is immediately added to the page (even if the commenter does not have edit permissions).
    • Moderated: Anyone can post a comment but it remains hidden for non-editors, and has to be approved by an editor (editing the page and removing the "HIDDEN" classname from the item).
    • Closed: Existing non hidden comments are displayed, but new comments cannot be added.
    • Disabled: Any existing comments are hidden, and new comments cannot be added.
    • Comments will be inserted between the anchors [[#b3comments]] and [[#b3end]].

The text of the blog post should be written between the page attributes and the anchor [[#b3comments]].

An "introduction" of the text will be included in the blog listings:

  • you can insert a [[#more]] anchor where you'd like the post to be split between the introduction and the main content;
  • if there is no [[#more]] anchor in the text, any paragraph(s) before the first heading will be considered an introduction;
  • if there are no headings, then the first paragraph of the page text will be considered an introduction.

After the page is saved, if its date is in the future, it is considered Draft and will appear faded-out for editors, and hidden for visitors without edit permissions. A framed header on the page will remind about this, and a "Publish" link will allow editors to automatically update the post with the current date, thus making it public/visible.

Posting and editing comments

When the "Comments" attribute of a page is "open" or "moderated", a form at the bottom of the page allows people to add their name and comments.

  • A checkbox can be checked if the visitor has used wiki markup rules (if $B3["EnableWikiMarkupInComments"] is set).
  • A Captcha form appears when the user has no "edit" permissions for the current page.

Comments are added to the current page, between the anchors [[#b3comments]] and [[#b3end]]. The comments format looks like this:

* %HIDDEN b3-comment wikimarkup% [=Author=]: %date%2017-06-11 02:30%% [=Comment text=]
  • A comment is a list item.
  • The first part are WikiStyles classes which can be used to style the comments, but also indicate to B3 whether the comment is hidden (waiting to be reviewed/moderated) and if it contains wiki markup.
  • "HIDDEN" and "wikimarkup" classes are optional.
  • An editor can edit the blog page and remove the "HIDDEN" class if the comment needs to be made public.
  • An editor can edit the blog page and remove the "wikimarkup" class: then B3 will not process the comment text through the PmWiki markup engine.
  • While the "add comment" form always posts the comments as first-level list items, an editor can manually insert or re-arrange comments as second and third level items, simulating a threaded conversation.

Blog Listings

A listing page contains the directive:


Or, with extended arguments:

(:title My personal blog:)
(:description My thoughts about board games, hiking and sci-fi:)
(:b3-list newpostform=1 perpage=10 tags=Eggs,Butter author=Name1,Name2 trail=0 pagename=OtherGroup.OtherPage:)

A listing page displays the latest 10 blog posts, with their titles, dates, authors, tags, an introduction and a "Read more..." link that leads to the full post. The posts are ordered in reverse chronological order, newer before older. When there are more than 10 posts, a "list trail" at the bottom allows reading older or newer posts.

The arguments are as follows:

  • newpostform=1 : Display a "new post form" at the top of the listing only to users with "edit" permissions. Set this to 2 to show the form to all visitors.
  • perpage=10 : How many posts to display per page.
  • tags=Eggs,Butter : Display only posts from selected tags/categories.
    • You can have more than one, separate them with commas. A post will be shown if it is in at least one among the selected categories.
    • To require more than one tags, precede them it with plus "+": tags=+Eggs,+Butter will only list posts that have both the Eggs and the Butter categories (eg Cake).
    • To exclude tags from the listing, precede them it with minus "-": tags=Eggs,-Butter will only list posts that are in the Eggs category but NOT in the Butter category. Or you can have tags=-Private to list all posts that do not have the Private tag.
    • You can use wildcards, the "*" character means "anything" (experimental feature).
  • author=Name1 : only list posts from a selected author (or multiple authors separated with commas, like for tags=). Strip all spaces and special characters.
  • trail=0 : disable the trail for the current page. See also $B3['EnableListTrail'] to disable it sitewide.
  • pagename=OtherGroup.OtherPage : this should only be used if you want to display a blog listing in a page, where all blog entries are in a different group. With this argument, B3 will scan the OtherGroup group for blog pages, and all links ("Read more", tags, trails) will point to the other group. Set here a page that contains an actual B3 listing.

On blog listings you should use the (:title ...:) and (:description ...:) directives to populate the RSS variables, see below.

There is also a page variable {$B3LinkRSS} that links to the RSS feed and allows people to copy it and add it into their News readers. Use {$B3LinkRSS} if the current page is a listing, or {OtherGroup.OtherPage$B3LinkRSS} when the listing is in another page.


The following variables can be defined in Blog.php before including the script.

  • $B3['DirUrl'] = '$FarmPubDirUrl/b3'; # The browser-accessible URL to the pub/b3 directory with styles and helper scripts. If you have a wikifarm on multiple (sub)domains, you may need to copy the b3 directory into the "pub" directory of individual fields and set here '$PubDirUrl/b3'.
  • $B3['TimeFmt'] = '%Y-%m-%d %H:%M %z'; # The time format for the dates stored in the pages. The format needs to be parsable by strtotime().
  • $B3['EnableComments'] = 'Open'; # Default comments settings when pre-filling the edit template, or when the individual post page doesn't have a known Comments: attribute. Can be Open, Closed, Disabled and Moderated, see above.
  • $B3['NewPostDefaultDate'] = 'Now + 60min'; # The default date stamp pre-filled in the "Add post" form.
  • $B3['DefaultText'] = 'Describe {title} here.'; # The default text pre-filled when a new post is created. If the text starts with 'page:', like 'page:News.Template#template#templateend lines=2..' then that page or section will be used as a template (specifications same as for the include directive).
  • $B3['EnableCaptcha'] = true; # To enable Captcha for comments, or not. Set this to false to not require Captcha verification for comments. Note that the Captcha is only shown to people without 'edit' permissions for the current blog entry. Note that for Captcha to be enabled, you need to place the captcha.php file in your cookbook directory.
  • $B3['PerPage'] = 10; # How many blog posts are to be displayed per listing page. If there are more pages, a list trail to previous and next pages will be shown at the bottom of the listing.
  • $B3['EnablePageTrail'] = true; # Whether a trail to older and newer articles should be enabled on blog posts (all categories). Set to false to disable.
    • If there is no older or newer page in the current set, the "previous" or "next" link will point to the main listing.
  • $B3['EnableListTrail'] = true; # Whether a trail to older and newer pages of articles should be enabled on blog listings (within the categories of the current listing). Set to false to disable.
    • Both page trails and list trails link through all pages for "editors" and only to public, non-draft pages for "readers" (people without edit permissions).
  • $B3['EnableWikiMarkupInComments'] = true; # Whether to allow wiki markup in comments, set this to false to disable it, and to hide the checkbox.
  • $B3['DefaultTags'] = 'Uncategorized'; # The default pre-filled tags in the edit template when a page is created.
  • $B3['RSSTitle'] = '{$WikiTitle}: {$Title}'; # The title of the listings in the RSS feeds.
  • $B3['ShareMarkup'] = '(:share:)'; # Automatically insert some markup at the bottom of the main blog entry, before the comments block. This is intended to allow a block of "sharing buttons" to social media platforms, for example the recipe ShareButtons.
  • $B3['ListSelfLink'] = true; # If true, using (:b3 pagename=OtherPage:) will have the "list links" to older and newer posts link to the same page. By default this is false, and links to older and newer posts open OtherPage.

Other variables, as set in the b3.php file, allow the control of the various templates and replacement patterns: although configurable, only experienced admins should change them.

If used together with Mini with $Mini['EnableLightbox'], include this line after the Mini installation block (hides a notice at the W3C feed validator):

  if($action=='b3rss') $Mini['EnableLightbox'] = 0;

Date format

From version 20200126 it is possible to define a custom date format for the time stamps both in the page subheader and in the comments. Previously the dates were rewritten in the time zone and language locale of the visitor's browser, in the default format for that language. Now it is possible to define custom date options and locales.

The following can be set in config.php before including b3.php:

$B3['DateOptions'] = array(
  'locale'  => 'fr-FR',
  'weekday' => 'long',
  'year'    => 'numeric',
  'month'   => 'long',
  'day'     => 'numeric',
  'hour'    => '2-digit',
  'minute'  => '2-digit'

The 'locale' entry can be 'fr', 'en', 'de' for a language, or 'fr-CA', 'en-ZA' for a specific dialect/country. If not defined, the language and the country of the visitor will be used, so someone in France will see the dates in French, someone in Japan in Japanese, and the times will be what is usual for that language/country (12 or 24 hours, AM/PM or not, leading zeros or not, specific separators and orders). The time stamps are always in the timezone of the visitor, unless you define a 'timeZone' entry.

The other entries are also all optional and define a preference for the date format, for example 'weekday' => 'long', means that the full day name will be shown like "Wednesday". If an entry is not defined, say 'weekday', it will not appear in the date. The format and order will be those that are usual for that language.

For a full reference, see the documentation for Date().toLocaleString() at MDN. (You have to define the entries in PHP, they will be converted to JSON and passed to the b3.js script.)

Very old browsers that do not support toLocaleString() with locales and options will either show the default local date format, or (if JavaScript is disabled) will show the actual time stamps in the format and time zone of the server.

To revert to the pre-20200126 format set in config.php:

$B3['DateOptions'] = array();

Notes about RSS

Every page containing a blog listing (:b3-list ...:) has its own RSS feed. Currently there are no "Comments feeds".

The RSS feed will have as a "title" the title of the listing page set with a (:title ...:) directive, and as a "description" the description of the listing page set with a (:description ...:) directive. See also $B3['RSSTitle'].

Then the RSS feed will show the latest "X" blog posts from the tags (categories) in the current listing, set in (:b3-list tags=Tag1,Tag2:) that is, the exact same blog posts that the wiki page shows to a "reader" without edit permissions. To change the number of posts, see $B3['PerPage'] above.

The date of every blog post in the RSS feed is the one in the "Date:" attribute in the page header, not the "last modified" timestamp (adding comments changes the latter but not the former).

You can place in your listing page the variable {$B3LinkRSS} which creates and displays a properly formatted link to the RSS feed of the listing. The HTML source of that page also contains a <link/> element with the RSS feed for automatic discovery by browsers and bots.


The following strings can be translated in an XLPage:

# Message when creating new post
  "b3msg_author" => "Please type your name at the Author: prompt.",
  "b3msg_exists" => "Attention, a page already exists with the same name, its text is open below.",
  "b3msg_newpost" => "You are about to create a new blog entry. It will remain hidden from listings/RSS while its date is in the future. Comments can be 'open', 'moderated', 'closed' or 'disabled'.",
  "Your Name" => "",

# In post headers, where appear "21 comments", "by Author Name" and "in Tag1, Tag2"
  "comments" => "",
  "by" => "",
  "in" => "",

# Add comment form
  "Leave a reply" => "",
  "Your name (required)" => "",
  "Your comment (required)" => "",
  "Your comment contains wikitext markup" => "",
  "Add comment" => "",
  "Please fill the required form fields" => "",
  "Comment posted" => "",
  # for Captcha
  "Enter value:" => "",

# Add new post form
  "New post title" => "",
  "Page name (optional)" => "",
  "Date" => "",
  "Add post" => "",

# Draft post header:
  "b3msg_draft" => "The current post is not yet public - its publication date is in the future. Only editors can see it.",
  "Publish" => "",

# Between the page trail and the comments, where "3 comments on My post" is shown
  "comments on" => "",

# for the variable {$B3LinkRSS} (may change)
  "RSS feed" => "",

# On Listing pages ("trail" links and "more" links)
  "Older posts" => "",
  "Newer posts" => "",
  "Read more..." => "",

# Error strings
  "This page does not appear to contain a b3 blog entry." => "",
  "This page does not currently accept comments." => "",
  "Cannot generate feed." => "",
  "Page does not appear to be a B3 listing." => "",


  • The anchors [[#b3]], [[#b3comments]] and [[#b3end]] need to be alone on their lines.
  • The recipe assumes a group-wide password protection (all pages in the news/blog group need to have the same permissions).
  • A comment can be posted if the commenter has read permissions, edit permissions are not required.
  • The listings only recognize pages that have both a "Date:" and an "Author:" not empty attributes.

Change log / Release notes

  • 20230131: Update for PHP 8.2, reported by Dave Cooke.
  • 20230101: When comments are disabled, the "(# comments)" information will not appear in the subheader (suggested by Luigi).
  • 20220507: Update for PHP 8.1.
  • 20201019: Add $B3['ListSelfLink'], see discussion at talk page.
  • 20200126: Fix tag links to use the tagpage title (if defined). Rewrite b3.js to use configurable date options.
  • 20200125: Fix handling of (:title ...:) in the same page (will take precedence over the "Title:" line), and hopefully improve compatibility with MultiLanguageViews.
  • 20200124: Add page variable $B3Public which is "public" when the page is a public blog post, and empty if the page is a draft post or another type of page. Can be used in (:pagelist ... $B3Public=public:) to list the entries with a pagelist instead of (:b3-list:)
  • 20190826: Add $B3['ShareMarkup'].
  • 20190610: Fix RSS feed validation error, reported by Said Achmiz.
  • 20180105: Only load CSS+JS when the page needs them, suggested by Said Achmiz.
  • 20171107: After posting a comment, the script will now scroll down to the comments section.
  • 20171013: Fix minor bug with localized timestamps when the hour is <= 9.
  • 20170810: The page creation will now abort if a valid page name cannot be deduced from the submitted form.
  • 20170809: Fix a bug with the default text, the variable {title} was expanded only once.
  • 20170725: Add $B3['DefaultText'], it is now possible to have the default text template written in a wiki page or section.
  • 20170712: i18n: add "b3msg_author", "Publish", "Page name (optional)", "Date", modify "b3msg_newpost", "b3msg_draft", "RSS feed"; remove seconds to $B3['TimeFmt']; remove Status: attribute and $B3['EnableStatus'] (only dates in the future make the post Draft); change new post form and template to select page name, title and date, add $B3['NewPostDefaultDate']; add "Publish" link to draft post headers; add warning if no author name; convert all post and comment date stamps to the timezone and language/locale of the visitor.
  • 20170630: Add $B3['EnableStatus'].
  • 20170623a: Validate Captcha tag (Captcha version 20170623 or newer recommended) and Comment form. Fix RSS server headers. Improve change summary for comments.
  • 20170623: Increase opacity for hidden posts to from 50% to 70%. Add XL message "b3msg_draft" if the post is not yet published, and hide the post from non-editors.
  • 20170617: Update for PHP 7.2.
  • 20170616: Hidden posts (either Draft status, or date in the future): add DRAFT classname and fade-out styles, hide comments and comment form. Fix bug where [[#more]] at the top of the text showed the paragraph below it instead of empty string.
  • 20170615: Add default author name "Your Name", remove border on subheaders in listings.
  • 20170612: Fix typos, add listing arguments trail and perpage, fix wildcard tag listings. Fix bug where posting a comment can clear the page history.
  • 20170611: Add list argument newpostform=2 to always show the new post form.
  • 20170610: First public release, ready to be tested.

See also

Cookbook /
Bloge  A bundle of blogging (beta)
BlogIt  Provides a complete blogging system, using in-built PmWiki features -- additional features are supported through existing cookbooks. (Active)
BlogSimple  Experimental blog bundle using pagelists (Stable)
BlogSimple2  Simple blog bundle, revision of BlogSimple (Stable)
BlogWithPageList  How to build a blog system with pagelists
Bundle4Blog  How to use PmWiki as a blogging engine.
ShareButtons  Social media sharing buttons without tracking or JavaScript (Experimental)
XESBlog  Provide blog application functionality for one point installation. (stable)


Recipe written and maintained by Petko.


See discussion at B3-Talk


The blog listing description appears in the RSS feed, but not in the page, how to also display it in the page?

The (:description ...:) directive only sets the hidden page "description" property and meta "description" tag. To display the description in the page insert the PageVariable {$Description}, or simply type the text (or another) where you want it to appear.

User notes +4: If you use, used or reviewed this recipe, you can add your name. These statistics appear in the Cookbook listings and will help newcomers browsing through the wiki.